diff options
863 files changed, 79335 insertions, 33285 deletions
diff --git a/.travis.yml b/.travis.yml index cb0b33679c..8c24291328 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: cpp +# OS config, depends on actual 'os' in build matrix dist: trusty - sudo: false env: @@ -9,7 +9,7 @@ env: - SCONS_CACHE=$HOME/.scons_cache - SCONS_CACHE_LIMIT=1024 - OPTIONS="verbose=yes progress=no gdnative_wrapper=yes" - - secure: AnjB84ZZYDxDQdWwsT9PJrD5tEnwcauzvVeSG+us2GxgfyJ2HFArkZ/IjlvbsfYIiiHghJ2Ssy9yPtUT921BtNNHoWuN/8YQziGrlwiSfLGmS/n8GFH22OYwzDSa7UC7ODts5La2I4+JzrdtF933TwE+4QzH4E3GyaKbznh402E= + - secure: "QLFRizqry/Y5pnEZvDlQz5S3YydQ+600u4rHEzFgUTd0heYeQaETXAQeMzp0ymuG1BkdRAl5YJoLVJgAzjwI9hrvugvoUlh2//SfpqZCHN/Q1fYbtGgNTn01R3VFEpcfYQL93I2EjrxVm0WTM4PwCvMO+hU0aWTRDvCt1Lty0kMR+RMDQOO/woqunoXh5wvFNxTJJkAmuLe0v962DJYOIwJAnqMLR0aFYjmeQJ20bc/2X5oLt+WuJDuf/lGj6WSlD6z/o/kL3YxHoUyw4A/HAZ2IX0IfNHKuay60ESWzl/NlobnePiPwHAE2pdDVu//q16fanb9VeYnBYRFse49TpFRb86Lo+Qz8nKDJqpQEIY0YKNCFqekrubqTM++Lj6QvGpykQZNxUhybmELcEsRG4PS0UMvCpebdnJD46nNB+DtO2Lgb4xXDLQwpq19z1wizq/XDQ5hz61TIIx8+i8TsgdSQKCTeWovd4HcD4CVjAD5XTLGgyRmI/zC2d+lTnKo6W9diLq/bX/Goq2QPeaTPABqv817IaJka2JyugQ7Qal/+gNTjYRRsimRCL9B2tVh+Uh8rWhTFhQL4QbP5P65HF+p8qojUzqtAhPMbZ8mxUtNukUI3liVgPgiMss96sG0nTVglFgkkAkEjIMFnqMSKnTfG812K4jIhp2jCO2Q3NeI=" cache: directories: @@ -20,78 +20,66 @@ matrix: - env: STATIC_CHECKS=yes os: linux compiler: gcc - - env: GODOT_TARGET=x11 TOOLS=yes CACHE_NAME=${GODOT_TARGET}-gcc-tools" + addons: + apt: + sources: + - llvm-toolchain-trusty-5.0 + packages: + - clang-format-5.0 + + coverity_scan: + project: + name: "godotengine/godot" + description: "Godot Engine Coverity scans" + notification_email: coverity@godotengine.org + build_command_prepend: "" + build_command: "scons p=x11 -j2 $OPTIONS" + branch_pattern: coverity_scan + + - env: GODOT_TARGET=x11 TOOLS=yes CACHE_NAME=${GODOT_TARGET}-tools-mono-gcc EXTRA_ARGS="module_mono_enabled=yes mono_glue=no" os: linux compiler: gcc - - env: GODOT_TARGET=x11 TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang" + addons: + apt: + sources: + - mono + packages: + - &linux_deps [libasound2-dev, libfreetype6-dev, libgl1-mesa-dev, libglu1-mesa-dev, libx11-dev, libxcursor-dev, libxi-dev, libxinerama-dev, libxrandr-dev] + - &linux_mono_deps [mono-devel, msbuild] + + - env: GODOT_TARGET=x11 TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang os: linux compiler: clang - #- env: GODOT_TARGET=windows TOOLS=yes CACHE_NAME=${GODOT_TARGET}-gcc-tools - # os: linux - # compiler: gcc - - env: GODOT_TARGET=android TOOLS=no CACHE_NAME=${GODOT_TARGET}-gcc + addons: + apt: + packages: + - *linux_deps + + - env: GODOT_TARGET=android TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang os: linux - compiler: gcc - - env: GODOT_TARGET=osx TOOLS=yes CACHE_NAME=${GODOT_TARGET}-clang-tools + compiler: clang + + - env: GODOT_TARGET=osx TOOLS=yes CACHE_NAME=${GODOT_TARGET}-tools-clang os: osx osx_image: xcode9.3 compiler: clang + - env: GODOT_TARGET=iphone TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang os: osx osx_image: xcode9.3 compiler: clang - - env: GODOT_TARGET=server TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang" - os: linux - compiler: clang - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-trusty-5.0 - packages: - - build-essential - - scons - - pkg-config - - libx11-dev - - libxcursor-dev - - libxi-dev - - libxinerama-dev - - libxrandr-dev - - libgl1-mesa-dev - - libglu1-mesa-dev - - libasound2-dev - - libfreetype6-dev - - # For cross-compiling to Windows. - #- binutils-mingw-w64-i686 - #- binutils-mingw-w64-x86-64 - #- gcc-mingw-w64-i686 - #- gcc-mingw-w64-x86-64 - #- g++-mingw-w64-i686 - #- g++-mingw-w64-x86-64 - #- mingw-w64 - # For style checks. - - clang-format-5.0 - - coverity_scan: - project: - name: "godotengine/godot" - description: "Godot Engine Coverity scans" - notification_email: coverity@godotengine.org - build_command_prepend: "" - build_command: "scons p=x11 -j2 $OPTIONS" - branch_pattern: coverity_scan + - env: GODOT_TARGET=server TOOLS=yes CACHE_NAME=${GODOT_TARGET}-tools-gcc + os: linux + compiler: gcc + addons: + apt: + packages: + - *linux_deps before_install: - if [ "$STATIC_CHECKS" = "yes" ]; then unset SCONS_CACHE; - else - if [ "$TRAVIS_BRANCH" = "coverity_scan" ]; then - echo "This job runs in the Coverity Scan branch and is not the STATIC_CHECKS job meant for it, so aborting with exit code 0."; - travis_terminate 0; - fi; fi install: @@ -115,5 +103,5 @@ script: - if [ "$STATIC_CHECKS" = "yes" ]; then sh ./misc/travis/clang-format.sh; else - scons -j2 CC=$CC CXX=$CXX platform=$GODOT_TARGET TOOLS=$TOOLS $OPTIONS; + scons -j2 CC=$CC CXX=$CXX platform=$GODOT_TARGET TOOLS=$TOOLS $EXTRA_ARGS $OPTIONS; fi diff --git a/AUTHORS.md b/AUTHORS.md index 67563298f2..12494a487d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -38,6 +38,7 @@ name is available. Ariel Manzur (punto-) Bastiaan Olij (BastiaanOlij) Ben Brookshire (sheepandshepherd) + Benjamin (Nallebeorn) Bernard Liebl (poke1024) Bojidar Marinov (bojidar-bg) Błażej Szczygieł (zaps166) @@ -61,6 +62,7 @@ name is available. Hubert Jarosz (Marqin) Hugo Locurcio (Calinou) Ian Bishop (ianb96) + Ibrahn Sahir (ibrahn) Ignacio Etcheverry (neikeq) Indah Sylvia (ISylvox) J08nY @@ -71,6 +73,7 @@ name is available. Juan Linietsky (reduz) Julian Murgia (StraToN) Justo Delgado (mrcdk) + Kelly Thomas (KellyThomas) Kostadin Damyanov (Max-Might) Leon Krause (eska014) Marc Gilleron (Zylann) @@ -85,6 +88,7 @@ name is available. Nathan Warden (NathanWarden) Nuno Donato (nunodonato) Ovnuniarchos + Pascal Richter (ShyRed) Patrick (firefly2442) Paul Batty (Paulb23) Paul Joannon (paulloz) diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..30d80990a6 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,37 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. +# Owners can be @users, @org/teams or emails + +core/* @reduz + +doc/* @godotengine/documentation + +drivers/gles2/* @karroffel +drivers/gles3/* @reduz + +editor/icons/* @djrm + +main/* @reduz + +misc/* @akien-mga + +modules/bullet/* @AndreaCatania +modules/enet/* @godotengine/network +modules/gnative/* @karroffel +modules/gdscript/* @reduz @vnen @bojidar-bg +modules/mbedtls/* @godotengine/network +modules/mobile_vr/* @BastiaanOlij +modules/mono/* @neikeq +modules/regex/* @LeeZH +modules/upnp/* @godotengine/network +modules/websocket/* @godotengine/network + +platform/javascript/* @eska014 +platform/uwp/* @vnen + +scene/main/* @reduz + +server/physics* @reduz @AndreaCatania +server/visual* @reduz @karroffel + +thirdparty/* @akien-mga diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 6b6ee4c509..f4340719dc 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -218,11 +218,21 @@ Comment: WebP codec Copyright: 2010, Google Inc. License: BSD-3-clause +Files: ./thirdparty/libwebsockets/ +Comment: libwebsockets +Copyright: 2010-2017, Andy Green +License: LGPL-2.1+SLE (libwebsockets) + Files: ./thirdparty/mbedtls/ Comment: Mbed TLS Copyright: 2006-2015, ARM Limited License: Apache-2.0 +Files: ./thirdparty/miniupnpc/ +Comment: MiniUPnPc +Copyright: 2005-2016, Thomas Bernard +License: BSD-3-clause + Files: ./thirdparty/minizip/ Comment: MiniZip Copyright: 1998-2010, Gilles Vollant @@ -244,6 +254,12 @@ Comment: BASE64 conversion methods Copyright: Ari Edelkind License: public-domain +Files: ./thirdparty/misc/clipper.cpp + ./thirdparty/misc/clipper.hpp +Comment: Clipper +Copyright: 2010-2017, Angus Johnson +License: BSL-1.0 + Files: ./thirdparty/misc/curl_hostcheck.c ./thirdparty/misc/curl_hostcheck.h Comment: curl @@ -378,30 +394,6 @@ License: Apache-2.0 See the License for the specific language governing permissions and limitations under the License. -License: BSD-2-clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - . - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - . - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. Neither the name of the author - nor the names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. - License: Bitstream Vera Fonts Copyright Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. @@ -444,6 +436,28 @@ License: Bitstream Vera Fonts Copyright authorization from the GNOME Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. +License: BSD-2-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. + License: BSD-3-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -472,6 +486,31 @@ License: BSD-3-clause OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +License: BSL-1.0 + Boost Software License - Version 1.0 - August 17th, 2003 + . + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + . + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + . + 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + License: CC-BY-3.0 Creative Commons Attribution 3.0 Unported . @@ -1022,6 +1061,564 @@ License: ISC ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +License: LGPL-2.1+SLE (libwebsockets) + Libwebsockets and included programs are provided under the terms of the GNU + Library General Public License (LGPL) 2.1, with the following exceptions: + . + 1) Any reference, whether in these modifications or in the GNU + Library General Public License 2.1, to this License, these terms, the + GNU Lesser Public License, GNU Library General Public License, LGPL, or + any similar reference shall refer to the GNU Library General Public + License 2.1 as modified by these paragraphs 1) through 4). + . + 2) Static linking of programs with the libwebsockets library does not + constitute a derivative work and does not require the author to provide + source code for the program, use the shared libwebsockets libraries, or + link their program against a user-supplied version of libwebsockets. + . + If you link the program to a modified version of libwebsockets, then the + changes to libwebsockets must be provided under the terms of the LGPL in + sections 1, 2, and 4. + . + 3) You do not have to provide a copy of the libwebsockets license with + programs that are linked to the libwebsockets library, nor do you have to + identify the libwebsockets license in your program or documentation as + required by section 6 of the LGPL. + . + However, programs must still identify their use of libwebsockets. The + following example statement can be included in user documentation to + satisfy this requirement: + . + "[program] is based in part on the work of the libwebsockets project + (https://libwebsockets.org)" + . + 4) Some sources included have their own, more liberal licenses, or options + to get original sources with the liberal terms. + . + Original liberal license retained + . + - lib/misc/sha-1.c - 3-clause BSD license retained, link to original + - win32port/zlib - ZLIB license (see zlib.h) + - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls) + . + Relicensed to libwebsocket license + . + - lib/misc/base64-decode.c - relicensed to LGPL2.1+SLE, link to original + - lib/misc/daemonize.c - relicensed from Public Domain to LGPL2.1+SLE, + link to original Public Domain version + . + Public Domain (CC-zero) to simplify reuse + . + - test-apps/*.c + - test-apps/*.h + - minimal-examples/* + - lwsws/* + . + ------ end of exceptions + . + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + . + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + . + [This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + . + Preamble + . + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + Licenses are intended to guarantee your freedom to share and change + free software--to make sure the software is free for all its users. + . + This license, the Lesser General Public License, applies to some + specially designated software packages--typically libraries--of the + Free Software Foundation and other authors who decide to use it. You + can use it too, but we suggest you first think carefully about whether + this license or the ordinary General Public License is the better + strategy to use in any particular case, based on the explanations below. + . + When we speak of free software, we are referring to freedom of use, + not price. Our General Public Licenses are designed to make sure that + you have the freedom to distribute copies of free software (and charge + for this service if you wish); that you receive source code or can get + it if you want it; that you can change the software and use pieces of + it in new free programs; and that you are informed that you can do + these things. + . + To protect your rights, we need to make restrictions that forbid + distributors to deny you these rights or to ask you to surrender these + rights. These restrictions translate to certain responsibilities for + you if you distribute copies of the library or if you modify it. + . + For example, if you distribute copies of the library, whether gratis + or for a fee, you must give the recipients all the rights that we gave + you. You must make sure that they, too, receive or can get the source + code. If you link other code with the library, you must provide + complete object files to the recipients, so that they can relink them + with the library after making changes to the library and recompiling + it. And you must show them these terms so they know their rights. + . + We protect your rights with a two-step method: (1) we copyright the + library, and (2) we offer you this license, which gives you legal + permission to copy, distribute and/or modify the library. + . + To protect each distributor, we want to make it very clear that + there is no warranty for the free library. Also, if the library is + modified by someone else and passed on, the recipients should know + that what they have is not the original version, so that the original + author's reputation will not be affected by problems that might be + introduced by others. + . + Finally, software patents pose a constant threat to the existence of + any free program. We wish to make sure that a company cannot + effectively restrict the users of a free program by obtaining a + restrictive license from a patent holder. Therefore, we insist that + any patent license obtained for a version of the library must be + consistent with the full freedom of use specified in this license. + . + Most GNU software, including some libraries, is covered by the + ordinary GNU General Public License. This license, the GNU Lesser + General Public License, applies to certain designated libraries, and + is quite different from the ordinary General Public License. We use + this license for certain libraries in order to permit linking those + libraries into non-free programs. + . + When a program is linked with a library, whether statically or using + a shared library, the combination of the two is legally speaking a + combined work, a derivative of the original library. The ordinary + General Public License therefore permits such linking only if the + entire combination fits its criteria of freedom. The Lesser General + Public License permits more lax criteria for linking other code with + the library. + . + We call this license the "Lesser" General Public License because it + does Less to protect the user's freedom than the ordinary General + Public License. It also provides other free software developers Less + of an advantage over competing non-free programs. These disadvantages + are the reason we use the ordinary General Public License for many + libraries. However, the Lesser license provides advantages in certain + special circumstances. + . + For example, on rare occasions, there may be a special need to + encourage the widest possible use of a certain library, so that it becomes + a de-facto standard. To achieve this, non-free programs must be + allowed to use the library. A more frequent case is that a free + library does the same job as widely used non-free libraries. In this + case, there is little to gain by limiting the free library to free + software only, so we use the Lesser General Public License. + . + In other cases, permission to use a particular library in non-free + programs enables a greater number of people to use a large body of + free software. For example, permission to use the GNU C Library in + non-free programs enables many more people to use the whole GNU + operating system, as well as its variant, the GNU/Linux operating + system. + . + Although the Lesser General Public License is Less protective of the + users' freedom, it does ensure that the user of a program that is + linked with the Library has the freedom and the wherewithal to run + that program using a modified version of the Library. + . + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a + "work based on the library" and a "work that uses the library". The + former contains code derived from the library, whereas the latter must + be combined with the library in order to run. + . + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + . + 0. This License Agreement applies to any software library or other + program which contains a notice placed by the copyright holder or + other authorized party saying it may be distributed under the terms of + this Lesser General Public License (also called "this License"). + Each licensee is addressed as "you". + . + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + . + The "Library", below, refers to any such software library or work + which has been distributed under these terms. A "work based on the + Library" means either the Library or any derivative work under + copyright law: that is to say, a work containing the Library or a + portion of it, either verbatim or with modifications and/or translated + straightforwardly into another language. (Hereinafter, translation is + included without limitation in the term "modification".) + . + "Source code" for a work means the preferred form of the work for + making modifications to it. For a library, complete source code means + all the source code for all modules it contains, plus any associated + interface definition files, plus the scripts used to control compilation + and installation of the library. + . + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running a program using the Library is not restricted, and output from + such a program is covered only if its contents constitute a work based + on the Library (independent of the use of the Library in a tool for + writing it). Whether that is true depends on what the Library does + and what the program that uses the Library does. + . + 1. You may copy and distribute verbatim copies of the Library's + complete source code as you receive it, in any medium, provided that + you conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep intact + all the notices that refer to this License and to the absence of any + warranty; and distribute a copy of this License along with the + Library. + . + You may charge a fee for the physical act of transferring a copy, + and you may at your option offer warranty protection in exchange for a + fee. + . + 2. You may modify your copy or copies of the Library or any portion + of it, thus forming a work based on the Library, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + . + a) The modified work must itself be a software library. + . + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + . + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + . + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + . + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + . + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Library, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote + it. + . + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + . + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + . + 3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so + that they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in + these notices. + . + Once this change is made in a given copy, it is irreversible for + that copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + . + This option is useful when you wish to copy part of the code of + the Library into a program that is not a library. + . + 4. You may copy and distribute the Library (or a portion or + derivative of it, under Section 2) in object code or executable form + under the terms of Sections 1 and 2 above provided that you accompany + it with the complete corresponding machine-readable source code, which + must be distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange. + . + If distribution of object code is made by offering access to copy + from a designated place, then offering equivalent access to copy the + source code from the same place satisfies the requirement to + distribute the source code, even though third parties are not + compelled to copy the source along with the object code. + . + 5. A program that contains no derivative of any portion of the + Library, but is designed to work with the Library by being compiled or + linked with it, is called a "work that uses the Library". Such a + work, in isolation, is not a derivative work of the Library, and + therefore falls outside the scope of this License. + . + However, linking a "work that uses the Library" with the Library + creates an executable that is a derivative of the Library (because it + contains portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. + Section 6 states terms for distribution of such executables. + . + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is not. + Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + . + If such an object file uses only numerical parameters, data + structure layouts and accessors, and small macros and small inline + functions (ten lines or less in length), then the use of the object + file is unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + . + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section 6. + Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + . + 6. As an exception to the Sections above, you may also combine or + link a "work that uses the Library" with the Library to produce a + work containing portions of the Library, and distribute that work + under terms of your choice, provided that the terms permit + modification of the work for the customer's own use and reverse + engineering for debugging such modifications. + . + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work + during execution displays copyright notices, you must include the + copyright notice for the Library among them, as well as a reference + directing the user to the copy of this License. Also, you must do one + of these things: + . + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + . + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + . + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + . + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + . + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + . + For an executable, the required form of the "work that uses the + Library" must include any data and utility programs needed for + reproducing the executable from it. However, as a special exception, + the materials to be distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies + the executable. + . + It may happen that this requirement contradicts the license + restrictions of other proprietary libraries that do not normally + accompany the operating system. Such a contradiction means you cannot + use both them and the Library together in an executable that you + distribute. + . + 7. You may place library facilities that are a work based on the + Library side-by-side in a single library together with other library + facilities not covered by this License, and distribute such a combined + library, provided that the separate distribution of the work based on + the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: + . + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + . + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + . + 8. You may not copy, modify, sublicense, link with, or distribute + the Library except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, + or rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + . + 9. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Library or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Library (or any work based on the + Library), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Library or works based on it. + . + 10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties with + this License. + . + 11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent + license would not permit royalty-free redistribution of the Library by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Library. + . + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. + . + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + . + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + . + 12. If the distribution and/or use of the Library is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Library under this License may add + an explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. + . + 13. The Free Software Foundation may publish revised and/or new + versions of the Lesser 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 Library + specifies a version number of this License which applies to it and + "any later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a + license version number, you may choose any version ever published by + the Free Software Foundation. + . + 14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. Our + decision will be guided by the two goals of preserving the free status + of all derivatives of our free software and of promoting the sharing + and reuse of software generally. + . + NO WARRANTY + . + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE LIBRARY "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 + LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + . + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE LIBRARY 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 + LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. + . + END OF TERMS AND CONDITIONS + . + How to Apply These Terms to Your New Libraries + . + If you develop a new library, and you want it to be of the greatest + possible use to the public, we recommend making it free software that + everyone can redistribute and change. You can do so by permitting + redistribution under these terms (or, alternatively, under the terms of the + ordinary General Public License). + . + To apply these terms, attach the following notices to the library. It is + safest to attach them to the start of each source file to most effectively + convey the exclusion of warranty; and each file should have at least the + "copyright" line and a pointer to where the full notice is found. + . + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + . + 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 + . + Also add information on how to contact you by electronic and paper mail. + . + You should also get your employer (if you work as a programmer) or your + school, if any, to sign a "copyright disclaimer" for the library, if + necessary. Here is a sample; alter the names: + . + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + . + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + . + That's all there is to it! + License: MPL-2.0 Mozilla Public License Version 2.0 ================================== @@ -22,7 +22,6 @@ generous deed immortalized in the next stable release of Godot Engine. ## Mini sponsors - Andreas Brandon Lamb Christian Uldall Pedersen Christopher Igoe @@ -44,6 +43,7 @@ generous deed immortalized in the next stable release of Godot Engine. Stephan Lanfermann Stoney Meyerhoeffer Thomas Mathews + VilliHaukka ## Gold donors @@ -51,11 +51,11 @@ generous deed immortalized in the next stable release of Godot Engine. Alexander Otto Asdf cheese65536 - Jake Bo + K9Kraken Kris Michael Manuele Finocchiaro + Nathanael Beisiegel Officine Pixel S.n.c. - Rémi Verschelde Zaven Muradyan Allen Schade @@ -64,7 +64,7 @@ generous deed immortalized in the next stable release of Godot Engine. Bernhard Liebl Catalin Moldovan DeepSquid - Duane Johnson + Fidget Sinner Florian Breisch Gary Oberbrunner Johannes Wuensch @@ -72,6 +72,7 @@ generous deed immortalized in the next stable release of Godot Engine. Joshua Lesperance Libre-Dépanne Matthew Bennett + Olafur Gislason Paul LaMotte Ranoller Svenne Krap @@ -82,20 +83,21 @@ generous deed immortalized in the next stable release of Godot Engine. Chris Serino Conrad Curry Craig Smith + Daniel Egger David Churchill Dean Harmon Dexter Miguel - Garrett Dockins Guilherme Felipe de C. G. da Silva John Justo Delgado Baudà + KTL Laurence Bannister Rami Robert Willes Robin Arys + Ronnie Ashlock Rufus Xavier Sarsaparilla ScottMakesGames - Testus Maximus Thomas Bjarnelöf William Connell Wojciech Chojnacki @@ -109,21 +111,20 @@ generous deed immortalized in the next stable release of Godot Engine. Chris Petrich Chris Wilson Cody Parker + Corey Auger D Daniel Eliasinski E.G. Eric Monson flesk - François Cantin G Barnes GGGames.org Giovanni Solimeno Hasen Judy Heath Hayes + Jay Horton Jeppe Zapp - Jeremi Biernacki joe513 - John O'Mahoney Jordan M Lucas Juraj Móza Justin Arnold @@ -137,10 +138,11 @@ generous deed immortalized in the next stable release of Godot Engine. Patrick Schnorbus Pete Goodwin Phyronnaz - SeokHui Lee - Simon De Greve + Ruben Soares Luis Sofox + Stoned Xander Ted + Tim Dalporto Trent McPheron Vladimir @@ -158,6 +160,7 @@ generous deed immortalized in the next stable release of Godot Engine. Arthur S. Muszynski Aubrey Falconer Avencherus + Bailey Bastian Böhm Benedikt Benjamin Beshara @@ -172,7 +175,8 @@ generous deed immortalized in the next stable release of Godot Engine. Christian Winter Christopher Schmitt Collin Shooltz - Daniel Egger + Daniel Delgado Corona + Daniel Johnson Daniel Kaplan DanielMaximiano Daniel Mircea @@ -180,18 +184,18 @@ generous deed immortalized in the next stable release of Godot Engine. David Cravens David May Dominik Wetzel + Duy Kevin Nguyen Edward Herbert Eric Martini Fabian Becker fengjiongmax Francesco Lisi - Frédéric Alix G3Dev sà rl - Geequlim Gerrit Großkopf Gerrit Procee Gilberto K. Otubo Guldoman + Gumichan01 Heribert Hirth hubert jenkins Hunter Jones @@ -211,7 +215,6 @@ generous deed immortalized in the next stable release of Godot Engine. Josh 'Cheeseness' Bush Juan Negrier Judd - JuDelCo Julian Murgia Justin Luk KC Chan @@ -221,26 +224,26 @@ generous deed immortalized in the next stable release of Godot Engine. Krzysztof Jankowski Lars pfeffer Linus Lind Lundgren + Luis Moraes Macil magodev Martin Eigel + Martins Odabi Matthew Fitzpatrick - Matthias Hölzl Max R.R. Collada - memoryruins + Maxwell mhilbrunner Michael Dürwald Michael Gringauz Michael Labbe Mikael Olsson MoM - monokrome Moritz Laass + Natrim nee Neil Blakey-Milner Nick Pavlica Niclas Eriksen - Nicolás Montaña Nicolas SAN AGUSTIN Niko Leopold nivardus @@ -257,6 +260,8 @@ generous deed immortalized in the next stable release of Godot Engine. Pierre-Igor Berthet Pietro Vertechi Piotr Kaczmarski + Rea + Rémi Verschelde Richman Stewart Roger Burgess Roger Smith @@ -264,12 +269,11 @@ generous deed immortalized in the next stable release of Godot Engine. Ryan Whited Samuel El-Borai Sasori Olkof - Scott D. Yelich Sootstone + Stefan Butucea Theo Cranmore Thibault Barbaroux Thomas Bell - Thomas Herzog & Xananax Thomas Kurz Tomasz Wacławek Tom Larrow diff --git a/SConstruct b/SConstruct index 63105bfa84..7ef4d646a7 100644 --- a/SConstruct +++ b/SConstruct @@ -2,7 +2,6 @@ EnsureSConsVersion(0, 98, 1) - import string import os import os.path @@ -10,9 +9,6 @@ import glob import sys import methods -# moved below to compensate with module version string -# methods.update_version() - # scan possible build platforms platform_list = [] # list of platforms @@ -53,9 +49,6 @@ for x in glob.glob("platform/*"): module_list = methods.detect_modules() - -# print "Detected Platforms: "+str(platform_list) - methods.save_active_platforms(active_platforms, active_platform_ids) custom_tools = ['default'] @@ -101,7 +94,6 @@ env_base.Decider('MD5-timestamp') # http://scons.org/doc/production/HTML/scons-user/ch06s04.html env_base.SetOption('implicit_cache', 1) - env_base.__class__.android_add_maven_repository = methods.android_add_maven_repository env_base.__class__.android_add_flat_dir = methods.android_add_flat_dir env_base.__class__.android_add_dependency = methods.android_add_dependency @@ -126,6 +118,7 @@ env_base.__class__.split_lib = methods.split_lib env_base.__class__.add_shared_library = methods.add_shared_library env_base.__class__.add_library = methods.add_library env_base.__class__.add_program = methods.add_program +env_base.__class__.CommandNoCache = methods.CommandNoCache env_base["x86_libtheora_opt_gcc"] = False env_base["x86_libtheora_opt_vc"] = False @@ -145,52 +138,53 @@ if profile: opts = Variables(customs, ARGUMENTS) # Target build options -opts.Add('arch', "Platform-dependent architecture (arm/arm64/x86/x64/mips/etc)", '') +opts.Add('arch', "Platform-dependent architecture (arm/arm64/x86/x64/mips/...)", '') opts.Add(EnumVariable('bits', "Target platform bits", 'default', ('default', '32', '64'))) opts.Add('p', "Platform (alias for 'platform')", '') opts.Add('platform', "Target platform (%s)" % ('|'.join(platform_list), ), '') opts.Add(EnumVariable('target', "Compilation target", 'debug', ('debug', 'release_debug', 'release'))) -opts.Add(BoolVariable('tools', "Build the tools a.k.a. the Godot editor", True)) -opts.Add(BoolVariable('use_lto', 'Use linking time optimization', False)) +opts.Add(BoolVariable('tools', "Build the tools (a.k.a. the Godot editor)", True)) +opts.Add(BoolVariable('use_lto', 'Use link-time optimization', False)) # Components opts.Add(BoolVariable('deprecated', "Enable deprecated features", True)) -opts.Add(BoolVariable('gdscript', "Build GDSCript support", True)) -opts.Add(BoolVariable('minizip', "Build minizip archive support", True)) -opts.Add(BoolVariable('xaudio2', "XAudio2 audio driver", False)) -opts.Add(BoolVariable('xml', "XML format support for resources", True)) +opts.Add(BoolVariable('gdscript', "Enable GDScript support", True)) +opts.Add(BoolVariable('minizip', "Enable ZIP archive support using minizip", True)) +opts.Add(BoolVariable('xaudio2', "Enable the XAudio2 audio driver", False)) +opts.Add(BoolVariable('xml', "Enable XML format support for resources", True)) # Advanced options -opts.Add(BoolVariable('disable_3d', "Disable 3D nodes for smaller executable", False)) -opts.Add(BoolVariable('disable_advanced_gui', "Disable advanced 3D gui nodes and behaviors", False)) +opts.Add(BoolVariable('disable_3d', "Disable 3D nodes for a smaller executable", False)) +opts.Add(BoolVariable('disable_advanced_gui', "Disable advanced 3D GUI nodes and behaviors", False)) opts.Add('extra_suffix', "Custom extra suffix added to the base filename of all generated binary files", '') -opts.Add('unix_global_settings_path', "UNIX-specific path to system-wide settings. Currently only used for templates", '') opts.Add(BoolVariable('verbose', "Enable verbose output for the compilation", False)) -opts.Add(BoolVariable('vsproj', "Generate Visual Studio Project", False)) +opts.Add(BoolVariable('vsproj', "Generate a Visual Studio solution", False)) opts.Add(EnumVariable('warnings', "Set the level of warnings emitted during compilation", 'no', ('extra', 'all', 'moderate', 'no'))) -opts.Add(BoolVariable('progress', "Show a progress indicator during build", True)) +opts.Add(BoolVariable('progress', "Show a progress indicator during compilation", True)) opts.Add(BoolVariable('dev', "If yes, alias for verbose=yes warnings=all", False)) -opts.Add(EnumVariable('macports_clang', "Build using clang from MacPorts", 'no', ('no', '5.0', 'devel'))) +opts.Add(EnumVariable('macports_clang', "Build using Clang from MacPorts", 'no', ('no', '5.0', 'devel'))) +opts.Add(BoolVariable('no_editor_splash', "Don't use the custom splash screen for the editor", False)) # Thirdparty libraries -opts.Add(BoolVariable('builtin_bullet', "Use the builtin bullet library", True)) -opts.Add(BoolVariable('builtin_enet', "Use the builtin enet library", True)) -opts.Add(BoolVariable('builtin_freetype', "Use the builtin freetype library", True)) -opts.Add(BoolVariable('builtin_libogg', "Use the builtin libogg library", True)) -opts.Add(BoolVariable('builtin_libpng', "Use the builtin libpng library", True)) -opts.Add(BoolVariable('builtin_libtheora', "Use the builtin libtheora library", True)) -opts.Add(BoolVariable('builtin_libvorbis', "Use the builtin libvorbis library", True)) -opts.Add(BoolVariable('builtin_libvpx', "Use the builtin libvpx library", True)) -opts.Add(BoolVariable('builtin_libwebp', "Use the builtin libwebp library", True)) -opts.Add(BoolVariable('builtin_mbedtls', "Use the builtin mbedTLS library", True)) -opts.Add(BoolVariable('builtin_opus', "Use the builtin opus library", True)) -opts.Add(BoolVariable('builtin_pcre2', "Use the builtin pcre2 library)", True)) -opts.Add(BoolVariable('builtin_recast', "Use the builtin recast library", True)) -opts.Add(BoolVariable('builtin_squish', "Use the builtin squish library", True)) -opts.Add(BoolVariable('builtin_thekla_atlas', "Use the builtin thekla_altas library", True)) -opts.Add(BoolVariable('builtin_zlib', "Use the builtin zlib library", True)) -opts.Add(BoolVariable('builtin_zstd', "Use the builtin zstd library", True)) -opts.Add(BoolVariable('no_editor_splash', "Don't use the custom splash screen for the editor", False)) +opts.Add(BoolVariable('builtin_bullet', "Use the built-in Bullet library", True)) +opts.Add(BoolVariable('builtin_enet', "Use the built-in ENet library", True)) +opts.Add(BoolVariable('builtin_freetype', "Use the built-in FreeType library", True)) +opts.Add(BoolVariable('builtin_libogg', "Use the built-in libogg library", True)) +opts.Add(BoolVariable('builtin_libpng', "Use the built-in libpng library", True)) +opts.Add(BoolVariable('builtin_libtheora', "Use the built-in libtheora library", True)) +opts.Add(BoolVariable('builtin_libvorbis', "Use the built-in libvorbis library", True)) +opts.Add(BoolVariable('builtin_libvpx', "Use the built-in libvpx library", True)) +opts.Add(BoolVariable('builtin_libwebp', "Use the built-in libwebp library", True)) +opts.Add(BoolVariable('builtin_libwebsockets', "Use the built-in libwebsockets library", True)) +opts.Add(BoolVariable('builtin_mbedtls', "Use the built-in mbedTLS library", True)) +opts.Add(BoolVariable('builtin_miniupnpc', "Use the built-in miniupnpc library", True)) +opts.Add(BoolVariable('builtin_opus', "Use the built-in Opus library", True)) +opts.Add(BoolVariable('builtin_pcre2', "Use the built-in PCRE2 library)", True)) +opts.Add(BoolVariable('builtin_recast', "Use the built-in Recast library", True)) +opts.Add(BoolVariable('builtin_squish', "Use the built-in squish library", True)) +opts.Add(BoolVariable('builtin_thekla_atlas', "Use the built-in thekla_altas library", True)) +opts.Add(BoolVariable('builtin_zlib', "Use the built-in zlib library", True)) +opts.Add(BoolVariable('builtin_zstd', "Use the built-in Zstd library", True)) # Compilation environment setup opts.Add("CXX", "C++ compiler") @@ -201,7 +195,6 @@ opts.Add("CXXFLAGS", "Custom flags for the C++ compiler") opts.Add("CFLAGS", "Custom flags for the C compiler") opts.Add("LINKFLAGS", "Custom flags for the linker") - # add platform specific options for k in platform_opts.keys(): @@ -232,14 +225,6 @@ env_base.Append(CPPPATH=['#core', '#core/math', '#editor', '#drivers', '#']) env_base.platform_exporters = platform_exporters env_base.platform_apis = platform_apis -""" -sys.path.append("./platform/"+env_base["platform"]) -import detect -detect.configure(env_base) -sys.path.remove("./platform/"+env_base["platform"]) -sys.modules.pop('detect') -""" - if (env_base['target'] == 'debug'): env_base.Append(CPPDEFINES=['DEBUG_MEMORY_ALLOC', 'SCI_NAMESPACE']) @@ -251,7 +236,6 @@ if not env_base['deprecated']: env_base.platforms = {} - selected_platform = "" if env_base['platform'] != "": @@ -260,7 +244,6 @@ elif env_base['p'] != "": selected_platform = env_base['p'] env_base["platform"] = selected_platform - if selected_platform in platform_list: sys.path.append("./platform/" + selected_platform) @@ -352,7 +335,6 @@ if selected_platform in platform_list: else: # 'no' env.Append(CCFLAGS=['-w']) env.Append(CCFLAGS=['-Werror=return-type']) - #env['platform_libsuffix'] = env['LIBSUFFIX'] suffix = "." + selected_platform @@ -396,7 +378,17 @@ if selected_platform in platform_list: sys.path.append(tmppath) env.current_module = x import config - if (config.can_build(selected_platform)): + # can_build changed number of arguments between 3.0 (1) and 3.1 (2), + # so try both to preserve compatibility for 3.0 modules + can_build = False + try: + can_build = config.can_build(env, selected_platform) + except TypeError: + print("Warning: module '%s' uses a deprecated `can_build` " + "signature in its config.py file, it should be " + "`can_build(env, platform)`." % x) + can_build = config.can_build(selected_platform) + if (can_build): config.configure(env) env.module_list.append(x) try: @@ -419,10 +411,6 @@ if selected_platform in platform_list: if (env.use_ptrcall): env.Append(CPPDEFINES=['PTRCALL_ENABLED']) - - # to test 64 bits compiltion - # env.Append(CPPFLAGS=['-m64']) - if env['tools']: env.Append(CPPDEFINES=['TOOLS_ENABLED']) if env['disable_3d']: @@ -431,10 +419,8 @@ if selected_platform in platform_list: env.Append(CPPDEFINES=['GDSCRIPT_ENABLED']) if env['disable_advanced_gui']: env.Append(CPPDEFINES=['ADVANCED_GUI_DISABLED']) - if env['minizip']: env.Append(CPPDEFINES=['MINIZIP_ENABLED']) - if env['xml']: env.Append(CPPDEFINES=['XML_ENABLED']) @@ -482,11 +468,10 @@ if selected_platform in platform_list: else: print("No valid target platform selected.") - print("The following were detected:") + print("The following platforms were detected:") for x in platform_list: print("\t" + x) - print("\nPlease run scons again with argument: platform=<string>") - + print("\nPlease run SCons again with the argument: platform=<string>") # The following only makes sense when the env is defined, and assumes it is if 'env' in locals(): @@ -16,6 +16,8 @@ if sys.version_info < (3,): return x def iteritems(d): return d.iteritems() + def itervalues(d): + return d.itervalues() def escape_string(s): if isinstance(s, unicode): s = s.encode('ascii') @@ -44,6 +46,8 @@ else: return codecs.utf_8_decode(x)[0] def iteritems(d): return iter(d.items()) + def itervalues(d): + return iter(d.values()) def charcode_to_c_escapes(c): rev_result = [] while c >= 256: diff --git a/core/SCsub b/core/SCsub index 383aaf0e12..c508ecc37e 100644 --- a/core/SCsub +++ b/core/SCsub @@ -2,6 +2,8 @@ Import('env') +import methods + env.core_sources = [] @@ -91,8 +93,19 @@ env.add_source_files(env.core_sources, "*.cpp") # Make binders import make_binders -env.Command(['method_bind.gen.inc', 'method_bind_ext.gen.inc'], 'make_binders.py', make_binders.run) +env.CommandNoCache(['method_bind.gen.inc', 'method_bind_ext.gen.inc'], 'make_binders.py', make_binders.run) + +# Authors +env.Depends('#core/authors.gen.h', "../AUTHORS.md") +env.CommandNoCache('#core/authors.gen.h', "../AUTHORS.md", methods.make_authors_header) + +# Donors +env.Depends('#core/donors.gen.h', "../DONORS.md") +env.CommandNoCache('#core/donors.gen.h', "../DONORS.md", methods.make_donors_header) +# License +env.Depends('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"]) +env.CommandNoCache('#core/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"], methods.make_license_header) # Chain load SCsubs SConscript('os/SCsub') diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index 3270b33f1c..7a14e85f20 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -348,6 +348,11 @@ bool _OS::get_borderless_window() const { return OS::get_singleton()->get_borderless_window(); } +void _OS::set_ime_active(const bool p_active) { + + return OS::get_singleton()->set_ime_active(p_active); +} + void _OS::set_ime_position(const Point2 &p_pos) { return OS::get_singleton()->set_ime_position(p_pos); @@ -399,7 +404,7 @@ Error _OS::shell_open(String p_uri) { int _OS::execute(const String &p_path, const Vector<String> &p_arguments, bool p_blocking, Array p_output) { - OS::ProcessID pid; + OS::ProcessID pid = -2; List<String> args; for (int i = 0; i < p_arguments.size(); i++) args.push_back(p_arguments[i]); @@ -412,6 +417,7 @@ int _OS::execute(const String &p_path, const Vector<String> &p_arguments, bool p else return pid; } + Error _OS::kill(int p_pid) { return OS::get_singleton()->kill(p_pid); @@ -793,6 +799,11 @@ uint32_t _OS::get_ticks_msec() const { return OS::get_singleton()->get_ticks_msec(); } +uint64_t _OS::get_ticks_usec() const { + + return OS::get_singleton()->get_ticks_usec(); +} + uint32_t _OS::get_splash_tick_msec() const { return OS::get_singleton()->get_splash_tick_msec(); @@ -1125,6 +1136,7 @@ void _OS::_bind_methods() { ClassDB::bind_method(D_METHOD("delay_usec", "usec"), &_OS::delay_usec); ClassDB::bind_method(D_METHOD("delay_msec", "msec"), &_OS::delay_msec); ClassDB::bind_method(D_METHOD("get_ticks_msec"), &_OS::get_ticks_msec); + ClassDB::bind_method(D_METHOD("get_ticks_usec"), &_OS::get_ticks_usec); ClassDB::bind_method(D_METHOD("get_splash_tick_msec"), &_OS::get_splash_tick_msec); ClassDB::bind_method(D_METHOD("get_locale"), &_OS::get_locale); ClassDB::bind_method(D_METHOD("get_latin_keyboard_variant"), &_OS::get_latin_keyboard_variant); @@ -2710,6 +2722,26 @@ Dictionary _Engine::get_version_info() const { return Engine::get_singleton()->get_version_info(); } +Dictionary _Engine::get_author_info() const { + return Engine::get_singleton()->get_author_info(); +} + +Array _Engine::get_copyright_info() const { + return Engine::get_singleton()->get_copyright_info(); +} + +Dictionary _Engine::get_donor_info() const { + return Engine::get_singleton()->get_donor_info(); +} + +Dictionary _Engine::get_license_info() const { + return Engine::get_singleton()->get_license_info(); +} + +String _Engine::get_license_text() const { + return Engine::get_singleton()->get_license_text(); +} + bool _Engine::is_in_physics_frame() const { return Engine::get_singleton()->is_in_physics_frame(); } @@ -2752,6 +2784,11 @@ void _Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_main_loop"), &_Engine::get_main_loop); ClassDB::bind_method(D_METHOD("get_version_info"), &_Engine::get_version_info); + ClassDB::bind_method(D_METHOD("get_author_info"), &_Engine::get_author_info); + ClassDB::bind_method(D_METHOD("get_copyright_info"), &_Engine::get_copyright_info); + ClassDB::bind_method(D_METHOD("get_donor_info"), &_Engine::get_donor_info); + ClassDB::bind_method(D_METHOD("get_license_info"), &_Engine::get_license_info); + ClassDB::bind_method(D_METHOD("get_license_text"), &_Engine::get_license_text); ClassDB::bind_method(D_METHOD("is_in_physics_frame"), &_Engine::is_in_physics_frame); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index a363f5970f..48b7b74005 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -183,6 +183,7 @@ public: virtual bool get_window_per_pixel_transparency_enabled() const; virtual void set_window_per_pixel_transparency_enabled(bool p_enabled); + virtual void set_ime_active(const bool p_active); virtual void set_ime_position(const Point2 &p_pos); Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); @@ -276,6 +277,7 @@ public: void delay_usec(uint32_t p_usec) const; void delay_msec(uint32_t p_msec) const; uint32_t get_ticks_msec() const; + uint64_t get_ticks_usec() const; uint32_t get_splash_tick_msec() const; bool can_use_threads() const; @@ -689,6 +691,11 @@ public: MainLoop *get_main_loop() const; Dictionary get_version_info() const; + Dictionary get_author_info() const; + Array get_copyright_info() const; + Dictionary get_donor_info() const; + Dictionary get_license_info() const; + String get_license_text() const; bool is_in_physics_frame() const; diff --git a/core/class_db.cpp b/core/class_db.cpp index 59b100e282..f97eaf6099 100644 --- a/core/class_db.cpp +++ b/core/class_db.cpp @@ -248,9 +248,9 @@ void ClassDB::set_current_api(APIType p_api) { current_api = p_api; } -HashMap<StringName, ClassDB::ClassInfo, StringNameHasher> ClassDB::classes; -HashMap<StringName, StringName, StringNameHasher> ClassDB::resource_base_extensions; -HashMap<StringName, StringName, StringNameHasher> ClassDB::compat_classes; +HashMap<StringName, ClassDB::ClassInfo> ClassDB::classes; +HashMap<StringName, StringName> ClassDB::resource_base_extensions; +HashMap<StringName, StringName> ClassDB::compat_classes; ClassDB::ClassInfo::ClassInfo() { diff --git a/core/class_db.h b/core/class_db.h index 2c77ffe65f..f1d1879236 100644 --- a/core/class_db.h +++ b/core/class_db.h @@ -114,10 +114,10 @@ public: APIType api; ClassInfo *inherits_ptr; - HashMap<StringName, MethodBind *, StringNameHasher> method_map; - HashMap<StringName, int, StringNameHasher> constant_map; + HashMap<StringName, MethodBind *> method_map; + HashMap<StringName, int> constant_map; HashMap<StringName, List<StringName> > enum_map; - HashMap<StringName, MethodInfo, StringNameHasher> signal_map; + HashMap<StringName, MethodInfo> signal_map; List<PropertyInfo> property_list; #ifdef DEBUG_METHODS_ENABLED List<StringName> constant_order; @@ -126,7 +126,7 @@ public: List<MethodInfo> virtual_methods; StringName category; #endif - HashMap<StringName, PropertySetGet, StringNameHasher> property_setget; + HashMap<StringName, PropertySetGet> property_setget; StringName inherits; StringName name; @@ -143,9 +143,9 @@ public: } static RWLock *lock; - static HashMap<StringName, ClassInfo, StringNameHasher> classes; - static HashMap<StringName, StringName, StringNameHasher> resource_base_extensions; - static HashMap<StringName, StringName, StringNameHasher> compat_classes; + static HashMap<StringName, ClassInfo> classes; + static HashMap<StringName, StringName> resource_base_extensions; + static HashMap<StringName, StringName> compat_classes; #ifdef DEBUG_METHODS_ENABLED static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount); diff --git a/core/color.cpp b/core/color.cpp index b2f5889166..88e57ec6e2 100644 --- a/core/color.cpp +++ b/core/color.cpp @@ -37,38 +37,38 @@ uint32_t Color::to_argb32() const { - uint32_t c = (uint8_t)(a * 255); + uint32_t c = (uint8_t)Math::round(a * 255); c <<= 8; - c |= (uint8_t)(r * 255); + c |= (uint8_t)Math::round(r * 255); c <<= 8; - c |= (uint8_t)(g * 255); + c |= (uint8_t)Math::round(g * 255); c <<= 8; - c |= (uint8_t)(b * 255); + c |= (uint8_t)Math::round(b * 255); return c; } uint32_t Color::to_abgr32() const { - uint32_t c = (uint8_t)(a * 255); + uint32_t c = (uint8_t)Math::round(a * 255); c <<= 8; - c |= (uint8_t)(b * 255); + c |= (uint8_t)Math::round(b * 255); c <<= 8; - c |= (uint8_t)(g * 255); + c |= (uint8_t)Math::round(g * 255); c <<= 8; - c |= (uint8_t)(r * 255); + c |= (uint8_t)Math::round(r * 255); return c; } uint32_t Color::to_rgba32() const { - uint32_t c = (uint8_t)(r * 255); + uint32_t c = (uint8_t)Math::round(r * 255); c <<= 8; - c |= (uint8_t)(g * 255); + c |= (uint8_t)Math::round(g * 255); c <<= 8; - c |= (uint8_t)(b * 255); + c |= (uint8_t)Math::round(b * 255); c <<= 8; - c |= (uint8_t)(a * 255); + c |= (uint8_t)Math::round(a * 255); return c; } @@ -368,7 +368,7 @@ Color Color::named(const String &p_name) { String _to_hex(float p_val) { - int v = p_val * 255; + int v = Math::round(p_val * 255); v = CLAMP(v, 0, 255); String ret; diff --git a/core/color_names.inc b/core/color_names.inc index b05684acc6..3ae42648d0 100644 --- a/core/color_names.inc +++ b/core/color_names.inc @@ -3,7 +3,7 @@ static Map<String, Color> _named_colors; static void _populate_named_colors() { - if(!_named_colors.empty()) return; + if (!_named_colors.empty()) return; _named_colors.insert("aliceblue", Color(0.94, 0.97, 1.00)); _named_colors.insert("antiquewhite", Color(0.98, 0.92, 0.84)); _named_colors.insert("aqua", Color(0.00, 1.00, 1.00)); diff --git a/core/command_queue_mt.h b/core/command_queue_mt.h index 3942b961d3..7978eaa7bf 100644 --- a/core/command_queue_mt.h +++ b/core/command_queue_mt.h @@ -54,9 +54,13 @@ #define _COMMA_10 , #define _COMMA_11 , #define _COMMA_12 , +#define _COMMA_13 , // 1-based comma separated list of ITEMs #define COMMA_SEP_LIST(ITEM, LENGTH) _COMMA_SEP_LIST_##LENGTH(ITEM) +#define _COMMA_SEP_LIST_13(ITEM) \ + _COMMA_SEP_LIST_12(ITEM) \ + , ITEM(13) #define _COMMA_SEP_LIST_12(ITEM) \ _COMMA_SEP_LIST_11(ITEM) \ , ITEM(12) @@ -97,6 +101,9 @@ // 1-based semicolon separated list of ITEMs #define SEMIC_SEP_LIST(ITEM, LENGTH) _SEMIC_SEP_LIST_##LENGTH(ITEM) +#define _SEMIC_SEP_LIST_13(ITEM) \ + _SEMIC_SEP_LIST_12(ITEM); \ + ITEM(13) #define _SEMIC_SEP_LIST_12(ITEM) \ _SEMIC_SEP_LIST_11(ITEM); \ ITEM(12) @@ -137,6 +144,9 @@ // 1-based space separated list of ITEMs #define SPACE_SEP_LIST(ITEM, LENGTH) _SPACE_SEP_LIST_##LENGTH(ITEM) +#define _SPACE_SEP_LIST_13(ITEM) \ + _SPACE_SEP_LIST_12(ITEM) \ + ITEM(13) #define _SPACE_SEP_LIST_12(ITEM) \ _SPACE_SEP_LIST_11(ITEM) \ ITEM(12) @@ -262,7 +272,7 @@ ss->sem->wait(); \ } -#define MAX_CMD_PARAMS 12 +#define MAX_CMD_PARAMS 13 class CommandQueueMT { @@ -290,15 +300,15 @@ class CommandQueueMT { }; DECL_CMD(0) - SPACE_SEP_LIST(DECL_CMD, 12) + SPACE_SEP_LIST(DECL_CMD, 13) /* comands that return */ DECL_CMD_RET(0) - SPACE_SEP_LIST(DECL_CMD_RET, 12) + SPACE_SEP_LIST(DECL_CMD_RET, 13) /* commands that don't return but sync */ DECL_CMD_SYNC(0) - SPACE_SEP_LIST(DECL_CMD_SYNC, 12) + SPACE_SEP_LIST(DECL_CMD_SYNC, 13) /***** BASE *******/ @@ -432,15 +442,15 @@ class CommandQueueMT { public: /* NORMAL PUSH COMMANDS */ DECL_PUSH(0) - SPACE_SEP_LIST(DECL_PUSH, 12) + SPACE_SEP_LIST(DECL_PUSH, 13) /* PUSH AND RET COMMANDS */ DECL_PUSH_AND_RET(0) - SPACE_SEP_LIST(DECL_PUSH_AND_RET, 12) + SPACE_SEP_LIST(DECL_PUSH_AND_RET, 13) /* PUSH AND RET SYNC COMMANDS*/ DECL_PUSH_AND_SYNC(0) - SPACE_SEP_LIST(DECL_PUSH_AND_SYNC, 12) + SPACE_SEP_LIST(DECL_PUSH_AND_SYNC, 13) void wait_and_flush_one() { ERR_FAIL_COND(!sync); diff --git a/core/engine.cpp b/core/engine.cpp index b2c34a853c..7c8024b946 100644 --- a/core/engine.cpp +++ b/core/engine.cpp @@ -30,6 +30,9 @@ #include "engine.h" +#include "authors.gen.h" +#include "donors.gen.h" +#include "license.gen.h" #include "version.h" #include "version_hash.gen.h" @@ -111,6 +114,78 @@ Dictionary Engine::get_version_info() const { return dict; } +static Array array_from_info(const char *const *info_list) { + Array arr; + for (int i = 0; info_list[i] != NULL; i++) { + arr.push_back(info_list[i]); + } + return arr; +} + +static Array array_from_info_count(const char *const *info_list, int info_count) { + Array arr; + for (int i = 0; i < info_count; i++) { + arr.push_back(info_list[i]); + } + return arr; +} + +Dictionary Engine::get_author_info() const { + Dictionary dict; + + dict["lead_developers"] = array_from_info(AUTHORS_LEAD_DEVELOPERS); + dict["project_managers"] = array_from_info(AUTHORS_PROJECT_MANAGERS); + dict["founders"] = array_from_info(AUTHORS_FOUNDERS); + dict["developers"] = array_from_info(AUTHORS_DEVELOPERS); + + return dict; +} + +Array Engine::get_copyright_info() const { + Array components; + for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) { + const ComponentCopyright &cp_info = COPYRIGHT_INFO[component_index]; + Dictionary component_dict; + component_dict["name"] = cp_info.name; + Array parts; + for (int i = 0; i < cp_info.part_count; i++) { + const ComponentCopyrightPart &cp_part = cp_info.parts[i]; + Dictionary part_dict; + part_dict["files"] = array_from_info_count(cp_part.files, cp_part.file_count); + part_dict["copyright"] = array_from_info_count(cp_part.copyright_statements, cp_part.copyright_count); + part_dict["license"] = cp_part.license; + parts.push_back(part_dict); + } + component_dict["parts"] = parts; + + components.push_back(component_dict); + } + return components; +} + +Dictionary Engine::get_donor_info() const { + Dictionary donors; + donors["platinum_sponsors"] = array_from_info(DONORS_SPONSOR_PLAT); + donors["gold_sponsors"] = array_from_info(DONORS_SPONSOR_GOLD); + donors["mini_sponsors"] = array_from_info(DONORS_SPONSOR_MINI); + donors["gold_donors"] = array_from_info(DONORS_GOLD); + donors["silver_donors"] = array_from_info(DONORS_SILVER); + donors["bronze_donors"] = array_from_info(DONORS_BRONZE); + return donors; +} + +Dictionary Engine::get_license_info() const { + Dictionary licenses; + for (int i = 0; i < LICENSE_COUNT; i++) { + licenses[LICENSE_NAMES[i]] = LICENSE_BODIES[i]; + } + return licenses; +} + +String Engine::get_license_text() const { + return String(GODOT_LICENSE_TEXT); +} + void Engine::add_singleton(const Singleton &p_singleton) { singletons.push_back(p_singleton); diff --git a/core/engine.h b/core/engine.h index 665992699a..031ba29cd6 100644 --- a/core/engine.h +++ b/core/engine.h @@ -118,6 +118,11 @@ public: #endif Dictionary get_version_info() const; + Dictionary get_author_info() const; + Array get_copyright_info() const; + Dictionary get_donor_info() const; + Dictionary get_license_info() const; + String get_license_text() const; Engine(); }; diff --git a/core/error_macros.h b/core/error_macros.h index 168b2e06fe..3587e01d54 100644 --- a/core/error_macros.h +++ b/core/error_macros.h @@ -311,14 +311,14 @@ extern bool _err_error_exists; _err_error_exists = false; \ } -#define WARN_DEPRECATED \ - { \ - static bool warning_shown=false;\ - if (!warning_shown) {\ - _err_print_error(FUNCTION_STR, __FILE__, __LINE__,"This method has been deprecated and will be removed in the future", ERR_HANDLER_WARNING); \ - _err_error_exists = false; \ - warning_shown=true;\ - }\ +#define WARN_DEPRECATED \ + { \ + static bool warning_shown = false; \ + if (!warning_shown) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future", ERR_HANDLER_WARNING); \ + _err_error_exists = false; \ + warning_shown = true; \ + } \ } #endif diff --git a/core/hashfuncs.h b/core/hashfuncs.h index ae99fa39c8..735e679d1e 100644 --- a/core/hashfuncs.h +++ b/core/hashfuncs.h @@ -33,6 +33,8 @@ #include "math_defs.h" #include "math_funcs.h" +#include "node_path.h" +#include "string_db.h" #include "typedefs.h" #include "ustring.h" @@ -131,6 +133,7 @@ static inline uint64_t make_uint64_t(T p_in) { } struct HashMapHasherDefault { + static _FORCE_INLINE_ uint32_t hash(const String &p_string) { return p_string.hash(); } static _FORCE_INLINE_ uint32_t hash(const char *p_cstr) { return hash_djb2(p_cstr); } static _FORCE_INLINE_ uint32_t hash(const uint64_t p_int) { return hash_one_uint64(p_int); } @@ -145,6 +148,10 @@ struct HashMapHasherDefault { static _FORCE_INLINE_ uint32_t hash(const uint8_t p_int) { return p_int; } static _FORCE_INLINE_ uint32_t hash(const int8_t p_int) { return (uint32_t)p_int; } static _FORCE_INLINE_ uint32_t hash(const wchar_t p_wchar) { return (uint32_t)p_wchar; } + + static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); } + static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); } + //static _FORCE_INLINE_ uint32_t hash(const void* p_ptr) { return uint32_t(uint64_t(p_ptr))*(0x9e3779b1L); } }; diff --git a/core/image.cpp b/core/image.cpp index 51fbe75dec..b0bed80a6f 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -1076,6 +1076,36 @@ void Image::shrink_x2() { } } +void Image::normalize() { + + bool used_mipmaps = has_mipmaps(); + if (used_mipmaps) { + clear_mipmaps(); + } + + lock(); + + for (int y = 0; y < height; y++) { + + for (int x = 0; x < width; x++) { + + Color c = get_pixel(x, y); + Vector3 v(c.r * 2.0 - 1.0, c.g * 2.0 - 1.0, c.b * 2.0 - 1.0); + v.normalize(); + c.r = v.x * 0.5 + 0.5; + c.g = v.y * 0.5 + 0.5; + c.b = v.z * 0.5 + 0.5; + set_pixel(x, y, c); + } + } + + unlock(); + + if (used_mipmaps) { + generate_mipmaps(true); + } +} + Error Image::generate_mipmaps(bool p_renormalize) { if (!_can_modify(format)) { @@ -2301,6 +2331,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha); ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear); ClassDB::bind_method(D_METHOD("normalmap_to_xy"), &Image::normalmap_to_xy); + ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb); ClassDB::bind_method(D_METHOD("bumpmap_to_normalmap", "bump_scale"), &Image::bumpmap_to_normalmap, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("blit_rect", "src", "src_rect", "dst"), &Image::blit_rect); @@ -2412,6 +2443,37 @@ void Image::normalmap_to_xy() { convert(Image::FORMAT_LA8); } +Ref<Image> Image::rgbe_to_srgb() { + + if (data.size() == 0) + return Ref<Image>(); + + ERR_FAIL_COND_V(format != FORMAT_RGBE9995, Ref<Image>()); + + Ref<Image> new_image; + new_image.instance(); + new_image->create(width, height, 0, Image::FORMAT_RGB8); + + lock(); + + new_image->lock(); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + new_image->set_pixel(col, row, get_pixel(col, row).to_srgb()); + } + } + + unlock(); + new_image->unlock(); + + if (has_mipmaps()) { + new_image->generate_mipmaps(); + } + + return new_image; +} + void Image::bumpmap_to_normalmap(float bump_scale) { ERR_FAIL_COND(!_can_modify(format)); convert(Image::FORMAT_RF); diff --git a/core/image.h b/core/image.h index 80a0c339dd..43516e2c0b 100644 --- a/core/image.h +++ b/core/image.h @@ -220,6 +220,7 @@ public: Error generate_mipmaps(bool p_renormalize = false); void clear_mipmaps(); + void normalize(); //for normal maps /** * Create a new image of a given size and format. Current image will be lost @@ -284,6 +285,7 @@ public: void premultiply_alpha(); void srgb_to_linear(); void normalmap_to_xy(); + Ref<Image> rgbe_to_srgb(); void bumpmap_to_normalmap(float bump_scale = 1.0); void blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest); diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 983b829d8d..786bec461b 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -112,7 +112,7 @@ void RotatedFileLogger::clear_old_backups() { int max_backups = max_files - 1; // -1 for the current file String basename = base_path.get_file().get_basename(); - String extension = "." + base_path.get_extension(); + String extension = base_path.get_extension(); DirAccess *da = DirAccess::open(base_path.get_base_dir()); if (!da) { @@ -123,7 +123,7 @@ void RotatedFileLogger::clear_old_backups() { String f = da->get_next(); Set<String> backups; while (f != String()) { - if (!da->current_is_dir() && f.begins_with(basename) && f.ends_with(extension) && f != base_path.get_file()) { + if (!da->current_is_dir() && f.begins_with(basename) && f.get_extension() == extension && f != base_path.get_file()) { backups.insert(f); } f = da->get_next(); @@ -152,7 +152,10 @@ void RotatedFileLogger::rotate_file() { OS::Time time = OS::get_singleton()->get_time(); sprintf(timestamp, "-%04d-%02d-%02d-%02d-%02d-%02d", date.year, date.month, date.day, time.hour, time.min, time.sec); - String backup_name = base_path.get_basename() + timestamp + "." + base_path.get_extension(); + String backup_name = base_path.get_basename() + timestamp; + if (base_path.get_extension() != String()) { + backup_name += "." + base_path.get_extension(); + } DirAccess *da = DirAccess::open(base_path.get_base_dir()); if (da) { diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp index b0f2ca754d..846c89510e 100644 --- a/core/io/multiplayer_api.cpp +++ b/core/io/multiplayer_api.cpp @@ -32,6 +32,61 @@ #include "core/io/marshalls.h" #include "scene/main/node.h" +_FORCE_INLINE_ bool _should_call_local(MultiplayerAPI::RPCMode mode, bool is_master, bool &r_skip_rpc) { + + switch (mode) { + + case MultiplayerAPI::RPC_MODE_DISABLED: { + //do nothing + } break; + case MultiplayerAPI::RPC_MODE_REMOTE: { + //do nothing also, no need to call local + } break; + case MultiplayerAPI::RPC_MODE_REMOTESYNC: + case MultiplayerAPI::RPC_MODE_MASTERSYNC: + case MultiplayerAPI::RPC_MODE_SLAVESYNC: + case MultiplayerAPI::RPC_MODE_SYNC: { + //call it, sync always results in call + return true; + } break; + case MultiplayerAPI::RPC_MODE_MASTER: { + if (is_master) + r_skip_rpc = true; //no other master so.. + return is_master; + } break; + case MultiplayerAPI::RPC_MODE_SLAVE: { + return !is_master; + } break; + } + return false; +} + +_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { + switch (mode) { + + case MultiplayerAPI::RPC_MODE_DISABLED: { + return false; + } break; + case MultiplayerAPI::RPC_MODE_REMOTE: { + return true; + } break; + case MultiplayerAPI::RPC_MODE_REMOTESYNC: + case MultiplayerAPI::RPC_MODE_SYNC: { + return true; + } break; + case MultiplayerAPI::RPC_MODE_MASTERSYNC: + case MultiplayerAPI::RPC_MODE_MASTER: { + return p_node->is_network_master(); + } break; + case MultiplayerAPI::RPC_MODE_SLAVESYNC: + case MultiplayerAPI::RPC_MODE_SLAVE: { + return !p_node->is_network_master() && p_remote_id == p_node->get_network_master(); + } break; + } + + return false; +} + void MultiplayerAPI::poll() { if (!network_peer.is_valid() || network_peer->get_connection_status() == NetworkedMultiplayerPeer::CONNECTION_DISCONNECTED) @@ -202,11 +257,19 @@ Node *MultiplayerAPI::_process_get_node(int p_from, const uint8_t *p_packet, int } void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { - if (!p_node->can_call_rpc(p_name, p_from)) - return; ERR_FAIL_COND(p_offset >= p_packet_len); + // Check that remote can call the RPC on this node + RPCMode rpc_mode = RPC_MODE_DISABLED; + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rpc_mode(p_name); + if (E) { + rpc_mode = E->get(); + } else if (p_node->get_script_instance()) { + rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_name); + } + ERR_FAIL_COND(!_can_call_mode(p_node, rpc_mode, p_from)); + int argc = p_packet[p_offset]; Vector<Variant> args; Vector<const Variant *> argp; @@ -238,11 +301,18 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_ void MultiplayerAPI::_process_rset(Node *p_node, const StringName &p_name, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset) { - if (!p_node->can_call_rset(p_name, p_from)) - return; - ERR_FAIL_COND(p_offset >= p_packet_len); + // Check that remote can call the RSET on this node + RPCMode rset_mode = RPC_MODE_DISABLED; + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rset_mode(p_name); + if (E) { + rset_mode = E->get(); + } else if (p_node->get_script_instance()) { + rset_mode = p_node->get_script_instance()->get_rset_mode(p_name); + } + ERR_FAIL_COND(!_can_call_mode(p_node, rset_mode, p_from)); + Variant value; decode_variant(value, &p_packet[p_offset], p_packet_len - p_offset); @@ -522,57 +592,6 @@ void MultiplayerAPI::_server_disconnected() { emit_signal("server_disconnected"); } -bool _should_call_native(Node::RPCMode mode, bool is_master, bool &r_skip_rpc) { - - switch (mode) { - - case Node::RPC_MODE_DISABLED: { - //do nothing - } break; - case Node::RPC_MODE_REMOTE: { - //do nothing also, no need to call local - } break; - case Node::RPC_MODE_SYNC: { - //call it, sync always results in call - return true; - } break; - case Node::RPC_MODE_MASTER: { - if (is_master) - r_skip_rpc = true; //no other master so.. - return is_master; - } break; - case Node::RPC_MODE_SLAVE: { - return !is_master; - } break; - } - return false; -} - -bool _should_call_script(ScriptInstance::RPCMode mode, bool is_master, bool &r_skip_rpc) { - switch (mode) { - - case ScriptInstance::RPC_MODE_DISABLED: { - //do nothing - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - //do nothing also, no need to call local - } break; - case ScriptInstance::RPC_MODE_SYNC: { - //call it, sync always results in call - return true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - if (is_master) - r_skip_rpc = true; //no other master so.. - return is_master; - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - return !is_master; - } break; - } - return false; -} - void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount) { ERR_FAIL_COND(!p_node->is_inside_tree()); @@ -587,17 +606,17 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const if (p_peer_id == 0 || p_peer_id == node_id || (p_peer_id < 0 && p_peer_id != -node_id)) { //check that send mode can use local call - const Map<StringName, Node::RPCMode>::Element *E = p_node->get_node_rpc_mode(p_method); + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rpc_mode(p_method); if (E) { - call_local_native = _should_call_native(E->get(), is_master, skip_rpc); + call_local_native = _should_call_local(E->get(), is_master, skip_rpc); } if (call_local_native) { // done below } else if (p_node->get_script_instance()) { //attempt with script - ScriptInstance::RPCMode rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_method); - call_local_script = _should_call_script(rpc_mode, is_master, skip_rpc); + RPCMode rpc_mode = p_node->get_script_instance()->get_rpc_mode(p_method); + call_local_script = _should_call_local(rpc_mode, is_master, skip_rpc); } } @@ -643,10 +662,10 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const bool set_local = false; - const Map<StringName, Node::RPCMode>::Element *E = p_node->get_node_rset_mode(p_property); + const Map<StringName, RPCMode>::Element *E = p_node->get_node_rset_mode(p_property); if (E) { - set_local = _should_call_native(E->get(), is_master, skip_rset); + set_local = _should_call_local(E->get(), is_master, skip_rset); } if (set_local) { @@ -660,9 +679,9 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const } } else if (p_node->get_script_instance()) { //attempt with script - ScriptInstance::RPCMode rpc_mode = p_node->get_script_instance()->get_rset_mode(p_property); + RPCMode rpc_mode = p_node->get_script_instance()->get_rset_mode(p_property); - set_local = _should_call_script(rpc_mode, is_master, skip_rset); + set_local = _should_call_local(rpc_mode, is_master, skip_rset); if (set_local) { @@ -778,6 +797,15 @@ void MultiplayerAPI::_bind_methods() { ADD_SIGNAL(MethodInfo("connected_to_server")); ADD_SIGNAL(MethodInfo("connection_failed")); ADD_SIGNAL(MethodInfo("server_disconnected")); + + BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); + BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); + BIND_ENUM_CONSTANT(RPC_MODE_SYNC); + BIND_ENUM_CONSTANT(RPC_MODE_MASTER); + BIND_ENUM_CONSTANT(RPC_MODE_SLAVE); + BIND_ENUM_CONSTANT(RPC_MODE_REMOTESYNC); + BIND_ENUM_CONSTANT(RPC_MODE_MASTERSYNC); + BIND_ENUM_CONSTANT(RPC_MODE_SLAVESYNC); } MultiplayerAPI::MultiplayerAPI() { diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h index 64f59d32d8..ef56c4c7f2 100644 --- a/core/io/multiplayer_api.h +++ b/core/io/multiplayer_api.h @@ -87,6 +87,18 @@ public: NETWORK_COMMAND_RAW, }; + enum RPCMode { + + RPC_MODE_DISABLED, // No rpc for this method, calls to this will be blocked (default) + RPC_MODE_REMOTE, // Using rpc() on it will call method / set property in all remote peers + RPC_MODE_SYNC, // Using rpc() on it will call method / set property in all remote peers and locally + RPC_MODE_MASTER, // Using rpc() on it will call method on wherever the master is, be it local or remote + RPC_MODE_SLAVE, // Using rpc() on it will call method for all slaves + RPC_MODE_REMOTESYNC, // Same as RPC_MODE_SYNC, compatibility + RPC_MODE_MASTERSYNC, // Using rpc() on it will call method / set property in the master peer and locally + RPC_MODE_SLAVESYNC, // Using rpc() on it will call method / set property in all slave peers and locally + }; + void poll(); void clear(); void set_root_node(Node *p_node); @@ -117,4 +129,6 @@ public: ~MultiplayerAPI(); }; +VARIANT_ENUM_CAST(MultiplayerAPI::RPCMode); + #endif // MULTIPLAYER_PROTOCOL_H diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index e01e2a84c5..16d5e3c282 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -175,7 +175,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error, const S String prop = c.substr(0, p).strip_edges(); String value = c.substr(p + 1, c.length()).strip_edges(); - if (prop == "X-Language") { + if (prop == "X-Language" || prop == "Language") { translation->set_locale(value); } } diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index 6908d7831d..021391da83 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -96,11 +96,11 @@ void AStar::remove_point(int p_id) { Point *p = points[p_id]; - for (int i = 0; i < p->neighbours.size(); i++) { + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { - Segment s(p_id, p->neighbours[i]->id); + Segment s(p_id, E->get()->id); segments.erase(s); - p->neighbours[i]->neighbours.erase(p); + E->get()->neighbours.erase(p); } memdelete(p); @@ -115,10 +115,10 @@ void AStar::connect_points(int p_id, int p_with_id, bool bidirectional) { Point *a = points[p_id]; Point *b = points[p_with_id]; - a->neighbours.push_back(b); + a->neighbours.insert(b); if (bidirectional) - b->neighbours.push_back(a); + b->neighbours.insert(a); Segment s(p_id, p_with_id); if (s.from == p_id) { @@ -168,8 +168,8 @@ PoolVector<int> AStar::get_point_connections(int p_id) { Point *p = points[p_id]; - for (int i = 0; i < p->neighbours.size(); i++) { - point_list.push_back(p->neighbours[i]->id); + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { + point_list.push_back(E->get()->id); } return point_list; @@ -242,9 +242,9 @@ bool AStar::_solve(Point *begin_point, Point *end_point) { bool found_route = false; - for (int i = 0; i < begin_point->neighbours.size(); i++) { + for (Set<Point *>::Element *E = begin_point->neighbours.front(); E; E = E->next()) { - Point *n = begin_point->neighbours[i]; + Point *n = E->get(); n->prev_point = begin_point; n->distance = _compute_cost(begin_point->id, n->id) * n->weight_scale; n->last_pass = pass; @@ -283,12 +283,10 @@ bool AStar::_solve(Point *begin_point, Point *end_point) { } Point *p = least_cost_point->self(); - // Open the neighbours for search - int es = p->neighbours.size(); - for (int i = 0; i < es; i++) { + for (Set<Point *>::Element *E = p->neighbours.front(); E; E = E->next()) { - Point *e = p->neighbours[i]; + Point *e = E->get(); real_t distance = _compute_cost(p->id, e->id) * e->weight_scale + p->distance; diff --git a/core/math/a_star.h b/core/math/a_star.h index f89e17c7bb..8c1b5f64cb 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -54,7 +54,7 @@ class AStar : public Reference { real_t weight_scale; uint64_t last_pass; - Vector<Point *> neighbours; + Set<Point *> neighbours; // Used for pathfinding Point *prev_point; diff --git a/core/math/aabb.h b/core/math/aabb.h index 39b8f403e7..cdb8eb48a3 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -76,6 +76,7 @@ public: _FORCE_INLINE_ bool smits_intersect_ray(const Vector3 &p_from, const Vector3 &p_dir, real_t t0, real_t t1) const; _FORCE_INLINE_ bool intersects_convex_shape(const Plane *p_planes, int p_plane_count) const; + _FORCE_INLINE_ bool inside_convex_shape(const Plane *p_planes, int p_plane_count) const; bool intersects_plane(const Plane &p_plane) const; _FORCE_INLINE_ bool has_point(const Vector3 &p_point) const; @@ -207,6 +208,25 @@ bool AABB::intersects_convex_shape(const Plane *p_planes, int p_plane_count) con return true; } +bool AABB::inside_convex_shape(const Plane *p_planes, int p_plane_count) const { + + Vector3 half_extents = size * 0.5; + Vector3 ofs = position + half_extents; + + for (int i = 0; i < p_plane_count; i++) { + const Plane &p = p_planes[i]; + Vector3 point( + (p.normal.x < 0) ? -half_extents.x : half_extents.x, + (p.normal.y < 0) ? -half_extents.y : half_extents.y, + (p.normal.z < 0) ? -half_extents.z : half_extents.z); + point += ofs; + if (p.is_point_over(point)) + return false; + } + + return true; +} + bool AABB::has_point(const Vector3 &p_point) const { if (p_point.x < position.x) diff --git a/core/math/delaunay.cpp b/core/math/delaunay.cpp new file mode 100644 index 0000000000..8cae92b7c0 --- /dev/null +++ b/core/math/delaunay.cpp @@ -0,0 +1 @@ +#include "delaunay.h" diff --git a/core/math/delaunay.h b/core/math/delaunay.h new file mode 100644 index 0000000000..09aebc773f --- /dev/null +++ b/core/math/delaunay.h @@ -0,0 +1,145 @@ +#ifndef DELAUNAY_H +#define DELAUNAY_H + +#include "math_2d.h" + +class Delaunay2D { +public: + struct Triangle { + + int points[3]; + bool bad; + Triangle() { bad = false; } + Triangle(int p_a, int p_b, int p_c) { + points[0] = p_a; + points[1] = p_b; + points[2] = p_c; + bad = false; + } + }; + + struct Edge { + int edge[2]; + bool bad; + Edge() { bad = false; } + Edge(int p_a, int p_b) { + bad = false; + edge[0] = p_a; + edge[1] = p_b; + } + }; + + static bool circum_circle_contains(const Vector<Vector2> &p_vertices, const Triangle &p_triangle, int p_vertex) { + + Vector2 p1 = p_vertices[p_triangle.points[0]]; + Vector2 p2 = p_vertices[p_triangle.points[1]]; + Vector2 p3 = p_vertices[p_triangle.points[2]]; + + real_t ab = p1.x * p1.x + p1.y * p1.y; + real_t cd = p2.x * p2.x + p2.y * p2.y; + real_t ef = p3.x * p3.x + p3.y * p3.y; + + Vector2 circum( + (ab * (p3.y - p2.y) + cd * (p1.y - p3.y) + ef * (p2.y - p1.y)) / (p1.x * (p3.y - p2.y) + p2.x * (p1.y - p3.y) + p3.x * (p2.y - p1.y)), + (ab * (p3.x - p2.x) + cd * (p1.x - p3.x) + ef * (p2.x - p1.x)) / (p1.y * (p3.x - p2.x) + p2.y * (p1.x - p3.x) + p3.y * (p2.x - p1.x))); + + circum *= 0.5; + float r = p1.distance_squared_to(circum); + float d = p_vertices[p_vertex].distance_squared_to(circum); + return d <= r; + } + + static bool edge_compare(const Vector<Vector2> &p_vertices, const Edge &p_a, const Edge &p_b) { + if (p_vertices[p_a.edge[0]].distance_to(p_vertices[p_b.edge[0]]) < CMP_EPSILON && p_vertices[p_a.edge[1]].distance_to(p_vertices[p_b.edge[1]]) < CMP_EPSILON) { + return true; + } + + if (p_vertices[p_a.edge[0]].distance_to(p_vertices[p_b.edge[1]]) < CMP_EPSILON && p_vertices[p_a.edge[1]].distance_to(p_vertices[p_b.edge[0]]) < CMP_EPSILON) { + return true; + } + + return false; + } + + static Vector<Triangle> triangulate(const Vector<Vector2> &p_points) { + + Vector<Vector2> points = p_points; + Vector<Triangle> triangles; + + Rect2 rect; + for (int i = 0; i < p_points.size(); i++) { + if (i == 0) { + rect.position = p_points[i]; + } else { + rect.expand_to(p_points[i]); + } + } + + float delta_max = MAX(rect.size.width, rect.size.height); + Vector2 center = rect.position + rect.size * 0.5; + + points.push_back(Vector2(center.x - 20 * delta_max, center.y - delta_max)); + points.push_back(Vector2(center.x, center.y + 20 * delta_max)); + points.push_back(Vector2(center.x + 20 * delta_max, center.y - delta_max)); + + triangles.push_back(Triangle(p_points.size() + 0, p_points.size() + 1, p_points.size() + 2)); + + for (int i = 0; i < p_points.size(); i++) { + //std::cout << "Traitement du point " << *p << std::endl; + //std::cout << "_triangles contains " << _triangles.size() << " elements" << std::endl; + + Vector<Edge> polygon; + + for (int j = 0; j < triangles.size(); j++) { + if (circum_circle_contains(points, triangles[j], i)) { + triangles[j].bad = true; + polygon.push_back(Edge(triangles[j].points[0], triangles[j].points[1])); + polygon.push_back(Edge(triangles[j].points[1], triangles[j].points[2])); + polygon.push_back(Edge(triangles[j].points[2], triangles[j].points[0])); + } + } + + for (int j = 0; j < triangles.size(); j++) { + if (triangles[j].bad) { + triangles.remove(j); + j--; + } + } + + for (int j = 0; j < polygon.size(); j++) { + for (int k = j + 1; k < polygon.size(); k++) { + if (edge_compare(points, polygon[j], polygon[k])) { + polygon[j].bad = true; + polygon[k].bad = true; + } + } + } + + for (int j = 0; j < polygon.size(); j++) { + + if (polygon[j].bad) { + continue; + } + triangles.push_back(Triangle(polygon[j].edge[0], polygon[j].edge[1], i)); + } + } + + for (int i = 0; i < triangles.size(); i++) { + bool invalid = false; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] >= p_points.size()) { + invalid = true; + break; + } + } + if (invalid) { + triangles.remove(i); + i--; + } + } + + return triangles; + } +}; + +#endif // DELAUNAY_H diff --git a/core/math/matrix3.cpp b/core/math/matrix3.cpp index 8ee8ccb457..2371f49561 100644 --- a/core/math/matrix3.cpp +++ b/core/math/matrix3.cpp @@ -356,8 +356,7 @@ void Basis::rotate(const Quat &p_quat) { *this = rotated(p_quat); } -// TODO: rename this to get_rotation_euler -Vector3 Basis::get_rotation() const { +Vector3 Basis::get_rotation_euler() const { // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). // See the comment in get_scale() for further information. @@ -371,6 +370,20 @@ Vector3 Basis::get_rotation() const { return m.get_euler(); } +Quat Basis::get_rotation_quat() const { + // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, + // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). + // See the comment in get_scale() for further information. + Basis m = orthonormalized(); + real_t det = m.determinant(); + if (det < 0) { + // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles. + m.scale(Vector3(-1, -1, -1)); + } + + return m.get_quat(); +} + void Basis::get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const { // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). @@ -591,10 +604,9 @@ Basis::operator String() const { } Quat Basis::get_quat() const { - //commenting this check because precision issues cause it to fail when it shouldn't - //#ifdef MATH_CHECKS - //ERR_FAIL_COND_V(is_rotation() == false, Quat()); - //#endif +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_rotation() == false, Quat()); +#endif real_t trace = elements[0][0] + elements[1][1] + elements[2][2]; real_t temp[4]; @@ -828,7 +840,7 @@ void Basis::set_diagonal(const Vector3 p_diag) { } Basis Basis::slerp(const Basis &target, const real_t &t) const { - // TODO: implement this directly without using quaternions to make it more efficient +// TODO: implement this directly without using quaternions to make it more efficient #ifdef MATH_CHECKS ERR_FAIL_COND_V(is_rotation() == false, Basis()); ERR_FAIL_COND_V(target.is_rotation() == false, Basis()); diff --git a/core/math/matrix3.h b/core/math/matrix3.h index 63d4f5d79d..cd1b51baa6 100644 --- a/core/math/matrix3.h +++ b/core/math/matrix3.h @@ -84,9 +84,11 @@ public: void rotate(const Quat &p_quat); Basis rotated(const Quat &p_quat) const; - Vector3 get_rotation() const; + Vector3 get_rotation_euler() const; void get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const; void get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const; + Quat get_rotation_quat() const; + Vector3 get_rotation() const { return get_rotation_euler(); }; Vector3 rotref_posscale_decomposition(Basis &rotref) const; diff --git a/core/math/quat.cpp b/core/math/quat.cpp index b938fc3cfd..67c9048a41 100644 --- a/core/math/quat.cpp +++ b/core/math/quat.cpp @@ -139,15 +139,15 @@ bool Quat::is_normalized() const { Quat Quat::inverse() const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V(is_normalized() == false, Quat(0, 0, 0, 0)); + ERR_FAIL_COND_V(is_normalized() == false, Quat()); #endif return Quat(-x, -y, -z, w); } Quat Quat::slerp(const Quat &q, const real_t &t) const { #ifdef MATH_CHECKS - ERR_FAIL_COND_V(is_normalized() == false, Quat(0, 0, 0, 0)); - ERR_FAIL_COND_V(q.is_normalized() == false, Quat(0, 0, 0, 0)); + ERR_FAIL_COND_V(is_normalized() == false, Quat()); + ERR_FAIL_COND_V(q.is_normalized() == false, Quat()); #endif Quat to1; real_t omega, cosom, sinom, scale0, scale1; @@ -192,7 +192,10 @@ Quat Quat::slerp(const Quat &q, const real_t &t) const { } Quat Quat::slerpni(const Quat &q, const real_t &t) const { - +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Quat()); + ERR_FAIL_COND_V(q.is_normalized() == false, Quat()); +#endif const Quat &from = *this; real_t dot = from.dot(q); @@ -211,7 +214,10 @@ Quat Quat::slerpni(const Quat &q, const real_t &t) const { } Quat Quat::cubic_slerp(const Quat &q, const Quat &prep, const Quat &postq, const real_t &t) const { - +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, Quat()); + ERR_FAIL_COND_V(q.is_normalized() == false, Quat()); +#endif //the only way to do slerp :| real_t t2 = (1.0 - t) * t * 2; Quat sp = this->slerp(q, t); diff --git a/core/math/quat.h b/core/math/quat.h index 3e1344a913..6dc8d66f60 100644 --- a/core/math/quat.h +++ b/core/math/quat.h @@ -84,7 +84,9 @@ public: } _FORCE_INLINE_ Vector3 xform(const Vector3 &v) const { - +#ifdef MATH_CHECKS + ERR_FAIL_COND_V(is_normalized() == false, v); +#endif Vector3 u(x, y, z); Vector3 uv = u.cross(v); return v + ((uv * w) + u.cross(uv)) * ((real_t)2); diff --git a/core/math/transform.cpp b/core/math/transform.cpp index 7cd186ca60..d1e190f4b9 100644 --- a/core/math/transform.cpp +++ b/core/math/transform.cpp @@ -120,11 +120,11 @@ Transform Transform::interpolate_with(const Transform &p_transform, real_t p_c) /* not sure if very "efficient" but good enough? */ Vector3 src_scale = basis.get_scale(); - Quat src_rot = basis.orthonormalized(); + Quat src_rot = basis.get_rotation_quat(); Vector3 src_loc = origin; Vector3 dst_scale = p_transform.basis.get_scale(); - Quat dst_rot = p_transform.basis; + Quat dst_rot = p_transform.basis.get_rotation_quat(); Vector3 dst_loc = p_transform.origin; Transform dst; //this could be made faster by using a single function in Basis.. diff --git a/core/math/triangle_mesh.cpp b/core/math/triangle_mesh.cpp index edd4ad3441..5475f733c3 100644 --- a/core/math/triangle_mesh.cpp +++ b/core/math/triangle_mesh.cpp @@ -88,6 +88,26 @@ int TriangleMesh::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, in return index; } +void TriangleMesh::get_indices(PoolVector<int> *r_triangles_indices) const { + + if (!valid) + return; + + const int triangles_num = triangles.size(); + + // Parse vertices indices + PoolVector<Triangle>::Read triangles_read = triangles.read(); + + r_triangles_indices->resize(triangles_num * 3); + PoolVector<int>::Write r_indices_write = r_triangles_indices->write(); + + for (int i = 0; i < triangles_num; ++i) { + r_indices_write[3 * i + 0] = triangles_read[i].indices[0]; + r_indices_write[3 * i + 1] = triangles_read[i].indices[1]; + r_indices_write[3 * i + 2] = triangles_read[i].indices[2]; + } +} + void TriangleMesh::create(const PoolVector<Vector3> &p_faces) { valid = false; @@ -490,6 +510,222 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V return inters; } +bool TriangleMesh::intersect_convex_shape(const Plane *p_planes, int p_plane_count) const { + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); + + //p_fully_inside = true; + + enum { + TEST_AABB_BIT = 0, + VISIT_LEFT_BIT = 1, + VISIT_RIGHT_BIT = 2, + VISIT_DONE_BIT = 3, + VISITED_BIT_SHIFT = 29, + NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1, + VISITED_BIT_MASK = ~NODE_IDX_MASK, + + }; + + int level = 0; + + PoolVector<Triangle>::Read trianglesr = triangles.read(); + PoolVector<Vector3>::Read verticesr = vertices.read(); + PoolVector<BVH>::Read bvhr = bvh.read(); + + const Triangle *triangleptr = trianglesr.ptr(); + const Vector3 *vertexptr = verticesr.ptr(); + int pos = bvh.size() - 1; + const BVH *bvhptr = bvhr.ptr(); + + stack[0] = pos; + while (true) { + + uint32_t node = stack[level] & NODE_IDX_MASK; + const BVH &b = bvhptr[node]; + bool done = false; + + switch (stack[level] >> VISITED_BIT_SHIFT) { + case TEST_AABB_BIT: { + + bool valid = b.aabb.intersects_convex_shape(p_planes, p_plane_count); + if (!valid) { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + if (b.face_index >= 0) { + + const Triangle &s = triangleptr[b.face_index]; + + for (int j = 0; j < 3; ++j) { + const Vector3 &point = vertexptr[s.indices[j]]; + const Vector3 &next_point = vertexptr[s.indices[(j + 1) % 3]]; + Vector3 res; + bool over = true; + for (int i = 0; i < p_plane_count; i++) { + const Plane &p = p_planes[i]; + + if (p.intersects_segment(point, next_point, &res)) { + bool inisde = true; + for (int k = 0; k < p_plane_count; k++) { + if (k == i) continue; + const Plane &pp = p_planes[k]; + if (pp.is_point_over(res)) { + inisde = false; + break; + } + } + if (inisde) return true; + } + + if (p.is_point_over(point)) { + over = false; + break; + } + } + if (over) return true; + } + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node; + } + } + continue; + } + case VISIT_LEFT_BIT: { + + stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.left | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_RIGHT_BIT: { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.right | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_DONE_BIT: { + + if (level == 0) { + done = true; + break; + } else + level--; + continue; + } + } + + if (done) + break; + } + + return false; +} + +bool TriangleMesh::inside_convex_shape(const Plane *p_planes, int p_plane_count, Vector3 p_scale) const { + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); + + enum { + TEST_AABB_BIT = 0, + VISIT_LEFT_BIT = 1, + VISIT_RIGHT_BIT = 2, + VISIT_DONE_BIT = 3, + VISITED_BIT_SHIFT = 29, + NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1, + VISITED_BIT_MASK = ~NODE_IDX_MASK, + + }; + + int level = 0; + + PoolVector<Triangle>::Read trianglesr = triangles.read(); + PoolVector<Vector3>::Read verticesr = vertices.read(); + PoolVector<BVH>::Read bvhr = bvh.read(); + + Transform scale(Basis().scaled(p_scale)); + + const Triangle *triangleptr = trianglesr.ptr(); + const Vector3 *vertexptr = verticesr.ptr(); + int pos = bvh.size() - 1; + const BVH *bvhptr = bvhr.ptr(); + + stack[0] = pos; + while (true) { + + uint32_t node = stack[level] & NODE_IDX_MASK; + const BVH &b = bvhptr[node]; + bool done = false; + + switch (stack[level] >> VISITED_BIT_SHIFT) { + case TEST_AABB_BIT: { + + bool intersects = scale.xform(b.aabb).intersects_convex_shape(p_planes, p_plane_count); + if (!intersects) return false; + + bool inside = scale.xform(b.aabb).inside_convex_shape(p_planes, p_plane_count); + if (inside) { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + if (b.face_index >= 0) { + const Triangle &s = triangleptr[b.face_index]; + for (int j = 0; j < 3; ++j) { + Vector3 point = scale.xform(vertexptr[s.indices[j]]); + for (int i = 0; i < p_plane_count; i++) { + const Plane &p = p_planes[i]; + if (p.is_point_over(point)) return false; + } + } + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node; + } + } + continue; + } + case VISIT_LEFT_BIT: { + + stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.left | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_RIGHT_BIT: { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.right | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_DONE_BIT: { + + if (level == 0) { + done = true; + break; + } else + level--; + continue; + } + } + + if (done) + break; + } + + return true; +} + bool TriangleMesh::is_valid() const { return valid; diff --git a/core/math/triangle_mesh.h b/core/math/triangle_mesh.h index 9f145f2afb..bf793fc50f 100644 --- a/core/math/triangle_mesh.h +++ b/core/math/triangle_mesh.h @@ -89,9 +89,15 @@ public: bool is_valid() const; bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal) const; bool intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal) const; + bool intersect_convex_shape(const Plane *p_planes, int p_plane_count) const; + bool inside_convex_shape(const Plane *p_planes, int p_plane_count, Vector3 p_scale = Vector3(1, 1, 1)) const; Vector3 get_area_normal(const AABB &p_aabb) const; PoolVector<Face3> get_faces() const; + PoolVector<Triangle> get_triangles() const { return triangles; } + PoolVector<Vector3> get_vertices() const { return vertices; } + void get_indices(PoolVector<int> *p_triangles_indices) const; + void create(const PoolVector<Vector3> &p_faces); TriangleMesh(); }; diff --git a/core/method_ptrcall.h b/core/method_ptrcall.h index 2007c3def5..677e8e1fb2 100644 --- a/core/method_ptrcall.h +++ b/core/method_ptrcall.h @@ -214,6 +214,50 @@ struct PtrToArg<const T *> { } \ } +#define MAKE_VECARG_ALT(m_type, m_type_alt) \ + template <> \ + struct PtrToArg<Vector<m_type_alt> > { \ + _FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) { \ + const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \ + Vector<m_type_alt> ret; \ + int len = dvs->size(); \ + ret.resize(len); \ + { \ + PoolVector<m_type>::Read r = dvs->read(); \ + for (int i = 0; i < len; i++) { \ + ret[i] = r[i]; \ + } \ + } \ + return ret; \ + } \ + _FORCE_INLINE_ static void encode(Vector<m_type_alt> p_vec, void *p_ptr) { \ + PoolVector<m_type> *dv = reinterpret_cast<PoolVector<m_type> *>(p_ptr); \ + int len = p_vec.size(); \ + dv->resize(len); \ + { \ + PoolVector<m_type>::Write w = dv->write(); \ + for (int i = 0; i < len; i++) { \ + w[i] = p_vec[i]; \ + } \ + } \ + } \ + }; \ + template <> \ + struct PtrToArg<const Vector<m_type_alt> &> { \ + _FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) { \ + const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \ + Vector<m_type_alt> ret; \ + int len = dvs->size(); \ + ret.resize(len); \ + { \ + PoolVector<m_type>::Read r = dvs->read(); \ + for (int i = 0; i < len; i++) { \ + ret[i] = r[i]; \ + } \ + } \ + return ret; \ + } \ + } MAKE_VECARG(String); MAKE_VECARG(uint8_t); MAKE_VECARG(int); @@ -221,6 +265,7 @@ MAKE_VECARG(float); MAKE_VECARG(Vector2); MAKE_VECARG(Vector3); MAKE_VECARG(Color); +MAKE_VECARG_ALT(String, StringName); //for stuff that gets converted to Array vectors #define MAKE_VECARR(m_type) \ diff --git a/core/node_path.cpp b/core/node_path.cpp index 64983fc091..487d5ee8c6 100644 --- a/core/node_path.cpp +++ b/core/node_path.cpp @@ -32,10 +32,7 @@ #include "print_string.h" -uint32_t NodePath::hash() const { - - if (!data) - return 0; +void NodePath::_update_hash_cache() const { uint32_t h = data->absolute ? 1 : 0; int pc = data->path.size(); @@ -49,13 +46,15 @@ uint32_t NodePath::hash() const { h = h ^ ssn[i].hash(); } - return h; + data->hash_cache_valid = true; + data->hash_cache = h; } void NodePath::prepend_period() { if (data->path.size() && data->path[0].operator String() != ".") { data->path.insert(0, "."); + data->hash_cache_valid = false; } } @@ -114,21 +113,33 @@ bool NodePath::operator==(const NodePath &p_path) const { if (data->absolute != p_path.data->absolute) return false; - if (data->path.size() != p_path.data->path.size()) + int path_size = data->path.size(); + + if (path_size != p_path.data->path.size()) { return false; + } + + int subpath_size = data->subpath.size(); - if (data->subpath.size() != p_path.data->subpath.size()) + if (subpath_size != p_path.data->subpath.size()) { return false; + } - for (int i = 0; i < data->path.size(); i++) { + const StringName *l_path_ptr = data->path.ptr(); + const StringName *r_path_ptr = p_path.data->path.ptr(); + + for (int i = 0; i < path_size; i++) { - if (data->path[i] != p_path.data->path[i]) + if (l_path_ptr[i] != r_path_ptr[i]) return false; } - for (int i = 0; i < data->subpath.size(); i++) { + const StringName *l_subpath_ptr = data->subpath.ptr(); + const StringName *r_subpath_ptr = p_path.data->subpath.ptr(); + + for (int i = 0; i < subpath_size; i++) { - if (data->subpath[i] != p_path.data->subpath[i]) + if (l_subpath_ptr[i] != r_subpath_ptr[i]) return false; } @@ -286,6 +297,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) { data->absolute = p_absolute; data->path = p_path; data->has_slashes = true; + data->hash_cache_valid = false; } NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) { @@ -301,6 +313,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p data->path = p_path; data->subpath = p_subpath; data->has_slashes = true; + data->hash_cache_valid = false; } void NodePath::simplify() { @@ -324,6 +337,7 @@ void NodePath::simplify() { } } } + data->hash_cache_valid = false; } NodePath NodePath::simplified() const { @@ -396,6 +410,7 @@ NodePath::NodePath(const String &p_path) { data->absolute = absolute ? true : false; data->has_slashes = has_slashes; data->subpath = subpath; + data->hash_cache_valid = false; if (slices == 0) return; diff --git a/core/node_path.h b/core/node_path.h index 288f39721f..71235029af 100644 --- a/core/node_path.h +++ b/core/node_path.h @@ -47,11 +47,15 @@ class NodePath { StringName concatenated_subpath; bool absolute; bool has_slashes; + mutable bool hash_cache_valid; + mutable uint32_t hash_cache; }; - Data *data; + mutable Data *data; void unref(); + void _update_hash_cache() const; + public: _FORCE_INLINE_ StringName get_sname() const { @@ -78,7 +82,14 @@ public: NodePath get_parent() const; - uint32_t hash() const; + _FORCE_INLINE_ uint32_t hash() const { + if (!data) + return 0; + if (!data->hash_cache_valid) { + _update_hash_cache(); + } + return data->hash_cache; + } operator String() const; bool is_empty() const; diff --git a/core/object.cpp b/core/object.cpp index 239700a4ab..1d2aeb7ba5 100644 --- a/core/object.cpp +++ b/core/object.cpp @@ -1677,6 +1677,7 @@ void Object::_bind_methods() { #ifdef TOOLS_ENABLED MethodInfo miget("_get", PropertyInfo(Variant::STRING, "property")); miget.return_val.name = "Variant"; + miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; BIND_VMETHOD(miget); MethodInfo plget("_get_property_list"); diff --git a/core/object.h b/core/object.h index 7963a43fd6..8dc3426d1d 100644 --- a/core/object.h +++ b/core/object.h @@ -31,6 +31,7 @@ #ifndef OBJECT_H #define OBJECT_H +#include "hash_map.h" #include "list.h" #include "map.h" #include "os/rw_lock.h" @@ -85,6 +86,7 @@ enum PropertyHint { PROPERTY_HINT_PROPERTY_OF_INSTANCE, ///< a property of an instance PROPERTY_HINT_PROPERTY_OF_SCRIPT, ///< a property of a script & base PROPERTY_HINT_OBJECT_TOO_BIG, ///< object is too big to send + PROPERTY_HINT_NODE_PATH_VALID_TYPES, PROPERTY_HINT_MAX, // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit }; @@ -450,7 +452,7 @@ private: Signal() { lock = 0; } }; - HashMap<StringName, Signal, StringNameHasher> signal_map; + HashMap<StringName, Signal> signal_map; List<Connection> connections; #ifdef DEBUG_ENABLED SafeRefCount _lock_index; diff --git a/core/os/os.h b/core/os/os.h index b36f94060c..adf01a90e7 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -232,6 +232,7 @@ public: virtual Size2 get_layered_buffer_size() { return Size2(0, 0); } virtual void swap_layered_buffer() {} + virtual void set_ime_active(const bool p_active) {} virtual void set_ime_position(const Point2 &p_pos) {} virtual void set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp) {} diff --git a/core/project_settings.cpp b/core/project_settings.cpp index ef485cb3b2..a7bfc8895b 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -137,7 +137,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { else { if (p_name == CoreStringNames::get_singleton()->_custom_features) { - Vector<String> custom_feature_array = p_value; + Vector<String> custom_feature_array = String(p_value).split(","); for (int i = 0; i < custom_feature_array.size(); i++) { custom_features.insert(custom_feature_array[i]); @@ -1071,7 +1071,6 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("rendering/quality/intended_usage/framebuffer_mode", 2); GLOBAL_DEF("debug/settings/profiler/max_functions", 16384); - GLOBAL_DEF("debug/settings/performance/update_frequency_msec", 250); //assigning here, because using GLOBAL_GET on every block for compressing can be slow Compression::zstd_long_distance_matching = GLOBAL_DEF("compression/formats/zstd/long_distance_matching", false); diff --git a/core/resource.cpp b/core/resource.cpp index 179333aa14..87ff4d3c2a 100644 --- a/core/resource.cpp +++ b/core/resource.cpp @@ -187,7 +187,6 @@ Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Res void Resource::configure_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource> > &remap_cache) { - print_line("configure for local: " + get_class()); List<PropertyInfo> plist; get_property_list(&plist); @@ -226,15 +225,20 @@ Ref<Resource> Resource::duplicate(bool p_subresources) const { if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; - Variant p = get(E->get().name).duplicate(true); - if (p.get_type() == Variant::OBJECT && p_subresources) { + Variant p = get(E->get().name); + + if ((p.get_type() == Variant::DICTIONARY || p.get_type() == Variant::ARRAY)) { + p = p.duplicate(p_subresources); //does not make a long of sense but should work? + } else if (p.get_type() == Variant::OBJECT && (p_subresources || (E->get().usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE))) { RES sr = p; - if (sr.is_valid()) - p = sr->duplicate(true); - } + if (sr.is_valid()) { + r->set(E->get().name, sr->duplicate(p_subresources)); + } + } else { - r->set(E->get().name, p); + r->set(E->get().name, p); + } } return Ref<Resource>(r); @@ -288,7 +292,7 @@ uint32_t Resource::hash_edited_version() const { for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { - if (E->get().type == Variant::OBJECT && E->get().hint == PROPERTY_HINT_RESOURCE_TYPE) { + if (E->get().usage & PROPERTY_USAGE_STORAGE && E->get().type == Variant::OBJECT && E->get().hint == PROPERTY_HINT_RESOURCE_TYPE) { RES res = get(E->get().name); if (res.is_valid()) { hash = hash_djb2_one_32(res->hash_edited_version(), hash); diff --git a/core/script_debugger_remote.cpp b/core/script_debugger_remote.cpp index 7a30a33c67..0473e2cc71 100644 --- a/core/script_debugger_remote.cpp +++ b/core/script_debugger_remote.cpp @@ -567,22 +567,46 @@ void ScriptDebuggerRemote::_send_object_id(ObjectID p_id) { if (ScriptInstance *si = obj->get_script_instance()) { if (!si->get_script().is_null()) { - Set<StringName> members; - si->get_script()->get_members(&members); - for (Set<StringName>::Element *E = members.front(); E; E = E->next()) { - - Variant m; - if (si->get(E->get(), m)) { - PropertyInfo pi(m.get_type(), String("Members/") + E->get()); - properties.push_back(PropertyDesc(pi, m)); + typedef Map<const Script *, Set<StringName> > ScriptMemberMap; + typedef Map<const Script *, Map<StringName, Variant> > ScriptConstantsMap; + + ScriptMemberMap members; + members[si->get_script().ptr()] = Set<StringName>(); + si->get_script()->get_members(&(members[si->get_script().ptr()])); + + ScriptConstantsMap constants; + constants[si->get_script().ptr()] = Map<StringName, Variant>(); + si->get_script()->get_constants(&(constants[si->get_script().ptr()])); + + Ref<Script> base = si->get_script()->get_base_script(); + while (base.is_valid()) { + + members[base.ptr()] = Set<StringName>(); + base->get_members(&(members[base.ptr()])); + + constants[base.ptr()] = Map<StringName, Variant>(); + base->get_constants(&(constants[base.ptr()])); + + base = base->get_base_script(); + } + + for (ScriptMemberMap::Element *sm = members.front(); sm; sm = sm->next()) { + for (Set<StringName>::Element *E = sm->get().front(); E; E = E->next()) { + Variant m; + if (si->get(E->get(), m)) { + String script_path = sm->key() == si->get_script().ptr() ? "" : sm->key()->get_path().get_file() + "/"; + PropertyInfo pi(m.get_type(), "Members/" + script_path + E->get()); + properties.push_back(PropertyDesc(pi, m)); + } } } - Map<StringName, Variant> constants; - si->get_script()->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - PropertyInfo pi(E->value().get_type(), (String("Constants/") + E->key())); - properties.push_back(PropertyDesc(pi, E->value())); + for (ScriptConstantsMap::Element *sc = constants.front(); sc; sc = sc->next()) { + for (Map<StringName, Variant>::Element *E = sc->get().front(); E; E = E->next()) { + String script_path = sc->key() == si->get_script().ptr() ? "" : sc->key()->get_path().get_file() + "/"; + PropertyInfo pi(E->value().get_type(), "Constants/" + script_path + E->key()); + properties.push_back(PropertyDesc(pi, E->value())); + } } } } @@ -658,8 +682,10 @@ void ScriptDebuggerRemote::_set_object_property(ObjectID p_id, const String &p_p return; String prop_name = p_property; - if (p_property.begins_with("Members/")) - prop_name = p_property.substr(8, p_property.length()); + if (p_property.begins_with("Members/")) { + Vector<String> ss = p_property.split("/"); + prop_name = ss[ss.size() - 1]; + } obj->set(prop_name, p_value); } @@ -854,7 +880,7 @@ void ScriptDebuggerRemote::idle_poll() { if (performance) { uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_perf_time > update_frequency) { + if (pt - last_perf_time > 1000) { last_perf_time = pt; int max = performance->get("MONITOR_MAX"); @@ -1081,7 +1107,7 @@ ScriptDebuggerRemote::ScriptDebuggerRemote() : eh.userdata = this; add_error_handler(&eh); - profile_info.resize(CLAMP(int(GLOBAL_GET("debug/settings/profiler/max_functions")), 128, 65535)); + profile_info.resize(CLAMP(int(ProjectSettings::get_singleton()->get("debug/settings/profiler/max_functions")), 128, 65535)); profile_info_ptrs.resize(profile_info.size()); } diff --git a/core/script_language.cpp b/core/script_language.cpp index ce9b138bb2..acbe3b34db 100644 --- a/core/script_language.cpp +++ b/core/script_language.cpp @@ -29,7 +29,6 @@ /*************************************************************************/ #include "script_language.h" -#include "project_settings.h" ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES]; int ScriptServer::_language_count = 0; @@ -284,7 +283,6 @@ ScriptDebugger::ScriptDebugger() { lines_left = -1; depth = -1; break_lang = NULL; - update_frequency = GLOBAL_GET("debug/settings/performance/update_frequency_msec"); } bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) { @@ -349,6 +347,20 @@ Variant::Type PlaceHolderScriptInstance::get_property_type(const StringName &p_n return Variant::NIL; } +void PlaceHolderScriptInstance::get_method_list(List<MethodInfo> *p_list) const { + + if (script.is_valid()) { + script->get_script_method_list(p_list); + } +} +bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const { + + if (script.is_valid()) { + return script->has_method(p_method); + } + return false; +} + void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const Map<StringName, Variant> &p_values) { Set<StringName> new_values; diff --git a/core/script_language.h b/core/script_language.h index 64c6f2eb81..e7748f93e2 100644 --- a/core/script_language.h +++ b/core/script_language.h @@ -31,6 +31,7 @@ #ifndef SCRIPT_LANGUAGE_H #define SCRIPT_LANGUAGE_H +#include "io/multiplayer_api.h" #include "map.h" #include "pair.h" #include "resource.h" @@ -157,16 +158,8 @@ public: virtual bool is_placeholder() const { return false; } - enum RPCMode { - RPC_MODE_DISABLED, - RPC_MODE_REMOTE, - RPC_MODE_SYNC, - RPC_MODE_MASTER, - RPC_MODE_SLAVE, - }; - - virtual RPCMode get_rpc_mode(const StringName &p_method) const = 0; - virtual RPCMode get_rset_mode(const StringName &p_variable) const = 0; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const = 0; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const = 0; virtual ScriptLanguage *get_language() = 0; virtual ~ScriptInstance(); @@ -311,8 +304,8 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = NULL) const; - virtual void get_method_list(List<MethodInfo> *p_list) const {} - virtual bool has_method(const StringName &p_method) const { return false; } + virtual void get_method_list(List<MethodInfo> *p_list) const; + virtual bool has_method(const StringName &p_method) const; virtual Variant call(const StringName &p_method, VARIANT_ARG_LIST) { return Variant(); } virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; @@ -332,8 +325,8 @@ public: virtual bool is_placeholder() const { return true; } - virtual RPCMode get_rpc_mode(const StringName &p_method) const { return RPC_MODE_DISABLED; } - virtual RPCMode get_rset_mode(const StringName &p_variable) const { return RPC_MODE_DISABLED; } + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const { return MultiplayerAPI::RPC_MODE_DISABLED; } + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const { return MultiplayerAPI::RPC_MODE_DISABLED; } PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner); ~PlaceHolderScriptInstance(); @@ -352,8 +345,6 @@ class ScriptDebugger { public: typedef void (*RequestSceneTreeMessageFunc)(void *); - int update_frequency; - struct LiveEditFuncs { void *udata; diff --git a/core/string_db.h b/core/string_db.h index 01d1ca4033..965385b136 100644 --- a/core/string_db.h +++ b/core/string_db.h @@ -31,7 +31,6 @@ #ifndef STRING_DB_H #define STRING_DB_H -#include "hash_map.h" #include "os/mutex.h" #include "safe_refcount.h" #include "ustring.h" @@ -168,11 +167,6 @@ public: ~StringName(); }; -struct StringNameHasher { - - static _FORCE_INLINE_ uint32_t hash(const StringName &p_string) { return p_string.hash(); } -}; - StringName _scs_create(const char *p_chr); #endif diff --git a/core/type_info.h b/core/type_info.h index c1af4fac69..bf497f1e5f 100644 --- a/core/type_info.h +++ b/core/type_info.h @@ -194,6 +194,7 @@ MAKE_TEMPLATE_TYPE_INFO(Vector, Color, Variant::POOL_COLOR_ARRAY) MAKE_TEMPLATE_TYPE_INFO(Vector, Variant, Variant::ARRAY) MAKE_TEMPLATE_TYPE_INFO(Vector, RID, Variant::ARRAY) MAKE_TEMPLATE_TYPE_INFO(Vector, Plane, Variant::ARRAY) +MAKE_TEMPLATE_TYPE_INFO(Vector, StringName, Variant::POOL_STRING_ARRAY) MAKE_TEMPLATE_TYPE_INFO(PoolVector, Plane, Variant::ARRAY) MAKE_TEMPLATE_TYPE_INFO(PoolVector, Face3, Variant::POOL_VECTOR3_ARRAY) diff --git a/core/variant.cpp b/core/variant.cpp index a6df95e310..c48aa57652 100644 --- a/core/variant.cpp +++ b/core/variant.cpp @@ -2012,6 +2012,19 @@ Variant::operator Vector<String>() const { } return to; } +Variant::operator Vector<StringName>() const { + + PoolVector<String> from = operator PoolVector<String>(); + Vector<StringName> to; + int len = from.size(); + to.resize(len); + for (int i = 0; i < len; i++) { + + to[i] = from[i]; + } + return to; +} + Variant::operator Vector<Vector3>() const { PoolVector<Vector3> from = operator PoolVector<Vector3>(); @@ -2444,6 +2457,17 @@ Variant::Variant(const Vector<String> &p_array) { *this = v; } +Variant::Variant(const Vector<StringName> &p_array) { + + type = NIL; + PoolVector<String> v; + int len = p_array.size(); + v.resize(len); + for (int i = 0; i < len; i++) + v.set(i, p_array[i]); + *this = v; +} + Variant::Variant(const Vector<Vector3> &p_array) { type = NIL; diff --git a/core/variant.h b/core/variant.h index f227e4bfdb..4b245d25e6 100644 --- a/core/variant.h +++ b/core/variant.h @@ -216,6 +216,7 @@ public: operator Vector<int>() const; operator Vector<real_t>() const; operator Vector<String>() const; + operator Vector<StringName>() const; operator Vector<Vector3>() const; operator Vector<Color>() const; operator Vector<RID>() const; @@ -280,6 +281,7 @@ public: Variant(const Vector<int> &p_int_array); Variant(const Vector<real_t> &p_real_array); Variant(const Vector<String> &p_string_array); + Variant(const Vector<StringName> &p_string_array); Variant(const Vector<Vector3> &p_vector3_array); Variant(const Vector<Color> &p_color_array); Variant(const Vector<Plane> &p_array); // helper diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 4158c2a60e..e6f36ecbf1 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -763,6 +763,7 @@ struct _VariantCall { VCALL_PTR1R(Basis, xform_inv); VCALL_PTR0R(Basis, get_orthogonal_index); VCALL_PTR0R(Basis, orthonormalized); + VCALL_PTR2R(Basis, slerp); VCALL_PTR0R(Transform, inverse); VCALL_PTR0R(Transform, affine_inverse); @@ -1803,6 +1804,7 @@ void register_variant_methods() { ADDFUNC1R(BASIS, VECTOR3, Basis, xform, VECTOR3, "v", varray()); ADDFUNC1R(BASIS, VECTOR3, Basis, xform_inv, VECTOR3, "v", varray()); ADDFUNC0R(BASIS, INT, Basis, get_orthogonal_index, varray()); + ADDFUNC2R(BASIS, BASIS, Basis, slerp, BASIS, "b", REAL, "t", varray()); ADDFUNC0R(TRANSFORM, TRANSFORM, Transform, inverse, varray()); ADDFUNC0R(TRANSFORM, TRANSFORM, Transform, affine_inverse, varray()); diff --git a/core/variant_op.cpp b/core/variant_op.cpp index 621af2dfb7..bfa69b1fde 100644 --- a/core/variant_op.cpp +++ b/core/variant_op.cpp @@ -3417,8 +3417,17 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const { Variant Variant::duplicate(bool deep) const { switch (type) { - // case OBJECT: - // return operator Object *()->duplicate(); + case OBJECT: { + /* breaks stuff :( + if (deep && !_get_obj().ref.is_null()) { + Ref<Resource> resource = _get_obj().ref; + if (resource.is_valid()) { + return resource->duplicate(true); + } + } + */ + return *this; + } break; case DICTIONARY: return operator Dictionary().duplicate(deep); case ARRAY: diff --git a/doc/classes/@GDScript.xml b/doc/classes/@GDScript.xml index cddc59ab71..b5f5fed3f9 100644 --- a/doc/classes/@GDScript.xml +++ b/doc/classes/@GDScript.xml @@ -4,7 +4,7 @@ Built-in GDScript functions. </brief_description> <description> - This contains the list of built-in gdscript functions. Mostly math functions and other utilities. Everything else is expanded by objects. + List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.) </description> <tutorials> </tutorials> @@ -1145,8 +1145,9 @@ <argument index="1" name="signal" type="String" default=""""> </argument> <description> - Stops the function execution and returns the current state. Call [method GDScriptFunctionState.resume] on the state to resume execution. This invalidates the state. - Returns anything that was passed to the resume function call. If passed an object and a signal, the execution is resumed when the object's signal is emitted. + Stops the function execution and returns the current suspended state to the calling function. + From the caller, call [method GDScriptFunctionState.resume] on the state to resume execution. This invalidates the state. Within the resumed function, [code]yield()[/code] returns whatever was passed to the [code]resume()[/code] function call. + If passed an object and a signal, the execution is resumed when the object emits the given signal. In this case, [code]yield()[/code] returns the argument passed to [code]emit_signal()[/code] if the signal takes only one argument, or an array containing all the arguments passed to [code]emit_signal()[/code] if the signal takes multiple arguments. </description> </method> </methods> diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 840d884a8c..7f94676e93 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -29,9 +29,6 @@ <member name="Geometry" type="Geometry" setter="" getter=""> [Geometry] singleton </member> - <member name="GodotSharp" type="GodotSharp" setter="" getter=""> - [GodotSharp] singleton - </member> <member name="IP" type="IP" setter="" getter=""> [IP] singleton </member> diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml index 730c395f10..b9061e0b87 100644 --- a/doc/classes/AABB.xml +++ b/doc/classes/AABB.xml @@ -7,7 +7,7 @@ AABB consists of a position, a size, and several utility functions. It is typically used for fast overlap tests. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/math/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/math/index.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index ea503f8aa9..c6b868c058 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -8,7 +8,7 @@ Animations are just data containers, and must be added to odes such as an [AnimationPlayer] or [AnimationTreePlayer] to be played back. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/animation/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/animation/index.html</link> </tutorials> <demos> </demos> @@ -24,6 +24,214 @@ Add a track to the Animation. The track type must be specified as any of the values in the TYPE_* enumeration. </description> </method> + <method name="animation_track_get_key_animation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="animation_track_insert_key"> + <return type="int"> + </return> + <argument index="0" name="track" type="int"> + </argument> + <argument index="1" name="time" type="float"> + </argument> + <argument index="2" name="animation" type="String"> + </argument> + <description> + </description> + </method> + <method name="animation_track_set_key_animation"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="animation" type="String"> + </argument> + <description> + </description> + </method> + <method name="audio_track_get_key_end_offset" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="audio_track_get_key_start_offset" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="audio_track_get_key_stream" qualifiers="const"> + <return type="Resource"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="audio_track_insert_key"> + <return type="int"> + </return> + <argument index="0" name="track" type="int"> + </argument> + <argument index="1" name="time" type="float"> + </argument> + <argument index="2" name="stream" type="Resource"> + </argument> + <argument index="3" name="start_offset" type="float" default="0"> + </argument> + <argument index="4" name="end_offset" type="float" default="0"> + </argument> + <description> + </description> + </method> + <method name="audio_track_set_key_end_offset"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="offset" type="float"> + </argument> + <description> + </description> + </method> + <method name="audio_track_set_key_start_offset"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="offset" type="float"> + </argument> + <description> + </description> + </method> + <method name="audio_track_set_key_stream"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="stream" type="Resource"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_get_key_in_handle" qualifiers="const"> + <return type="Vector2"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_get_key_out_handle" qualifiers="const"> + <return type="Vector2"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_get_key_value" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_insert_key"> + <return type="int"> + </return> + <argument index="0" name="track" type="int"> + </argument> + <argument index="1" name="time" type="float"> + </argument> + <argument index="2" name="value" type="float"> + </argument> + <argument index="3" name="in_handle" type="Vector2" default="Vector2( 0, 0 )"> + </argument> + <argument index="4" name="out_handle" type="Vector2" default="Vector2( 0, 0 )"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_interpolate" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="track" type="int"> + </argument> + <argument index="1" name="time" type="float"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_set_key_in_handle"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="in_handle" type="Vector2"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_set_key_out_handle"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="out_handle" type="Vector2"> + </argument> + <description> + </description> + </method> + <method name="bezier_track_set_key_value"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="key_idx" type="int"> + </argument> + <argument index="2" name="value" type="float"> + </argument> + <description> + </description> + </method> <method name="clear"> <return type="void"> </return> @@ -347,6 +555,16 @@ Set the path of a track. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by ":". Example: "character/skeleton:ankle" or "character/mesh:transform/local" </description> </method> + <method name="track_swap"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="with_idx" type="int"> + </argument> + <description> + </description> + </method> <method name="transform_track_insert_key"> <return type="int"> </return> @@ -430,6 +648,12 @@ <constant name="TYPE_METHOD" value="2" enum="TrackType"> Method tracks call functions with given arguments per key. </constant> + <constant name="TYPE_BEZIER" value="3" enum="TrackType"> + </constant> + <constant name="TYPE_AUDIO" value="4" enum="TrackType"> + </constant> + <constant name="TYPE_ANIMATION" value="5" enum="TrackType"> + </constant> <constant name="INTERPOLATION_NEAREST" value="0" enum="InterpolationType"> No interpolation (nearest value). </constant> @@ -448,5 +672,7 @@ <constant name="UPDATE_TRIGGER" value="2" enum="UpdateMode"> Update at the keyframes. </constant> + <constant name="UPDATE_CAPTURE" value="3" enum="UpdateMode"> + </constant> </constants> </class> diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index 673bbbea59..e1b3c7a9c9 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -7,8 +7,8 @@ An animation player is used for general purpose playback of [Animation] resources. It contains a dictionary of animations (referenced by name) and custom blend times between their transitions. Additionally, animations can be played and blended in different channels. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/step_by_step/animations.html - http://docs.godotengine.org/en/3.0/tutorials/animation/index.html + <link>http://docs.godotengine.org/en/3.0/getting_started/step_by_step/animations.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/animation/index.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/AnimationTrackEditPlugin.xml b/doc/classes/AnimationTrackEditPlugin.xml new file mode 100644 index 0000000000..f322a556b1 --- /dev/null +++ b/doc/classes/AnimationTrackEditPlugin.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AnimationTrackEditPlugin" inherits="Reference" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/AnimationTreePlayer.xml b/doc/classes/AnimationTreePlayer.xml index 49b4c6b43e..8c32d5f6a3 100644 --- a/doc/classes/AnimationTreePlayer.xml +++ b/doc/classes/AnimationTreePlayer.xml @@ -50,6 +50,14 @@ Returns the name of the [member master_player]'s [Animation] bound to this animation node. </description> </method> + <method name="animation_node_get_position" qualifiers="const"> + <return type="float"> + </return> + <argument index="0" name="id" type="String"> + </argument> + <description> + </description> + </method> <method name="animation_node_set_animation"> <return type="void"> </return> diff --git a/doc/classes/AudioEffectEQ10.xml b/doc/classes/AudioEffectEQ10.xml index f88a954417..daca342ace 100644 --- a/doc/classes/AudioEffectEQ10.xml +++ b/doc/classes/AudioEffectEQ10.xml @@ -16,7 +16,6 @@ Band 8 : 4000 Hz Band 9 : 8000 Hz Band 10 : 16000 Hz - See also [AudioEffectEQ], [AudioEffectEQ6], [AudioEffectEQ21]. </description> <tutorials> diff --git a/doc/classes/AudioEffectEQ21.xml b/doc/classes/AudioEffectEQ21.xml index 86d2189f27..99d35604fc 100644 --- a/doc/classes/AudioEffectEQ21.xml +++ b/doc/classes/AudioEffectEQ21.xml @@ -27,7 +27,6 @@ Band 19 : 11000 Hz Band 20 : 16000 Hz Band 21 : 22000 Hz - See also [AudioEffectEQ], [AudioEffectEQ6], [AudioEffectEQ10]. </description> <tutorials> diff --git a/doc/classes/AudioEffectEQ6.xml b/doc/classes/AudioEffectEQ6.xml index 8f789cdbdd..047c4c960f 100644 --- a/doc/classes/AudioEffectEQ6.xml +++ b/doc/classes/AudioEffectEQ6.xml @@ -12,7 +12,6 @@ Band 4 : 1000 Hz Band 5 : 3200 Hz Band 6 : 10000 Hz - See also [AudioEffectEQ], [AudioEffectEQ10], [AudioEffectEQ21]. </description> <tutorials> diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 7b3ba632cc..51df1e99dd 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -7,7 +7,7 @@ AudioServer is a low level server interface for audio access. It is in charge of creating sample data (playable audio) as well as its playback via a voice interface. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/audio/audio_buses.html + <link>http://docs.godotengine.org/en/3.0/tutorials/audio/audio_buses.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/AudioStream.xml b/doc/classes/AudioStream.xml index d332277248..15bbb1625c 100644 --- a/doc/classes/AudioStream.xml +++ b/doc/classes/AudioStream.xml @@ -7,7 +7,7 @@ Base class for audio streams. Audio streams are used for music playback, or other types of streamed sounds that don't fit or require more flexibility than a [Sample]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html + <link>http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index 7d9fcf3e01..00d03d2b20 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -7,8 +7,8 @@ Plays background audio. </description> <tutorials> - http://docs.godotengine.org/en/latest/learning/features/audio/index.html - http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html + <link>http://docs.godotengine.org/en/latest/learning/features/audio/index.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index 81beab245d..03b0a3aa81 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -7,8 +7,8 @@ Plays audio that dampens with distance from screen center. </description> <tutorials> - http://docs.godotengine.org/en/latest/learning/features/audio/index.html - http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html + <link>http://docs.godotengine.org/en/latest/learning/features/audio/index.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index 311efb7495..2746938c1d 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -7,8 +7,8 @@ Plays a sound effect with directed sound effects, dampens with distance if needed, generates effect of hearable position in space. </description> <tutorials> - http://docs.godotengine.org/en/latest/learning/features/audio/index.html - http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html + <link>http://docs.godotengine.org/en/latest/learning/features/audio/index.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/audio/audio_streams.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/BakedLightmap.xml b/doc/classes/BakedLightmap.xml index 45c60302a6..77895249e5 100644 --- a/doc/classes/BakedLightmap.xml +++ b/doc/classes/BakedLightmap.xml @@ -5,7 +5,7 @@ <description> </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/baked_lightmaps.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/baked_lightmaps.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index b9dc763820..fe8debe1a9 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -8,8 +8,8 @@ For such use, it is composed of a scaling and a rotation matrix, in that order (M = R.S). </description> <tutorials> - http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html - http://docs.godotengine.org/en/latest/tutorials/math/rotations.html + <link>http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html</link> + <link>http://docs.godotengine.org/en/latest/tutorials/math/rotations.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index c8622be4ad..a04e38af5c 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -11,8 +11,8 @@ Ultimately, a transform notification can be requested, which will notify the node that its global position changed in case the parent tree changed. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html - http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/CanvasLayer.xml b/doc/classes/CanvasLayer.xml index 3e4d1b29f7..d4412e15c9 100644 --- a/doc/classes/CanvasLayer.xml +++ b/doc/classes/CanvasLayer.xml @@ -7,8 +7,8 @@ Canvas drawing layer. [CanvasItem] nodes that are direct or indirect children of a [code]CanvasLayer[/code] will be drawn in that layer. The layer is a numeric index that defines the draw order. The default 2D scene renders with index 0, so a [code]CanvasLayer[/code] with index -1 will be drawn below, and one with index 1 will be drawn above. This is very useful for HUDs (in layer 1+ or above), or backgrounds (in layer -1 or below). </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html - http://docs.godotengine.org/en/3.0/tutorials/2d/canvas_layers.html + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/canvas_layers.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/CollisionShape.xml b/doc/classes/CollisionShape.xml index 95fa1175c3..682c9340df 100644 --- a/doc/classes/CollisionShape.xml +++ b/doc/classes/CollisionShape.xml @@ -7,7 +7,7 @@ Editor facility for creating and editing collision shapes in 3D space. You can use this node to represent all sorts of collision shapes, for example, add this to an [Area] to give it a detection shape, or add it to a [PhysicsBody] to create a solid object. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method get_shape] to get the actual shape. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/CollisionShape2D.xml b/doc/classes/CollisionShape2D.xml index 3136f132bf..3312fad99c 100644 --- a/doc/classes/CollisionShape2D.xml +++ b/doc/classes/CollisionShape2D.xml @@ -7,7 +7,7 @@ Editor facility for creating and editing collision shapes in 2D space. You can use this node to represent all sorts of collision shapes, for example, add this to an [Area2D] to give it a detection shape, or add it to a [PhysicsBody2D] to create a solid object. [b]IMPORTANT[/b]: this is an Editor-only helper to create shapes, use [method get_shape] to get the actual shape. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index 2214264dca..1bd902c20e 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -31,6 +31,9 @@ <member name="raw_mode" type="bool" setter="set_raw_mode" getter="is_raw_mode"> If [code]true[/code], allows the color R, G, B component values to go beyond 1.0, which can be used for certain special operations that require it (like tinting without darkening or rendering sprites in HDR). </member> + <member name="deferred_mode" type="bool" setter="set_deferred_mode" getter="is_deferred_mode"> + If [code]true[/code], the color will apply only after user releases mouse button, otherwise it will apply immediatly even in mouse motion event (which can cause performance issues). + </member> </members> <signals> <signal name="color_changed"> diff --git a/doc/classes/ColorPickerButton.xml b/doc/classes/ColorPickerButton.xml index 656fce587f..9d7df14014 100644 --- a/doc/classes/ColorPickerButton.xml +++ b/doc/classes/ColorPickerButton.xml @@ -11,7 +11,7 @@ <demos> </demos> <methods> - <method name="get_picker" qualifiers="const"> + <method name="get_picker"> <return type="ColorPicker"> </return> <description> diff --git a/doc/classes/ConfigFile.xml b/doc/classes/ConfigFile.xml index a42d0f196e..ec0381bda5 100644 --- a/doc/classes/ConfigFile.xml +++ b/doc/classes/ConfigFile.xml @@ -117,7 +117,7 @@ <argument index="2" name="value" type="Variant"> </argument> <description> - Assigns a value to the specified key of the the specified section. If the section and/or the key do not exist, they are created. Passing a [code]null[/code] value deletes the specified key if it exists, and deletes the section if it ends up empty once the key has been removed. + Assigns a value to the specified key of the specified section. If the section and/or the key do not exist, they are created. Passing a [code]null[/code] value deletes the specified key if it exists, and deletes the section if it ends up empty once the key has been removed. </description> </method> </methods> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 9413a8aa34..52382337cf 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -13,8 +13,8 @@ [Theme] resources change the Control's appearance. If you change the [Theme] on a [code]Control[/code] node, it affects all of its children. To override some of the theme's parameters, call one of the [code]add_*_override[/code] methods, like [method add_font_override]. You can override the theme with the inspector. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/gui/index.html - http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html + <link>http://docs.godotengine.org/en/3.0/tutorials/gui/index.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/DirectionalLight.xml b/doc/classes/DirectionalLight.xml index 5e492b74da..ef75182811 100644 --- a/doc/classes/DirectionalLight.xml +++ b/doc/classes/DirectionalLight.xml @@ -7,7 +7,7 @@ A DirectionalLight is a type of [Light] node that emits light constantly in one direction (the negative z axis of the node). It is used lights with strong intensity that are located far away from the scene to model sunlight or moonlight. The worldspace location of the DirectionalLight transform (origin) is ignored, only the basis is used do determine light direction. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml index 040ccf4462..d8ad208fa7 100644 --- a/doc/classes/Directory.xml +++ b/doc/classes/Directory.xml @@ -23,7 +23,7 @@ [/codeblock] </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/step_by_step/filesystem.html + <link>http://docs.godotengine.org/en/3.0/getting_started/step_by_step/filesystem.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml index 406bd7b7df..e48eb82691 100644 --- a/doc/classes/EditorImportPlugin.xml +++ b/doc/classes/EditorImportPlugin.xml @@ -5,10 +5,7 @@ </brief_description> <description> EditorImportPlugins provide a way to extend the editor's resource import functionality. Use them to import resources from custom files or to provide alternatives to the editor's existing importers. Register your [EditorPlugin] with [method EditorPlugin.add_import_plugin]. - EditorImportPlugins work by associating with specific file extensions and a resource type. See [method get_recognized_extension] and [method get_resource_type]). They may optionally specify some import presets that affect the import process. EditorImportPlugins are responsible for creating the resources and saving them in the [code].import[/code] directory. - - Below is an example EditorImportPlugin that imports a [Mesh] from a file with the extension ".special" or ".spec": [codeblock] tool diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml new file mode 100644 index 0000000000..381eef5a40 --- /dev/null +++ b/doc/classes/EditorInspector.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorInspector" inherits="ScrollContainer" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <signals> + <signal name="object_id_selected"> + <argument index="0" name="id" type="int"> + </argument> + <description> + </description> + </signal> + <signal name="property_keyed"> + <argument index="0" name="property" type="String"> + </argument> + <description> + </description> + </signal> + <signal name="resource_selected"> + <argument index="0" name="res" type="Object"> + </argument> + <argument index="1" name="prop" type="String"> + </argument> + <description> + </description> + </signal> + </signals> + <constants> + </constants> +</class> diff --git a/doc/classes/EditorInspectorPlugin.xml b/doc/classes/EditorInspectorPlugin.xml new file mode 100644 index 0000000000..9bda768b14 --- /dev/null +++ b/doc/classes/EditorInspectorPlugin.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorInspectorPlugin" inherits="Reference" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="add_custom_control"> + <return type="void"> + </return> + <argument index="0" name="control" type="Control"> + </argument> + <description> + </description> + </method> + <method name="add_property_editor"> + <return type="void"> + </return> + <argument index="0" name="property" type="String"> + </argument> + <argument index="1" name="editor" type="Control"> + </argument> + <description> + </description> + </method> + <method name="add_property_editor_for_multiple_properties"> + <return type="void"> + </return> + <argument index="0" name="label" type="String"> + </argument> + <argument index="1" name="properties" type="PoolStringArray"> + </argument> + <argument index="2" name="editor" type="Control"> + </argument> + <description> + </description> + </method> + <method name="can_handle" qualifiers="virtual"> + <return type="bool"> + </return> + <argument index="0" name="object" type="Object"> + </argument> + <description> + </description> + </method> + <method name="parse_begin" qualifiers="virtual"> + <return type="void"> + </return> + <argument index="0" name="object" type="Object"> + </argument> + <description> + </description> + </method> + <method name="parse_category" qualifiers="virtual"> + <return type="void"> + </return> + <argument index="0" name="object" type="Object"> + </argument> + <argument index="1" name="category" type="String"> + </argument> + <description> + </description> + </method> + <method name="parse_end" qualifiers="virtual"> + <return type="void"> + </return> + <description> + </description> + </method> + <method name="parse_property" qualifiers="virtual"> + <return type="bool"> + </return> + <argument index="0" name="object" type="Object"> + </argument> + <argument index="1" name="type" type="int"> + </argument> + <argument index="2" name="path" type="String"> + </argument> + <argument index="3" name="hint" type="int"> + </argument> + <argument index="4" name="hint_text" type="String"> + </argument> + <argument index="5" name="usage" type="int"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index e8e2c4fd74..b9945f3f73 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -7,7 +7,7 @@ Plugins are used by the editor to extend functionality. The most common types of plugins are those which edit a given node or resource type, import plugins and export plugins. </description> <tutorials> - http://docs.godotengine.org/en/3.0/development/plugins/index.html + <link>http://docs.godotengine.org/en/3.0/development/plugins/index.html</link> </tutorials> <demos> </demos> @@ -81,7 +81,7 @@ <method name="add_export_plugin"> <return type="void"> </return> - <argument index="0" name="exporter" type="EditorExportPlugin"> + <argument index="0" name="plugin" type="EditorExportPlugin"> </argument> <description> </description> @@ -94,6 +94,14 @@ <description> </description> </method> + <method name="add_inspector_plugin"> + <return type="void"> + </return> + <argument index="0" name="plugin" type="EditorInspectorPlugin"> + </argument> + <description> + </description> + </method> <method name="add_scene_import_plugin"> <return type="void"> </return> @@ -135,6 +143,12 @@ This is used, for example, in shader editors to let the plugin know that it must apply the shader code being written by the user to the object. </description> </method> + <method name="build" qualifiers="virtual"> + <return type="bool"> + </return> + <description> + </description> + </method> <method name="clear" qualifiers="virtual"> <return type="void"> </return> @@ -341,7 +355,7 @@ <method name="remove_export_plugin"> <return type="void"> </return> - <argument index="0" name="exporter" type="EditorExportPlugin"> + <argument index="0" name="plugin" type="EditorExportPlugin"> </argument> <description> </description> @@ -354,6 +368,14 @@ <description> </description> </method> + <method name="remove_inspector_plugin"> + <return type="void"> + </return> + <argument index="0" name="plugin" type="EditorInspectorPlugin"> + </argument> + <description> + </description> + </method> <method name="remove_scene_import_plugin"> <return type="void"> </return> diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml new file mode 100644 index 0000000000..9b5452ec14 --- /dev/null +++ b/doc/classes/EditorProperty.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorProperty" inherits="Container" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="get_edited_object"> + <return type="Object"> + </return> + <description> + </description> + </method> + <method name="get_edited_property"> + <return type="String"> + </return> + <description> + </description> + </method> + <method name="update_property" qualifiers="virtual"> + <return type="void"> + </return> + <description> + </description> + </method> + </methods> + <members> + <member name="checkable" type="bool" setter="set_checkable" getter="is_checkable"> + </member> + <member name="checked" type="bool" setter="set_checked" getter="is_checked"> + </member> + <member name="draw_red" type="bool" setter="set_draw_red" getter="is_draw_red"> + </member> + <member name="keying" type="bool" setter="set_keying" getter="is_keying"> + </member> + <member name="label" type="String" setter="set_label" getter="get_label"> + </member> + <member name="read_only" type="bool" setter="set_read_only" getter="is_read_only"> + </member> + </members> + <signals> + <signal name="multiple_properties_changed"> + <argument index="0" name="properties" type="PoolStringArray"> + </argument> + <argument index="1" name="value" type="Array"> + </argument> + <description> + </description> + </signal> + <signal name="object_id_selected"> + <argument index="0" name="property" type="String"> + </argument> + <argument index="1" name="id" type="int"> + </argument> + <description> + </description> + </signal> + <signal name="property_changed"> + <argument index="0" name="property" type="String"> + </argument> + <argument index="1" name="value" type="Nil"> + </argument> + <description> + </description> + </signal> + <signal name="property_checked"> + <argument index="0" name="property" type="String"> + </argument> + <argument index="1" name="bool" type="String"> + </argument> + <description> + </description> + </signal> + <signal name="property_keyed"> + <argument index="0" name="property" type="String"> + </argument> + <description> + </description> + </signal> + <signal name="property_keyed_with_value"> + <argument index="0" name="property" type="String"> + </argument> + <argument index="1" name="value" type="Nil"> + </argument> + <description> + </description> + </signal> + <signal name="resource_selected"> + <argument index="0" name="path" type="String"> + </argument> + <argument index="1" name="resource" type="Object"> + </argument> + <description> + </description> + </signal> + <signal name="selected"> + <argument index="0" name="path" type="String"> + </argument> + <argument index="1" name="focusable_idx" type="int"> + </argument> + <description> + </description> + </signal> + </signals> + <constants> + </constants> +</class> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 5325a67de3..bd85075b7e 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -67,7 +67,7 @@ <description> </description> </method> - <method name="get_project_settings_dir"> + <method name="get_project_settings_dir" qualifiers="const"> <return type="String"> </return> <description> @@ -144,6 +144,8 @@ </description> </method> <method name="set_project_metadata"> + <return type="void"> + </return> <argument index="0" name="section" type="String"> </argument> <argument index="1" name="key" type="String"> diff --git a/doc/classes/EditorSpatialGizmo.xml b/doc/classes/EditorSpatialGizmo.xml index 58d2671968..3636442b85 100644 --- a/doc/classes/EditorSpatialGizmo.xml +++ b/doc/classes/EditorSpatialGizmo.xml @@ -24,8 +24,6 @@ </return> <argument index="0" name="triangles" type="TriangleMesh"> </argument> - <argument index="1" name="bounds" type="AABB"> - </argument> <description> Add collision triangles to the gizmo for picking. A [TriangleMesh] can be generated from a regular [Mesh] too. Call this function during [method redraw]. </description> diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index cc2ae4e768..f921b76b21 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -11,6 +11,34 @@ <demos> </demos> <methods> + <method name="get_author_info" qualifiers="const"> + <return type="Dictionary"> + </return> + <description> + Returns engine author information in a Dictionary. + "lead_developers" - Array of Strings, lead developer names + "founders" - Array of Strings, founder names + "project_managers" - Array of Strings, project manager names + "developers" - Array of Strings, developer names + </description> + </method> + <method name="get_copyright_info" qualifiers="const"> + <return type="Array"> + </return> + <description> + Returns an Array of copyright information Dictionaries. + "name" - String, component name + "parts" - Array of Dictionaries {"files", "copyright", "license"} describing subsections of the component + </description> + </method> + <method name="get_donor_info" qualifiers="const"> + <return type="Dictionary"> + </return> + <description> + Returns a Dictionary of Arrays of donor names. + {"platinum_sponsors", "gold_sponsors", "mini_sponsors", "gold_donors", "silver_donors", "bronze_donors"} + </description> + </method> <method name="get_frames_drawn"> <return type="int"> </return> @@ -25,6 +53,20 @@ Returns the frames per second of the running game. </description> </method> + <method name="get_license_info" qualifiers="const"> + <return type="Dictionary"> + </return> + <description> + Returns Dictionary of licenses used by Godot and included third party components. + </description> + </method> + <method name="get_license_text" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns Godot license text. + </description> + </method> <method name="get_main_loop" qualifiers="const"> <return type="MainLoop"> </return> @@ -45,7 +87,6 @@ </return> <description> Returns the current engine version information in a Dictionary. - "major" - Holds the major version number as an int "minor" - Holds the minor version number as an int "patch" - Holds the patch version number as an int diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index 18adaa645c..cd2584ed43 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -13,8 +13,8 @@ - Adjustments </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/environment_and_post_processing.html - http://docs.godotengine.org/en/3.0/tutorials/3d/high_dynamic_range.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/environment_and_post_processing.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/high_dynamic_range.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/File.xml b/doc/classes/File.xml index bd368e967a..20054ac9dc 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -9,20 +9,20 @@ [codeblock] func save(content): var file = File.new() - file.open("user://save_game.dat", file.WRITE) + file.open("user://save_game.dat", File.WRITE) file.store_string(content) file.close() func load(): var file = File.new() - file.open("user://save_game.dat", file.READ) + file.open("user://save_game.dat", File.READ) var content = file.get_as_text() file.close() return content [/codeblock] </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/step_by_step/filesystem.html + <link>http://docs.godotengine.org/en/3.0/getting_started/step_by_step/filesystem.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index b165d8fb8c..247228d265 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -33,6 +33,12 @@ <description> </description> </method> + <method name="get_line_edit"> + <return type="LineEdit"> + </return> + <description> + </description> + </method> <method name="get_vbox"> <return type="VBoxContainer"> </return> diff --git a/doc/classes/GIProbe.xml b/doc/classes/GIProbe.xml index fde91d09ac..77dea73564 100644 --- a/doc/classes/GIProbe.xml +++ b/doc/classes/GIProbe.xml @@ -5,7 +5,7 @@ <description> </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/gi_probes.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/gi_probes.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index 018b548ef1..08e2f649a0 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -10,8 +10,8 @@ For more information on HTTP, see https://developer.mozilla.org/en-US/docs/Web/HTTP (or read RFC 2616 to get it straight from the source: https://tools.ietf.org/html/rfc2616). </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/networking/http_client_class.html - http://docs.godotengine.org/en/3.0/tutorials/networking/ssl_certificates.html + <link>http://docs.godotengine.org/en/3.0/tutorials/networking/http_client_class.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/networking/ssl_certificates.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml index ec9f86993f..c5bb10a23a 100644 --- a/doc/classes/HTTPRequest.xml +++ b/doc/classes/HTTPRequest.xml @@ -8,7 +8,7 @@ Can be used to make HTTP requests, i.e. download or upload files or web content via HTTP. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/networking/ssl_certificates.html + <link>http://docs.godotengine.org/en/3.0/tutorials/networking/ssl_certificates.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 760b0c6bdc..9fc7672a80 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -393,6 +393,12 @@ Resizes the image to the nearest power of 2 for the width and height. If [code]square[/code] is [code]true[/code] then set width and height to be the same. </description> </method> + <method name="rgbe_to_srgb"> + <return type="Image"> + </return> + <description> + </description> + </method> <method name="save_png" qualifiers="const"> <return type="int" enum="Error"> </return> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index f92f8da5dd..a4346c1485 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -7,7 +7,7 @@ A Singleton that deals with inputs. This includes key presses, mouse buttons and movement, joypads, and input actions. Actions and their events can be set in the Project Settings / Input Map tab. Or be set with [InputMap]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/index.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index cbed2285df..993d62d188 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -7,8 +7,8 @@ Base class of all sort of input event. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html - http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventAction.xml b/doc/classes/InputEventAction.xml index e50c43c045..16000231cb 100644 --- a/doc/classes/InputEventAction.xml +++ b/doc/classes/InputEventAction.xml @@ -7,7 +7,7 @@ Contains a generic action which can be targeted from several type of inputs. Actions can be created from the project settings menu [code]Project > Project Settings > Input Map[/code]. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html#actions + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html#actions</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventJoypadButton.xml b/doc/classes/InputEventJoypadButton.xml index 9614b0805b..adaeae685e 100644 --- a/doc/classes/InputEventJoypadButton.xml +++ b/doc/classes/InputEventJoypadButton.xml @@ -7,7 +7,7 @@ Input event type for gamepad buttons. For joysticks see [InputEventJoypadMotion]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventJoypadMotion.xml b/doc/classes/InputEventJoypadMotion.xml index b01f2a3fe1..f86aec4ce0 100644 --- a/doc/classes/InputEventJoypadMotion.xml +++ b/doc/classes/InputEventJoypadMotion.xml @@ -7,7 +7,7 @@ Stores information about joystick motions. One [code]InputEventJoypadMotion[/code] represents one axis at a time. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml index 410738c68e..7503e53188 100644 --- a/doc/classes/InputEventKey.xml +++ b/doc/classes/InputEventKey.xml @@ -7,7 +7,7 @@ Stores key presses on the keyboard. Supports key presses, key releases and [member echo] events. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventMouse.xml b/doc/classes/InputEventMouse.xml index 96a0116c3c..06de96890a 100644 --- a/doc/classes/InputEventMouse.xml +++ b/doc/classes/InputEventMouse.xml @@ -7,7 +7,7 @@ Stores general mouse events information. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventMouseButton.xml b/doc/classes/InputEventMouseButton.xml index 73190c7283..50641dceed 100644 --- a/doc/classes/InputEventMouseButton.xml +++ b/doc/classes/InputEventMouseButton.xml @@ -7,7 +7,7 @@ Contains mouse click information. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/mouse_and_input_coordinates.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/mouse_and_input_coordinates.html</link> </tutorials> <demos> </demos> @@ -21,7 +21,7 @@ If [code]true[/code] the mouse button's state is a double-click. If [code]false[/code] the mouse button's state is released. </member> <member name="factor" type="float" setter="set_factor" getter="get_factor"> - TO TALK in PR, reduz said : i think it's used for apple touch but i don't remember what it does + Magnitude. Amount (or delta) of the event. Used for scroll events, indicates scroll amount (vertically or horizontally). Only supported on some platforms, sensitivity varies by platform. May be 0 if not supported. </member> <member name="pressed" type="bool" setter="set_pressed" getter="is_pressed"> If [code]true[/code] the mouse button's state is pressed. If [code]false[/code] the mouse button's state is released. diff --git a/doc/classes/InputEventMouseMotion.xml b/doc/classes/InputEventMouseMotion.xml index 83aa28c693..05e3e79d26 100644 --- a/doc/classes/InputEventMouseMotion.xml +++ b/doc/classes/InputEventMouseMotion.xml @@ -7,7 +7,7 @@ Contains mouse motion information. Supports relative, absolute positions and speed. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/mouse_and_input_coordinates.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/mouse_and_input_coordinates.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventScreenDrag.xml b/doc/classes/InputEventScreenDrag.xml index a2ac8d587f..f777d90ccb 100644 --- a/doc/classes/InputEventScreenDrag.xml +++ b/doc/classes/InputEventScreenDrag.xml @@ -8,7 +8,7 @@ Contains screen drag information. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventScreenTouch.xml b/doc/classes/InputEventScreenTouch.xml index 64b9550f9d..39cd0a9657 100644 --- a/doc/classes/InputEventScreenTouch.xml +++ b/doc/classes/InputEventScreenTouch.xml @@ -8,7 +8,7 @@ Stores multi-touch press/release information. Supports touch press, touch release and [member index] for multi-touch count and order. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputEventWithModifiers.xml b/doc/classes/InputEventWithModifiers.xml index 2d046cbfd2..9c1814fedd 100644 --- a/doc/classes/InputEventWithModifiers.xml +++ b/doc/classes/InputEventWithModifiers.xml @@ -7,7 +7,7 @@ Contains keys events information with modifiers support like [code]SHIFT[/code] or [code]ALT[/code]. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/InputMap.xml b/doc/classes/InputMap.xml index 3399a3f096..2f5fb49dba 100644 --- a/doc/classes/InputMap.xml +++ b/doc/classes/InputMap.xml @@ -7,7 +7,7 @@ Manages all [InputEventAction] which can be created/modified from the project settings menu [code]Project > Project Settings > Input Map[/code] or in code with [method add_action] and [method action_add_event]. See [method Node._input]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html#inputmap + <link>http://docs.godotengine.org/en/3.0/tutorials/inputs/inputevent.html#inputmap</link> </tutorials> <demos> </demos> diff --git a/doc/classes/JavaScript.xml b/doc/classes/JavaScript.xml index 1d40b990a9..17588717c2 100644 --- a/doc/classes/JavaScript.xml +++ b/doc/classes/JavaScript.xml @@ -7,7 +7,7 @@ The JavaScript singleton is implemented only in HTML5 export. It's used to access the browser's JavaScript context. This allows interaction with embedding pages or calling third-party JavaScript APIs. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/workflow/export/exporting_for_web.html#calling-javascript-from-script + <link>http://docs.godotengine.org/en/3.0/getting_started/workflow/export/exporting_for_web.html#calling-javascript-from-script</link> </tutorials> <demos> </demos> diff --git a/doc/classes/KinematicBody.xml b/doc/classes/KinematicBody.xml index 5553602db2..ae33ed5205 100644 --- a/doc/classes/KinematicBody.xml +++ b/doc/classes/KinematicBody.xml @@ -9,7 +9,7 @@ Kinematic Characters: KinematicBody also has an API for moving objects (the [method move_and_collide] and [method move_and_slide] methods) while performing collision tests. This makes them really useful to implement characters that collide against a world, but that don't require advanced physics. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/kinematic_character_2d.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/kinematic_character_2d.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Light.xml b/doc/classes/Light.xml index e05ed2d0b1..e9b36c2f9d 100644 --- a/doc/classes/Light.xml +++ b/doc/classes/Light.xml @@ -7,7 +7,7 @@ Light is the abstract base class for light nodes, so it shouldn't be used directly (It can't be instanced). Other types of light nodes inherit from it. Light contains the common variables and parameters used for lighting. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml index 7a7036c857..8c114a55c9 100644 --- a/doc/classes/MultiplayerAPI.xml +++ b/doc/classes/MultiplayerAPI.xml @@ -60,9 +60,8 @@ <return type="void"> </return> <description> - Method used for polling the MultiplayerAPI. - You only need to worry about this if you are using [member Node.custom_multiplayer] override. - SceneTree will poll the default MultiplayerAPI for you. + Method used for polling the MultiplayerAPI. You only need to worry about this if you are using [member Node.custom_multiplayer] override or you set [member SceneTree.multiplayer_poll] to [code]false[/code]. By default [SceneTree] will poll its MultiplayerAPI for you. + NOTE: This method results in RPCs and RSETs being called, so they will be executed in the same context of this function (e.g. [code]_process[/code], [code]physics[/code], [Thread]). </description> </method> <method name="send_bytes"> @@ -136,5 +135,29 @@ </signal> </signals> <constants> + <constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode"> + Used with [method Node.rpc_config] or [method Node.rset_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. + </constant> + <constant name="RPC_MODE_REMOTE" value="1" enum="RPCMode"> + Used with [method Node.rpc_config] or [method Node.rset_config] to set a method to be called or a property to be changed only on the remote end, not locally. Analogous to the [code]remote[/code] keyword. Calls and property changes are accepted from all remote peers, no matter if they are node's master or slaves. + </constant> + <constant name="RPC_MODE_SYNC" value="2" enum="RPCMode"> + Behave like [code]RPC_MODE_REMOTE[/code] but also make the call or property change locally. Analogous to the [code]sync[/code] keyword. + </constant> + <constant name="RPC_MODE_MASTER" value="3" enum="RPCMode"> + Used with [method Node.rpc_config] or [method Node.rset_config] to set a method to be called or a property to be changed only on the network master for this node. Analogous to the [code]master[/code] keyword. Only accepts calls or property changes from the node's network slaves, see [method Node.set_network_master]. + </constant> + <constant name="RPC_MODE_SLAVE" value="4" enum="RPCMode"> + Used with [method Node.rpc_config] or [method Node.rset_config] to set a method to be called or a property to be changed only on slaves for this node. Analogous to the [code]slave[/code] keyword. Only accepts calls or property changes from the node's network master, see [method Node.set_network_master]. + </constant> + <constant name="RPC_MODE_REMOTESYNC" value="5" enum="RPCMode"> + Behave like [code]RPC_MODE_REMOTE[/code] but also make the call or property change locally. Same as [code]RPC_MODE_SYNC[/code] which is only kept for compatibility. Analogous to the [code]remotesync[/code] keyword. + </constant> + <constant name="RPC_MODE_MASTERSYNC" value="6" enum="RPCMode"> + Behave like [code]RPC_MODE_MASTER[/code] but also make the call or property change locally. Analogous to the [code]mastersync[/code] keyword. + </constant> + <constant name="RPC_MODE_SLAVESYNC" value="7" enum="RPCMode"> + Behave like [code]RPC_MODE_SLAVE[/code] but also make the call or property change locally. Analogous to the [code]slavesync[/code] keyword. + </constant> </constants> </class> diff --git a/doc/classes/NetworkedMultiplayerPeer.xml b/doc/classes/NetworkedMultiplayerPeer.xml index 2780334384..e878b3a746 100644 --- a/doc/classes/NetworkedMultiplayerPeer.xml +++ b/doc/classes/NetworkedMultiplayerPeer.xml @@ -7,7 +7,7 @@ Manages the connection to network peers. Assigns unique IDs to each client connected to the server. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/networking/high_level_multiplayer.html + <link>http://docs.godotengine.org/en/3.0/tutorials/networking/high_level_multiplayer.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 05ac6b1c0e..8bae412053 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -17,7 +17,7 @@ [b]Networking with nodes:[/b] After connecting to a server (or making one, see [NetworkedMultiplayerENet]) it is possible to use the built-in RPC (remote procedure call) system to communicate over the network. By calling [method rpc] with a method name, it will be called locally and in all connected peers (peers = clients and the server that accepts connections). To identify which node receives the RPC call Godot will use its [NodePath] (make sure node names are the same on all peers). Also take a look at the high-level networking tutorial and corresponding demos. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scenes_and_nodes.html + <link>http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scenes_and_nodes.html</link> </tutorials> <demos> </demos> @@ -572,10 +572,10 @@ </return> <argument index="0" name="method" type="String"> </argument> - <argument index="1" name="mode" type="int" enum="Node.RPCMode"> + <argument index="1" name="mode" type="int" enum="MultiplayerAPI.RPCMode"> </argument> <description> - Changes the RPC mode for the given [code]method[/code] to the given [code]mode[/code]. See [enum RPCMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]sync[/code], [code]master[/code], [code]slave[/code]). By default, methods are not exposed to networking (and RPCs). Also see [method rset] and [method rset_config] for properties. + Changes the RPC mode for the given [code]method[/code] to the given [code]mode[/code]. See [enum MultiplayerAPI.RPCMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]sync[/code], [code]master[/code], [code]slave[/code]). By default, methods are not exposed to networking (and RPCs). Also see [method rset] and [method rset_config] for properties. </description> </method> <method name="rpc_id" qualifiers="vararg"> @@ -625,10 +625,10 @@ </return> <argument index="0" name="property" type="String"> </argument> - <argument index="1" name="mode" type="int" enum="Node.RPCMode"> + <argument index="1" name="mode" type="int" enum="MultiplayerAPI.RPCMode"> </argument> <description> - Changes the RPC mode for the given [code]property[/code] to the given [code]mode[/code]. See [enum RPCMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]sync[/code], [code]master[/code], [code]slave[/code]). By default, properties are not exposed to networking (and RPCs). Also see [method rpc] and [method rpc_config] for methods. + Changes the RPC mode for the given [code]property[/code] to the given [code]mode[/code]. See [enum MultiplayerAPI.RPCMode]. An alternative is annotating methods and properties with the corresponding keywords ([code]remote[/code], [code]sync[/code], [code]master[/code], [code]slave[/code]). By default, properties are not exposed to networking (and RPCs). Also see [method rpc] and [method rpc_config] for methods. </description> </method> <method name="rset_id"> @@ -860,21 +860,6 @@ <constant name="NOTIFICATION_INTERNAL_PHYSICS_PROCESS" value="26"> Notification received every frame when the internal physics process flag is set (see [method set_physics_process_internal]). </constant> - <constant name="RPC_MODE_DISABLED" value="0" enum="RPCMode"> - Used with [method rpc_config] or [method rset_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods. - </constant> - <constant name="RPC_MODE_REMOTE" value="1" enum="RPCMode"> - Used with [method rpc_config] or [method rset_config] to set a method to be called or a property to be changed only on the remote end, not locally. Analogous to the [code]remote[/code] keyword. - </constant> - <constant name="RPC_MODE_SYNC" value="2" enum="RPCMode"> - Used with [method rpc_config] or [method rset_config] to set a method to be called or a property to be changed both on the remote end and locally. Analogous to the [code]sync[/code] keyword. - </constant> - <constant name="RPC_MODE_MASTER" value="3" enum="RPCMode"> - Used with [method rpc_config] or [method rset_config] to set a method to be called or a property to be changed only on the network master for this node. Analogous to the [code]master[/code] keyword. See [method set_network_master]. - </constant> - <constant name="RPC_MODE_SLAVE" value="4" enum="RPCMode"> - Used with [method rpc_config] or [method rset_config] to set a method to be called or a property to be changed only on slaves for this node. Analogous to the [code]slave[/code] keyword. See [method set_network_master]. - </constant> <constant name="PAUSE_MODE_INHERIT" value="0" enum="PauseMode"> Inherits pause mode from the node's parent. For the root node, it is equivalent to PAUSE_MODE_STOP. Default. </constant> diff --git a/doc/classes/Node2D.xml b/doc/classes/Node2D.xml index a61678041f..13eabeca17 100644 --- a/doc/classes/Node2D.xml +++ b/doc/classes/Node2D.xml @@ -7,7 +7,7 @@ A 2D game object, with a position, rotation and scale. All 2D physics nodes and sprites inherit from Node2D. Use Node2D as a parent node to move, scale and rotate children in a 2D project. Also gives control on the node's render order. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/custom_drawing_in_2d.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index e4375cfb79..1526b1be8c 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -94,17 +94,24 @@ <argument index="3" name="output" type="Array" default="[ ]"> </argument> <description> - Execute the file at the given path, optionally blocking until it returns. - Platform path resolution will take place. The resolved file must exist and be executable. - Returns a process id. - For example: + Execute the file at the given path with the arguments passed as an array of strings. Platform path resolution will take place. The resolved file must exist and be executable. + The arguments are used in the given order and separated by a space, so [code]OS.execute('ping', ['-c', '3', 'godotengine.org'])[/code] will resolve to [code]ping -c 3 godotengine.org[/code] in the system's shell. + This method has slightly different behaviour based on whether the [code]blocking[/code] mode is enabled. + When [code]blocking[/code] is enabled, the Godot thread will pause its execution while waiting for the process to terminate. The shell output of the process will be written to the [code]output[/code] array as a single string. When the process terminates, the Godot thread will resume execution. + When [code]blocking[/code] is disabled, the Godot thread will continue while the new process runs. It is not possible to retrieve the shell output in non-blocking mode, so [code]output[/code] will be empty. + The return value also depends on the blocking mode. When blocking, the method will return -2 (no process ID information is available in blocking mode). When non-blocking, the method returns a process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process forking (non-blocking) or opening (blocking) fails, the method will return -1. + Example of blocking mode and retrieving the shell output: [codeblock] var output = [] - var pid = OS.execute('ls', [], true, output) + OS.execute('ls', ['-l', '/tmp'], true, output) [/codeblock] - If you wish to access a shell built-in or perform a composite command, a platform specific shell can be invoked. For example: + Example of non-blocking mode, running another instance of the project and storing its process ID: [codeblock] - var pid = OS.execute('CMD.exe', ['/C', 'cd %TEMP% && dir'], true, output) + var pid = OS.execute(OS.get_executable_path(), [], false) + [/codeblock] + If you wish to access a shell built-in or perform a composite command, a platform-specific shell can be invoked. For example: + [codeblock] + OS.execute('CMD.exe', ['/C', 'cd %TEMP% && dir'], true, output) [/codeblock] </description> </method> @@ -285,7 +292,6 @@ </argument> <description> Returns the dots per inch density of the specified screen. - On Android Devices, the actual screen densities are grouped into six generalized densities: ldpi - 120 dpi mdpi - 160 dpi @@ -356,6 +362,13 @@ Returns the amount of time passed in milliseconds since the engine started. </description> </method> + <method name="get_ticks_usec" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the amount of time passed in microseconds since the engine started. + </description> + </method> <method name="get_time" qualifiers="const"> <return type="Dictionary"> </return> @@ -527,7 +540,8 @@ <argument index="0" name="pid" type="int"> </argument> <description> - Kill a process ID (this method can be used to kill processes that were not spawned by the game). + Kill (terminate) the process identified by the given process ID ([code]pid[/code]), e.g. the one returned by [method execute] in non-blocking mode. + Note that this method can also be used to kill processes that were not spawned by the game. </description> </method> <method name="native_video_is_playing"> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 0717836366..ab49bc468c 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -16,7 +16,7 @@ </demos> <methods> <method name="_get" qualifiers="virtual"> - <return type="void"> + <return type="Variant"> </return> <argument index="0" name="property" type="String"> </argument> diff --git a/doc/classes/OmniLight.xml b/doc/classes/OmniLight.xml index 0ed133f52e..ff2e77ffbe 100644 --- a/doc/classes/OmniLight.xml +++ b/doc/classes/OmniLight.xml @@ -7,7 +7,7 @@ An OmniDirectional light is a type of [Light] node that emits lights in all directions. The light is attenuated through the distance and this attenuation can be configured by changing the energy, radius and attenuation parameters of [Light]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/OrientedPathFollow.xml b/doc/classes/OrientedPathFollow.xml index c32e545ff5..85d60936ad 100644 --- a/doc/classes/OrientedPathFollow.xml +++ b/doc/classes/OrientedPathFollow.xml @@ -5,7 +5,7 @@ </brief_description> <description> This node behaves like [PathFollow], except it uses its parent [Path] up vector information to enforce orientation. - Make sure to check if the curve of this node's parent [Path] has up vectors enabled. See [PathFollow] and [Curve3D] for further information. + Make sure to check if the curve of this node's parent [Path] has up vectors enabled. See [PathFollow] and [Curve3D] for further information. </description> <tutorials> </tutorials> diff --git a/doc/classes/Particles.xml b/doc/classes/Particles.xml index 04177aca25..b03cf6cadb 100644 --- a/doc/classes/Particles.xml +++ b/doc/classes/Particles.xml @@ -22,6 +22,7 @@ <return type="void"> </return> <description> + Restarts the particle emmission, clearing existing particles. </description> </method> </methods> @@ -33,14 +34,19 @@ Particle draw order. Uses [code]DRAW_ORDER_*[/code] values. Default value: [code]DRAW_ORDER_INDEX[/code]. </member> <member name="draw_pass_1" type="Mesh" setter="set_draw_pass_mesh" getter="get_draw_pass_mesh"> + [Mesh] that is drawn for the first draw pass. </member> <member name="draw_pass_2" type="Mesh" setter="set_draw_pass_mesh" getter="get_draw_pass_mesh"> + [Mesh] that is drawn for the second draw pass. </member> <member name="draw_pass_3" type="Mesh" setter="set_draw_pass_mesh" getter="get_draw_pass_mesh"> + [Mesh] that is drawn for the third draw pass. </member> <member name="draw_pass_4" type="Mesh" setter="set_draw_pass_mesh" getter="get_draw_pass_mesh"> + [Mesh] that is drawn for the fourth draw pass. </member> <member name="draw_passes" type="int" setter="set_draw_passes" getter="get_draw_passes"> + The number of draw passes when rendering particles. </member> <member name="emitting" type="bool" setter="set_emitting" getter="is_emitting"> If [code]true[/code] particles are being emitted. Default value: [code]true[/code]. @@ -62,6 +68,7 @@ If [code]true[/code] only [code]amount[/code] particles will be emitted. Default value: [code]false[/code]. </member> <member name="preprocess" type="float" setter="set_pre_process_time" getter="get_pre_process_time"> + Amount of time to preprocess the particles before animation starts. Lets you start the animation some time after particles have started emitting. </member> <member name="process_material" type="Material" setter="set_process_material" getter="get_process_material"> [Material] for processing particles. Can be a [ParticlesMaterial] or a [ShaderMaterial]. @@ -73,6 +80,7 @@ Speed scaling ratio. Default value: [code]1[/code]. </member> <member name="visibility_aabb" type="AABB" setter="set_visibility_aabb" getter="get_visibility_aabb"> + The [AABB] that determines the area of the world part of which needs to be visible on screen for the particle system to be active. </member> </members> <constants> @@ -86,6 +94,7 @@ Particles are drawn in order of depth. </constant> <constant name="MAX_DRAW_PASSES" value="4"> + Maximum number of draw passes supported. </constant> </constants> </class> diff --git a/doc/classes/PhysicalBone.xml b/doc/classes/PhysicalBone.xml index 80b3c11270..99f551b865 100644 --- a/doc/classes/PhysicalBone.xml +++ b/doc/classes/PhysicalBone.xml @@ -9,12 +9,24 @@ <demos> </demos> <methods> + <method name="get_simulate_physics"> + <return type="bool"> + </return> + <description> + </description> + </method> <method name="is_simulating_physics"> <return type="bool"> </return> <description> </description> </method> + <method name="is_static_body"> + <return type="bool"> + </return> + <description> + </description> + </method> </methods> <members> <member name="body_offset" type="Transform" setter="set_body_offset" getter="get_body_offset"> @@ -31,10 +43,6 @@ </member> <member name="mass" type="float" setter="set_mass" getter="get_mass"> </member> - <member name="simulate_physics" type="bool" setter="set_simulate_physics" getter="get_simulate_physics"> - </member> - <member name="static_body" type="bool" setter="set_static_body" getter="is_static_body"> - </member> <member name="weight" type="float" setter="set_weight" getter="get_weight"> </member> </members> diff --git a/doc/classes/Physics2DDirectSpaceState.xml b/doc/classes/Physics2DDirectSpaceState.xml index b55702dac4..f0fee77a5a 100644 --- a/doc/classes/Physics2DDirectSpaceState.xml +++ b/doc/classes/Physics2DDirectSpaceState.xml @@ -7,7 +7,7 @@ Direct access object to a space in the [Physics2DServer]. It's used mainly to do queries against objects and areas residing in a given space. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Physics2DServer.xml b/doc/classes/Physics2DServer.xml index 4a678d9f6b..c302797704 100644 --- a/doc/classes/Physics2DServer.xml +++ b/doc/classes/Physics2DServer.xml @@ -140,6 +140,18 @@ Removes a shape from an area. It does not delete the shape, so it can be reassigned later. </description> </method> + <method name="area_set_area_monitor_callback"> + <return type="void"> + </return> + <argument index="0" name="area" type="RID"> + </argument> + <argument index="1" name="receiver" type="Object"> + </argument> + <argument index="2" name="method" type="String"> + </argument> + <description> + </description> + </method> <method name="area_set_collision_layer"> <return type="void"> </return> @@ -180,6 +192,16 @@ 5: The shape index of the area where the object entered/exited. </description> </method> + <method name="area_set_monitorable"> + <return type="void"> + </return> + <argument index="0" name="area" type="RID"> + </argument> + <argument index="1" name="monitorable" type="bool"> + </argument> + <description> + </description> + </method> <method name="area_set_param"> <return type="void"> </return> diff --git a/doc/classes/PhysicsBody.xml b/doc/classes/PhysicsBody.xml index e252aaa048..14053c6a35 100644 --- a/doc/classes/PhysicsBody.xml +++ b/doc/classes/PhysicsBody.xml @@ -7,7 +7,7 @@ PhysicsBody is an abstract base class for implementing a physics body. All *Body types inherit from it. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/PhysicsBody2D.xml b/doc/classes/PhysicsBody2D.xml index 42b1eac5e4..ccc704c7ec 100644 --- a/doc/classes/PhysicsBody2D.xml +++ b/doc/classes/PhysicsBody2D.xml @@ -7,7 +7,7 @@ PhysicsBody2D is an abstract base class for implementing a physics body. All *Body2D types inherit from it. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/PhysicsDirectSpaceState.xml b/doc/classes/PhysicsDirectSpaceState.xml index fabf153dac..3f0e1a4f70 100644 --- a/doc/classes/PhysicsDirectSpaceState.xml +++ b/doc/classes/PhysicsDirectSpaceState.xml @@ -7,7 +7,7 @@ Direct access object to a space in the [PhysicsServer]. It's used mainly to do queries against objects and areas residing in a given space. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/PhysicsServer.xml b/doc/classes/PhysicsServer.xml index f5311974f2..6efbfdb519 100644 --- a/doc/classes/PhysicsServer.xml +++ b/doc/classes/PhysicsServer.xml @@ -149,6 +149,18 @@ Removes a shape from an area. It does not delete the shape, so it can be reassigned later. </description> </method> + <method name="area_set_area_monitor_callback"> + <return type="void"> + </return> + <argument index="0" name="area" type="RID"> + </argument> + <argument index="1" name="receiver" type="Object"> + </argument> + <argument index="2" name="method" type="String"> + </argument> + <description> + </description> + </method> <method name="area_set_collision_layer"> <return type="void"> </return> @@ -189,6 +201,16 @@ 5: The shape index of the area where the object entered/exited. </description> </method> + <method name="area_set_monitorable"> + <return type="void"> + </return> + <argument index="0" name="area" type="RID"> + </argument> + <argument index="1" name="monitorable" type="bool"> + </argument> + <description> + </description> + </method> <method name="area_set_param"> <return type="void"> </return> diff --git a/doc/classes/Plane.xml b/doc/classes/Plane.xml index ca035ad383..6f616401cb 100644 --- a/doc/classes/Plane.xml +++ b/doc/classes/Plane.xml @@ -7,7 +7,7 @@ Plane represents a normalized plane equation. Basically, "normal" is the normal of the plane (a,b,c normalized), and "d" is the distance from the origin to the plane (in the direction of "normal"). "Over" or "Above" the plane is considered the side of the plane towards where the normal is pointing. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/math/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/math/index.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 166a4be2b0..89d8c43c00 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -303,6 +303,14 @@ Return whether the item is a separator. If it is, it would be displayed as a line. </description> </method> + <method name="is_item_shortcut_disabled" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <description> + </description> + </method> <method name="remove_item"> <return type="void"> </return> @@ -433,6 +441,16 @@ <description> </description> </method> + <method name="set_item_shortcut_disabled"> + <return type="void"> + </return> + <argument index="0" name="idx" type="int"> + </argument> + <argument index="1" name="disabled" type="bool"> + </argument> + <description> + </description> + </method> <method name="set_item_submenu"> <return type="void"> </return> diff --git a/doc/classes/PrismMesh.xml b/doc/classes/PrismMesh.xml index 268395c37a..11815d299b 100644 --- a/doc/classes/PrismMesh.xml +++ b/doc/classes/PrismMesh.xml @@ -14,7 +14,7 @@ </methods> <members> <member name="left_to_right" type="float" setter="set_left_to_right" getter="get_left_to_right"> - Displacement of of the upper edge along the x-axis. 0.0 positions edge straight above the bottome left edge. Defaults to 0.5 (positioned on the midpoint). + Displacement of the upper edge along the x-axis. 0.0 positions edge straight above the bottome left edge. Defaults to 0.5 (positioned on the midpoint). </member> <member name="size" type="Vector3" setter="set_size" getter="get_size"> Size of the prism. Defaults to (2.0, 2.0, 2.0). diff --git a/doc/classes/ProceduralSky.xml b/doc/classes/ProceduralSky.xml index 664c80be5e..df0519b2ad 100644 --- a/doc/classes/ProceduralSky.xml +++ b/doc/classes/ProceduralSky.xml @@ -54,7 +54,7 @@ Amount of energy contribution from the sun. </member> <member name="sun_latitude" type="float" setter="set_sun_latitude" getter="get_sun_latitude"> - The suns height using polar coordinates. + The suns height using polar coordinates. </member> <member name="sun_longitude" type="float" setter="set_sun_longitude" getter="get_sun_longitude"> The direction of the sun using polar coordinates. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 44bba8bd20..666f6b4710 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -156,6 +156,536 @@ </description> </method> </methods> + <members> + <member name="application/boot_splash/fullsize" type="bool" setter="" getter=""> + Scale the boot splash image to the full window length when engine starts (will leave it as default pixel size otherwise). + </member> + <member name="application/boot_splash/image" type="String" setter="" getter=""> + Path to an image used for boot splash. + </member> + <member name="application/config/custom_user_dir_name" type="String" setter="" getter=""> + This directory is used for storing persistent data (user:// filesystem). If a custom name is set, then system paths will be used to store this on Desktop (AppData on Windows, user ~/.config on Unixes, etc), else the Godot config folder is used. This name needs to be unique, and it's recommended to set it to something before publishing. + the "use_custom_user_dir" setting must be enabled for this to take effect. + </member> + <member name="application/config/icon" type="String" setter="" getter=""> + Icon used for the project, set when project loads. Exporters will use this icon when possible to. + </member> + <member name="application/config/name" type="String" setter="" getter=""> + Name of the project. It is used from both project manager and by the exporters. Overriding this as name.locale allows setting it in multiple languages. + </member> + <member name="application/config/use_custom_user_dir" type="bool" setter="" getter=""> + Allow the project to save to it's own custom user dir (in AppData on windows or ~/.config on unixes). This setting only works for desktop exporters. A name must be set in the "custom_user_dir_name" setting for this to take effect. + </member> + <member name="application/run/disable_stderr" type="bool" setter="" getter=""> + Disable printing to stderr on exported build. + </member> + <member name="application/run/disable_stdout" type="bool" setter="" getter=""> + Disable printing to stdout on exported build. + </member> + <member name="application/run/frame_delay_msec" type="int" setter="" getter=""> + Force a delay between frames in the main loop. This may be useful if you plan to disable vsync. + </member> + <member name="application/run/low_processor_mode" type="bool" setter="" getter=""> + Turn on low processor mode. This setting only works on desktops. The screen is not redrawn if nothing changes visually. This is meant for writing applications and editors, but is pretty useless (and can hurt performance) on games. + </member> + <member name="application/run/low_processor_mode_sleep_usec" type="int" setter="" getter=""> + Amount of sleeping between frames when the low_processor_mode is enabled. This effectively reduces CPU usage when this mode is enabled. + </member> + <member name="application/run/main_scene" type="String" setter="" getter=""> + Path to the main scene file that will be loaded when the project runs. + </member> + <member name="audio/channel_disable_threshold_db" type="float" setter="" getter=""> + Audio buses will disable automatically when sound goes below a given DB threshold for a given time. This saves CPU as effects assigned to that bus will no longer do any processing. + </member> + <member name="audio/channel_disable_time" type="float" setter="" getter=""> + Audio buses will disable automatically when sound goes below a given DB threshold for a given time. This saves CPU as effects assigned to that bus will no longer do any processing. + </member> + <member name="audio/driver" type="String" setter="" getter=""> + </member> + <member name="audio/mix_rate" type="int" setter="" getter=""> + Mix rate used for audio. In general, it's better to not touch this and leave it to the host operating system. + </member> + <member name="audio/output_latency" type="int" setter="" getter=""> + </member> + <member name="audio/video_delay_compensation_ms" type="int" setter="" getter=""> + Setting to harcode audio delay when playing video. Best to leave this untouched unless you know what you are doing. + </member> + <member name="compression/formats/gzip/compression_level" type="int" setter="" getter=""> + Default compression level for gzip. Affects compressed scenes and resources. + </member> + <member name="compression/formats/zlib/compression_level" type="int" setter="" getter=""> + Default compression level for zlib. Affects compressed scenes and resources. + </member> + <member name="compression/formats/zstd/compression_level" type="int" setter="" getter=""> + Default compression level for zstd. Affects compressed scenes and resources. + </member> + <member name="compression/formats/zstd/long_distance_matching" type="bool" setter="" getter=""> + Enable long distance matching in zstd. + </member> + <member name="compression/formats/zstd/window_log_size" type="int" setter="" getter=""> + </member> + <member name="debug/settings/crash_handler/message" type="String" setter="" getter=""> + </member> + <member name="debug/settings/fps/force_fps" type="int" setter="" getter=""> + </member> + <member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter=""> + Maximum call stack allowed for debugging GDScript. + </member> + <member name="debug/settings/profiler/max_functions" type="int" setter="" getter=""> + Maximum amount of functions per frame allowed when profiling. + </member> + <member name="debug/settings/stdout/print_fps" type="bool" setter="" getter=""> + Print frames per second to stdout. Not very useful in general. + </member> + <member name="debug/settings/stdout/verbose_stdout" type="bool" setter="" getter=""> + Print more information to stdout when running. It shows info such as memory leaks, which scenes and resources are being loaded, etc. + </member> + <member name="debug/settings/visual_script/max_call_stack" type="int" setter="" getter=""> + Maximum call stack in visual scripting, to avoid infinite recursion. + </member> + <member name="display/mouse_cursor/custom_image" type="String" setter="" getter=""> + Custom image for the mouse cursor. + </member> + <member name="display/mouse_cursor/custom_image_hotspot" type="Vector2" setter="" getter=""> + Hotspot for the custom mouse cursor image. + </member> + <member name="display/window/allow_per_pixel_transparency" type="bool" setter="" getter=""> + Allow per pixel transparency in a Desktop window. This affects performance if not needed, so leave it off. + </member> + <member name="display/window/dpi/allow_hidpi" type="bool" setter="" getter=""> + Allow HiDPI display on Windows and OSX. On Desktop Linux, this can't be enabled or disabled. + </member> + <member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter=""> + Force keep the screen on, so the screensaver does not take over. Works on Desktop and Mobile. + </member> + <member name="display/window/handheld/orientation" type="String" setter="" getter=""> + Default orientation for cell phone or tablet. + </member> + <member name="display/window/per_pixel_transparency" type="bool" setter="" getter=""> + </member> + <member name="display/window/per_pixel_transparency_splash" type="bool" setter="" getter=""> + </member> + <member name="display/window/size/always_on_top" type="bool" setter="" getter=""> + Force the window to be always on top. + </member> + <member name="display/window/size/borderless" type="bool" setter="" getter=""> + Force the window to be borderless. + </member> + <member name="display/window/size/fullscreen" type="bool" setter="" getter=""> + Set the window to full screen when it starts. + </member> + <member name="display/window/size/height" type="int" setter="" getter=""> + Set the main window height. On desktop, this is the default window size. Stretch mode settings use this also as a reference when enabled. + </member> + <member name="display/window/size/resizable" type="bool" setter="" getter=""> + Allow the window to be resizable by default. + </member> + <member name="display/window/size/test_height" type="int" setter="" getter=""> + Test a different height for the window. The main use for this is to test with stretch modes. + </member> + <member name="display/window/size/test_width" type="int" setter="" getter=""> + Test a different width for the window. The main use for this is to test with stretch modes. + </member> + <member name="display/window/size/width" type="int" setter="" getter=""> + Set the main window width. On desktop, this is the default window size. Stretch mode settings use this also as a reference when enabled. + </member> + <member name="display/window/vsync/use_vsync" type="bool" setter="" getter=""> + Use VSync. Don't be stupid, don't turn this off. + </member> + <member name="editor/active" type="bool" setter="" getter=""> + Internal editor setting, don't touch. + </member> + <member name="gui/common/default_scroll_deadzone" type="int" setter="" getter=""> + </member> + <member name="gui/common/swap_ok_cancel" type="bool" setter="" getter=""> + Enable swap OK and Cancel buttons on dialogs. This is because Windows/MacOS/Desktop Linux may use them in different order, so the GUI swaps them depending on the host OS. Disable this behavior by turning this setting off. + </member> + <member name="gui/theme/custom" type="String" setter="" getter=""> + Use a custom theme resource, set a path to it here. + </member> + <member name="gui/theme/custom_font" type="String" setter="" getter=""> + USe a custom default font resource, set a path to it here. + </member> + <member name="gui/theme/use_hidpi" type="bool" setter="" getter=""> + Make sure the theme used works with hidpi. + </member> + <member name="gui/timers/incremental_search_max_interval_msec" type="int" setter="" getter=""> + Timer setting for incremental search in Tree, IntemList, etc. controls. + </member> + <member name="gui/timers/text_edit_idle_detect_sec" type="int" setter="" getter=""> + Timer for detecting idle in the editor. + </member> + <member name="input/ui_accept" type="Array" setter="" getter=""> + </member> + <member name="input/ui_cancel" type="Array" setter="" getter=""> + </member> + <member name="input/ui_down" type="Array" setter="" getter=""> + </member> + <member name="input/ui_end" type="Array" setter="" getter=""> + </member> + <member name="input/ui_focus_next" type="Array" setter="" getter=""> + </member> + <member name="input/ui_focus_prev" type="Array" setter="" getter=""> + </member> + <member name="input/ui_home" type="Array" setter="" getter=""> + </member> + <member name="input/ui_left" type="Array" setter="" getter=""> + </member> + <member name="input/ui_page_down" type="Array" setter="" getter=""> + </member> + <member name="input/ui_page_up" type="Array" setter="" getter=""> + </member> + <member name="input/ui_right" type="Array" setter="" getter=""> + </member> + <member name="input/ui_select" type="Array" setter="" getter=""> + </member> + <member name="input/ui_up" type="Array" setter="" getter=""> + </member> + <member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter=""> + </member> + <member name="input_devices/pointing/emulate_touch_from_mouse" type="bool" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_1" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_10" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_11" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_12" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_13" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_14" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_15" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_16" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_17" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_18" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_19" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_2" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_20" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_3" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_4" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_5" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_6" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_7" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_8" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_physics/layer_9" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_1" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_10" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_11" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_12" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_13" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_14" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_15" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_16" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_17" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_18" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_19" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_2" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_20" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_3" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_4" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_5" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_6" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_7" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_8" type="String" setter="" getter=""> + </member> + <member name="layer_names/2d_render/layer_9" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_1" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_10" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_11" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_12" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_13" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_14" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_15" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_16" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_17" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_18" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_19" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_2" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_20" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_3" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_4" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_5" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_6" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_7" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_8" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_physics/layer_9" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_1" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_10" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_11" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_12" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_13" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_14" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_15" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_16" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_17" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_18" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_19" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_2" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_20" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_3" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_4" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_5" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_6" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_7" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_8" type="String" setter="" getter=""> + </member> + <member name="layer_names/3d_render/layer_9" type="String" setter="" getter=""> + </member> + <member name="locale/fallback" type="String" setter="" getter=""> + </member> + <member name="locale/test" type="String" setter="" getter=""> + </member> + <member name="logging/file_logging/enable_file_logging" type="bool" setter="" getter=""> + Log all output to a file. + </member> + <member name="logging/file_logging/log_path" type="String" setter="" getter=""> + Path to logs withint he project. Using an user:// based path is recommended. + </member> + <member name="logging/file_logging/max_log_files" type="int" setter="" getter=""> + Amount of log files (used for rotation)/ + </member> + <member name="memory/limits/message_queue/max_size_kb" type="int" setter="" getter=""> + Godot uses a message queue to defer some function calls. If you run out of space on it (you will see an error), you can increase the size here. + </member> + <member name="memory/limits/multithreaded_server/rid_pool_prealloc" type="int" setter="" getter=""> + This is used by servers when used in multi threading mode (servers and visual). RIDs are preallocated to avoid stalling the server requesting them on threads. If servers get stalled too often when loading resources in a thread, increase this number. + </member> + <member name="network/limits/debugger_stdout/max_chars_per_second" type="int" setter="" getter=""> + Maximum amount of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. + </member> + <member name="network/limits/debugger_stdout/max_errors_per_frame" type="int" setter="" getter=""> + Maximum amount of errors allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. + </member> + <member name="network/limits/debugger_stdout/max_messages_per_frame" type="int" setter="" getter=""> + Maximum amount of messages allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. + </member> + <member name="network/limits/packet_peer_stream/max_buffer_po2" type="int" setter="" getter=""> + Default size of packet peer stream for deserializing godot data. Over this size, data is dropped. + </member> + <member name="network/remote_fs/max_pages" type="int" setter="" getter=""> + Maximum amount of pages used for remote filesystem (used by debugging). + </member> + <member name="network/remote_fs/page_read_ahead" type="int" setter="" getter=""> + Amount of read ahead used by remote filesystem. Improves latency. + </member> + <member name="network/remote_fs/page_size" type="int" setter="" getter=""> + Page size used by remote filesystem. + </member> + <member name="network/ssl/certificates" type="String" setter="" getter=""> + If your game or application uses HTTPS, a certificates file is needed. It must be set here. + </member> + <member name="node/name_casing" type="int" setter="" getter=""> + When creating nodes names automatically, set the type of casing in this project. This is mostly an editor setting. + </member> + <member name="node/name_num_separator" type="int" setter="" getter=""> + What to use to separate node name from number. This is mostly an editor setting. + </member> + <member name="physics/2d/physics_engine" type="String" setter="" getter=""> + </member> + <member name="physics/2d/thread_model" type="int" setter="" getter=""> + Set whether physics is run on the main thread or a separate one. Running the server on a thread increases performance, but restricts API Access to only physics process. + </member> + <member name="physics/3d/physics_engine" type="String" setter="" getter=""> + </member> + <member name="physics/common/physics_fps" type="int" setter="" getter=""> + Frames per second used in the physics. Physics always needs a fixed amount of frames per second. + </member> + <member name="physics/common/physics_jitter_fix" type="float" setter="" getter=""> + Fix to improve physics jitter, specially on monitors where refresh rate is different than physics FPS. + </member> + <member name="rendering/environment/default_clear_color" type="Color" setter="" getter=""> + Default background clear color. + </member> + <member name="rendering/limits/buffers/blend_shape_max_buffer_size_kb" type="int" setter="" getter=""> + Max buffer size for blend shapes. Any blend shape bigger than this will not work. + </member> + <member name="rendering/limits/buffers/canvas_polygon_buffer_size_kb" type="int" setter="" getter=""> + Max buffer size for drawing polygons. Any polygon bigger than this will not work. + </member> + <member name="rendering/limits/buffers/canvas_polygon_index_buffer_size_kb" type="int" setter="" getter=""> + Max index buffer size for drawing polygons. Any polygon bigger than this will not work. + </member> + <member name="rendering/limits/buffers/immediate_buffer_size_kb" type="int" setter="" getter=""> + Max buffer size for drawing immediate objects (ImmediateGeometry nodes). Nodes using more than this size will not work. + </member> + <member name="rendering/limits/rendering/max_renderable_elements" type="int" setter="" getter=""> + Max amount of elements renderable in a frame. If more than this are visible per frame, they will be dropped. Keep in mind elements refer to mesh surfaces and not mesh themselves. + </member> + <member name="rendering/limits/time/time_rollover_secs" type="int" setter="" getter=""> + Shaders have a time variable that constantly increases. At some point it needs to be rolled back to zero to avoid numerical errors on shader animations. This setting specifies when. + </member> + <member name="rendering/quality/2d/use_pixel_snap" type="bool" setter="" getter=""> + Force snapping of polygons to pixels in 2D rendering. May help in some pixel art styles. + </member> + <member name="rendering/quality/depth_prepass/disable_for_vendors" type="String" setter="" getter=""> + Disable depth pre-pass for some GPU vendors (usually mobile), as their architecture already does this. + </member> + <member name="rendering/quality/depth_prepass/enable" type="bool" setter="" getter=""> + Do a previous depth pass before rendering materials. This increases performance in scenes with high overdraw, when complex materials and lighting are used. + </member> + <member name="rendering/quality/directional_shadow/size" type="int" setter="" getter=""> + Size in pixels of the directional shadow. + </member> + <member name="rendering/quality/directional_shadow/size.mobile" type="int" setter="" getter=""> + </member> + <member name="rendering/quality/driver/driver_name" type="String" setter="" getter=""> + </member> + <member name="rendering/quality/filters/anisotropic_filter_level" type="int" setter="" getter=""> + Maximum Anisotropic filter level used for textures when anisotropy enabled. + </member> + <member name="rendering/quality/filters/use_nearest_mipmap_filter" type="bool" setter="" getter=""> + Force to use nearest mipmap filtering when using mipmaps. This may increase performance in mobile as less memory bandwidth is used. + </member> + <member name="rendering/quality/intended_usage/framebuffer_allocation" type="int" setter="" getter=""> + Strategy used for framebuffer allocation. The simpler it is, the less memory it uses (but the least features it supports). + </member> + <member name="rendering/quality/intended_usage/framebuffer_allocation.mobile" type="int" setter="" getter=""> + </member> + <member name="rendering/quality/intended_usage/framebuffer_mode" type="int" setter="" getter=""> + </member> + <member name="rendering/quality/reflections/high_quality_ggx" type="bool" setter="" getter=""> + For reflection probes and panorama backgrounds (sky), use a high amount of samples to create ggx blurred versions (used for roughness). + </member> + <member name="rendering/quality/reflections/high_quality_ggx.mobile" type="bool" setter="" getter=""> + </member> + <member name="rendering/quality/reflections/texture_array_reflections" type="bool" setter="" getter=""> + For reflection probes and panorama backgrounds (sky), use a texure array instead of mipmaps. This reduces jitter noise on reflections, but costs more performance and memory. + </member> + <member name="rendering/quality/reflections/texture_array_reflections.mobile" type="bool" setter="" getter=""> + </member> + <member name="rendering/quality/shading/force_vertex_shading" type="bool" setter="" getter=""> + Force vertex shading for all rendering. This can increase performance a lot, but also reduces quality inmensely. Can work to optimize on very low end mobile. + </member> + <member name="rendering/quality/shading/force_vertex_shading.mobile" type="bool" setter="" getter=""> + </member> + <member name="rendering/quality/shadow_atlas/quadrant_0_subdiv" type="int" setter="" getter=""> + Subdivision quadrant size for shadow mapping. See shadow mapping documentation. + </member> + <member name="rendering/quality/shadow_atlas/quadrant_1_subdiv" type="int" setter="" getter=""> + Subdivision quadrant size for shadow mapping. See shadow mapping documentation. + </member> + <member name="rendering/quality/shadow_atlas/quadrant_2_subdiv" type="int" setter="" getter=""> + Subdivision quadrant size for shadow mapping. See shadow mapping documentation. + </member> + <member name="rendering/quality/shadow_atlas/quadrant_3_subdiv" type="int" setter="" getter=""> + Subdivision quadrant size for shadow mapping. See shadow mapping documentation. + </member> + <member name="rendering/quality/shadow_atlas/size" type="int" setter="" getter=""> + Size for shadow atlas (used for point and omni lights). See documentation. + </member> + <member name="rendering/quality/shadow_atlas/size.mobile" type="int" setter="" getter=""> + </member> + <member name="rendering/quality/shadows/filter_mode" type="int" setter="" getter=""> + Shadow filter mode. The more complex the filter, the more memory bandwidth required. + </member> + <member name="rendering/quality/shadows/filter_mode.mobile" type="int" setter="" getter=""> + </member> + <member name="rendering/quality/subsurface_scattering/follow_surface" type="bool" setter="" getter=""> + Improves quality of subsurface scattering, but cost significantly increases. + </member> + <member name="rendering/quality/subsurface_scattering/quality" type="int" setter="" getter=""> + Quality setting for subsurface scaterring (samples taken). + </member> + <member name="rendering/quality/subsurface_scattering/scale" type="int" setter="" getter=""> + </member> + <member name="rendering/quality/subsurface_scattering/weight_samples" type="bool" setter="" getter=""> + Weight subsurface scattering samples. Helps to avoid reading samples from unrelated parts of the screen. + </member> + <member name="rendering/quality/voxel_cone_tracing/high_quality" type="bool" setter="" getter=""> + Use high quality voxel cone tracing (looks better, but requires a higher end GPU). + </member> + <member name="rendering/threads/thread_model" type="int" setter="" getter=""> + Thread model for rendering. Rendering on a thread can vastly improve performance, but syncinc to the main thread can cause a bit more jitter. + </member> + <member name="rendering/vram_compression/import_etc" type="bool" setter="" getter=""> + If the project uses this compression (usually low end mobile), texture importer will import these. + </member> + <member name="rendering/vram_compression/import_etc2" type="bool" setter="" getter=""> + If the project uses this compression (usually high end mobile), texture importer will import these. + </member> + <member name="rendering/vram_compression/import_pvrtc" type="bool" setter="" getter=""> + If the project uses this compression (usually iOS), texture importer will import these. + </member> + <member name="rendering/vram_compression/import_s3tc" type="bool" setter="" getter=""> + If the project uses this compression (usually Desktop and Consoles), texture importer will import these. + </member> + <member name="script" type="Script" setter="" getter=""> + </member> + </members> <constants> </constants> </class> diff --git a/doc/classes/Quat.xml b/doc/classes/Quat.xml index 4121790881..c755e6b02a 100644 --- a/doc/classes/Quat.xml +++ b/doc/classes/Quat.xml @@ -6,12 +6,11 @@ <description> A unit quaternion used for representing 3D rotations. It is similar to [Basis], which implements matrix representation of rotations, and can be parametrized using both an axis-angle pair or Euler angles. But due to its compactness and the way it is stored in memory, certain operations (obtaining axis-angle and performing SLERP, in particular) are more efficient and robust against floating point errors. - Quaternions need to be (re)normalized. </description> <tutorials> - http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions - http://docs.godotengine.org/en/latest/tutorials/math/rotations.html + <link>http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions</link> + <link>http://docs.godotengine.org/en/latest/tutorials/math/rotations.html</link> </tutorials> <demos> </demos> @@ -19,45 +18,45 @@ <method name="Quat"> <return type="Quat"> </return> - <argument index="0" name="x" type="float"> - </argument> - <argument index="1" name="y" type="float"> - </argument> - <argument index="2" name="z" type="float"> - </argument> - <argument index="3" name="w" type="float"> + <argument index="0" name="from" type="Basis"> </argument> <description> - Returns a quaternion defined by these values. + Returns the rotation matrix corresponding to the given quaternion. </description> </method> <method name="Quat"> <return type="Quat"> </return> - <argument index="0" name="axis" type="Vector3"> - </argument> - <argument index="1" name="angle" type="float"> + <argument index="0" name="euler" type="Vector3"> </argument> <description> - Returns a quaternion that will rotate around the given axis by the specified angle. The axis must be a normalized vector. + Returns a quaternion that will perform a rotation specified by Euler angles (in the YXZ convention: first Z, then X, and Y last), given in the vector format as (X-angle, Y-angle, Z-angle). </description> </method> <method name="Quat"> <return type="Quat"> </return> - <argument index="0" name="euler" type="Vector3"> + <argument index="0" name="axis" type="Vector3"> + </argument> + <argument index="1" name="angle" type="float"> </argument> <description> - Returns a quaternion that will perform a rotation specified by Euler angles (in the YXZ convention: first Z, then X, and Y last), given in the vector format as (X-angle, Y-angle, Z-angle). + Returns a quaternion that will rotate around the given axis by the specified angle. The axis must be a normalized vector. </description> </method> <method name="Quat"> <return type="Quat"> </return> - <argument index="0" name="from" type="Basis"> + <argument index="0" name="x" type="float"> + </argument> + <argument index="1" name="y" type="float"> + </argument> + <argument index="2" name="z" type="float"> + </argument> + <argument index="3" name="w" type="float"> </argument> <description> - Returns the rotation matrix corresponding to the given quaternion. + Returns a quaternion defined by these values. </description> </method> <method name="cubic_slerp"> @@ -129,7 +128,7 @@ <method name="set_axis_angle"> <argument index="0" name="axis" type="Vector3"> </argument> - <argument index="1" name="phi" type="float"> + <argument index="1" name="angle" type="float"> </argument> <description> Set the quaternion to a rotation which rotates around axis by the specified angle, in radians. The axis must be a normalized vector. diff --git a/doc/classes/Range.xml b/doc/classes/Range.xml index 545f5cc83b..fa7e20eff6 100644 --- a/doc/classes/Range.xml +++ b/doc/classes/Range.xml @@ -29,6 +29,10 @@ </method> </methods> <members> + <member name="allow_greater" type="bool" setter="set_allow_greater" getter="is_greater_allowed"> + </member> + <member name="allow_lesser" type="bool" setter="set_allow_lesser" getter="is_lesser_allowed"> + </member> <member name="exp_edit" type="bool" setter="set_exp_ratio" getter="is_ratio_exp"> If [code]true[/code] and [code]min_value[/code] is greater than 0, [code]value[/code] will be represented exponentially rather than linearly. </member> diff --git a/doc/classes/Rect2.xml b/doc/classes/Rect2.xml index 1961aee8b5..1eea940da9 100644 --- a/doc/classes/Rect2.xml +++ b/doc/classes/Rect2.xml @@ -7,7 +7,7 @@ Rect2 consists of a position, a size, and several utility functions. It is typically used for fast overlap tests. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/math/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/math/index.html</link> </tutorials> <demos> </demos> @@ -42,6 +42,7 @@ <return type="Rect2"> </return> <description> + Returns a [code]Rect2[/code] with equivalent position and area, modified so that the top-left corner is the origin and [code]width[/code] and [code]height[/code] are positive. </description> </method> <method name="clip"> diff --git a/doc/classes/ReflectionProbe.xml b/doc/classes/ReflectionProbe.xml index 36a0c9cfda..a9a897ebaf 100644 --- a/doc/classes/ReflectionProbe.xml +++ b/doc/classes/ReflectionProbe.xml @@ -5,7 +5,7 @@ <description> </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/reflection_probes.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/reflection_probes.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 4ec4bbee4f..618f2f42b2 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -8,7 +8,7 @@ Note that assignments to [member bbcode_text] clear the tag stack and reconstruct it from the property's contents. Any edits made to [member bbcode_text] will erase previous edits made from other manual sources such as [method append_bbcode] and the [code]push_*[/code] / [method pop] methods. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/gui/bbcode_in_richtextlabel.html + <link>http://docs.godotengine.org/en/3.0/tutorials/gui/bbcode_in_richtextlabel.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/RigidBody.xml b/doc/classes/RigidBody.xml index 3190aed5ed..4253560f67 100644 --- a/doc/classes/RigidBody.xml +++ b/doc/classes/RigidBody.xml @@ -10,7 +10,7 @@ If you need to override the default physics behavior, you can write a custom force integration. See [member custom_integrator]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 55063bbe24..f5a19ede0c 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -7,8 +7,8 @@ As one of the most important classes, the [code]SceneTree[/code] manages the hierarchy of nodes in a scene as well as scenes themselves. Nodes can be added, retrieved and removed. The whole scene tree (and thus the current scene) can be paused. Scenes can be loaded, switched and reloaded. You can also use the SceneTree to organize your nodes into groups: every node can be assigned as many groups as you want to create, e.g. a "enemy" group. You can then iterate these groups or even call methods and set properties on all the group's members at once. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scene_tree.html - http://docs.godotengine.org/en/3.0/tutorials/viewports/multiple_resolutions.html + <link>http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scene_tree.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/viewports/multiple_resolutions.html</link> </tutorials> <demos> </demos> @@ -34,7 +34,7 @@ <argument index="2" name="method" type="String"> </argument> <description> - Calls [code]method[/code] on each member of the given group, respecting the given [enum GROUP_CALL] flags. + Calls [code]method[/code] on each member of the given group, respecting the given [enum GroupCallFlags]. </description> </method> <method name="change_scene"> @@ -160,7 +160,7 @@ <argument index="2" name="notification" type="int"> </argument> <description> - Sends the given notification to all members of the [code]group[/code], respecting the given [enum GROUP_CALL] flags. + Sends the given notification to all members of the [code]group[/code], respecting the given [enum GroupCallFlags]. </description> </method> <method name="queue_delete"> @@ -220,7 +220,7 @@ <argument index="3" name="value" type="Variant"> </argument> <description> - Sets the given [code]property[/code] to [code]value[/code] on all members of the given group, respecting the given [enum GROUP_CALL] flags. + Sets the given [code]property[/code] to [code]value[/code] on all members of the given group, respecting the given [enum GroupCallFlags]. </description> </method> <method name="set_input_as_handled"> @@ -269,6 +269,10 @@ <member name="multiplayer" type="MultiplayerAPI" setter="set_multiplayer" getter="get_multiplayer"> The default [MultiplayerAPI] instance for this SceneTree. </member> + <member name="multiplayer_poll" type="bool" setter="set_multiplayer_poll_enabled" getter="is_multiplayer_poll_enabled"> + If [code]true[/code] (default) enable the automatic polling of the [MultiplayerAPI] for this SceneTree during [signal idle_frame]. + When [code]false[/code] you need to manually call [method MultiplayerAPI.poll] for processing network packets and delivering RPCs/RSETs. This allows to run RPCs/RSETs in a different loop (e.g. physics, thread, specific time step) and for manual [Mutex] protecion when accessing the [MultiplayerAPI] from threads. + </member> <member name="network_peer" type="NetworkedMultiplayerPeer" setter="set_network_peer" getter="get_network_peer"> The peer object to handle the RPC system (effectively enabling networking when set). Depending on the peer itself, the SceneTree will become a network server (check with [method is_network_server()]) and will set root node's network mode to master (see NETWORK_MODE_* constants in [Node]), or it will become a regular peer with root node set to slave. All child nodes are set to inherit the network mode by default. Handling of networking-related events (connection, disconnection, new clients) is done by connecting to SceneTree's signals. </member> diff --git a/doc/classes/Script.xml b/doc/classes/Script.xml index 97f6a60f01..09c60afc2f 100644 --- a/doc/classes/Script.xml +++ b/doc/classes/Script.xml @@ -8,7 +8,7 @@ The 'new' method of a script subclass creates a new instance. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scripting.html + <link>http://docs.godotengine.org/en/3.0/getting_started/step_by_step/scripting.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/ScrollContainer.xml b/doc/classes/ScrollContainer.xml index f16b3920c2..02f58a88cb 100644 --- a/doc/classes/ScrollContainer.xml +++ b/doc/classes/ScrollContainer.xml @@ -11,6 +11,18 @@ <demos> </demos> <methods> + <method name="get_h_scrollbar"> + <return type="HScrollBar"> + </return> + <description> + </description> + </method> + <method name="get_v_scrollbar"> + <return type="VScrollBar"> + </return> + <description> + </description> + </method> </methods> <members> <member name="scroll_deadzone" type="int" setter="set_deadzone" getter="get_deadzone"> @@ -42,4 +54,8 @@ </signals> <constants> </constants> + <theme_items> + <theme_item name="bg" type="StyleBox"> + </theme_item> + </theme_items> </class> diff --git a/doc/classes/Shader.xml b/doc/classes/Shader.xml index 7c07778a05..76049d8947 100644 --- a/doc/classes/Shader.xml +++ b/doc/classes/Shader.xml @@ -7,7 +7,7 @@ This class allows you to define a custom shader program that can be used for various materials to render objects. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/shading/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/shading/index.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/ShaderMaterial.xml b/doc/classes/ShaderMaterial.xml index 058e00e46c..5abba9fba9 100644 --- a/doc/classes/ShaderMaterial.xml +++ b/doc/classes/ShaderMaterial.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ShaderMaterial" inherits="Material" category="Core" version="3.1"> <brief_description> - A material that uses a custom [Shader] program + A material that uses a custom [Shader] program. </brief_description> <description> A material that uses a custom [Shader] program to render either items to screen or process particles. You can create multiple materials for the same shader but configure different values for the uniforms defined in the shader. @@ -17,7 +17,7 @@ <argument index="0" name="param" type="String"> </argument> <description> - Returns the current value set for this material of a uniform in the shader + Returns the current value set for this material of a uniform in the shader. </description> </method> <method name="set_shader_param"> @@ -28,13 +28,13 @@ <argument index="1" name="value" type="Variant"> </argument> <description> - Changes the value set for this material of a uniform in the shader + Changes the value set for this material of a uniform in the shader. </description> </method> </methods> <members> <member name="shader" type="Shader" setter="set_shader" getter="get_shader"> - The [Shader] program used to render this material + The [Shader] program used to render this material. </member> </members> <constants> diff --git a/doc/classes/Shape.xml b/doc/classes/Shape.xml index 582e4f80c3..fcd01bc25a 100644 --- a/doc/classes/Shape.xml +++ b/doc/classes/Shape.xml @@ -7,7 +7,7 @@ Base class for all 3D shape resources. All 3D shapes that inherit from this can be set into a [PhysicsBody] or [Area]. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Shape2D.xml b/doc/classes/Shape2D.xml index ad20bf607a..6c13496fc4 100644 --- a/doc/classes/Shape2D.xml +++ b/doc/classes/Shape2D.xml @@ -7,7 +7,7 @@ Base class for all 2D Shapes. All 2D shape types inherit from this. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/physics_introduction.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/Skeleton.xml b/doc/classes/Skeleton.xml index 67e10e8f0a..4d826002fe 100644 --- a/doc/classes/Skeleton.xml +++ b/doc/classes/Skeleton.xml @@ -5,6 +5,8 @@ </brief_description> <description> Skeleton provides a hierarchical interface for managing bones, including pose, rest and animation (see [Animation]). Skeleton will support rag doll dynamics in the future. + The overall transform of a bone with respect to the skeleton is determined by the following hierarchical order: rest pose, custom pose and pose. + Note that "global pose" below refers to the overall transform of the bone with respect to skeleton, so it not the actual global/world transform of the bone. </description> <tutorials> </tutorials> @@ -60,6 +62,7 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> + Return the custom pose of the specified bone. Custom pose is applied on top of the rest pose. </description> </method> <method name="get_bone_global_pose" qualifiers="const"> @@ -68,6 +71,7 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> + Return the overall transform of the specified bone, with respect to the skeleton. Being relative to the skeleton frame, this is not the actual "global" transform of the bone. </description> </method> <method name="get_bone_name" qualifiers="const"> @@ -76,7 +80,7 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> - Return the name of the bone at index "index" + Return the name of the bone at index "index". </description> </method> <method name="get_bone_parent" qualifiers="const"> @@ -94,7 +98,7 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> - Return the pose transform for bone "bone_idx". + Return the pose transform of the specified bone. Pose is applied on top of the custom pose, which is applied on top the rest pose. </description> </method> <method name="get_bone_rest" qualifiers="const"> @@ -112,6 +116,7 @@ <argument index="0" name="bone_idx" type="int"> </argument> <description> + Return the combination of custom pose and pose. The returned transform is in skeleton's reference frame. </description> </method> <method name="get_bound_child_nodes_to_bone" qualifiers="const"> @@ -147,14 +152,20 @@ <description> </description> </method> - <method name="physical_bones_simulation"> + <method name="physical_bones_start_simulation"> <return type="void"> </return> - <argument index="0" name="start" type="bool"> + <argument index="0" name="bones" type="Array" default="[ ]"> </argument> <description> </description> </method> + <method name="physical_bones_stop_simulation"> + <return type="void"> + </return> + <description> + </description> + </method> <method name="set_bone_custom_pose"> <return type="void"> </return> diff --git a/doc/classes/Spatial.xml b/doc/classes/Spatial.xml index 9ef60109de..d9242d8c42 100644 --- a/doc/classes/Spatial.xml +++ b/doc/classes/Spatial.xml @@ -5,11 +5,10 @@ </brief_description> <description> Most basic 3D game object, with a 3D [Transform] and visibility settings. All other 3D game objects inherit from Spatial. Use Spatial as a parent node to move, scale, rotate and show/hide children in a 3D project. - Affine operations (rotate, scale, translate) happen in parent's local coordinate system, unless the Spatial object is set as top level. Affine operations in this coordinate system correspond to direct affine operations on the Spatial's transform. The word local below refers to this coordinate system. The coordinate system that is attached to the Spatial object itself is referred to as object-local coordinate system. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/introduction_to_3d.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/introduction_to_3d.html</link> </tutorials> <demos> </demos> @@ -284,7 +283,6 @@ </member> <member name="rotation" type="Vector3" setter="set_rotation" getter="get_rotation"> Rotation part of the local transformation, specified in terms of YXZ-Euler angles in the format (X-angle, Y-angle, Z-angle), in radians. - Note that in the mathematical sense, rotation is a matrix and not a vector. The three Euler angles, which are the three indepdent parameters of the Euler-angle parametrization of the rotation matrix, are stored in a [Vector3] data structure not because the rotation is a vector, but only because [Vector3] exists as a convenient data-structure to store 3 floating point numbers. Therefore, applying affine operations on the rotation "vector" is not meaningful. </member> <member name="rotation_degrees" type="Vector3" setter="set_rotation_degrees" getter="get_rotation_degrees"> diff --git a/doc/classes/SpatialMaterial.xml b/doc/classes/SpatialMaterial.xml index 5feaf70e9d..b45f2a13d4 100644 --- a/doc/classes/SpatialMaterial.xml +++ b/doc/classes/SpatialMaterial.xml @@ -5,7 +5,7 @@ <description> </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/spatial_material.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/spatial_material.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/SpotLight.xml b/doc/classes/SpotLight.xml index 57a802d325..1f81e9e5c2 100644 --- a/doc/classes/SpotLight.xml +++ b/doc/classes/SpotLight.xml @@ -7,7 +7,7 @@ A SpotLight light is a type of [Light] node that emits lights in a specific direction, in the shape of a cone. The light is attenuated through the distance and this attenuation can be configured by changing the energy, radius and attenuation parameters of [Light]. TODO: Image of a spotlight. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/lights_and_shadows.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/SpriteBase3D.xml b/doc/classes/SpriteBase3D.xml index 5eb4eb09af..fd4b583928 100644 --- a/doc/classes/SpriteBase3D.xml +++ b/doc/classes/SpriteBase3D.xml @@ -11,6 +11,12 @@ <demos> </demos> <methods> + <method name="generate_triangle_mesh" qualifiers="const"> + <return type="TriangleMesh"> + </return> + <description> + </description> + </method> <method name="get_item_rect" qualifiers="const"> <return type="Rect2"> </return> diff --git a/doc/classes/SpriteFrames.xml b/doc/classes/SpriteFrames.xml index e806547b7d..91129b5850 100644 --- a/doc/classes/SpriteFrames.xml +++ b/doc/classes/SpriteFrames.xml @@ -17,7 +17,7 @@ <argument index="0" name="anim" type="String"> </argument> <description> - Adds a new animation to the the library. + Adds a new animation to the library. </description> </method> <method name="add_frame"> diff --git a/doc/classes/StreamPeerSSL.xml b/doc/classes/StreamPeerSSL.xml index d7350ac1d5..3081abd5c4 100644 --- a/doc/classes/StreamPeerSSL.xml +++ b/doc/classes/StreamPeerSSL.xml @@ -7,7 +7,7 @@ SSL Stream peer. This object can be used to connect to SSL servers. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/networking/ssl_certificates.html + <link>http://docs.godotengine.org/en/3.0/tutorials/networking/ssl_certificates.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index a55e184474..0ba1066dfd 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -643,6 +643,20 @@ Returns the right side of the string from a given position. </description> </method> + <method name="rsplit"> + <return type="PoolStringArray"> + </return> + <argument index="0" name="divisor" type="String"> + </argument> + <argument index="1" name="allow_empty" type="bool" default="True"> + </argument> + <argument index="2" name="maxsplit" type="int" default="0"> + </argument> + <description> + Splits the string by a [code]divisor[/code] string and returns an array of the substrings, starting from right. Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". + If [code]maxsplit[/code] is specified, then it is number of splits to do, default is 0 which splits all the items. + </description> + </method> <method name="rstrip"> <return type="String"> </return> @@ -688,20 +702,6 @@ If [code]maxsplit[/code] is given, at most maxsplit number of splits occur, and the remainder of the string is returned as the final element of the list (thus, the list will have at most maxsplit+1 elements) </description> </method> - <method name="rsplit"> - <return type="PoolStringArray"> - </return> - <argument index="0" name="divisor" type="String"> - </argument> - <argument index="1" name="allow_empty" type="bool" default="True"> - </argument> - <argument index="2" name="maxsplit" type="int" default="0"> - </argument> - <description> - Splits the string by a [code]divisor[/code] string and returns an array of the substrings, starting from right. Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". - If [code]maxsplit[/code] is specified, then it is number of splits to do, default is 0 which splits all the items. - </description> - </method> <method name="split_floats"> <return type="PoolRealArray"> </return> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 11fc00d129..ee9b7383e5 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -386,7 +386,7 @@ <member name="v_scroll_speed" type="float" setter="set_v_scroll_speed" getter="get_v_scroll_speed"> If [code]true[/code], enables text wrapping when it goes beyond he edge of what is visible. </member> - <member name="wrap_lines" type="bool" setter="set_wrap" getter="is_wrapping"> + <member name="wrap_enabled" type="bool" setter="set_wrap_enabled" getter="is_wrap_enabled"> </member> </members> <signals> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 775fef4fb7..656063771d 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -7,7 +7,7 @@ Node for 2D tile-based maps. Tilemaps use a [TileSet] which contain a list of tiles (textures plus optional collision, navigation, and/or occluder shapes) which are used to create grid-based maps. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/2d/using_tilemaps.html + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/using_tilemaps.html</link> </tutorials> <demos> </demos> @@ -52,7 +52,7 @@ <argument index="0" name="bit" type="int"> </argument> <description> - Returns [code]true[/code] if the given collision layer bit is set. + Returns [code]true[/code] if the given collision layer bit is set. </description> </method> <method name="get_collision_mask_bit" qualifiers="const"> @@ -61,7 +61,7 @@ <argument index="0" name="bit" type="int"> </argument> <description> - Returns [code]true[/code] if the given collision mask bit is set. + Returns [code]true[/code] if the given collision mask bit is set. </description> </method> <method name="get_used_cells" qualifiers="const"> @@ -77,14 +77,14 @@ <argument index="0" name="id" type="int"> </argument> <description> - Returns an array of all cells with the given tile id. + Returns an array of all cells with the given tile id. </description> </method> <method name="get_used_rect"> <return type="Rect2"> </return> <description> - Returns a rectangle enclosing the used (non-empty) tiles of the map. + Returns a rectangle enclosing the used (non-empty) tiles of the map. </description> </method> <method name="is_cell_transposed" qualifiers="const"> @@ -128,7 +128,7 @@ <argument index="1" name="ignore_half_ofs" type="bool" default="false"> </argument> <description> - Returns the global position corresponding to the given tilemap (grid-based) coordinates. + Returns the global position corresponding to the given tilemap (grid-based) coordinates. Optionally, the tilemap's half offset can be ignored. </description> </method> @@ -193,7 +193,7 @@ <argument index="1" name="value" type="bool"> </argument> <description> - Sets the given collision mask bit. + Sets the given collision mask bit. </description> </method> <method name="update_bitmask_area"> @@ -223,7 +223,7 @@ <argument index="0" name="world_position" type="Vector2"> </argument> <description> - Returns the tilemap (grid-based) coordinatescorresponding to the given global position. + Returns the tilemap (grid-based) coordinatescorresponding to the given global position. </description> </method> </methods> diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 8f7969505e..bdf8634a6c 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -450,7 +450,9 @@ <constants> <constant name="BITMASK_2X2" value="0" enum="BitmaskMode"> </constant> - <constant name="BITMASK_3X3" value="1" enum="BitmaskMode"> + <constant name="BITMASK_3X3_MINIMAL" value="1" enum="BitmaskMode"> + </constant> + <constant name="BITMASK_3X3" value="2" enum="BitmaskMode"> </constant> <constant name="BIND_TOPLEFT" value="1" enum="AutotileBindings"> </constant> diff --git a/doc/classes/ToolButton.xml b/doc/classes/ToolButton.xml index 1dbfd63010..d8db95a854 100644 --- a/doc/classes/ToolButton.xml +++ b/doc/classes/ToolButton.xml @@ -5,7 +5,6 @@ </brief_description> <description> This is a helper class to generate a flat [Button] (see [method Button.set_flat]), creating a ToolButton is equivalent to: - [codeblock] var btn = Button.new() btn.set_flat(true) diff --git a/doc/classes/Transform.xml b/doc/classes/Transform.xml index 4567f1681c..0dd8038b36 100644 --- a/doc/classes/Transform.xml +++ b/doc/classes/Transform.xml @@ -7,8 +7,8 @@ Represents one or many transformations in 3D space such as translation, rotation, or scaling. It consists of a [Basis] "basis" and an [Vector3] "origin". It is similar to a 3x4 matrix. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/math/index.html - http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html + <link>http://docs.godotengine.org/en/3.0/tutorials/math/index.html</link> + <link>http://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 6448b26972..57e0f2825a 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -308,7 +308,7 @@ <argument index="0" name="child" type="Object"> </argument> <description> - Removes the child TreeItem at index [code]index[/code]. + Removes the given child TreeItem. </description> </method> <method name="select"> diff --git a/doc/classes/Tween.xml b/doc/classes/Tween.xml index 2332c1a7aa..d82694d328 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -181,6 +181,12 @@ Returns [code]true[/code] if any tweens are currently running. Note that this method doesn't consider tweens that have ended. </description> </method> + <method name="is_stopped" qualifiers="const"> + <return type="bool"> + </return> + <description> + </description> + </method> <method name="remove"> <return type="bool"> </return> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index e3fbaff62d..6ffeddf5c1 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -7,7 +7,7 @@ 2-element structure that can be used to represent positions in 2d space or any other pair of numeric values. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/math/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/math/index.html</link> </tutorials> <demos> </demos> @@ -34,7 +34,7 @@ <return type="float"> </return> <description> - Returns the vector's angle in radians with respect to the x-axis, or [code](1, 0)[/code] vector. + Returns the vector's angle in radians with respect to the x-axis, or [code](1, 0)[/code] vector. Equivalent to the result of atan2 when called with the vector's x and y as parameters: [code]atan2(x, y)[/code]. </description> </method> @@ -76,6 +76,7 @@ <return type="Vector2"> </return> <description> + Returns the vector with all components rounded up. </description> </method> <method name="clamped"> @@ -142,7 +143,7 @@ <return type="Vector2"> </return> <description> - Remove the fractional part of x and y. + Returns the vector with all components rounded down. </description> </method> <method name="is_normalized"> @@ -190,7 +191,7 @@ <argument index="0" name="n" type="Vector2"> </argument> <description> - Returns the vector reflected from a plane defined by the given normal. + Returns the vector reflected from a plane defined by the given normal. </description> </method> <method name="rotated"> @@ -206,6 +207,7 @@ <return type="Vector2"> </return> <description> + Returns the vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. </description> </method> <method name="slerp"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 84c16c9fc5..62a480166a 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -7,7 +7,7 @@ Vector3 is one of the core classes of the engine, and includes several built-in helper functions to perform basic vector math operations. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/math/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/math/index.html</link> </tutorials> <demos> </demos> @@ -151,7 +151,7 @@ <argument index="1" name="t" type="float"> </argument> <description> - Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], a percentage of how far along the interpolation is. + Returns the result of the linear interpolation between this vector and [code]b[/code] by amount [code]t[/code]. [code]t[/code] is in the range of [code]0.0 - 1.0[/code], a percentage of how far along the interpolation is. </description> </method> <method name="max_axis"> @@ -208,6 +208,7 @@ <return type="Vector3"> </return> <description> + Returns the vector with all components rounded to the nearest integer, with halfway cases rounded away from zero. </description> </method> <method name="slerp"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 4878f7d932..af0712d357 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -6,14 +6,14 @@ <description> A Viewport creates a different view into the screen, or a sub-view inside another viewport. Children 2D Nodes will display on it, and children Camera 3D nodes will render on it too. Optionally, a viewport can have its own 2D or 3D world, so they don't share what they draw with other viewports. - If a viewport is a child of a [Control], it will automatically take up its same rect and position, otherwise they must be set manually. + If a viewport is a child of a [ViewportContainer], it will automatically take up its size, otherwise it must be set manually. Viewports can also choose to be audio listeners, so they generate positional audio depending on a 2D or 3D camera child of it. Also, viewports can be assigned to different screens in case the devices have multiple screens. Finally, viewports can also behave as render targets, in which case they will not be visible unless the associated texture is used to draw. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html - http://docs.godotengine.org/en/3.0/tutorials/viewports/index.html + <link>http://docs.godotengine.org/en/3.0/tutorials/2d/2d_transforms.html</link> + <link>http://docs.godotengine.org/en/3.0/tutorials/viewports/index.html</link> </tutorials> <demos> </demos> @@ -276,7 +276,7 @@ Do not update the render target. </constant> <constant name="UPDATE_ONCE" value="1" enum="UpdateMode"> - Update the render target once, then switch to [code]UPDATE_DISABLED[/code] + Update the render target once, then switch to [code]UPDATE_DISABLED[/code]. </constant> <constant name="UPDATE_WHEN_VISIBLE" value="2" enum="UpdateMode"> Update the render target only when it is visible. This is the default value. @@ -329,6 +329,7 @@ Objects are displayed without light information. </constant> <constant name="DEBUG_DRAW_OVERDRAW" value="2" enum="DebugDraw"> + Objected are displayed semi-transparent with additive blending so you can see where they intersect. </constant> <constant name="DEBUG_DRAW_WIREFRAME" value="3" enum="DebugDraw"> Objects are displayed in wireframe style. @@ -353,10 +354,13 @@ <constant name="USAGE_3D_NO_EFFECTS" value="3" enum="Usage"> </constant> <constant name="CLEAR_MODE_ALWAYS" value="0" enum="ClearMode"> + Always clear the render target before drawing. </constant> <constant name="CLEAR_MODE_NEVER" value="1" enum="ClearMode"> + Never clear the render target. </constant> <constant name="CLEAR_MODE_ONLY_NEXT_FRAME" value="2" enum="ClearMode"> + Clear the render target next frame, then switch to [code]CLEAR_MODE_NEVER[/code]. </constant> </constants> </class> diff --git a/doc/classes/ViewportTexture.xml b/doc/classes/ViewportTexture.xml index 83ffc6bcfd..67f1e09c75 100644 --- a/doc/classes/ViewportTexture.xml +++ b/doc/classes/ViewportTexture.xml @@ -1,8 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ViewportTexture" inherits="Texture" category="Core" version="3.1"> <brief_description> + Texture which displays the content of a [Viewport]. </brief_description> <description> + Displays the content of a [Viewport] node as a dynamic [Texture]. This can be used to mix controls, 2D, and 3D elements in the same scene. + To create a ViewportTexture in code, use the [method Viewport.get_texture] method on the target viewport. </description> <tutorials> </tutorials> @@ -12,6 +15,7 @@ </methods> <members> <member name="viewport_path" type="NodePath" setter="set_viewport_path_in_scene" getter="get_viewport_path_in_scene"> + The path to the [Viewport] node to display. This is relative to the scene root, not to the node which uses the texture. </member> </members> <constants> diff --git a/doc/classes/World.xml b/doc/classes/World.xml index 9fc0e139b5..540848e40a 100644 --- a/doc/classes/World.xml +++ b/doc/classes/World.xml @@ -7,7 +7,7 @@ Class that has everything pertaining to a world. A physics space, a visual scenario and a sound space. Spatial nodes register their resources into the current world. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/World2D.xml b/doc/classes/World2D.xml index 5f6a5b8ad4..780cdd181a 100644 --- a/doc/classes/World2D.xml +++ b/doc/classes/World2D.xml @@ -7,7 +7,7 @@ Class that has everything pertaining to a 2D world. A physics space, a visual scenario and a sound space. 2D nodes register their resources into the current 2D world. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html + <link>http://docs.godotengine.org/en/3.0/tutorials/physics/ray-casting.html</link> </tutorials> <demos> </demos> diff --git a/doc/classes/WorldEnvironment.xml b/doc/classes/WorldEnvironment.xml index 422ca3a558..e68ad2800e 100644 --- a/doc/classes/WorldEnvironment.xml +++ b/doc/classes/WorldEnvironment.xml @@ -9,7 +9,7 @@ The [code]WorldEnvironment[/code] allows the user to specify default lighting parameters (e.g. ambient lighting), various post-processing effects (e.g. SSAO, DOF, Tonemapping), and how to draw the background (e.g. solid color, skybox). Usually, these are added in order to improve the realism/color balance of the scene. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/environment_and_post_processing.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/environment_and_post_processing.html</link> </tutorials> <demos> </demos> diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py index adbd810d11..93ad823d42 100755 --- a/doc/tools/makerst.py +++ b/doc/tools/makerst.py @@ -4,11 +4,15 @@ import codecs import sys import os +import re import xml.etree.ElementTree as ET input_list = [] cur_file = "" +# http(s)://docs.godotengine.org/<langcode>/<tag>/path/to/page.html(#fragment-tag) +godot_docs_pattern = re.compile('^http(?:s)?:\/\/docs\.godotengine\.org\/(?:[a-zA-Z0-9\.\-_]*)\/(?:[a-zA-Z0-9\.\-_]*)\/(.*)\.html(#.*)?$') + for arg in sys.argv[1:]: if arg.endswith(os.sep): arg = arg[:-1] @@ -155,9 +159,19 @@ def rstize_text(text, cclass): text = pre_text + "\n\n" + post_text pos += 2 + next_brac_pos = text.find('[') + + # Escape \ character, otherwise it ends up as an escape character in rst + pos = 0 + while True: + pos = text.find('\\', pos, next_brac_pos) + if pos == -1: + break + text = text[:pos] + "\\\\" + text[pos + 1:] + pos += 2 + # Escape * character to avoid interpreting it as emphasis pos = 0 - next_brac_pos = text.find('[') while True: pos = text.find('*', pos, next_brac_pos) if pos == -1: @@ -578,6 +592,32 @@ def make_rst_class(node): f.write(make_heading('Description', '-')) f.write(rstize_text(descr.text.strip(), name) + "\n\n") + global godot_docs_pattern + tutorials = node.find('tutorials') + if tutorials != None and len(tutorials) > 0: + f.write(make_heading('Tutorials', '-')) + for t in tutorials: + link = t.text.strip() + match = godot_docs_pattern.search(link); + if match: + groups = match.groups() + if match.lastindex == 2: + # Doc reference with fragment identifier: emit direct link to section with reference to page, for example: + # `#calling-javascript-from-script in Exporting For Web` + f.write("- `" + groups[1] + " <../" + groups[0] + ".html" + groups[1] + ">`_ in :doc:`../" + groups[0] + "`\n") + # Commented out alternative: Instead just emit: + # `Subsection in Exporting For Web` + # f.write("- `Subsection <../" + groups[0] + ".html" + groups[1] + ">`_ in :doc:`../" + groups[0] + "`\n") + elif match.lastindex == 1: + # Doc reference, for example: + # `Math` + f.write("- :doc:`../" + groups[0] + "`\n") + else: + # External link, for example: + # `http://enet.bespin.org/usergroup0.html` + f.write("- `" + link + " <" + link + ">`_\n") + f.write("\n") + methods = node.find('methods') if methods != None and len(list(methods)) > 0: f.write(make_heading('Member Function Description', '-')) diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index 312d5aa378..98f60b875c 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -67,7 +67,7 @@ public: void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture) {} void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance, bool p_roughness) {} - void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) {} + void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) {} void environment_set_tonemap(RID p_env, VS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale) {} diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index bb39cbcbd5..f7712be5d0 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -144,7 +144,7 @@ void RasterizerSceneGLES2::environment_set_fog(RID p_env, bool p_enable, float p void RasterizerSceneGLES2::environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_in, float p_fade_out, float p_depth_tolerance, bool p_roughness) { } -void RasterizerSceneGLES2::environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VisualServer::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) { +void RasterizerSceneGLES2::environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VisualServer::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) { } void RasterizerSceneGLES2::environment_set_tonemap(RID p_env, VS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale) { diff --git a/drivers/gles2/rasterizer_scene_gles2.h b/drivers/gles2/rasterizer_scene_gles2.h index 99f034afed..110222f709 100644 --- a/drivers/gles2/rasterizer_scene_gles2.h +++ b/drivers/gles2/rasterizer_scene_gles2.h @@ -212,7 +212,7 @@ public: virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture); virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_in, float p_fade_out, float p_depth_tolerance, bool p_roughness); - virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness); + virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness); virtual void environment_set_tonemap(RID p_env, VS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale); diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 6e7e1793e1..de7359a18b 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -1303,7 +1303,6 @@ Transform2D RasterizerStorageGLES2::skeleton_bone_get_transform_2d(RID p_skeleto } void RasterizerStorageGLES2::skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) { - } void RasterizerStorageGLES2::update_dirty_skeletons() { diff --git a/drivers/gles2/shader_compiler_gles2.cpp b/drivers/gles2/shader_compiler_gles2.cpp index ad6c2f850a..aa55e72083 100644 --- a/drivers/gles2/shader_compiler_gles2.cpp +++ b/drivers/gles2/shader_compiler_gles2.cpp @@ -712,7 +712,7 @@ ShaderCompilerGLES2::ShaderCompilerGLES2() { actions[VS::SHADER_CANVAS_ITEM].renames["WORLD_MATRIX"] = "modelview_matrix"; actions[VS::SHADER_CANVAS_ITEM].renames["PROJECTION_MATRIX"] = "projection_matrix"; - actions[VS::SHADER_CANVAS_ITEM].renames["EXTRA_MATRIX"] == "extra_matrix"; + actions[VS::SHADER_CANVAS_ITEM].renames["EXTRA_MATRIX"] = "extra_matrix"; actions[VS::SHADER_CANVAS_ITEM].renames["TIME"] = "time"; actions[VS::SHADER_CANVAS_ITEM].renames["AT_LIGHT_PASS"] = "at_light_pass"; actions[VS::SHADER_CANVAS_ITEM].renames["INSTANCE_CUSTOM"] = "instance_custom"; diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index bb4c8ab4d7..c2377e0c3e 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -832,6 +832,9 @@ void RasterizerCanvasGLES3::_canvas_item_render_commands(Item *p_item, Item *cur if (!particles) break; + if (particles->inactive && !particles->emitting) + break; + glVertexAttrib4f(VS::ARRAY_COLOR, 1, 1, 1, 1); //not used, so keep white VisualServerRaster::redraw_request(); diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 03ff84c093..caa3921dc4 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -896,7 +896,7 @@ void RasterizerSceneGLES3::environment_set_ssr(RID p_env, bool p_enable, int p_m env->ssr_roughness = p_roughness; } -void RasterizerSceneGLES3::environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VisualServer::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) { +void RasterizerSceneGLES3::environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VisualServer::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) { Environment *env = environment_owner.getornull(p_env); ERR_FAIL_COND(!env); @@ -908,6 +908,7 @@ void RasterizerSceneGLES3::environment_set_ssao(RID p_env, bool p_enable, float env->ssao_intensity2 = p_intensity2; env->ssao_bias = p_bias; env->ssao_light_affect = p_light_affect; + env->ssao_ao_channel_affect = p_ao_channel_affect; env->ssao_color = p_color; env->ssao_filter = p_blur; env->ssao_quality = p_quality; @@ -2507,6 +2508,7 @@ void RasterizerSceneGLES3::_setup_environment(Environment *env, const CameraMatr state.env_radiance_data.ambient_contribution = env->ambient_sky_contribution; state.ubo_data.ambient_occlusion_affect_light = env->ssao_light_affect; + state.ubo_data.ambient_occlusion_affect_ssao = env->ssao_ao_channel_affect; //fog diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index a6faeef473..524212b9c1 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -140,6 +140,7 @@ public: float reflection_multiplier; float subsurface_scatter_width; float ambient_occlusion_affect_light; + float ambient_occlusion_affect_ssao; uint32_t fog_depth_enabled; float fog_depth_begin; @@ -151,6 +152,7 @@ public: float fog_height_max; float fog_height_curve; // make sure this struct is padded to be a multiple of 16 bytes for webgl + float pad[3]; } ubo_data; @@ -385,6 +387,7 @@ public: float ssao_radius2; float ssao_bias; float ssao_light_affect; + float ssao_ao_channel_affect; Color ssao_color; VS::EnvironmentSSAOQuality ssao_quality; float ssao_bilateral_sharpness; @@ -465,6 +468,7 @@ public: ssao_radius2 = 0.0; ssao_bias = 0.01; ssao_light_affect = 0; + ssao_ao_channel_affect = 0; ssao_filter = VS::ENV_SSAO_BLUR_3x3; ssao_quality = VS::ENV_SSAO_QUALITY_LOW; ssao_bilateral_sharpness = 4; @@ -543,7 +547,7 @@ public: virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture); virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_in, float p_fade_out, float p_depth_tolerance, bool p_roughness); - virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness); + virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness); virtual void environment_set_tonemap(RID p_env, VS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale); diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index f5481c597c..0e111e59a9 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -90,6 +90,7 @@ layout(std140) uniform SceneData { //ubo:0 mediump float reflection_multiplier; mediump float subsurface_scatter_width; mediump float ambient_occlusion_affect_light; + mediump float ambient_occlusion_affect_ao_channel; bool fog_depth_enabled; highp float fog_depth_begin; @@ -670,6 +671,7 @@ layout(std140) uniform SceneData { mediump float reflection_multiplier; mediump float subsurface_scatter_width; mediump float ambient_occlusion_affect_light; + mediump float ambient_occlusion_affect_ao_channel; bool fog_depth_enabled; highp float fog_depth_begin; @@ -2128,18 +2130,16 @@ FRAGMENT_SHADER_CODE #else -#if defined(ENABLE_AO) - - float ambient_scale=0.0; // AO is supplied by material -#else //approximate ambient scale for SSAO, since we will lack full ambient float max_emission=max(emission.r,max(emission.g,emission.b)); float max_ambient=max(ambient_light.r,max(ambient_light.g,ambient_light.b)); float max_diffuse=max(diffuse_light.r,max(diffuse_light.g,diffuse_light.b)); float total_ambient = max_ambient+max_diffuse+max_emission; float ambient_scale = (total_ambient>0.0) ? (max_ambient+ambient_occlusion_affect_light*max_diffuse)/total_ambient : 0.0; -#endif //ENABLE_AO +#if defined(ENABLE_AO) + ambient_scale=mix(0.0,ambient_scale,ambient_occlusion_affect_ao_channel); +#endif diffuse_buffer=vec4(emission+diffuse_light+ambient_light,ambient_scale); specular_buffer=vec4(specular_light,metallic); diff --git a/drivers/unix/socket_helpers.h b/drivers/unix/socket_helpers.h index 5ef9ad3088..5b42c13eae 100644 --- a/drivers/unix/socket_helpers.h +++ b/drivers/unix/socket_helpers.h @@ -124,6 +124,13 @@ static int _socket_create(IP::Type &p_type, int type, int protocol) { WARN_PRINT("Unable to set/unset IPv4 address mapping over IPv6"); } } + if (protocol == IPPROTO_UDP && p_type != IP::TYPE_IPV6) { + // Enable broadcasting for UDP sockets if it's not IPv6 only (IPv6 has no broadcast option). + int broadcast = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof(broadcast)) != 0) { + WARN_PRINT("Error when enabling broadcasting"); + } + } return sockfd; } diff --git a/editor/SCsub b/editor/SCsub index 4ca6b9e3fd..a9343f7f36 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -7,7 +7,6 @@ import os import os.path from compat import encode_utf8, byte_to_str, open_utf8, escape_string - def make_certs_header(target, source, env): src = source[0].srcnode().abspath @@ -147,268 +146,6 @@ def make_translations_header(target, source, env): g.close() - -def make_authors_header(target, source, env): - - sections = ["Project Founders", "Lead Developer", "Project Manager", "Developers"] - sections_id = ["dev_founders", "dev_lead", "dev_manager", "dev_names"] - - src = source[0].srcnode().abspath - dst = target[0].srcnode().abspath - f = open_utf8(src, "r") - g = open_utf8(dst, "w") - - g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") - g.write("#ifndef _EDITOR_AUTHORS_H\n") - g.write("#define _EDITOR_AUTHORS_H\n") - - current_section = "" - reading = False - - def close_section(): - g.write("\t0\n") - g.write("};\n") - - for line in f: - if reading: - if line.startswith(" "): - g.write("\t\"" + escape_string(line.strip()) + "\",\n") - continue - if line.startswith("## "): - if reading: - close_section() - reading = False - for i in range(len(sections)): - if line.strip().endswith(sections[i]): - current_section = escape_string(sections_id[i]) - reading = True - g.write("static const char *" + current_section + "[] = {\n") - break - - if reading: - close_section() - - g.write("#endif\n") - - g.close() - f.close() - -def make_donors_header(target, source, env): - - sections = ["Platinum sponsors", "Gold sponsors", "Mini sponsors", "Gold donors", "Silver donors", "Bronze donors"] - sections_id = ["donor_s_plat", "donor_s_gold", "donor_s_mini", "donor_gold", "donor_silver", "donor_bronze"] - - src = source[0].srcnode().abspath - dst = target[0].srcnode().abspath - f = open_utf8(src, "r") - g = open_utf8(dst, "w") - - g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") - g.write("#ifndef _EDITOR_DONORS_H\n") - g.write("#define _EDITOR_DONORS_H\n") - - current_section = "" - reading = False - - def close_section(): - g.write("\t0\n") - g.write("};\n") - - for line in f: - if reading >= 0: - if line.startswith(" "): - g.write("\t\"" + escape_string(line.strip()) + "\",\n") - continue - if line.startswith("## "): - if reading: - close_section() - reading = False - for i in range(len(sections)): - if line.strip().endswith(sections[i]): - current_section = escape_string(sections_id[i]) - reading = True - g.write("static const char *" + current_section + "[] = {\n") - break - - if reading: - close_section() - - g.write("#endif\n") - - g.close() - f.close() - - -def make_license_header(target, source, env): - - src_copyright = source[0].srcnode().abspath - src_license = source[1].srcnode().abspath - dst = target[0].srcnode().abspath - f = open_utf8(src_license, "r") - fc = open_utf8(src_copyright, "r") - g = open_utf8(dst, "w") - - g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") - g.write("#ifndef _EDITOR_LICENSE_H\n") - g.write("#define _EDITOR_LICENSE_H\n") - g.write("static const char *about_license =") - - for line in f: - escaped_string = escape_string(line.strip()) - g.write("\n\t\"" + escaped_string + "\\n\"") - - g.write(";\n") - - tp_current = 0 - tp_file = "" - tp_comment = "" - tp_copyright = "" - tp_license = "" - - tp_licensename = "" - tp_licensebody = "" - - tp = [] - tp_licensetext = [] - for line in fc: - if line.startswith("#"): - continue - - if line.startswith("Files:"): - tp_file = line[6:].strip() - tp_current = 1 - elif line.startswith("Comment:"): - tp_comment = line[8:].strip() - tp_current = 2 - elif line.startswith("Copyright:"): - tp_copyright = line[10:].strip() - tp_current = 3 - elif line.startswith("License:"): - if tp_current != 0: - tp_license = line[8:].strip() - tp_current = 4 - else: - tp_licensename = line[8:].strip() - tp_current = 5 - elif line.startswith(" "): - if tp_current == 1: - tp_file += "\n" + line.strip() - elif tp_current == 3: - tp_copyright += "\n" + line.strip() - elif tp_current == 5: - if line.strip() == ".": - tp_licensebody += "\n" - else: - tp_licensebody += line[1:] - else: - if tp_current != 0: - if tp_current == 5: - tp_licensetext.append([tp_licensename, tp_licensebody]) - - tp_licensename = "" - tp_licensebody = "" - else: - added = False - for i in tp: - if i[0] == tp_comment: - i[1].append([tp_file, tp_copyright, tp_license]) - added = True - break - if not added: - tp.append([tp_comment,[[tp_file, tp_copyright, tp_license]]]) - - tp_file = [] - tp_comment = "" - tp_copyright = [] - tp_license = "" - tp_current = 0 - - tp_licensetext.append([tp_licensename, tp_licensebody]) - - about_thirdparty = "" - about_tp_copyright_count = "" - about_tp_license = "" - about_tp_copyright = "" - about_tp_file = "" - - for i in tp: - about_thirdparty += "\t\"" + i[0] + "\",\n" - about_tp_copyright_count += str(len(i[1])) + ", " - for j in i[1]: - file_body = "" - copyright_body = "" - for k in j[0].split("\n"): - if file_body != "": - file_body += "\\n\"\n" - escaped_string = escape_string(k.strip()) - file_body += "\t\"" + escaped_string - for k in j[1].split("\n"): - if copyright_body != "": - copyright_body += "\\n\"\n" - escaped_string = escape_string(k.strip()) - copyright_body += "\t\"" + escaped_string - - about_tp_file += "\t" + file_body + "\",\n" - about_tp_copyright += "\t" + copyright_body + "\",\n" - about_tp_license += "\t\"" + j[2] + "\",\n" - - about_license_name = "" - about_license_body = "" - - for i in tp_licensetext: - body = "" - for j in i[1].split("\n"): - if body != "": - body += "\\n\"\n" - escaped_string = escape_string(j.strip()) - body += "\t\"" + escaped_string - - about_license_name += "\t\"" + i[0] + "\",\n" - about_license_body += "\t" + body + "\",\n" - - g.write("static const char *about_thirdparty[] = {\n") - g.write(about_thirdparty) - g.write("\t0\n") - g.write("};\n") - g.write("#define THIRDPARTY_COUNT " + str(len(tp)) + "\n") - - g.write("static const int about_tp_copyright_count[] = {\n\t") - g.write(about_tp_copyright_count) - g.write("0\n};\n") - - g.write("static const char *about_tp_file[] = {\n") - g.write(about_tp_file) - g.write("\t0\n") - g.write("};\n") - - g.write("static const char *about_tp_copyright[] = {\n") - g.write(about_tp_copyright) - g.write("\t0\n") - g.write("};\n") - - g.write("static const char *about_tp_license[] = {\n") - g.write(about_tp_license) - g.write("\t0\n") - g.write("};\n") - - g.write("static const char *about_license_name[] = {\n") - g.write(about_license_name) - g.write("\t0\n") - g.write("};\n") - g.write("#define LICENSE_COUNT " + str(len(tp_licensetext)) + "\n") - - g.write("static const char *about_license_body[] = {\n") - g.write(about_license_body) - g.write("\t0\n") - g.write("};\n") - - g.write("#endif\n") - - g.close() - fc.close() - f.close() - - def _make_doc_data_class_path(to_path): g = open_utf8(os.path.join(to_path,"doc_data_class_path.gen.h"), "w") g.write("static const int _doc_data_class_path_count = " + str(len(env.doc_class_path)) + ";\n") @@ -455,10 +192,10 @@ if env['tools']: docs = sorted(docs) env.Depends("#editor/doc_data_compressed.gen.h", docs) - env.Command("#editor/doc_data_compressed.gen.h", docs, make_doc_header) + env.CommandNoCache("#editor/doc_data_compressed.gen.h", docs, make_doc_header) # Certificates env.Depends("#editor/certs_compressed.gen.h", "#thirdparty/certs/ca-certificates.crt") - env.Command("#editor/certs_compressed.gen.h", "#thirdparty/certs/ca-certificates.crt", make_certs_header) + env.CommandNoCache("#editor/certs_compressed.gen.h", "#thirdparty/certs/ca-certificates.crt", make_certs_header) import glob path = env.Dir('.').abspath @@ -466,26 +203,13 @@ if env['tools']: # Translations tlist = glob.glob(path + "/translations/*.po") env.Depends('#editor/translations.gen.h', tlist) - env.Command('#editor/translations.gen.h', tlist, make_translations_header) + env.CommandNoCache('#editor/translations.gen.h', tlist, make_translations_header) # Fonts flist = glob.glob(path + "/../thirdparty/fonts/*.ttf") flist.append(glob.glob(path + "/../thirdparty/fonts/*.otf")) env.Depends('#editor/builtin_fonts.gen.h', flist) - env.Command('#editor/builtin_fonts.gen.h', flist, make_fonts_header) - - # Authors - env.Depends('#editor/authors.gen.h', "../AUTHORS.md") - env.Command('#editor/authors.gen.h', "../AUTHORS.md", make_authors_header) - - # Donors - env.Depends('#editor/donors.gen.h', "../DONORS.md") - env.Command('#editor/donors.gen.h', "../DONORS.md", make_donors_header) - - # License - env.Depends('#editor/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"]) - env.Command('#editor/license.gen.h', ["../COPYRIGHT.txt", "../LICENSE.txt"], make_license_header) - + env.CommandNoCache('#editor/builtin_fonts.gen.h', flist, make_fonts_header) env.add_source_files(env.editor_sources, "*.cpp") env.add_source_files(env.editor_sources, ["#thirdparty/misc/clipper.cpp"]) diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp new file mode 100644 index 0000000000..197599442b --- /dev/null +++ b/editor/animation_bezier_editor.cpp @@ -0,0 +1,1183 @@ +#include "animation_bezier_editor.h" + +float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) { + float h = p_h; + h = (h - v_scroll) / v_zoom; + h = (get_size().height / 2) - h; + return h; +} + +static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) { + /* Formula from Wikipedia article on Bezier curves. */ + real_t omt = (1.0 - t); + real_t omt2 = omt * omt; + real_t omt3 = omt2 * omt; + real_t t2 = t * t; + real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; +} + +void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { + + float scale = timeline->get_zoom_scale(); + int limit = timeline->get_name_limit(); + int right_limit = get_size().width - timeline->get_buttons_width(); + + //selection may have altered the order of keys + Map<float, int> key_order; + + for (int i = 0; i < animation->track_get_key_count(p_track); i++) { + float ofs = animation->track_get_key_time(p_track, i); + if (moving_selection && track == p_track && selection.has(i)) { + ofs += moving_selection_offset.x; + } + + key_order[ofs] = i; + } + + for (Map<float, int>::Element *E = key_order.front(); E; E = E->next()) { + + int i = E->get(); + + if (!E->next()) + break; + + int i_n = E->next()->get(); + + float offset = animation->track_get_key_time(p_track, i); + float height = animation->bezier_track_get_key_value(p_track, i); + Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i); + if (track == p_track && moving_handle != 0 && moving_handle_key == i) { + out_handle = moving_handle_right; + } + + if (moving_selection && track == p_track && selection.has(i)) { + offset += moving_selection_offset.x; + height += moving_selection_offset.y; + } + + out_handle += Vector2(offset, height); + + float offset_n = animation->track_get_key_time(p_track, i_n); + float height_n = animation->bezier_track_get_key_value(p_track, i_n); + Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n); + if (track == p_track && moving_handle != 0 && moving_handle_key == i_n) { + in_handle = moving_handle_left; + } + + if (moving_selection && track == p_track && selection.has(i_n)) { + offset_n += moving_selection_offset.x; + height_n += moving_selection_offset.y; + } + + in_handle += Vector2(offset_n, height_n); + + Vector2 start(offset, height); + Vector2 end(offset_n, height_n); + + int from_x = (offset - timeline->get_value()) * scale + limit; + int point_start = from_x; + int to_x = (offset_n - timeline->get_value()) * scale + limit; + int point_end = to_x; + + if (from_x > right_limit) //not visible + continue; + + if (to_x < limit) //not visible + continue; + + from_x = MAX(from_x, limit); + to_x = MIN(to_x, right_limit); + + Vector<Vector2> lines; + + Vector2 prev_pos; + + for (int j = from_x; j <= to_x; j++) { + + float t = (j - limit) / scale + timeline->get_value(); + + float h; + + if (j == point_end) { + h = end.y; //make sure it always connects + } else if (j == point_start) { + h = start.y; //make sure it always connects + } else { //custom interpolation, used because it needs to show paths affected by moving the selection or handles + int iterations = 10; + float low = 0; + float high = 1; + float middle; + + //narrow high and low as much as possible + for (int k = 0; k < iterations; k++) { + + middle = (low + high) / 2; + + Vector2 interp = _bezier_interp(middle, start, out_handle, in_handle, end); + + if (interp.x < t) { + low = middle; + } else { + high = middle; + } + } + + //interpolate the result: + Vector2 low_pos = _bezier_interp(low, start, out_handle, in_handle, end); + Vector2 high_pos = _bezier_interp(high, start, out_handle, in_handle, end); + + float c = (t - low_pos.x) / (high_pos.x - low_pos.x); + + h = low_pos.linear_interpolate(high_pos, c).y; + } + + h = _bezier_h_to_pixel(h); + + Vector2 pos(j, h); + + if (j > from_x) { + lines.push_back(prev_pos); + lines.push_back(pos); + } + prev_pos = pos; + } + + if (lines.size() >= 2) { + draw_multiline(lines, p_color); + } + } +} + +void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right) { + + Vector2 from = p_from; + Vector2 to = p_to; + + if (from.x == to.x) + return; + if (to.x < from.x) { + SWAP(to, from); + } + + if (to.x < p_clip_left) + return; + + if (from.x > p_clip_right) + return; + + if (to.x > p_clip_right) { + float c = (p_clip_right - from.x) / (to.x - from.x); + to = from.linear_interpolate(to, c); + } + + if (from.x < p_clip_left) { + float c = (p_clip_left - from.x) / (to.x - from.x); + from = from.linear_interpolate(to, c); + } + + draw_line(from, to, p_color); +} + +void AnimationBezierTrackEdit::_notification(int p_what) { + + if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { + bezier_icon = get_icon("KeyBezierPoint", "EditorIcons"); + bezier_handle_icon = get_icon("KeyBezierHandle", "EditorIcons"); + selected_icon = get_icon("KeyBezierSelected", "EditorIcons"); + if (handle_mode_option->get_item_count() == 0) { + handle_mode_option->add_icon_item(get_icon("BezierHandlesFree", "EditorIcons"), TTR("Free"), HANDLE_MODE_FREE); + handle_mode_option->add_icon_item(get_icon("BezierHandlesBalanced", "EditorIcons"), TTR("Balanced"), HANDLE_MODE_BALANCED); + handle_mode_option->add_icon_item(get_icon("BezierHandlesMirror", "EditorIcons"), TTR("Mirror"), HANDLE_MODE_MIRROR); + } + } + if (p_what == NOTIFICATION_RESIZED) { + + int right_limit = get_size().width - timeline->get_buttons_width(); + int hsep = get_constant("hseparation", "ItemList"); + int vsep = get_constant("vseparation", "ItemList"); + + handle_mode_option->set_position(Vector2(right_limit + hsep, get_size().height - handle_mode_option->get_combined_minimum_size().height - vsep)); + handle_mode_option->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, handle_mode_option->get_combined_minimum_size().height)); + } + if (p_what == NOTIFICATION_DRAW) { + if (animation.is_null()) + return; + + int limit = timeline->get_name_limit(); + + if (has_focus()) { + Color accent = get_color("accent_color", "Editor"); + accent.a *= 0.7; + draw_rect(Rect2(Point2(), get_size()), accent, false); + } + + Ref<Font> font = get_font("font", "Label"); + Color color = get_color("font_color", "Label"); + int hsep = get_constant("hseparation", "ItemList"); + int vsep = get_constant("vseparation", "ItemList"); + Color linecolor = color; + linecolor.a = 0.2; + + draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor); + + int right_limit = get_size().width - timeline->get_buttons_width(); + + draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor); + + Ref<Texture> close_icon = get_icon("Close", "EditorIcons"); + + close_icon_rect.position = Vector2(get_size().width - close_icon->get_width() - hsep, hsep); + close_icon_rect.size = close_icon->get_size(); + draw_texture(close_icon, close_icon_rect.position); + + String base_path = animation->track_get_path(track); + int end = base_path.find(":"); + if (end != -1) { + base_path = base_path.substr(0, end + 1); + } + + // NAMES AND ICON + int vofs = vsep; + int margin = 0; + + { + int ofs = 0; + + NodePath path = animation->track_get_path(track); + + Node *node = NULL; + + if (root && root->has_node(path)) { + node = root->get_node(path); + } + + String text; + + int h = font->get_height(); + + if (node) { + Ref<Texture> icon; + if (has_icon(node->get_class(), "EditorIcons")) { + icon = get_icon(node->get_class(), "EditorIcons"); + } else { + icon = get_icon("Node", "EditorIcons"); + } + + h = MAX(h, icon->get_height()); + + draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); + + margin = icon->get_width(); + + text = node->get_name(); + ofs += hsep; + ofs += icon->get_width(); + + Vector2 string_pos = Point2(ofs, vofs + (h - font->get_height()) / 2 + font->get_ascent()); + string_pos = string_pos.floor(); + draw_string(font, string_pos, text, color, limit - ofs - hsep); + + vofs += h + vsep; + } + } + + // RELATED TRACKS TITLES + + Map<int, Color> subtrack_colors; + subtracks.clear(); + + for (int i = 0; i < animation->get_track_count(); i++) { + if (animation->track_get_type(i) != Animation::TYPE_BEZIER) + continue; + String path = animation->track_get_path(i); + if (!path.begins_with(base_path)) + continue; //another node + path = path.replace_first(base_path, ""); + + Color cc = color; + Rect2 rect = Rect2(margin, vofs, limit - margin - hsep, font->get_height() + vsep); + if (i != track) { + cc.a *= 0.7; + uint32_t hash = path.hash(); + hash = ((hash >> 16) ^ hash) * 0x45d9f3b; + hash = ((hash >> 16) ^ hash) * 0x45d9f3b; + hash = (hash >> 16) ^ hash; + float h = (hash % 65535) / 65536.0; + Color subcolor; + subcolor.set_hsv(h, 0.2, 0.8); + subcolor.a = 0.5; + draw_rect(Rect2(0, vofs + font->get_height() * 0.1, margin - hsep, font->get_height() * 0.8), subcolor); + subtrack_colors[i] = subcolor; + + subtracks[i] = rect; + } else { + Color ac = get_color("accent_color", "Editor"); + ac.a = 0.5; + draw_rect(rect, ac); + } + draw_string(font, Point2(margin, vofs + font->get_ascent()), path, cc, limit - margin - hsep); + + vofs += font->get_height() + vsep; + } + + Color accent = get_color("accent_color", "Editor"); + + { //guides + float min_left_scale = font->get_height() + vsep; + + float scale = 1; + + while (scale / v_zoom < min_left_scale * 2) { + scale *= 5; + } + + bool first = true; + int prev_iv = 0; + for (int i = font->get_height(); i < get_size().height; i++) { + + float ofs = get_size().height / 2 - i; + ofs *= v_zoom; + ofs += v_scroll; + + int iv = int(ofs / scale); + if (ofs < 0) + iv -= 1; + if (!first && iv != prev_iv) { + + Color lc = linecolor; + lc.a *= 0.5; + draw_line(Point2(limit, i), Point2(right_limit, i), lc); + Color c = color; + c.a *= 0.5; + draw_string(font, Point2(limit + 8, i - 2), itos((iv + 1) * scale), c); + } + + first = false; + prev_iv = iv; + } + } + + { //draw OTHER curves + + float scale = timeline->get_zoom_scale(); + Ref<Texture> point = get_icon("KeyValue", "EditorIcons"); + for (Map<int, Color>::Element *E = subtrack_colors.front(); E; E = E->next()) { + + _draw_track(E->key(), E->get()); + + for (int i = 0; i < animation->track_get_key_count(E->key()); i++) { + + float offset = animation->track_get_key_time(E->key(), i); + float value = animation->bezier_track_get_key_value(E->key(), i); + + Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); + + if (pos.x >= limit && pos.x <= right_limit) { + draw_texture(point, pos - point->get_size() / 2, E->get()); + } + } + } + + //draw edited curve + _draw_track(track, accent); + } + + //draw editor handles + { + + float scale = timeline->get_zoom_scale(); + edit_points.clear(); + + for (int i = 0; i < animation->track_get_key_count(track); i++) { + + float offset = animation->track_get_key_time(track, i); + float value = animation->bezier_track_get_key_value(track, i); + + if (moving_selection && selection.has(i)) { + offset += moving_selection_offset.x; + value += moving_selection_offset.y; + } + + Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); + + Vector2 in_vec = animation->bezier_track_get_key_in_handle(track, i); + if (moving_handle != 0 && moving_handle_key == i) { + in_vec = moving_handle_left; + } + Vector2 pos_in = Vector2(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); + + Vector2 out_vec = animation->bezier_track_get_key_out_handle(track, i); + + if (moving_handle != 0 && moving_handle_key == i) { + out_vec = moving_handle_right; + } + + Vector2 pos_out = Vector2(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); + + _draw_line_clipped(pos, pos_in, accent, limit, right_limit); + _draw_line_clipped(pos, pos_out, accent, limit, right_limit); + + EditPoint ep; + if (pos.x >= limit && pos.x <= right_limit) { + ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); + ep.point_rect.size = bezier_icon->get_size(); + if (selection.has(i)) { + draw_texture(selected_icon, ep.point_rect.position); + } else { + draw_texture(bezier_icon, ep.point_rect.position); + } + ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); + } + if (pos_in.x >= limit && pos_in.x <= right_limit) { + ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); + ep.in_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.in_rect.position); + ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); + } + if (pos_out.x >= limit && pos_out.x <= right_limit) { + ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); + ep.out_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.out_rect.position); + ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + } + edit_points.push_back(ep); + } + } + + if (box_selecting) { + Color bs = accent; + bs.a *= 0.5; + Vector2 bs_from = box_selection_from; + Vector2 bs_to = box_selection_to; + if (bs_from.x > bs_to.x) { + SWAP(bs_from.x, bs_to.x); + } + if (bs_from.y > bs_to.y) { + SWAP(bs_from.y, bs_to.y); + } + draw_rect(Rect2(bs_from, bs_to - bs_from), bs); + } + +#if 0 + // KEYFAMES // + + { + + float scale = timeline->get_zoom_scale(); + int limit_end = get_size().width - timeline->get_buttons_width(); + + for (int i = 0; i < animation->track_get_key_count(track); i++) { + + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + if (editor->is_key_selected(track, i) && editor->is_moving_selection()) { + offset += editor->get_moving_selection_offset(); + } + offset = offset * scale + limit; + draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end); + } + } +#endif + } +} + +Ref<Animation> AnimationBezierTrackEdit::get_animation() const { + return animation; +} + +void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) { + + animation = p_animation; + track = p_track; + update(); +} + +Size2 AnimationBezierTrackEdit::get_minimum_size() const { + + return Vector2(1, 1); +} + +void AnimationBezierTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) { + timeline = p_timeline; + timeline->connect("zoom_changed", this, "_zoom_changed"); +} +void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) { + editor = p_editor; +} + +void AnimationBezierTrackEdit::_play_position_draw() { + + if (!animation.is_valid() || play_position_pos < 0) + return; + + float scale = timeline->get_zoom_scale(); + int h = get_size().height; + + int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit(); + + if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { + Color color = get_color("accent_color", "Editor"); + play_position->draw_line(Point2(px, 0), Point2(px, h), color); + } +} + +void AnimationBezierTrackEdit::set_play_position(float p_pos) { + + play_position_pos = p_pos; + play_position->update(); +} + +void AnimationBezierTrackEdit::update_play_position() { + play_position->update(); +} + +void AnimationBezierTrackEdit::set_root(Node *p_root) { + root = p_root; +} +void AnimationBezierTrackEdit::_zoom_changed() { + update(); +} + +String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const { + + return Control::get_tooltip(p_pos); +} + +void AnimationBezierTrackEdit::_clear_selection() { + selection.clear(); + update(); +} + +void AnimationBezierTrackEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) { + + if (!(animation == p_anim)) + return; + //selection.clear(); + _clear_selection(); +} + +void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) { + + if (!(animation == p_anim)) + return; + + int idx = animation->track_find_key(p_track, p_pos, true); + ERR_FAIL_COND(idx < 0); + + selection.insert(idx); + update(); +} + +void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) { + + if (p_event->is_pressed()) { + if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->is_shortcut(p_event)) { + duplicate_selection(); + accept_event(); + } + + if (ED_GET_SHORTCUT("animation_editor/delete_selection")->is_shortcut(p_event)) { + delete_selection(); + accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + if (mb->get_command()) { + timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05); + } else { + if (v_zoom < 1000) { + v_zoom *= 1.2; + } + } + update(); + } + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) { + if (mb->get_command()) { + timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05); + } else { + if (v_zoom > 0.01) { + v_zoom /= 1.2; + } + } + update(); + } + + if (mb.is_valid() && mb->get_button_index() == BUTTON_MIDDLE) { + + if (mb->is_pressed()) { + int x = mb->get_position().x - timeline->get_name_limit(); + panning_timeline_from = x / timeline->get_zoom_scale(); + panning_timeline = true; + panning_timeline_at = timeline->get_value(); + } else { + panning_timeline = false; + } + } + + if (mb.is_valid() && mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { + + menu_insert_key = mb->get_position(); + Vector2 popup_pos = get_global_transform().xform(mb->get_position()); + + menu->clear(); + menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); + if (selection.size()) { + menu->add_separator(); + menu->add_icon_item(get_icon("Duplicate", "EditorIcons"), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE); + menu->add_separator(); + menu->add_icon_item(get_icon("Remove", "EditorIcons"), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE); + } + + menu->set_as_minsize(); + menu->set_position(popup_pos); + menu->popup(); + } + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + if (close_icon_rect.has_point(mb->get_position())) { + emit_signal("close_request"); + return; + } + for (Map<int, Rect2>::Element *E = subtracks.front(); E; E = E->next()) { + if (E->get().has_point(mb->get_position())) { + set_animation_and_track(animation, E->key()); + return; + } + } + + for (int i = 0; i < edit_points.size(); i++) { + + //first check point + //command makes it ignore the main point, so control point editors can be force-edited + //path 2D editing in the 3D and 2D editors works the same way + if (!mb->get_command()) { + if (edit_points[i].point_rect.has_point(mb->get_position())) { + if (mb->get_shift()) { + //add to selection + if (selection.has(i)) { + selection.erase(i); + } else { + selection.insert(i); + } + update(); + select_single_attempt = -1; + } else if (selection.has(i)) { + moving_selection_attempt = true; + moving_selection = false; + moving_selection_from_key = i; + moving_selection_offset = Vector2(); + select_single_attempt = i; + update(); + } else { + + moving_selection_attempt = true; + moving_selection = true; + moving_selection_from_key = i; + moving_selection_offset = Vector2(); + selection.clear(); + selection.insert(i); + update(); + } + return; + } + } + + if (edit_points[i].in_rect.has_point(mb->get_position())) { + moving_handle = -1; + moving_handle_key = i; + moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); + moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); + update(); + return; + } + + if (edit_points[i].out_rect.has_point(mb->get_position())) { + moving_handle = 1; + moving_handle_key = i; + moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); + moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); + update(); + return; + ; + } + } + + //insert new point + if (mb->get_command() && mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { + + Array new_point; + new_point.resize(5); + + float h = (get_size().height / 2 - mb->get_position().y) * v_zoom + v_scroll; + + new_point[0] = h; + new_point[1] = -0.25; + new_point[2] = 0; + new_point[3] = 0.25; + new_point[4] = 0; + + float time = ((mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); + while (animation->track_find_key(track, time, true) != -1) { + time += 0.001; + } + + undo_redo->create_action("Add Bezier Point"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, time); + undo_redo->commit_action(); + + //then attempt to move + int index = animation->track_find_key(track, time, true); + ERR_FAIL_COND(index == -1); + _clear_selection(); + selection.insert(index); + + moving_selection_attempt = true; + moving_selection = false; + moving_selection_from_key = index; + moving_selection_offset = Vector2(); + select_single_attempt = -1; + update(); + + return; + } + + //box select + if (mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { + box_selecting_attempt = true; + box_selecting = false; + box_selecting_add = false; + box_selection_from = mb->get_position(); + return; + } + } + + if (box_selecting_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + if (box_selecting) { + //do actual select + if (!box_selecting_add) { + _clear_selection(); + } + + Vector2 bs_from = box_selection_from; + Vector2 bs_to = box_selection_to; + if (bs_from.x > bs_to.x) { + SWAP(bs_from.x, bs_to.x); + } + if (bs_from.y > bs_to.y) { + SWAP(bs_from.y, bs_to.y); + } + Rect2 selection_rect(bs_from, bs_to - bs_from); + + for (int i = 0; i < edit_points.size(); i++) { + + if (edit_points[i].point_rect.intersects(selection_rect)) { + selection.insert(i); + } + } + } else { + _clear_selection(); //clicked and nothing happened, so clear the selection + } + box_selecting_attempt = false; + box_selecting = false; + update(); + } + + if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + undo_redo->create_action("Move Bezier Points"); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key)); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key)); + undo_redo->commit_action(); + + moving_handle = 0; + update(); + } + + if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + if (moving_selection) { + //combit it + + undo_redo->create_action("Move Bezier Points"); + + List<AnimMoveRestore> to_restore; + // 1-remove the keys + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); + } + // 2- remove overlapped keys + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float newtime = animation->track_get_key_time(track, E->get()) + moving_selection_offset.x; + + int idx = animation->track_find_key(track, newtime, true); + if (idx == -1) + continue; + + if (selection.has(idx)) + continue; //already in selection, don't save + + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", track, newtime); + AnimMoveRestore amr; + + amr.key = animation->track_get_key_value(track, idx); + amr.track = track; + amr.time = newtime; + + to_restore.push_back(amr); + } + + // 3-move the keys (re insert them) + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float newpos = animation->track_get_key_time(track, E->get()) + moving_selection_offset.x; + /* + if (newpos<0) + continue; //no add at the beginning + */ + Array key = animation->track_get_key_value(track, E->get()); + float h = key[0]; + h += moving_selection_offset.y; + key[0] = h; + undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, newpos, key, 1); + } + + // 4-(undo) remove inserted keys + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float newpos = animation->track_get_key_time(track, E->get()) + moving_selection_offset.x; + /* + if (newpos<0) + continue; //no remove what no inserted + */ + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, newpos); + } + + // 5-(undo) reinsert keys + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float oldpos = animation->track_get_key_time(track, E->get()); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, oldpos, animation->track_get_key_value(track, E->get()), 1); + } + + // 6-(undo) reinsert overlapped keys + for (List<AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { + + AnimMoveRestore &amr = E->get(); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1); + } + + // 6-(undo) reinsert overlapped keys + for (List<AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { + + AnimMoveRestore &amr = E->get(); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1); + } + + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + + // 7-reselect + + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float oldpos = animation->track_get_key_time(track, E->get()); + float newpos = oldpos + moving_selection_offset.x; + + undo_redo->add_do_method(this, "_select_at_anim", animation, track, newpos); + undo_redo->add_undo_method(this, "_select_at_anim", animation, track, oldpos); + } + + undo_redo->commit_action(); + + moving_selection = false; + } else if (select_single_attempt != -1) { + selection.clear(); + selection.insert(select_single_attempt); + } + + moving_selection_attempt = false; + update(); + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) { + v_scroll += mm->get_relative().y * v_zoom; + if (v_scroll > 100000) + v_scroll = 100000; + if (v_scroll < -100000) + v_scroll = -100000; + + int x = mm->get_position().x - timeline->get_name_limit(); + float ofs = x / timeline->get_zoom_scale(); + float diff = ofs - panning_timeline_from; + timeline->set_value(panning_timeline_at - diff); + + update(); + } + if (moving_selection_attempt && mm.is_valid()) { + + if (!moving_selection) { + moving_selection = true; + select_single_attempt = -1; + } + + float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; + float x = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); + + moving_selection_offset = Vector2(x - animation->track_get_key_time(track, moving_selection_from_key), y - animation->bezier_track_get_key_value(track, moving_selection_from_key)); + update(); + } + + if (box_selecting_attempt && mm.is_valid()) { + + if (!box_selecting) { + box_selecting = true; + box_selecting_add = mm->get_shift(); + } + + box_selection_to = mm->get_position(); + + if (get_local_mouse_position().y < 0) { + //avoid cursor from going too above, so it does not lose focus with viewport + warp_mouse(Vector2(get_local_mouse_position().x, 0)); + } + update(); + } + + if (moving_handle != 0 && mm.is_valid()) { + + float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; + float x = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); + + Vector2 key_pos = Vector2(animation->track_get_key_time(track, moving_handle_key), animation->bezier_track_get_key_value(track, moving_handle_key)); + + Vector2 moving_handle_value = Vector2(x, y) - key_pos; + + moving_handle_left = animation->bezier_track_get_key_in_handle(track, moving_handle_key); + moving_handle_right = animation->bezier_track_get_key_out_handle(track, moving_handle_key); + + if (moving_handle == -1) { + moving_handle_left = moving_handle_value; + if (moving_handle_left.x > 0) { + moving_handle_left.x = 0; + } + + if (handle_mode_option->get_selected() == HANDLE_MODE_BALANCED) { + Vector2 scale = Vector2(timeline->get_zoom_scale(), v_zoom); + moving_handle_right = (-(moving_handle_left * scale).normalized() * (moving_handle_right * scale).length()) / scale; + + } else if (handle_mode_option->get_selected() == HANDLE_MODE_MIRROR) { + moving_handle_right = -moving_handle_left; + } + } + + if (moving_handle == 1) { + moving_handle_right = moving_handle_value; + if (moving_handle_right.x < 0) { + moving_handle_right.x = 0; + } + + if (handle_mode_option->get_selected() == HANDLE_MODE_BALANCED) { + Vector2 scale = Vector2(timeline->get_zoom_scale(), v_zoom); + moving_handle_left = (-(moving_handle_right * scale).normalized() * (moving_handle_left * scale).length()) / scale; + } else if (handle_mode_option->get_selected() == HANDLE_MODE_MIRROR) { + moving_handle_left = -moving_handle_right; + } + } + + update(); + } +} + +void AnimationBezierTrackEdit::_menu_selected(int p_index) { + + switch (p_index) { + case MENU_KEY_INSERT: { + + Array new_point; + new_point.resize(5); + + float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; + + new_point[0] = h; + new_point[1] = -0.25; + new_point[2] = 0; + new_point[3] = 0.25; + new_point[4] = 0; + + float time = ((menu_insert_key.x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); + while (animation->track_find_key(track, time, true) != -1) { + time += 0.001; + } + + undo_redo->create_action("Add Bezier Point"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, time); + undo_redo->commit_action(); + + } break; + case MENU_KEY_DUPLICATE: { + duplicate_selection(); + } break; + case MENU_KEY_DELETE: { + delete_selection(); + } break; + } +} + +void AnimationBezierTrackEdit::duplicate_selection() { + + if (selection.size() == 0) + return; + + float top_time = 1e10; + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float t = animation->track_get_key_time(track, E->get()); + if (t < top_time) + top_time = t; + } + + undo_redo->create_action(TTR("Anim Duplicate Keys")); + + List<Pair<int, float> > new_selection_values; + + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + float t = animation->track_get_key_time(track, E->get()); + float dst_time = t + (timeline->get_play_position() - top_time); + int existing_idx = animation->track_find_key(track, dst_time, true); + + undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get())); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, dst_time); + + Pair<int, float> p; + p.first = track; + p.second = dst_time; + new_selection_values.push_back(p); + + if (existing_idx != -1) { + + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, existing_idx), animation->track_get_key_transition(track, existing_idx)); + } + } + + undo_redo->commit_action(); + + //reselect duplicated + + selection.clear(); + for (List<Pair<int, float> >::Element *E = new_selection_values.front(); E; E = E->next()) { + + int track = E->get().first; + float time = E->get().second; + + int existing_idx = animation->track_find_key(track, time, true); + + if (existing_idx == -1) + continue; + + selection.insert(existing_idx); + } + + update(); +} + +void AnimationBezierTrackEdit::delete_selection() { + if (selection.size()) { + undo_redo->create_action(TTR("Anim Delete Keys")); + + for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, E->get()), animation->track_get_key_value(track, E->get()), 1); + } + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + undo_redo->commit_action(); + //selection.clear(); + } +} + +void AnimationBezierTrackEdit::set_block_animation_update_ptr(bool *p_block_ptr) { + block_animation_update_ptr = p_block_ptr; +} + +void AnimationBezierTrackEdit::_bind_methods() { + + ClassDB::bind_method("_zoom_changed", &AnimationBezierTrackEdit::_zoom_changed); + ClassDB::bind_method("_menu_selected", &AnimationBezierTrackEdit::_menu_selected); + ClassDB::bind_method("_gui_input", &AnimationBezierTrackEdit::_gui_input); + ClassDB::bind_method("_play_position_draw", &AnimationBezierTrackEdit::_play_position_draw); + + ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection); + ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection); + ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_clear_selection); + + ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag"))); + ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); + ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::REAL, "ofs"))); + ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); + ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("clear_selection")); + ADD_SIGNAL(MethodInfo("close_request")); + + ADD_SIGNAL(MethodInfo("move_selection_begin")); + ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::REAL, "ofs"))); + ADD_SIGNAL(MethodInfo("move_selection_commit")); + ADD_SIGNAL(MethodInfo("move_selection_cancel")); +} + +AnimationBezierTrackEdit::AnimationBezierTrackEdit() { + undo_redo = NULL; + timeline = NULL; + root = NULL; + menu = NULL; + block_animation_update_ptr = NULL; + + moving_selection_attempt = false; + moving_selection = false; + select_single_attempt = -1; + box_selecting = false; + box_selecting_attempt = false; + + moving_handle = 0; + + play_position_pos = 0; + play_position = memnew(Control); + play_position->set_mouse_filter(MOUSE_FILTER_PASS); + add_child(play_position); + play_position->set_anchors_and_margins_preset(PRESET_WIDE); + play_position->connect("draw", this, "_play_position_draw"); + set_focus_mode(FOCUS_CLICK); + + v_scroll = 0; + v_zoom = 1; + + panning_timeline = false; + set_clip_contents(true); + handle_mode = HANDLE_MODE_FREE; + handle_mode_option = memnew(OptionButton); + add_child(handle_mode_option); + + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("id_pressed", this, "_menu_selected"); + + //set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection +} diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h new file mode 100644 index 0000000000..544690844a --- /dev/null +++ b/editor/animation_bezier_editor.h @@ -0,0 +1,141 @@ +#ifndef ANIMATION_BEZIER_EDITOR_H +#define ANIMATION_BEZIER_EDITOR_H + +#include "animation_track_editor.h" + +class AnimationBezierTrackEdit : public Control { + + GDCLASS(AnimationBezierTrackEdit, Control) + + enum HandleMode { + HANDLE_MODE_FREE, + HANDLE_MODE_BALANCED, + HANDLE_MODE_MIRROR + }; + + enum { + MENU_KEY_INSERT, + MENU_KEY_DUPLICATE, + MENU_KEY_DELETE + }; + + HandleMode handle_mode; + OptionButton *handle_mode_option; + + AnimationTimelineEdit *timeline; + UndoRedo *undo_redo; + Node *root; + Control *play_position; //separate control used to draw so updates for only position changed are much faster + float play_position_pos; + + Ref<Animation> animation; + int track; + + Vector<Rect2> view_rects; + + Ref<Texture> bezier_icon; + Ref<Texture> bezier_handle_icon; + Ref<Texture> selected_icon; + + Rect2 close_icon_rect; + + Map<int, Rect2> subtracks; + + float v_scroll; + float v_zoom; + + PopupMenu *menu; + + void _zoom_changed(); + + void _gui_input(const Ref<InputEvent> &p_event); + void _menu_selected(int p_index); + + bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up) + + void _play_position_draw(); + + Vector2 insert_at_pos; + + bool moving_selection_attempt; + int select_single_attempt; + bool moving_selection; + int moving_selection_from_key; + + Vector2 moving_selection_offset; + + bool box_selecting_attempt; + bool box_selecting; + bool box_selecting_add; + Vector2 box_selection_from; + Vector2 box_selection_to; + + int moving_handle; //0 no move -1 or +1 out + int moving_handle_key; + Vector2 moving_handle_left; + Vector2 moving_handle_right; + + void _clear_selection(); + void _clear_selection_for_anim(const Ref<Animation> &p_anim); + void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos); + + Vector2 menu_insert_key; + + struct AnimMoveRestore { + + int track; + float time; + Variant key; + float transition; + }; + + AnimationTrackEditor *editor; + + struct EditPoint { + Rect2 point_rect; + Rect2 in_rect; + Rect2 out_rect; + }; + + Vector<EditPoint> edit_points; + + Set<int> selection; + + bool panning_timeline; + float panning_timeline_from; + float panning_timeline_at; + + void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right); + void _draw_track(int p_track, const Color &p_color); + + float _bezier_h_to_pixel(float p_h); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual String get_tooltip(const Point2 &p_pos) const; + + Ref<Animation> get_animation() const; + + void set_animation_and_track(const Ref<Animation> &p_animation, int p_track); + virtual Size2 get_minimum_size() const; + + void set_undo_redo(UndoRedo *p_undo_redo); + void set_timeline(AnimationTimelineEdit *p_timeline); + void set_editor(AnimationTrackEditor *p_editor); + void set_root(Node *p_root); + + void set_block_animation_update_ptr(bool *p_block_ptr); + + void set_play_position(float p_pos); + void update_play_position(); + + void duplicate_selection(); + void delete_selection(); + + AnimationBezierTrackEdit(); +}; + +#endif // ANIMATION_BEZIER_EDITOR_H diff --git a/editor/animation_editor.cpp b/editor/animation_editor.cpp deleted file mode 100644 index a03bf76d1b..0000000000 --- a/editor/animation_editor.cpp +++ /dev/null @@ -1,4146 +0,0 @@ -/*************************************************************************/ -/* animation_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#include "animation_editor.h" - -#include "editor/plugins/animation_player_editor_plugin.h" -#include "editor_node.h" -#include "editor_settings.h" -#include "io/resource_saver.h" -#include "os/keyboard.h" -#include "os/os.h" -#include "pair.h" -#include "scene/gui/separator.h" -#include "scene/main/viewport.h" - -/* Missing to fix: - - *Set - *Find better source for hint for edited value keys - * + button on track to add a key - * when clicked for first time, erase selection of not selected at first - * automatically create discrete/continuous tracks!! - *when create track do undo/redo -*/ - -class AnimationCurveEdit : public Control { - GDCLASS(AnimationCurveEdit, Control); - -public: - enum Mode { - MODE_DISABLED, - MODE_SINGLE, - MODE_MULTIPLE - }; - -private: - Set<float> multiples; - float transition; - Mode mode; - - LineEdit *value_edit; - - void _notification(int p_what) { - - if (p_what == NOTIFICATION_DRAW) { - - RID ci = get_canvas_item(); - - Size2 s = get_size(); - Rect2 r(Point2(), s); - - //r=r.grow(3); - Ref<StyleBox> sb = get_stylebox("normal", "LineEdit"); - sb->draw(ci, r); - r.size -= sb->get_minimum_size(); - r.position += sb->get_offset(); - //VisualServer::get_singleton()->canvas_item_add - - Ref<Font> f = get_font("font", "Label"); - r = r.grow(-2); - Color color = get_color("font_color", "Label"); - - int points = 48; - if (mode == MODE_MULTIPLE) { - - Color mcolor = color; - mcolor.a *= 0.3; - - Set<float>::Element *E = multiples.front(); - for (int j = 0; j < 16; j++) { - - if (!E) - break; - - float prev = 1.0; - float exp = E->get(); - bool flip = false; //hint_text=="attenuation"; - - for (int i = 1; i <= points; i++) { - - float ifl = i / float(points); - float iflp = (i - 1) / float(points); - - float h = 1.0 - Math::ease(ifl, exp); - - if (flip) { - ifl = 1.0 - ifl; - iflp = 1.0 - iflp; - } - - VisualServer::get_singleton()->canvas_item_add_line(ci, r.position + Point2(iflp * r.size.width, prev * r.size.height), r.position + Point2(ifl * r.size.width, h * r.size.height), mcolor); - prev = h; - } - - E = E->next(); - } - } - - float exp = transition; - if (mode != MODE_DISABLED) { - - float prev = 1.0; - - bool flip = false; //hint_text=="attenuation"; - - for (int i = 1; i <= points; i++) { - - float ifl = i / float(points); - float iflp = (i - 1) / float(points); - - float h = 1.0 - Math::ease(ifl, exp); - - if (flip) { - ifl = 1.0 - ifl; - iflp = 1.0 - iflp; - } - - VisualServer::get_singleton()->canvas_item_add_line(ci, r.position + Point2(iflp * r.size.width, prev * r.size.height), r.position + Point2(ifl * r.size.width, h * r.size.height), color); - prev = h; - } - } - - if (mode == MODE_DISABLED) { - f->draw(ci, Point2(5, 5 + f->get_ascent()), TTR("Disabled"), color); - } else if (mode == MODE_MULTIPLE) { - f->draw(ci, Point2(5, 5 + f->get_ascent() + value_edit->get_size().height), TTR("All Selection"), color); - } - } - } - - void _gui_input(const Ref<InputEvent> &p_ev) { - - Ref<InputEventMouseMotion> mm = p_ev; - if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { - - if (mode == MODE_DISABLED) - return; - - value_edit->release_focus(); - - float rel = mm->get_relative().x; - if (rel == 0) - return; - - bool flip = false; - - if (flip) - rel = -rel; - - float val = transition; - if (val == 0) - return; - bool sg = val < 0; - val = Math::absf(val); - - val = Math::log(val) / Math::log((float)2.0); - //logspace - val += rel * 0.05; - // - - val = Math::pow((float)2.0, val); - if (sg) - val = -val; - - force_transition(val); - } - } - - void _edit_value_changed(const String &p_value_str) { - - force_transition(p_value_str.to_float()); - } - -public: - static void _bind_methods() { - - //ClassDB::bind_method("_update_obj",&AnimationKeyEdit::_update_obj); - ClassDB::bind_method("_gui_input", &AnimationCurveEdit::_gui_input); - ClassDB::bind_method("_edit_value_changed", &AnimationCurveEdit::_edit_value_changed); - ADD_SIGNAL(MethodInfo("transition_changed")); - } - - void set_mode(Mode p_mode) { - - mode = p_mode; - value_edit->set_visible(mode != MODE_DISABLED); - update(); - } - - void clear_multiples() { - multiples.clear(); - update(); - } - void set_multiple(float p_transition) { - - multiples.insert(p_transition); - } - - void set_transition(float p_transition) { - transition = Math::stepify(p_transition, 0.01); - value_edit->set_text(String::num(transition)); - update(); - } - - float get_transition() const { - return transition; - } - - void force_transition(float p_value) { - if (mode == MODE_DISABLED) - return; - set_transition(p_value); - emit_signal("transition_changed", p_value); - } - - AnimationCurveEdit() { - - transition = 1.0; - set_default_cursor_shape(CURSOR_HSPLIT); - mode = MODE_DISABLED; - - value_edit = memnew(LineEdit); - value_edit->hide(); - value_edit->connect("text_entered", this, "_edit_value_changed"); - add_child(value_edit); - } -}; - -class AnimationKeyEdit : public Object { - - GDCLASS(AnimationKeyEdit, Object); - -public: - bool setting; - bool hidden; - - static void _bind_methods() { - - ClassDB::bind_method("_update_obj", &AnimationKeyEdit::_update_obj); - ClassDB::bind_method("_key_ofs_changed", &AnimationKeyEdit::_key_ofs_changed); - } - - //PopupDialog *ke_dialog; - - void _fix_node_path(Variant &value) { - - NodePath np = value; - - if (np == NodePath()) - return; - - Node *root = EditorNode::get_singleton()->get_tree()->get_root(); - - Node *np_node = root->get_node(np); - ERR_FAIL_COND(!np_node); - - Node *edited_node = root->get_node(base); - ERR_FAIL_COND(!edited_node); - - value = edited_node->get_path_to(np_node); - } - - void _update_obj(const Ref<Animation> &p_anim) { - if (setting) - return; - if (hidden) - return; - if (!(animation == p_anim)) - return; - notify_change(); - } - - void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) { - if (hidden) - return; - if (!(animation == p_anim)) - return; - if (from != key_ofs) - return; - key_ofs = to; - if (setting) - return; - notify_change(); - } - - bool _set(const StringName &p_name, const Variant &p_value) { - - int key = animation->track_find_key(track, key_ofs, true); - ERR_FAIL_COND_V(key == -1, false); - - String name = p_name; - if (name == "time") { - - float new_time = p_value; - if (new_time == key_ofs) - return true; - - int existing = animation->track_find_key(track, new_time, true); - - setting = true; - undo_redo->create_action(TTR("Anim Change Keyframe Time"), UndoRedo::MERGE_ENDS); - - Variant val = animation->track_get_key_value(track, key); - float trans = animation->track_get_key_transition(track, key); - - undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, key); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, val, trans); - undo_redo->add_do_method(this, "_key_ofs_changed", animation, key_ofs, new_time); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, new_time); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_ofs, val, trans); - undo_redo->add_undo_method(this, "_key_ofs_changed", animation, new_time, key_ofs); - - if (existing != -1) { - Variant v = animation->track_get_key_value(track, existing); - float trans = animation->track_get_key_transition(track, existing); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, new_time, v, trans); - } - - undo_redo->commit_action(); - setting = false; - - return true; - } else if (name == "easing") { - - float val = p_value; - float prev_val = animation->track_get_key_transition(track, key); - setting = true; - undo_redo->create_action(TTR("Anim Change Transition"), UndoRedo::MERGE_ENDS); - undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val); - undo_redo->add_do_method(this, "_update_obj", animation); - undo_redo->add_undo_method(this, "_update_obj", animation); - undo_redo->commit_action(); - setting = false; - return true; - } - - switch (animation->track_get_type(track)) { - - case Animation::TYPE_TRANSFORM: { - - Dictionary d_old = animation->track_get_key_value(track, key); - Dictionary d_new = d_old; - d_new[p_name] = p_value; - setting = true; - undo_redo->create_action(TTR("Anim Change Transform")); - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); - undo_redo->add_do_method(this, "_update_obj", animation); - undo_redo->add_undo_method(this, "_update_obj", animation); - undo_redo->commit_action(); - setting = false; - return true; - - } break; - case Animation::TYPE_VALUE: { - - if (name == "value") { - - Variant value = p_value; - - if (value.get_type() == Variant::NODE_PATH) { - - _fix_node_path(value); - } - - setting = true; - undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); - Variant prev = animation->track_get_key_value(track, key); - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev); - undo_redo->add_do_method(this, "_update_obj", animation); - undo_redo->add_undo_method(this, "_update_obj", animation); - undo_redo->commit_action(); - setting = false; - return true; - } - - } break; - case Animation::TYPE_METHOD: { - - Dictionary d_old = animation->track_get_key_value(track, key); - Dictionary d_new = d_old; - - bool change_notify_deserved = false; - bool mergeable = false; - - if (name == "name") { - - d_new["method"] = p_value; - } - - if (name == "arg_count") { - - Vector<Variant> args = d_old["args"]; - args.resize(p_value); - d_new["args"] = args; - change_notify_deserved = true; - } - - if (name.begins_with("args/")) { - - Vector<Variant> args = d_old["args"]; - int idx = name.get_slice("/", 1).to_int(); - ERR_FAIL_INDEX_V(idx, args.size(), false); - - String what = name.get_slice("/", 2); - if (what == "type") { - Variant::Type t = Variant::Type(int(p_value)); - - if (t != args[idx].get_type()) { - Variant::CallError err; - if (Variant::can_convert(args[idx].get_type(), t)) { - Variant old = args[idx]; - Variant *ptrs[1] = { &old }; - args[idx] = Variant::construct(t, (const Variant **)ptrs, 1, err); - } else { - - args[idx] = Variant::construct(t, NULL, 0, err); - } - change_notify_deserved = true; - d_new["args"] = args; - } - } - if (what == "value") { - - Variant value = p_value; - if (value.get_type() == Variant::NODE_PATH) { - - _fix_node_path(value); - } - - args[idx] = value; - d_new["args"] = args; - mergeable = true; - } - } - - if (mergeable) - undo_redo->create_action(TTR("Anim Change Call"), UndoRedo::MERGE_ENDS); - else - undo_redo->create_action(TTR("Anim Change Call")); - - Variant prev = animation->track_get_key_value(track, key); - setting = true; - undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); - undo_redo->add_do_method(this, "_update_obj", animation); - undo_redo->add_undo_method(this, "_update_obj", animation); - undo_redo->commit_action(); - setting = false; - if (change_notify_deserved) - notify_change(); - return true; - } break; - } - - return false; - } - - bool _get(const StringName &p_name, Variant &r_ret) const { - - int key = animation->track_find_key(track, key_ofs, true); - ERR_FAIL_COND_V(key == -1, false); - - String name = p_name; - if (name == "time") { - r_ret = key_ofs; - return true; - } else if (name == "easing") { - r_ret = animation->track_get_key_transition(track, key); - return true; - } - - switch (animation->track_get_type(track)) { - - case Animation::TYPE_TRANSFORM: { - - Dictionary d = animation->track_get_key_value(track, key); - ERR_FAIL_COND_V(!d.has(name), false); - r_ret = d[p_name]; - return true; - - } break; - case Animation::TYPE_VALUE: { - - if (name == "value") { - r_ret = animation->track_get_key_value(track, key); - return true; - } - - } break; - case Animation::TYPE_METHOD: { - - Dictionary d = animation->track_get_key_value(track, key); - - if (name == "name") { - - ERR_FAIL_COND_V(!d.has("method"), false); - r_ret = d["method"]; - return true; - } - - ERR_FAIL_COND_V(!d.has("args"), false); - - Vector<Variant> args = d["args"]; - - if (name == "arg_count") { - - r_ret = args.size(); - return true; - } - - if (name.begins_with("args/")) { - - int idx = name.get_slice("/", 1).to_int(); - ERR_FAIL_INDEX_V(idx, args.size(), false); - - String what = name.get_slice("/", 2); - if (what == "type") { - r_ret = args[idx].get_type(); - return true; - } - if (what == "value") { - r_ret = args[idx]; - return true; - } - } - - } break; - } - - return false; - } - void _get_property_list(List<PropertyInfo> *p_list) const { - - if (animation.is_null()) - return; - - ERR_FAIL_INDEX(track, animation->get_track_count()); - int key = animation->track_find_key(track, key_ofs, true); - ERR_FAIL_COND(key == -1); - - p_list->push_back(PropertyInfo(Variant::REAL, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01")); - - switch (animation->track_get_type(track)) { - - case Animation::TYPE_TRANSFORM: { - - p_list->push_back(PropertyInfo(Variant::VECTOR3, "location")); - p_list->push_back(PropertyInfo(Variant::QUAT, "rotation")); - p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); - - } break; - case Animation::TYPE_VALUE: { - - Variant v = animation->track_get_key_value(track, key); - - if (hint.type != Variant::NIL) { - - PropertyInfo pi = hint; - pi.name = "value"; - p_list->push_back(pi); - } else { - - PropertyHint hint = PROPERTY_HINT_NONE; - String hint_string; - - if (v.get_type() == Variant::OBJECT) { - //could actually check the object property if exists..? yes i will! - Ref<Resource> res = v; - if (res.is_valid()) { - - hint = PROPERTY_HINT_RESOURCE_TYPE; - hint_string = res->get_class(); - } - } - - if (v.get_type() != Variant::NIL) - p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string)); - } - - } break; - case Animation::TYPE_METHOD: { - - p_list->push_back(PropertyInfo(Variant::STRING, "name")); - p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,5,1")); - - Dictionary d = animation->track_get_key_value(track, key); - ERR_FAIL_COND(!d.has("args")); - Vector<Variant> args = d["args"]; - String vtypes; - for (int i = 0; i < Variant::VARIANT_MAX; i++) { - - if (i > 0) - vtypes += ","; - vtypes += Variant::get_type_name(Variant::Type(i)); - } - - for (int i = 0; i < args.size(); i++) { - - p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/type", PROPERTY_HINT_ENUM, vtypes)); - if (args[i].get_type() != Variant::NIL) - p_list->push_back(PropertyInfo(args[i].get_type(), "args/" + itos(i) + "/value")); - } - - } break; - } - - /* - if (animation->track_get_type(track)!=Animation::TYPE_METHOD) - p_list->push_back( PropertyInfo( Variant::REAL, "easing", PROPERTY_HINT_EXP_EASING)); - */ - } - - UndoRedo *undo_redo; - Ref<Animation> animation; - int track; - float key_ofs; - - PropertyInfo hint; - NodePath base; - - void notify_change() { - - _change_notify(); - } - - AnimationKeyEdit() { - hidden = true; - key_ofs = 0; - track = -1; - setting = false; - } -}; - -void AnimationKeyEditor::_menu_add_track(int p_type) { - - ERR_FAIL_COND(!animation.is_valid()); - - switch (p_type) { - - case ADD_TRACK_MENU_ADD_CALL_TRACK: { - if (root) { - call_select->popup_centered_ratio(); - break; - } - } break; - case ADD_TRACK_MENU_ADD_VALUE_TRACK: - case ADD_TRACK_MENU_ADD_TRANSFORM_TRACK: { - - undo_redo->create_action(TTR("Anim Add Track")); - undo_redo->add_do_method(animation.ptr(), "add_track", p_type); - undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), "."); - undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); - undo_redo->commit_action(); - - } break; - } -} - -void AnimationKeyEditor::_anim_duplicate_keys(bool transpose) { - //duplicait! - if (selection.size() && animation.is_valid() && selected_track >= 0 && selected_track < animation->get_track_count()) { - - int top_track = 0x7FFFFFFF; - float top_time = 1e10; - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - const SelectedKey &sk = E->key(); - - float t = animation->track_get_key_time(sk.track, sk.key); - if (t < top_time) - top_time = t; - if (sk.track < top_track) - top_track = sk.track; - } - ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10); - - // - - int start_track = transpose ? selected_track : top_track; - - undo_redo->create_action(TTR("Anim Duplicate Keys")); - - List<Pair<int, float> > new_selection_values; - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - const SelectedKey &sk = E->key(); - - float t = animation->track_get_key_time(sk.track, sk.key); - - float dst_time = t + (timeline_pos - top_time); - int dst_track = sk.track + (start_track - top_track); - - if (dst_track < 0 || dst_track >= animation->get_track_count()) - continue; - - if (animation->track_get_type(dst_track) != animation->track_get_type(sk.track)) - continue; - - int existing_idx = animation->track_find_key(dst_track, dst_time, true); - - undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", dst_track, dst_time); - - Pair<int, float> p; - p.first = dst_track; - p.second = dst_time; - new_selection_values.push_back(p); - - if (existing_idx != -1) { - - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(dst_track, existing_idx), animation->track_get_key_transition(dst_track, existing_idx)); - } - } - - undo_redo->commit_action(); - - //reselect duplicated - - Map<SelectedKey, KeyInfo> new_selection; - for (List<Pair<int, float> >::Element *E = new_selection_values.front(); E; E = E->next()) { - - int track = E->get().first; - float time = E->get().second; - - int existing_idx = animation->track_find_key(track, time, true); - - if (existing_idx == -1) - continue; - SelectedKey sk2; - sk2.track = track; - sk2.key = existing_idx; - - KeyInfo ki; - ki.pos = time; - - new_selection[sk2] = ki; - } - - selection = new_selection; - track_editor->update(); - _edit_if_single_selection(); - } -} - -void AnimationKeyEditor::_menu_track(int p_type) { - - ERR_FAIL_COND(!animation.is_valid()); - - last_menu_track_opt = p_type; - switch (p_type) { - - case TRACK_MENU_SCALE: - case TRACK_MENU_SCALE_PIVOT: { - - scale_dialog->popup_centered(Size2(200, 100)); - } break; - case TRACK_MENU_MOVE_UP: { - - int idx = selected_track; - if (idx > 0 && idx < animation->get_track_count()) { - undo_redo->create_action(TTR("Move Anim Track Up")); - undo_redo->add_do_method(animation.ptr(), "track_move_down", idx); - undo_redo->add_undo_method(animation.ptr(), "track_move_up", idx - 1); - undo_redo->commit_action(); - selected_track = idx - 1; - } - - } break; - case TRACK_MENU_MOVE_DOWN: { - - int idx = selected_track; - if (idx >= 0 && idx < animation->get_track_count() - 1) { - undo_redo->create_action(TTR("Move Anim Track Down")); - undo_redo->add_do_method(animation.ptr(), "track_move_up", idx); - undo_redo->add_undo_method(animation.ptr(), "track_move_down", idx + 1); - undo_redo->commit_action(); - selected_track = idx + 1; - } - - } break; - case TRACK_MENU_REMOVE: { - - int idx = selected_track; - if (idx >= 0 && idx < animation->get_track_count()) { - undo_redo->create_action(TTR("Remove Anim Track")); - undo_redo->add_do_method(animation.ptr(), "remove_track", idx); - undo_redo->add_undo_method(animation.ptr(), "add_track", animation->track_get_type(idx), idx); - undo_redo->add_undo_method(animation.ptr(), "track_set_path", idx, animation->track_get_path(idx)); - //todo interpolation - for (int i = 0; i < animation->track_get_key_count(idx); i++) { - - Variant v = animation->track_get_key_value(idx, i); - float time = animation->track_get_key_time(idx, i); - float trans = animation->track_get_key_transition(idx, i); - - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", idx, time, v); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", idx, i, trans); - } - - undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", idx, animation->track_get_interpolation_type(idx)); - if (animation->track_get_type(idx) == Animation::TYPE_VALUE) { - undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", idx, animation->value_track_get_update_mode(idx)); - } - - undo_redo->commit_action(); - } - - } break; - case TRACK_MENU_DUPLICATE: - case TRACK_MENU_DUPLICATE_TRANSPOSE: { - - _anim_duplicate_keys(p_type == TRACK_MENU_DUPLICATE_TRANSPOSE); - } break; - case TRACK_MENU_SET_ALL_TRANS_LINEAR: - case TRACK_MENU_SET_ALL_TRANS_CONSTANT: - case TRACK_MENU_SET_ALL_TRANS_OUT: - case TRACK_MENU_SET_ALL_TRANS_IN: - case TRACK_MENU_SET_ALL_TRANS_INOUT: - case TRACK_MENU_SET_ALL_TRANS_OUTIN: { - - if (!selection.size() || !animation.is_valid()) - break; - - float t = 0; - switch (p_type) { - case TRACK_MENU_SET_ALL_TRANS_LINEAR: t = 1.0; break; - case TRACK_MENU_SET_ALL_TRANS_CONSTANT: t = 0.0; break; - case TRACK_MENU_SET_ALL_TRANS_OUT: t = 0.5; break; - case TRACK_MENU_SET_ALL_TRANS_IN: t = 2.0; break; - case TRACK_MENU_SET_ALL_TRANS_INOUT: t = -0.5; break; - case TRACK_MENU_SET_ALL_TRANS_OUTIN: t = -2.0; break; - } - - undo_redo->create_action(TTR("Set Transitions to:") + " " + rtos(t)); - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - const SelectedKey &sk = E->key(); - - undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", sk.track, sk.key, t); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", sk.track, sk.key, animation->track_get_key_transition(sk.track, sk.key)); - } - - undo_redo->commit_action(); - - } break; - case TRACK_MENU_NEXT_STEP: { - - if (animation.is_null()) - break; - float step = animation->get_step(); - if (step == 0) - step = 1; - - float pos = timeline_pos; - - pos = Math::stepify(pos + step, step); - if (pos > animation->get_length()) - pos = animation->get_length(); - timeline_pos = pos; - track_pos->update(); - emit_signal("timeline_changed", pos, true); - - } break; - case TRACK_MENU_PREV_STEP: { - if (animation.is_null()) - break; - float step = animation->get_step(); - if (step == 0) - step = 1; - - float pos = timeline_pos; - pos = Math::stepify(pos - step, step); - if (pos < 0) - pos = 0; - timeline_pos = pos; - track_pos->update(); - emit_signal("timeline_changed", pos, true); - - } break; - - case TRACK_MENU_OPTIMIZE: { - - optimize_dialog->popup_centered(Size2(250, 180)); - } break; - case TRACK_MENU_CLEAN_UP: { - - cleanup_dialog->popup_centered_minsize(Size2(300, 0)); - } break; - case TRACK_MENU_CLEAN_UP_CONFIRM: { - - if (cleanup_all->is_pressed()) { - List<StringName> names; - AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E->get())); - } - } else { - _cleanup_animation(animation); - } - } break; - case CURVE_SET_LINEAR: { - curve_edit->force_transition(1.0); - - } break; - case CURVE_SET_IN: { - - curve_edit->force_transition(4.0); - - } break; - case CURVE_SET_OUT: { - - curve_edit->force_transition(0.25); - } break; - case CURVE_SET_INOUT: { - curve_edit->force_transition(-4); - - } break; - case CURVE_SET_OUTIN: { - - curve_edit->force_transition(-0.25); - } break; - case CURVE_SET_CONSTANT: { - - curve_edit->force_transition(0); - } break; - } -} - -void AnimationKeyEditor::_cleanup_animation(Ref<Animation> p_animation) { - - for (int i = 0; i < p_animation->get_track_count(); i++) { - - bool prop_exists = false; - Variant::Type valid_type = Variant::NIL; - Object *obj = NULL; - - RES res; - Vector<StringName> leftover_path; - - Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); - - if (res.is_valid()) { - obj = res.ptr(); - } else if (node) { - obj = node; - } - - if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) { - valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists); - } - - if (!obj && cleanup_tracks->is_pressed()) { - - p_animation->remove_track(i); - i--; - continue; - } - - if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || cleanup_keys->is_pressed() == false) - continue; - - for (int j = 0; j < p_animation->track_get_key_count(i); j++) { - - Variant v = p_animation->track_get_key_value(i, j); - - if (!Variant::can_convert(v.get_type(), valid_type)) { - p_animation->track_remove_key(i, j); - j--; - } - } - - if (p_animation->track_get_key_count(i) == 0 && cleanup_tracks->is_pressed()) { - p_animation->remove_track(i); - i--; - } - } - - undo_redo->clear_history(); - _update_paths(); -} - -void AnimationKeyEditor::_animation_optimize() { - - animation->optimize(optimize_linear_error->get_value(), optimize_angular_error->get_value(), optimize_max_angle->get_value()); - track_editor->update(); - undo_redo->clear_history(); -} - -float AnimationKeyEditor::_get_zoom_scale() const { - - float zv = zoom->get_value(); - if (zv < 1) { - zv = 1.0 - zv; - return Math::pow(1.0f + zv, 8.0f) * 100; - } else { - return 1.0 / Math::pow(zv, 8.0f) * 100; - } -} - -void AnimationKeyEditor::_track_position_draw() { - - if (!animation.is_valid()) { - return; - } - - Ref<StyleBox> style = get_stylebox("normal", "TextEdit"); - Size2 size = track_editor->get_size() - style->get_minimum_size(); - Size2 ofs = style->get_offset(); - - int settings_limit = size.width - right_data_size_cache; - int name_limit = settings_limit * name_column_ratio; - - float keys_from = h_scroll->get_value(); - float zoom_scale = _get_zoom_scale(); - float keys_to = keys_from + (settings_limit - name_limit) / zoom_scale; - - //will move to separate control! (for speedup) - if (timeline_pos >= keys_from && timeline_pos < keys_to) { - //draw position - int pixel = (timeline_pos - h_scroll->get_value()) * zoom_scale; - pixel += name_limit; - track_pos->draw_line(ofs + Point2(pixel, 0), ofs + Point2(pixel, size.height), get_color("accent_color", "Editor")); - } -} - -void AnimationKeyEditor::_track_editor_draw() { - - if (animation.is_valid() && animation->get_track_count()) { - if (selected_track < 0) - selected_track = 0; - else if (selected_track >= animation->get_track_count()) - selected_track = animation->get_track_count() - 1; - } - - track_pos->update(); - Control *te = track_editor; - Ref<StyleBox> style = get_stylebox("normal", "TextEdit"); - te->draw_style_box(style, Rect2(Point2(), track_editor->get_size())); - - if (te->has_focus()) { - te->draw_style_box(get_stylebox("bg_focus", "Tree"), Rect2(Point2(), track_editor->get_size())); - } - - if (!animation.is_valid()) { - v_scroll->hide(); - h_scroll->hide(); - length->set_editable(false); - step->set_editable(false); - loop->set_disabled(true); - menu_add_track->set_disabled(true); - menu_track->set_disabled(true); - edit_button->set_disabled(true); - key_editor_tab->hide(); - move_up_button->set_disabled(true); - move_down_button->set_disabled(true); - remove_button->set_disabled(true); - - return; - } - - length->set_editable(true); - step->set_editable(true); - loop->set_disabled(false); - menu_add_track->set_disabled(false); - menu_track->set_disabled(false); - edit_button->set_disabled(false); - move_up_button->set_disabled(false); - move_down_button->set_disabled(false); - remove_button->set_disabled(false); - if (edit_button->is_pressed()) - key_editor_tab->show(); - - te_drawing = true; - - Size2 size = te->get_size() - style->get_minimum_size(); - Size2 ofs = style->get_offset(); - - Ref<Font> font = te->get_font("font", "Tree"); - int sep = get_constant("vseparation", "Tree"); - int hsep = get_constant("hseparation", "Tree"); - Color color = get_color("font_color", "Tree"); - Color sepcolor = color; - sepcolor.a = 0.2; - Color timecolor = color; - timecolor.a = 0.2; - Color hover_color = color; - hover_color.a = 0.05; - Color select_color = color; - select_color.a = 0.1; - Color invalid_path_color = get_color("error_color", "Editor"); - Color track_select_color = get_color("highlighted_font_color", "Editor"); - - Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons"); - Ref<Texture> move_up_icon = get_icon("MoveUp", "EditorIcons"); - Ref<Texture> move_down_icon = get_icon("MoveDown", "EditorIcons"); - Ref<Texture> remove_icon_hl = get_icon("RemoveHl", "EditorIcons"); - Ref<Texture> move_up_icon_hl = get_icon("MoveUpHl", "EditorIcons"); - Ref<Texture> move_down_icon_hl = get_icon("MoveDownHl", "EditorIcons"); - Ref<Texture> add_key_icon = get_icon("TrackAddKey", "EditorIcons"); - Ref<Texture> add_key_icon_hl = get_icon("TrackAddKeyHl", "EditorIcons"); - Ref<Texture> down_icon = get_icon("select_arrow", "Tree"); - Ref<Texture> checked = get_icon("checked", "Tree"); - Ref<Texture> unchecked = get_icon("unchecked", "Tree"); - - Ref<Texture> wrap_icon[2] = { - get_icon("InterpWrapClamp", "EditorIcons"), - get_icon("InterpWrapLoop", "EditorIcons"), - }; - - Ref<Texture> interp_icon[3] = { - get_icon("InterpRaw", "EditorIcons"), - get_icon("InterpLinear", "EditorIcons"), - get_icon("InterpCubic", "EditorIcons") - }; - Ref<Texture> cont_icon[3] = { - get_icon("TrackContinuous", "EditorIcons"), - get_icon("TrackDiscrete", "EditorIcons"), - get_icon("TrackTrigger", "EditorIcons") - }; - Ref<Texture> type_icon[3] = { - get_icon("KeyValue", "EditorIcons"), - get_icon("KeyXform", "EditorIcons"), - get_icon("KeyCall", "EditorIcons") - }; - - Ref<Texture> valid_icon = get_icon("KeyValid", "EditorIcons"); - Ref<Texture> invalid_icon = get_icon("KeyInvalid", "EditorIcons"); - const Color modulate_selected = Color(0x84 / 255.0, 0xc2 / 255.0, 0xff / 255.0); - - Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons"); - - int right_separator_ofs = right_data_size_cache; - - int h = font->get_height() + sep; - - int fit = (size.height / h) - 1; - int total = animation->get_track_count(); - if (total < fit) { - v_scroll->hide(); - v_scroll->set_max(total); - v_scroll->set_page(fit); - } else { - v_scroll->show(); - v_scroll->set_max(total); - v_scroll->set_page(fit); - } - - int left_check_ofs = checked->get_width(); - int settings_limit = size.width - right_separator_ofs; - int name_limit = settings_limit * name_column_ratio; - - Color linecolor = color; - linecolor.a = 0.2; - te->draw_line(ofs + Point2(name_limit, 0), ofs + Point2(name_limit, size.height), linecolor); - te->draw_line(ofs + Point2(settings_limit, 0), ofs + Point2(settings_limit, size.height), linecolor); - te->draw_texture(hsize_icon, ofs + Point2(name_limit - hsize_icon->get_width() - hsep, (h - hsize_icon->get_height()) / 2)); - - te->draw_line(ofs + Point2(0, h), ofs + Point2(size.width, h), linecolor); - // draw time - - float keys_from; - float keys_to; - float zoom_scale; - - { - - int zoomw = settings_limit - name_limit; - - float scale = _get_zoom_scale(); - zoom_scale = scale; - - float l = animation->get_length(); - if (l <= 0) - l = 0.001; //avoid crashor - - int end_px = (l - h_scroll->get_value()) * scale; - int begin_px = -h_scroll->get_value() * scale; - Color notimecol = get_color("dark_color_2", "Editor"); - - { - - te->draw_rect(Rect2(ofs + Point2(name_limit, 0), Point2(zoomw - 1, h)), notimecol); - - if (begin_px < zoomw && end_px > 0) { - - if (begin_px < 0) - begin_px = 0; - if (end_px > zoomw) - end_px = zoomw; - - te->draw_rect(Rect2(ofs + Point2(name_limit + begin_px, 0), Point2(end_px - begin_px - 1, h)), timecolor); - } - } - - keys_from = h_scroll->get_value(); - keys_to = keys_from + zoomw / scale; - - { - float time_min = 0; - float time_max = animation->get_length(); - for (int i = 0; i < animation->get_track_count(); i++) { - - if (animation->track_get_key_count(i) > 0) { - - float beg = animation->track_get_key_time(i, 0); - if (beg < time_min) - time_min = beg; - float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1); - if (end > time_max) - time_max = end; - } - } - - float extra = (zoomw / scale) * 0.5; - - if (time_min < -0.001) - time_min -= extra; - time_max += extra; - h_scroll->set_min(time_min); - h_scroll->set_max(time_max); - - if (zoomw / scale < (time_max - time_min)) { - h_scroll->show(); - - } else { - - h_scroll->hide(); - } - } - - h_scroll->set_page(zoomw / scale); - - Color color_time_sec = color; - Color color_time_dec = color; - color_time_dec.a *= 0.5; -#define SC_ADJ 100 - int min = 30; - int dec = 1; - int step = 1; - int decimals = 2; - bool step_found = false; - - const int period_width = font->get_char_size('.').width; - int max_digit_width = font->get_char_size('0').width; - for (int i = 1; i <= 9; i++) { - const int digit_width = font->get_char_size('0' + i).width; - max_digit_width = MAX(digit_width, max_digit_width); - } - const int max_sc = int(Math::ceil(zoomw / scale)); - const int max_sc_width = String::num(max_sc).length() * max_digit_width; - - while (!step_found) { - - min = max_sc_width; - if (decimals > 0) - min += period_width + max_digit_width * decimals; - - static const int _multp[3] = { 1, 2, 5 }; - for (int i = 0; i < 3; i++) { - - step = (_multp[i] * dec); - if (step * scale / SC_ADJ > min) { - step_found = true; - break; - } - } - if (step_found) - break; - dec *= 10; - decimals--; - if (decimals < 0) - decimals = 0; - } - - for (int i = 0; i < zoomw; i++) { - - float pos = h_scroll->get_value() + double(i) / scale; - float prev = h_scroll->get_value() + (double(i) - 1.0) / scale; - - int sc = int(Math::floor(pos * SC_ADJ)); - int prev_sc = int(Math::floor(prev * SC_ADJ)); - bool sub = (sc % SC_ADJ); - - if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { - - int scd = sc < 0 ? prev_sc : sc; - te->draw_line(ofs + Point2(name_limit + i, 0), ofs + Point2(name_limit + i, h), linecolor); - te->draw_string(font, ofs + Point2(name_limit + i + 3, (h - font->get_height()) / 2 + font->get_ascent()).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), sub ? color_time_dec : color_time_sec, zoomw - i); - } - } - } - - color.a *= 0.5; - - for (int i = 0; i < fit; i++) { - - //this code sucks, i always forget how it works - - int idx = v_scroll->get_value() + i; - if (idx >= animation->get_track_count()) - break; - int y = h + i * h + sep; - - bool prop_exists = false; - Variant::Type valid_type = Variant::NIL; - Object *obj = NULL; - - RES res; - Vector<StringName> leftover_path; - - Node *node = root ? root->get_node_and_resource(animation->track_get_path(idx), res, leftover_path) : (Node *)NULL; - - if (res.is_valid()) { - obj = res.ptr(); - } else if (node) { - obj = node; - } - - if (obj && animation->track_get_type(idx) == Animation::TYPE_VALUE) { - // While leftover_path might be still empty, we wouldn't be able to get here anyway - valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists); - } - - // Draw background color of the whole track - if (/*mouse_over.over!=MouseOver::OVER_NONE &&*/ idx == mouse_over.track) { - Color sepc = hover_color; - te->draw_rect(Rect2(ofs + Point2(0, y), Size2(size.width, h - 1)), sepc); - } - - if (selected_track == idx) { - Color tc = select_color; - //tc.a*=0.7; - te->draw_rect(Rect2(ofs + Point2(0, y), Size2(size.width - 1, h - 1)), tc); - } - - // Draw track enabled state check box - Ref<Texture> check_box = animation->track_is_enabled(idx) ? checked : unchecked; - te->draw_texture(check_box, ofs + Point2(0, y + (h - checked->get_height()) / 2).floor()); - - // Draw track type glyph and node path - te->draw_texture(type_icon[animation->track_get_type(idx)], ofs + Point2(left_check_ofs + sep, y + (h - type_icon[0]->get_height()) / 2).floor()); - NodePath np = animation->track_get_path(idx); - Node *n = root ? root->get_node(np) : (Node *)NULL; - Color ncol = color; - if (n && editor_selection->is_selected(n)) - ncol = track_select_color; - te->draw_string(font, Point2(ofs + Point2(left_check_ofs + sep + type_icon[0]->get_width() + sep, y + font->get_ascent() + (sep / 2))).floor(), np, ncol, name_limit - (left_check_ofs + sep) - (type_icon[0]->get_width() + sep) - 5); - - // Draw separator line below track area - if (!obj) - te->draw_line(ofs + Point2(0, y + h / 2), ofs + Point2(name_limit, y + h / 2), invalid_path_color); - - te->draw_line(ofs + Point2(0, y + h), ofs + Point2(size.width, y + h), sepcolor); - - Point2 icon_ofs = ofs + Point2(size.width, y + (h - remove_icon->get_height()) / 2).floor(); - icon_ofs.y += 4 * EDSCALE; - - /* icon_ofs.x-=remove_icon->get_width(); - - te->draw_texture((mouse_over.over==MouseOver::OVER_REMOVE && mouse_over.track==idx)?remove_icon_hl:remove_icon,icon_ofs); - icon_ofs.x-=hsep; - icon_ofs.x-=move_down_icon->get_width(); - te->draw_texture((mouse_over.over==MouseOver::OVER_DOWN && mouse_over.track==idx)?move_down_icon_hl:move_down_icon,icon_ofs); - icon_ofs.x-=hsep; - icon_ofs.x-=move_up_icon->get_width(); - te->draw_texture((mouse_over.over==MouseOver::OVER_UP && mouse_over.track==idx)?move_up_icon_hl:move_up_icon,icon_ofs); - icon_ofs.x-=hsep; - te->draw_line(Point2(icon_ofs.x,ofs.y+y),Point2(icon_ofs.x,ofs.y+y+h),sepcolor); - - icon_ofs.x-=hsep; - */ - track_ofs[0] = size.width - icon_ofs.x + ofs.x; - icon_ofs.x -= down_icon->get_width(); - te->draw_texture(down_icon, icon_ofs - Size2(0, 4 * EDSCALE)); - - int wrap_type = animation->track_get_interpolation_loop_wrap(idx) ? 1 : 0; - icon_ofs.x -= hsep; - icon_ofs.x -= wrap_icon[wrap_type]->get_width(); - te->draw_texture(wrap_icon[wrap_type], icon_ofs); - - icon_ofs.x -= hsep; - te->draw_line(Point2(icon_ofs.x, ofs.y + y), Point2(icon_ofs.x, ofs.y + y + h), sepcolor); - - track_ofs[1] = size.width - icon_ofs.x + ofs.x; - - icon_ofs.x -= down_icon->get_width(); - te->draw_texture(down_icon, icon_ofs - Size2(0, 4 * EDSCALE)); - - int interp_type = animation->track_get_interpolation_type(idx); - ERR_CONTINUE(interp_type < 0 || interp_type >= 3); - icon_ofs.x -= hsep; - icon_ofs.x -= interp_icon[interp_type]->get_width(); - te->draw_texture(interp_icon[interp_type], icon_ofs); - - icon_ofs.x -= hsep; - te->draw_line(Point2(icon_ofs.x, ofs.y + y), Point2(icon_ofs.x, ofs.y + y + h), sepcolor); - - track_ofs[2] = size.width - icon_ofs.x + ofs.x; - - if (animation->track_get_type(idx) == Animation::TYPE_VALUE) { - - int umode = animation->value_track_get_update_mode(idx); - - icon_ofs.x -= hsep; - icon_ofs.x -= down_icon->get_width(); - te->draw_texture(down_icon, icon_ofs - Size2(0, 4 * EDSCALE)); - - icon_ofs.x -= hsep; - icon_ofs.x -= cont_icon[umode]->get_width(); - te->draw_texture(cont_icon[umode], icon_ofs); - } else { - - icon_ofs.x -= hsep * 2 + cont_icon[0]->get_width() + down_icon->get_width(); - } - - icon_ofs.x -= hsep; - te->draw_line(Point2(icon_ofs.x, ofs.y + y), Point2(icon_ofs.x, ofs.y + y + h), sepcolor); - - track_ofs[3] = size.width - icon_ofs.x + ofs.x; - - icon_ofs.x -= hsep; - icon_ofs.x -= add_key_icon->get_width(); - te->draw_texture((mouse_over.over == MouseOver::OVER_ADD_KEY && mouse_over.track == idx) ? add_key_icon_hl : add_key_icon, icon_ofs); - track_ofs[4] = size.width - icon_ofs.x + ofs.x; - - //draw the keys; - int tt = animation->track_get_type(idx); - float key_vofs = Math::floor((float)(h - type_icon[tt]->get_height()) / 2); - float key_hofs = -Math::floor((float)type_icon[tt]->get_height() / 2); - - int kc = animation->track_get_key_count(idx); - bool first = true; - - for (int i = 0; i < kc; i++) { - - float time = animation->track_get_key_time(idx, i); - if (time < keys_from) - continue; - if (time > keys_to) { - - if (first && i > 0 && animation->track_get_key_value(idx, i) == animation->track_get_key_value(idx, i - 1)) { - //draw whole line - te->draw_line(ofs + Vector2(name_limit, y + h / 2), ofs + Point2(settings_limit, y + h / 2), color); - } - - break; - } - - float x = key_hofs + name_limit + (time - keys_from) * zoom_scale; - - Ref<Texture> tex = type_icon[tt]; - Color modulate = Color(1, 1, 1); - - bool is_hover = false; - bool is_selected = false; - - SelectedKey sk; - sk.key = i; - sk.track = idx; - if (selection.has(sk)) { - - if (click.click == ClickOver::CLICK_MOVE_KEYS) - continue; - is_selected = true; - } - - if (mouse_over.over == MouseOver::OVER_KEY && mouse_over.track == idx && mouse_over.over_key == i) - is_hover = true; - - Variant value = animation->track_get_key_value(idx, i); - - if (prop_exists && !Variant::can_convert(value.get_type(), valid_type)) { - - tex = invalid_icon; - if (is_hover) - modulate = Color(1.5, 1.5, 1.5); - else - modulate = Color(1, 1, 1); - } else if (is_selected) { - - tex = valid_icon; - modulate = modulate_selected; - if (is_hover) - modulate = modulate.lightened(0.2); - } else if (is_hover) { - - tex = valid_icon; - modulate = Color(1, 1, 1); - } - - if (first && i > 0 && value == animation->track_get_key_value(idx, i - 1)) { - - te->draw_line(ofs + Vector2(name_limit, y + h / 2), ofs + Point2(x, y + h / 2), color); - } - - if (i < kc - 1 && value == animation->track_get_key_value(idx, i + 1)) { - float x_n = key_hofs + name_limit + (animation->track_get_key_time(idx, i + 1) - keys_from) * zoom_scale; - - x_n = MIN(x_n, settings_limit); - te->draw_line(ofs + Point2(x_n, y + h / 2), ofs + Point2(x, y + h / 2), color); - } - - te->draw_texture(tex, ofs + Point2(x, y + key_vofs).floor(), modulate); - - first = false; - } - } - - switch (click.click) { - case ClickOver::CLICK_SELECT_KEYS: { - - Color box_color = get_color("accent_color", "Editor"); - box_color.a = 0.35; - te->draw_rect(Rect2(click.at, click.to - click.at), box_color); - - } break; - case ClickOver::CLICK_MOVE_KEYS: { - - float from_t = 1e20; - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - float t = animation->track_get_key_time(E->key().track, E->key().key); - if (t < from_t) - from_t = t; - } - - float motion = from_t + (click.to.x - click.at.x) / zoom_scale; - if (step->get_value()) - motion = Math::stepify(motion, step->get_value()); - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - - int idx = E->key().track; - int i = idx - (int)v_scroll->get_value(); - if (i < 0 || i >= fit) - continue; - int y = h + i * h + sep; - - float key_vofs = Math::floor((float)(h - valid_icon->get_height()) / 2); - float key_hofs = -Math::floor((float)valid_icon->get_height() / 2); - - float time = animation->track_get_key_time(idx, E->key().key); - float diff = time - from_t; - - float t = motion + diff; - - float x = (t - keys_from) * zoom_scale; - //x+=click.to.x - click.at.x; - if (x < 0 || x >= (settings_limit - name_limit)) - continue; - - x += name_limit; - - te->draw_texture(valid_icon, ofs + Point2(x + key_hofs, y + key_vofs).floor(), modulate_selected); - } - } break; - default: {}; - } - - te_drawing = false; -} - -void AnimationKeyEditor::_track_name_changed(const String &p_name) { - - ERR_FAIL_COND(!animation.is_valid()); - undo_redo->create_action(TTR("Anim Track Rename")); - undo_redo->add_do_method(animation.ptr(), "track_set_path", track_name_editing, p_name); - undo_redo->add_undo_method(animation.ptr(), "track_set_path", track_name_editing, animation->track_get_path(track_name_editing)); - undo_redo->commit_action(); - track_name->hide(); -} - -void AnimationKeyEditor::_track_menu_selected(int p_idx) { - - ERR_FAIL_COND(!animation.is_valid()); - - if (interp_editing != -1) { - - ERR_FAIL_INDEX(interp_editing, animation->get_track_count()); - undo_redo->create_action(TTR("Anim Track Change Interpolation")); - undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", interp_editing, p_idx); - undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", interp_editing, animation->track_get_interpolation_type(interp_editing)); - undo_redo->commit_action(); - } else if (cont_editing != -1) { - - ERR_FAIL_INDEX(cont_editing, animation->get_track_count()); - - undo_redo->create_action(TTR("Anim Track Change Value Mode")); - undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", cont_editing, p_idx); - undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", cont_editing, animation->value_track_get_update_mode(cont_editing)); - undo_redo->commit_action(); - } else if (wrap_editing != -1) { - - ERR_FAIL_INDEX(wrap_editing, animation->get_track_count()); - - undo_redo->create_action(TTR("Anim Track Change Wrap Mode")); - undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", wrap_editing, p_idx ? true : false); - undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", wrap_editing, animation->track_get_interpolation_loop_wrap(wrap_editing)); - undo_redo->commit_action(); - } else { - switch (p_idx) { - - case RIGHT_MENU_DUPLICATE: - _anim_duplicate_keys(); - break; - case RIGHT_MENU_DUPLICATE_TRANSPOSE: - _anim_duplicate_keys(true); - break; - case RIGHT_MENU_REMOVE: - _anim_delete_keys(); - break; - } - } -} - -struct _AnimMoveRestore { - - int track; - float time; - Variant key; - float transition; -}; - -void AnimationKeyEditor::_clear_selection_for_anim(const Ref<Animation> &p_anim) { - - if (!(animation == p_anim)) - return; - //selection.clear(); - _clear_selection(); -} - -void AnimationKeyEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) { - - if (!(animation == p_anim)) - return; - - int idx = animation->track_find_key(p_track, p_pos, true); - ERR_FAIL_COND(idx < 0); - - SelectedKey sk; - sk.track = p_track; - sk.key = idx; - KeyInfo ki; - ki.pos = p_pos; - - selection.insert(sk, ki); -} - -PropertyInfo AnimationKeyEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path) { - - r_base_path = NodePath(); - ERR_FAIL_COND_V(!animation.is_valid(), PropertyInfo()); - ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo()); - - if (!root) - return PropertyInfo(); - - NodePath path = animation->track_get_path(p_idx); - - if (!root->has_node_and_resource(path)) - return PropertyInfo(); - - RES res; - Vector<StringName> leftover_path; - Node *node = root->get_node_and_resource(path, res, leftover_path, true); - - if (node) { - r_base_path = node->get_path(); - } - - if (leftover_path.empty()) - return PropertyInfo(); - - Variant property_info_base; - if (res.is_valid()) - property_info_base = res; - else if (node) - property_info_base = node; - - for (int i = 0; i < leftover_path.size() - 1; i++) { - property_info_base = property_info_base.get_named(leftover_path[i]); - } - - List<PropertyInfo> pinfo; - property_info_base.get_property_list(&pinfo); - - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - - if (E->get().name == leftover_path[leftover_path.size() - 1]) { - return E->get(); - } - } - - return PropertyInfo(); -} - -void AnimationKeyEditor::_curve_transition_changed(float p_what) { - - if (selection.size() == 0) - return; - if (selection.size() == 1) - undo_redo->create_action(TTR("Edit Node Curve"), UndoRedo::MERGE_ENDS); - else - undo_redo->create_action(TTR("Edit Selection Curve"), UndoRedo::MERGE_ENDS); - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - - int track = E->key().track; - int key = E->key().key; - float prev_val = animation->track_get_key_transition(track, key); - undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, p_what); - undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val); - } - - undo_redo->commit_action(); -} - -void AnimationKeyEditor::_toggle_edit_curves() { - - if (edit_button->is_pressed()) - key_editor_tab->show(); - else - key_editor_tab->hide(); -} - -bool AnimationKeyEditor::_edit_if_single_selection() { - - if (selection.size() != 1) { - - if (selection.size() == 0) { - curve_edit->set_mode(AnimationCurveEdit::MODE_DISABLED); - //print_line("disable"); - } else { - - curve_edit->set_mode(AnimationCurveEdit::MODE_MULTIPLE); - curve_edit->set_transition(1.0); - curve_edit->clear_multiples(); - //add all - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - - curve_edit->set_multiple(animation->track_get_key_transition(E->key().track, E->key().key)); - } - //print_line("multiple"); - } - return false; - } - curve_edit->set_mode(AnimationCurveEdit::MODE_SINGLE); - //print_line("regular"); - - int idx = selection.front()->key().track; - int key = selection.front()->key().key; - { - - key_edit->animation = animation; - key_edit->track = idx; - key_edit->key_ofs = animation->track_get_key_time(idx, key); - key_edit->hint = _find_hint_for_track(idx, key_edit->base); - key_edit->notify_change(); - - curve_edit->set_transition(animation->track_get_key_transition(idx, key)); - - /*key_edit_dialog->set_size( Size2( 200,200) ); - key_edit_dialog->set_position( track_editor->get_global_position() + ofs + mpos +Point2(-100,20)); - key_edit_dialog->popup();*/ - } - - return true; -} - -void AnimationKeyEditor::_anim_delete_keys() { - if (selection.size()) { - undo_redo->create_action(TTR("Anim Delete Keys")); - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); - } - undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); - undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - undo_redo->commit_action(); - //selection.clear(); - accept_event(); - _edit_if_single_selection(); - } -} - -void AnimationKeyEditor::_track_editor_gui_input(const Ref<InputEvent> &p_input) { - - Control *te = track_editor; - Ref<StyleBox> style = get_stylebox("normal", "TextEdit"); - - if (!animation.is_valid()) { - return; - } - - Size2 size = te->get_size() - style->get_minimum_size(); - Size2 ofs = style->get_offset(); - - Ref<Font> font = te->get_font("font", "Tree"); - int sep = get_constant("vseparation", "Tree"); - int hsep = get_constant("hseparation", "Tree"); - Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons"); - Ref<Texture> move_up_icon = get_icon("MoveUp", "EditorIcons"); - Ref<Texture> move_down_icon = get_icon("MoveDown", "EditorIcons"); - Ref<Texture> down_icon = get_icon("select_arrow", "Tree"); - Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons"); - Ref<Texture> add_key_icon = get_icon("TrackAddKey", "EditorIcons"); - Ref<Texture> check_icon = get_icon("checked", "Tree"); - - Ref<Texture> wrap_icon[2] = { - get_icon("InterpWrapClamp", "EditorIcons"), - get_icon("InterpWrapLoop", "EditorIcons"), - }; - Ref<Texture> interp_icon[3] = { - get_icon("InterpRaw", "EditorIcons"), - get_icon("InterpLinear", "EditorIcons"), - get_icon("InterpCubic", "EditorIcons") - }; - Ref<Texture> cont_icon[3] = { - get_icon("TrackContinuous", "EditorIcons"), - get_icon("TrackDiscrete", "EditorIcons"), - get_icon("TrackTrigger", "EditorIcons") - }; - Ref<Texture> type_icon[3] = { - get_icon("KeyValue", "EditorIcons"), - get_icon("KeyXform", "EditorIcons"), - get_icon("KeyCall", "EditorIcons") - }; - int right_separator_ofs = right_data_size_cache; - - int h = font->get_height() + sep; - - int fit = (size.height / h) - 1; - int total = animation->get_track_count(); - if (total < fit) { - v_scroll->hide(); - } else { - v_scroll->show(); - v_scroll->set_max(total); - v_scroll->set_page(fit); - } - - int left_check_ofs = check_icon->get_width(); - int settings_limit = size.width - right_separator_ofs; - int name_limit = settings_limit * name_column_ratio; - - Ref<InputEventKey> key = p_input; - if (key.is_valid()) { - - if (key->get_scancode() == KEY_D && key->is_pressed() && key->get_command()) { - - if (key->get_shift()) - _menu_track(TRACK_MENU_DUPLICATE_TRANSPOSE); - else - _menu_track(TRACK_MENU_DUPLICATE); - - accept_event(); - - } else if (key->get_scancode() == KEY_DELETE && key->is_pressed() && click.click == ClickOver::CLICK_NONE) { - - _anim_delete_keys(); - } else if (animation.is_valid() && animation->get_track_count() > 0) { - - if (key->is_pressed() && (key->is_action("ui_up") || key->is_action("ui_page_up"))) { - - if (key->is_action("ui_up")) - selected_track--; - if (v_scroll->is_visible_in_tree() && key->is_action("ui_page_up")) - selected_track--; - - if (selected_track < 0) - selected_track = 0; - - if (v_scroll->is_visible_in_tree()) { - if (v_scroll->get_value() > selected_track) - v_scroll->set_value(selected_track); - } - - track_editor->update(); - accept_event(); - } - - if (key->is_pressed() && (key->is_action("ui_down") || key->is_action("ui_page_down"))) { - - if (key->is_action("ui_down")) - selected_track++; - else if (v_scroll->is_visible_in_tree() && key->is_action("ui_page_down")) - selected_track += v_scroll->get_page(); - - if (selected_track >= animation->get_track_count()) - selected_track = animation->get_track_count() - 1; - - if (v_scroll->is_visible_in_tree() && v_scroll->get_page() + v_scroll->get_value() < selected_track + 1) { - v_scroll->set_value(selected_track - v_scroll->get_page() + 1); - } - - track_editor->update(); - accept_event(); - } - } - } - - Ref<InputEventMouseButton> mb = p_input; - - if (mb.is_valid()) { - - if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { - - if (mb->get_command()) { - - zoom->set_value(zoom->get_value() + zoom->get_step()); - } else { - - v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * mb->get_factor() / 8); - } - } - - if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { - - if (mb->get_command()) { - - zoom->set_value(zoom->get_value() - zoom->get_step()); - } else { - - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8); - } - } - - if (mb->get_button_index() == BUTTON_WHEEL_RIGHT && mb->is_pressed()) { - - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); - } - - if (mb->get_button_index() == BUTTON_WHEEL_LEFT && mb->is_pressed()) { - - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8); - } - - if (mb->get_button_index() == BUTTON_RIGHT && mb->is_pressed()) { - - Point2 mpos = mb->get_position() - ofs; - - if (selection.size() == 0) { - // Auto-select on right-click if nothing is selected - // Note: This code is pretty much duplicated from the left click code, - // both codes could be moved into a function to avoid the duplicated code. - Point2 mpos = mb->get_position() - ofs; - - if (mpos.y < h) { - return; - } - - mpos.y -= h; - - int idx = mpos.y / h; - idx += v_scroll->get_value(); - if (idx < 0 || idx >= animation->get_track_count()) - return; - - if (mpos.x < name_limit) { - } else if (mpos.x < settings_limit) { - float pos = mpos.x - name_limit; - pos /= _get_zoom_scale(); - pos += h_scroll->get_value(); - float w_time = (type_icon[0]->get_width() / _get_zoom_scale()) / 2.0; - - int kidx = animation->track_find_key(idx, pos); - int kidx_n = kidx + 1; - int key = -1; - - if (kidx >= 0 && kidx < animation->track_get_key_count(idx)) { - - float kpos = animation->track_get_key_time(idx, kidx); - if (ABS(pos - kpos) <= w_time) { - - key = kidx; - } - } - - if (key == -1 && kidx_n >= 0 && kidx_n < animation->track_get_key_count(idx)) { - - float kpos = animation->track_get_key_time(idx, kidx_n); - if (ABS(pos - kpos) <= w_time) { - - key = kidx_n; - } - } - - if (key == -1) { - - click.click = ClickOver::CLICK_SELECT_KEYS; - click.at = mb->get_position(); - click.to = click.at; - click.shift = mb->get_shift(); - selected_track = idx; - track_editor->update(); - //drag select region - return; - } - - SelectedKey sk; - sk.track = idx; - sk.key = key; - KeyInfo ki; - ki.pos = animation->track_get_key_time(idx, key); - click.shift = mb->get_shift(); - click.selk = sk; - - if (!mb->get_shift() && !selection.has(sk)) - _clear_selection(); - - selection.insert(sk, ki); - - click.click = ClickOver::CLICK_MOVE_KEYS; - click.at = mb->get_position(); - click.to = click.at; - update(); - selected_track = idx; - track_editor->update(); - - if (_edit_if_single_selection() && mb->get_command()) { - edit_button->set_pressed(true); - key_editor_tab->show(); - } - } - } - - if (selection.size()) { - // User has right clicked and we have a selection, show a popup menu with options - track_menu->clear(); - track_menu->set_size(Point2(1, 1)); - track_menu->add_item(TTR("Duplicate Selection"), RIGHT_MENU_DUPLICATE); - track_menu->add_item(TTR("Duplicate Transposed"), RIGHT_MENU_DUPLICATE_TRANSPOSE); - track_menu->add_item(TTR("Remove Selection"), RIGHT_MENU_REMOVE); - - track_menu->set_position(te->get_global_position() + mpos); - - interp_editing = -1; - cont_editing = -1; - wrap_editing = -1; - - track_menu->popup(); - } - } - - if (mb->get_button_index() == BUTTON_LEFT && !(mb->get_button_mask() & ~BUTTON_MASK_LEFT)) { - - if (mb->is_pressed()) { - - Point2 mpos = mb->get_position() - ofs; - - if (mpos.y < h) { - - if (mpos.x < name_limit && mpos.x > (name_limit - hsep - hsize_icon->get_width())) { - - click.click = ClickOver::CLICK_RESIZE_NAMES; - click.at = mb->get_position(); - click.to = click.at; - click.at.y = name_limit; - } - - if (mpos.x >= name_limit && mpos.x < settings_limit) { - //seek - //int zoomw = settings_limit-name_limit; - float scale = _get_zoom_scale(); - float pos = h_scroll->get_value() + (mpos.x - name_limit) / scale; - if (animation->get_step()) - pos = Math::stepify(pos, animation->get_step()); - - if (pos < 0) - pos = 0; - if (pos >= animation->get_length()) - pos = animation->get_length(); - timeline_pos = pos; - click.click = ClickOver::CLICK_DRAG_TIMELINE; - click.at = mb->get_position(); - click.to = click.at; - emit_signal("timeline_changed", pos, false); - } - - return; - } - - mpos.y -= h; - - int idx = mpos.y / h; - idx += v_scroll->get_value(); - if (idx < 0) - return; - - if (idx >= animation->get_track_count()) { - - if (mpos.x >= name_limit && mpos.x < settings_limit) { - - click.click = ClickOver::CLICK_SELECT_KEYS; - click.at = mb->get_position(); - click.to = click.at; - //drag select region - } - - return; - } - - if (mpos.x < left_check_ofs) { - // Checkbox on the very left to enable/disable tracks. - - animation->track_set_enabled(idx, !animation->track_is_enabled(idx)); - - } else if (mpos.x < name_limit - (type_icon[0]->get_width() / 2.0)) { - //name column - - // area - if (idx != selected_track) { - - selected_track = idx; - track_editor->update(); - return; - } - - Rect2 area(ofs.x + left_check_ofs + sep, ofs.y + ((int(mpos.y) / h) + 1) * h, name_limit - left_check_ofs - sep, h); - track_name->set_text(animation->track_get_path(idx)); - track_name->set_position(te->get_global_position() + area.position); - track_name->set_size(area.size); - track_name->show_modal(); - track_name->grab_focus(); - track_name->select_all(); - track_name_editing = idx; - - } else if (mpos.x < settings_limit) { - - float pos = mpos.x - name_limit; - pos /= _get_zoom_scale(); - pos += h_scroll->get_value(); - float w_time = (type_icon[0]->get_width() / _get_zoom_scale()) / 2.0; - - int kidx = animation->track_find_key(idx, pos); - int kidx_n = kidx + 1; - int key = -1; - - if (kidx >= 0 && kidx < animation->track_get_key_count(idx)) { - - float kpos = animation->track_get_key_time(idx, kidx); - if (ABS(pos - kpos) <= w_time) { - - key = kidx; - } - } - - if (key == -1 && kidx_n >= 0 && kidx_n < animation->track_get_key_count(idx)) { - - float kpos = animation->track_get_key_time(idx, kidx_n); - if (ABS(pos - kpos) <= w_time) { - - key = kidx_n; - } - } - - if (key == -1) { - - click.click = ClickOver::CLICK_SELECT_KEYS; - click.at = mb->get_position(); - click.to = click.at; - click.shift = mb->get_shift(); - selected_track = idx; - track_editor->update(); - //drag select region - return; - } - - SelectedKey sk; - sk.track = idx; - sk.key = key; - KeyInfo ki; - ki.pos = animation->track_get_key_time(idx, key); - click.shift = mb->get_shift(); - click.selk = sk; - - if (!mb->get_shift() && !selection.has(sk)) - _clear_selection(); - - selection.insert(sk, ki); - - click.click = ClickOver::CLICK_MOVE_KEYS; - click.at = mb->get_position(); - click.to = click.at; - update(); - selected_track = idx; - track_editor->update(); - - if (_edit_if_single_selection() && mb->get_command()) { - edit_button->set_pressed(true); - key_editor_tab->show(); - } - } else { - //button column - int ofsx = size.width - mpos.x; - if (ofsx < 0) - return; - /* - if (ofsx < remove_icon->get_width()) { - - undo_redo->create_action("Remove Anim Track"); - undo_redo->add_do_method(animation.ptr(),"remove_track",idx); - undo_redo->add_undo_method(animation.ptr(),"add_track",animation->track_get_type(idx),idx); - undo_redo->add_undo_method(animation.ptr(),"track_set_path",idx,animation->track_get_path(idx)); - //todo interpolation - for(int i=0;i<animation->track_get_key_count(idx);i++) { - - Variant v = animation->track_get_key_value(idx,i); - float time = animation->track_get_key_time(idx,i); - float trans = animation->track_get_key_transition(idx,i); - - undo_redo->add_undo_method(animation.ptr(),"track_insert_key",idx,time,v); - undo_redo->add_undo_method(animation.ptr(),"track_set_key_transition",idx,i,trans); - - } - - undo_redo->add_undo_method(animation.ptr(),"track_set_interpolation_type",idx,animation->track_get_interpolation_type(idx)); - if (animation->track_get_type(idx)==Animation::TYPE_VALUE) { - undo_redo->add_undo_method(animation.ptr(),"value_track_set_continuous",idx,animation->value_track_is_continuous(idx)); - - } - - undo_redo->commit_action(); - - - return; - } - - ofsx-=hsep+remove_icon->get_width(); - - if (ofsx < move_down_icon->get_width()) { - - if (idx < animation->get_track_count() -1) { - undo_redo->create_action("Move Anim Track Down"); - undo_redo->add_do_method(animation.ptr(),"track_move_up",idx); - undo_redo->add_undo_method(animation.ptr(),"track_move_down",idx+1); - undo_redo->commit_action(); - } - return; - } - - ofsx-=hsep+move_down_icon->get_width(); - - if (ofsx < move_up_icon->get_width()) { - - if (idx >0) { - undo_redo->create_action("Move Anim Track Up"); - undo_redo->add_do_method(animation.ptr(),"track_move_down",idx); - undo_redo->add_undo_method(animation.ptr(),"track_move_up",idx-1); - undo_redo->commit_action(); - } - return; - } - - - ofsx-=hsep*3+move_up_icon->get_width(); - */ - - if (ofsx < track_ofs[1]) { - - track_menu->clear(); - track_menu->set_size(Point2(1, 1)); - static const char *interp_name[2] = { "Clamp Loop Interp", "Wrap Loop Interp" }; - for (int i = 0; i < 2; i++) { - track_menu->add_icon_item(wrap_icon[i], interp_name[i]); - } - - int popup_y = ofs.y + ((int(mpos.y) / h) + 2) * h; - int popup_x = size.width - track_ofs[1]; - - track_menu->set_position(te->get_global_position() + Point2(popup_x, popup_y)); - - wrap_editing = idx; - interp_editing = -1; - cont_editing = -1; - - track_menu->popup(); - - return; - } - - if (ofsx < track_ofs[2]) { - - track_menu->clear(); - track_menu->set_size(Point2(1, 1)); - static const char *interp_name[3] = { "Nearest", "Linear", "Cubic" }; - for (int i = 0; i < 3; i++) { - track_menu->add_icon_item(interp_icon[i], interp_name[i]); - } - - int popup_y = ofs.y + ((int(mpos.y) / h) + 2) * h; - int popup_x = size.width - track_ofs[2]; - - track_menu->set_position(te->get_global_position() + Point2(popup_x, popup_y)); - - interp_editing = idx; - cont_editing = -1; - wrap_editing = -1; - - track_menu->popup(); - - return; - } - - if (ofsx < track_ofs[3]) { - - track_menu->clear(); - track_menu->set_size(Point2(1, 1)); - String cont_name[3] = { TTR("Continuous"), TTR("Discrete"), TTR("Trigger") }; - for (int i = 0; i < 3; i++) { - track_menu->add_icon_item(cont_icon[i], cont_name[i]); - } - - int popup_y = ofs.y + ((int(mpos.y) / h) + 2) * h; - int popup_x = size.width - track_ofs[3]; - - track_menu->set_position(te->get_global_position() + Point2(popup_x, popup_y)); - - interp_editing = -1; - wrap_editing = -1; - cont_editing = idx; - - track_menu->popup(); - - return; - } - - if (ofsx < track_ofs[4]) { - - Animation::TrackType tt = animation->track_get_type(idx); - - float pos = timeline_pos; - int existing = animation->track_find_key(idx, pos, true); - - Variant newval; - - if (tt == Animation::TYPE_TRANSFORM) { - Dictionary d; - d["location"] = Vector3(); - d["rotation"] = Quat(); - d["scale"] = Vector3(); - newval = d; - - } else if (tt == Animation::TYPE_METHOD) { - - Dictionary d; - d["method"] = ""; - d["args"] = Vector<Variant>(); - - newval = d; - } else if (tt == Animation::TYPE_VALUE) { - - NodePath np; - PropertyInfo inf = _find_hint_for_track(idx, np); - if (inf.type != Variant::NIL) { - - Variant::CallError err; - newval = Variant::construct(inf.type, NULL, 0, err); - } - - if (newval.get_type() == Variant::NIL) { - //popup a new type - cvi_track = idx; - cvi_pos = pos; - - type_menu->set_position(get_global_position() + mpos + ofs); - type_menu->popup(); - return; - } - } - - undo_redo->create_action(TTR("Anim Add Key")); - - undo_redo->add_do_method(animation.ptr(), "track_insert_key", idx, pos, newval, 1); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", idx, pos); - - if (existing != -1) { - Variant v = animation->track_get_key_value(idx, existing); - float trans = animation->track_get_key_transition(idx, existing); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", idx, pos, v, trans); - } - - undo_redo->commit_action(); - - return; - } - } - - } else { - - switch (click.click) { - case ClickOver::CLICK_SELECT_KEYS: { - - float zoom_scale = _get_zoom_scale(); - float keys_from = h_scroll->get_value(); - float keys_to = keys_from + (settings_limit - name_limit) / zoom_scale; - - float from_time = keys_from + (click.at.x - (name_limit + ofs.x)) / zoom_scale; - float to_time = keys_from + (click.to.x - (name_limit + ofs.x)) / zoom_scale; - - if (to_time < from_time) - SWAP(from_time, to_time); - - if (from_time > keys_to || to_time < keys_from) - break; - - if (from_time < keys_from) - from_time = keys_from; - - if (to_time >= keys_to) - to_time = keys_to; - - int from_track = int(click.at.y - ofs.y - h - sep) / h + v_scroll->get_value(); - int to_track = int(click.to.y - ofs.y - h - sep) / h + v_scroll->get_value(); - int from_mod = int(click.at.y - ofs.y - sep) % h; - int to_mod = int(click.to.y - ofs.y - sep) % h; - - if (to_track < from_track) { - - SWAP(from_track, to_track); - SWAP(from_mod, to_mod); - } - - if ((from_mod > (h / 2)) && ((click.at.y - ofs.y) >= (h + sep))) { - from_track++; - } - - if (to_mod < h / 2) { - to_track--; - } - - if (from_track > to_track) { - if (!click.shift) - _clear_selection(); - _edit_if_single_selection(); - break; - } - - int tracks_from = v_scroll->get_value(); - int tracks_to = v_scroll->get_value() + fit - 1; - if (tracks_to >= animation->get_track_count()) - tracks_to = animation->get_track_count() - 1; - - tracks_from = 0; - tracks_to = animation->get_track_count() - 1; - if (to_track > tracks_to) - to_track = tracks_to; - if (from_track < tracks_from) - from_track = tracks_from; - - if (from_track > tracks_to || to_track < tracks_from) { - if (!click.shift) - _clear_selection(); - _edit_if_single_selection(); - break; - } - - if (!click.shift) - _clear_selection(); - - int higher_track = 0x7FFFFFFF; - for (int i = from_track; i <= to_track; i++) { - - int kc = animation->track_get_key_count(i); - for (int j = 0; j < kc; j++) { - - float t = animation->track_get_key_time(i, j); - if (t < from_time) - continue; - if (t > to_time) - break; - - if (i < higher_track) - higher_track = i; - - SelectedKey sk; - sk.track = i; - sk.key = j; - KeyInfo ki; - ki.pos = t; - selection[sk] = ki; - } - } - - if (higher_track != 0x7FFFFFFF) { - selected_track = higher_track; - track_editor->update(); - } - - _edit_if_single_selection(); - - } break; - case ClickOver::CLICK_MOVE_KEYS: { - - if (selection.empty()) - break; - if (click.at == click.to) { - - if (!click.shift) { - - KeyInfo ki = selection[click.selk]; - _clear_selection(); - selection[click.selk] = ki; - _edit_if_single_selection(); - } - - break; - } - - float from_t = 1e20; - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - float t = animation->track_get_key_time(E->key().track, E->key().key); - if (t < from_t) - from_t = t; - } - - float motion = from_t + (click.to.x - click.at.x) / _get_zoom_scale(); - if (step->get_value()) - motion = Math::stepify(motion, step->get_value()); - - undo_redo->create_action(TTR("Anim Move Keys")); - - List<_AnimMoveRestore> to_restore; - - // 1-remove the keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); - } - // 2- remove overlapped keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float newtime = E->get().pos - from_t + motion; - int idx = animation->track_find_key(E->key().track, newtime, true); - if (idx == -1) - continue; - SelectedKey sk; - sk.key = idx; - sk.track = E->key().track; - if (selection.has(sk)) - continue; //already in selection, don't save - - undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime); - _AnimMoveRestore amr; - - amr.key = animation->track_get_key_value(E->key().track, idx); - amr.track = E->key().track; - amr.time = newtime; - amr.transition = animation->track_get_key_transition(E->key().track, idx); - - to_restore.push_back(amr); - } - - // 3-move the keys (re insert them) - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float newpos = E->get().pos - from_t + motion; - /* - if (newpos<0) - continue; //no add at the beginning - */ - undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); - } - - // 4-(undo) remove inserted keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float newpos = E->get().pos + -from_t + motion; - /* - if (newpos<0) - continue; //no remove what no inserted - */ - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos); - } - - // 5-(undo) reinsert keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); - } - - // 6-(undo) reinsert overlapped keys - for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { - - _AnimMoveRestore &amr = E->get(); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); - } - - // 6-(undo) reinsert overlapped keys - for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { - - _AnimMoveRestore &amr = E->get(); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); - } - - undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); - undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - - // 7-reselect - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float oldpos = E->get().pos; - float newpos = oldpos - from_t + motion; - //if (newpos>=0) - undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos); - undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos); - } - - undo_redo->commit_action(); - _edit_if_single_selection(); - - } break; - default: {} - } - - //button released - click.click = ClickOver::CLICK_NONE; - track_editor->update(); - } - } - } - - Ref<InputEventMouseMotion> mm = p_input; - - if (mm.is_valid()) { - - mouse_over.over = MouseOver::OVER_NONE; - mouse_over.track = -1; - te->update(); - track_editor->set_tooltip(""); - - if (!track_editor->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) - track_editor->call_deferred("grab_focus"); - - if (click.click != ClickOver::CLICK_NONE) { - - switch (click.click) { - case ClickOver::CLICK_RESIZE_NAMES: { - - float base = click.at.y; - float clickp = click.at.x - ofs.x; - float dif = base - clickp; - - float target = mm->get_position().x + dif - ofs.x; - - float ratio = target / settings_limit; - - if (ratio > 0.9) - ratio = 0.9; - else if (ratio < 0.2) - ratio = 0.2; - - name_column_ratio = ratio; - - } break; - case ClickOver::CLICK_DRAG_TIMELINE: { - - Point2 mpos = mm->get_position() - ofs; - /* - if (mpos.x<name_limit) - mpos.x=name_limit; - if (mpos.x>settings_limit) - mpos.x=settings_limit; - */ - - //int zoomw = settings_limit-name_limit; - float scale = _get_zoom_scale(); - float pos = h_scroll->get_value() + (mpos.x - name_limit) / scale; - if (animation->get_step()) { - pos = Math::stepify(pos, animation->get_step()); - } - if (pos < 0) - pos = 0; - if (pos >= animation->get_length()) - pos = animation->get_length(); - - if (pos < h_scroll->get_value()) { - h_scroll->set_value(pos); - } else if (pos > h_scroll->get_value() + (settings_limit - name_limit) / scale) { - h_scroll->set_value(pos - (settings_limit - name_limit) / scale); - } - - timeline_pos = pos; - emit_signal("timeline_changed", pos, true); - - } break; - case ClickOver::CLICK_SELECT_KEYS: { - - click.to = mm->get_position(); - if (click.to.y < h && click.at.y > h && mm->get_relative().y < 0) { - - float prev = v_scroll->get_value(); - v_scroll->set_value(v_scroll->get_value() - 1); - if (prev != v_scroll->get_value()) - click.at.y += h; - } - if (click.to.y > size.height && click.at.y < size.height && mm->get_relative().y > 0) { - - float prev = v_scroll->get_value(); - v_scroll->set_value(v_scroll->get_value() + 1); - if (prev != v_scroll->get_value()) - click.at.y -= h; - } - - } break; - case ClickOver::CLICK_MOVE_KEYS: { - - click.to = mm->get_position(); - } break; - default: {} - } - - return; - } else if (mm->get_button_mask() & BUTTON_MASK_MIDDLE) { - - int rel = mm->get_relative().x; - float relf = rel / _get_zoom_scale(); - h_scroll->set_value(h_scroll->get_value() - relf); - } - - if (mm->get_button_mask() == 0) { - - Point2 mpos = mm->get_position() - ofs; - - if (mpos.y < h) { - return; - } - - mpos.y -= h; - - int idx = mpos.y / h; - idx += v_scroll->get_value(); - if (idx < 0 || idx >= animation->get_track_count()) - return; - - mouse_over.track = idx; - - if (mpos.x < name_limit) { - //name column - - mouse_over.over = MouseOver::OVER_NAME; - - } else if (mpos.x < settings_limit) { - - float pos = mpos.x - name_limit; - pos /= _get_zoom_scale(); - pos += h_scroll->get_value(); - float w_time = (type_icon[0]->get_width() / _get_zoom_scale()) / 2.0; - - int kidx = animation->track_find_key(idx, pos); - int kidx_n = kidx + 1; - - bool found = false; - - if (kidx >= 0 && kidx < animation->track_get_key_count(idx)) { - - float kpos = animation->track_get_key_time(idx, kidx); - if (ABS(pos - kpos) <= w_time) { - - mouse_over.over = MouseOver::OVER_KEY; - mouse_over.track = idx; - mouse_over.over_key = kidx; - found = true; - } - } - - if (!found && kidx_n >= 0 && kidx_n < animation->track_get_key_count(idx)) { - - float kpos = animation->track_get_key_time(idx, kidx_n); - if (ABS(pos - kpos) <= w_time) { - - mouse_over.over = MouseOver::OVER_KEY; - mouse_over.track = idx; - mouse_over.over_key = kidx_n; - found = true; - } - } - - if (found) { - - String text; - text = "time: " + rtos(animation->track_get_key_time(idx, mouse_over.over_key)) + "\n"; - - switch (animation->track_get_type(idx)) { - - case Animation::TYPE_TRANSFORM: { - - Dictionary d = animation->track_get_key_value(idx, mouse_over.over_key); - if (d.has("location")) - text += "location: " + String(d["location"]) + "\n"; - if (d.has("rotation")) - text += "rot: " + String(d["rotation"]) + "\n"; - if (d.has("scale")) - text += "scale: " + String(d["scale"]) + "\n"; - } break; - case Animation::TYPE_VALUE: { - - Variant v = animation->track_get_key_value(idx, mouse_over.over_key); - //text+="value: "+String(v)+"\n"; - - bool prop_exists = false; - Variant::Type valid_type = Variant::NIL; - Object *obj = NULL; - - RES res; - Vector<StringName> leftover_path; - Node *node = root->get_node_and_resource(animation->track_get_path(idx), res, leftover_path); - - if (res.is_valid()) { - obj = res.ptr(); - } else if (node) { - obj = node; - } - - if (obj) { - valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists); - } - - text += "type: " + Variant::get_type_name(v.get_type()) + "\n"; - if (prop_exists && !Variant::can_convert(v.get_type(), valid_type)) { - text += "value: " + String(v) + " (Invalid, expected type: " + Variant::get_type_name(valid_type) + ")\n"; - } else { - text += "value: " + String(v) + "\n"; - } - - } break; - case Animation::TYPE_METHOD: { - - Dictionary d = animation->track_get_key_value(idx, mouse_over.over_key); - if (d.has("method")) - text += String(d["method"]); - text += "("; - Vector<Variant> args; - if (d.has("args")) - args = d["args"]; - for (int i = 0; i < args.size(); i++) { - - if (i > 0) - text += ", "; - text += String(args[i]); - } - text += ")\n"; - - } break; - } - text += "easing: " + rtos(animation->track_get_key_transition(idx, mouse_over.over_key)); - - track_editor->set_tooltip(text); - return; - } - - } else { - //button column - int ofsx = size.width - mpos.x; - if (ofsx < 0) - return; - /* - if (ofsx < remove_icon->get_width()) { - - mouse_over.over=MouseOver::OVER_REMOVE; - - return; - } - - ofsx-=hsep+remove_icon->get_width(); - - if (ofsx < move_down_icon->get_width()) { - - mouse_over.over=MouseOver::OVER_DOWN; - return; - } - - ofsx-=hsep+move_down_icon->get_width(); - - if (ofsx < move_up_icon->get_width()) { - - mouse_over.over=MouseOver::OVER_UP; - return; - } - - ofsx-=hsep*3+move_up_icon->get_width(); - -*/ - - if (ofsx < down_icon->get_width() + wrap_icon[0]->get_width() + hsep * 3) { - - mouse_over.over = MouseOver::OVER_WRAP; - return; - } - - ofsx -= hsep * 3 + wrap_icon[0]->get_width() + down_icon->get_width(); - - if (ofsx < down_icon->get_width() + interp_icon[0]->get_width() + hsep * 3) { - - mouse_over.over = MouseOver::OVER_INTERP; - return; - } - - ofsx -= hsep * 2 + interp_icon[0]->get_width() + down_icon->get_width(); - - if (ofsx < down_icon->get_width() + cont_icon[0]->get_width() + hsep * 3) { - - mouse_over.over = MouseOver::OVER_VALUE; - return; - } - - ofsx -= hsep * 3 + cont_icon[0]->get_width() + down_icon->get_width(); - - if (ofsx < add_key_icon->get_width()) { - - mouse_over.over = MouseOver::OVER_ADD_KEY; - return; - } - } - } - } - - Ref<InputEventMagnifyGesture> magnify_gesture = p_input; - if (magnify_gesture.is_valid()) { - zoom->set_value(zoom->get_value() * magnify_gesture->get_factor()); - } - - Ref<InputEventPanGesture> pan_gesture = p_input; - if (pan_gesture.is_valid()) { - - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8); - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); - } -} - -void AnimationKeyEditor::_notification(int p_what) { - - switch (p_what) { - case NOTIFICATION_VISIBILITY_CHANGED: { - - update_keying(); - EditorNode::get_singleton()->update_keying(); - emit_signal("keying_changed"); - } break; - - case NOTIFICATION_ENTER_TREE: { - - key_editor->edit(key_edit); - - zoomicon->set_custom_minimum_size(Size2(24 * EDSCALE, 0)); - zoomicon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); - - menu_track->set_icon(get_icon("Tools", "EditorIcons")); - menu_track->get_popup()->add_item(TTR("Scale Selection"), TRACK_MENU_SCALE); - menu_track->get_popup()->add_item(TTR("Scale From Cursor"), TRACK_MENU_SCALE_PIVOT); - menu_track->get_popup()->add_separator(); - menu_track->get_popup()->add_item(TTR("Duplicate Selection"), TRACK_MENU_DUPLICATE); - menu_track->get_popup()->add_item(TTR("Duplicate Transposed"), TRACK_MENU_DUPLICATE_TRANSPOSE); - menu_track->get_popup()->add_separator(); - menu_track->get_popup()->add_item(TTR("Goto Next Step"), TRACK_MENU_NEXT_STEP, KEY_MASK_CMD | KEY_RIGHT); - menu_track->get_popup()->add_item(TTR("Goto Prev Step"), TRACK_MENU_PREV_STEP, KEY_MASK_CMD | KEY_LEFT); - menu_track->get_popup()->add_separator(); - PopupMenu *tpp = memnew(PopupMenu); - tpp->add_item(TTR("Linear"), TRACK_MENU_SET_ALL_TRANS_LINEAR); - tpp->add_item(TTR("Constant"), TRACK_MENU_SET_ALL_TRANS_CONSTANT); - tpp->add_item(TTR("In"), TRACK_MENU_SET_ALL_TRANS_IN); - tpp->add_item(TTR("Out"), TRACK_MENU_SET_ALL_TRANS_OUT); - tpp->add_item(TTR("In-Out"), TRACK_MENU_SET_ALL_TRANS_INOUT); - tpp->add_item(TTR("Out-In"), TRACK_MENU_SET_ALL_TRANS_OUTIN); - tpp->set_name(TTR("Transitions")); - tpp->connect("id_pressed", this, "_menu_track"); - optimize_dialog->connect("confirmed", this, "_animation_optimize"); - - menu_track->get_popup()->add_child(tpp); - - menu_track->get_popup()->add_item(TTR("Optimize Animation"), TRACK_MENU_OPTIMIZE); - menu_track->get_popup()->add_item(TTR("Clean-Up Animation"), TRACK_MENU_CLEAN_UP); - - curve_linear->connect("pressed", this, "_menu_track", varray(CURVE_SET_LINEAR)); - curve_in->connect("pressed", this, "_menu_track", varray(CURVE_SET_IN)); - curve_out->connect("pressed", this, "_menu_track", varray(CURVE_SET_OUT)); - curve_inout->connect("pressed", this, "_menu_track", varray(CURVE_SET_INOUT)); - curve_outin->connect("pressed", this, "_menu_track", varray(CURVE_SET_OUTIN)); - curve_constant->connect("pressed", this, "_menu_track", varray(CURVE_SET_CONSTANT)); - - edit_button->connect("pressed", this, "_toggle_edit_curves"); - - curve_edit->connect("transition_changed", this, "_curve_transition_changed"); - call_select->connect("selected", this, "_add_call_track"); - - _update_menu(); - - } break; - - case NOTIFICATION_THEME_CHANGED: { - zoomicon->set_texture(get_icon("Zoom", "EditorIcons")); - - menu_add_track->set_icon(get_icon("Add", "EditorIcons")); - - menu_track->set_icon(get_icon("Tools", "EditorIcons")); - - menu_add_track->get_popup()->set_item_icon(ADD_TRACK_MENU_ADD_VALUE_TRACK, get_icon("KeyValue", "EditorIcons")); - menu_add_track->get_popup()->set_item_icon(ADD_TRACK_MENU_ADD_TRANSFORM_TRACK, get_icon("KeyXform", "EditorIcons")); - menu_add_track->get_popup()->set_item_icon(ADD_TRACK_MENU_ADD_CALL_TRACK, get_icon("KeyCall", "EditorIcons")); - - curve_linear->set_icon(get_icon("CurveLinear", "EditorIcons")); - curve_in->set_icon(get_icon("CurveIn", "EditorIcons")); - curve_out->set_icon(get_icon("CurveOut", "EditorIcons")); - curve_inout->set_icon(get_icon("CurveInOut", "EditorIcons")); - curve_outin->set_icon(get_icon("CurveOutIn", "EditorIcons")); - curve_constant->set_icon(get_icon("CurveConstant", "EditorIcons")); - - move_up_button->set_icon(get_icon("MoveUp", "EditorIcons")); - move_down_button->set_icon(get_icon("MoveDown", "EditorIcons")); - remove_button->set_icon(get_icon("Remove", "EditorIcons")); - edit_button->set_icon(get_icon("EditKey", "EditorIcons")); - - loop->set_icon(get_icon("Loop", "EditorIcons")); - - { - - right_data_size_cache = 0; - int hsep = get_constant("hseparation", "Tree"); - Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons"); - Ref<Texture> move_up_icon = get_icon("MoveUp", "EditorIcons"); - Ref<Texture> move_down_icon = get_icon("MoveDown", "EditorIcons"); - Ref<Texture> down_icon = get_icon("select_arrow", "Tree"); - Ref<Texture> add_key_icon = get_icon("TrackAddKey", "EditorIcons"); - Ref<Texture> interp_icon[3] = { - get_icon("InterpRaw", "EditorIcons"), - get_icon("InterpLinear", "EditorIcons"), - get_icon("InterpCubic", "EditorIcons") - }; - Ref<Texture> cont_icon[3] = { - get_icon("TrackContinuous", "EditorIcons"), - get_icon("TrackDiscrete", "EditorIcons"), - get_icon("TrackTrigger", "EditorIcons") - }; - - Ref<Texture> wrap_icon[2] = { - get_icon("InterpWrapClamp", "EditorIcons"), - get_icon("InterpWrapLoop", "EditorIcons"), - }; - right_data_size_cache = down_icon->get_width() * 3 + add_key_icon->get_width() + interp_icon[0]->get_width() + cont_icon[0]->get_width() + wrap_icon[0]->get_width() + hsep * 9; - } - } break; - } -} - -void AnimationKeyEditor::_scroll_changed(double) { - - if (te_drawing) - return; - - track_editor->update(); -} - -void AnimationKeyEditor::_update_paths() { - - if (animation.is_valid()) { - //timeline->set_max(animation->get_length()); - //timeline->set_step(0.01); - track_editor->update(); - length->set_value(animation->get_length()); - step->set_value(animation->get_step()); - } -} - -void AnimationKeyEditor::_root_removed() { - - root = NULL; -} - -void AnimationKeyEditor::_update_menu() { - - updating = true; - - if (animation.is_valid()) { - - length->set_value(animation->get_length()); - loop->set_pressed(animation->has_loop()); - step->set_value(animation->get_step()); - } - - track_editor->update(); - updating = false; -} -void AnimationKeyEditor::_clear_selection() { - - selection.clear(); - key_edit->animation = Ref<Animation>(); - key_edit->track = 0; - key_edit->key_ofs = 0; - key_edit->hint = PropertyInfo(); - key_edit->base = NodePath(); - key_edit->notify_change(); -} - -void AnimationKeyEditor::set_animation(const Ref<Animation> &p_anim) { - - if (animation.is_valid()) - animation->disconnect("changed", this, "_update_paths"); - animation = p_anim; - if (animation.is_valid()) - animation->connect("changed", this, "_update_paths"); - - timeline_pos = 0; - _clear_selection(); - - _update_menu(); - selected_track = -1; - _edit_if_single_selection(); - - EditorNode::get_singleton()->update_keying(); -} - -void AnimationKeyEditor::set_root(Node *p_root) { - - if (root) - root->disconnect("tree_exiting", this, "_root_removed"); - - root = p_root; - - if (root) - root->connect("tree_exiting", this, "_root_removed", make_binds(), CONNECT_ONESHOT); -} - -Node *AnimationKeyEditor::get_root() const { - - return root; -} - -void AnimationKeyEditor::update_keying() { - - bool keying_enabled = is_visible_in_tree() && animation.is_valid(); - - if (keying_enabled == keying) - return; - - keying = keying_enabled; - _update_menu(); - emit_signal("keying_changed"); -} - -bool AnimationKeyEditor::has_keying() const { - - return keying; -} - -void AnimationKeyEditor::_query_insert(const InsertData &p_id) { - - if (insert_frame != Engine::get_singleton()->get_frames_drawn()) { - //clear insert list for the frame if frame changed - if (insert_confirm->is_visible_in_tree()) - return; //do nothing - insert_data.clear(); - insert_query = false; - } - insert_frame = Engine::get_singleton()->get_frames_drawn(); - - for (List<InsertData>::Element *E = insert_data.front(); E; E = E->next()) { - //prevent insertion of multiple tracks - if (E->get().path == p_id.path) - return; //already inserted a track for this on this frame - } - - insert_data.push_back(p_id); - - if (p_id.track_idx == -1) { - if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { - //potential new key, does not exist - if (insert_data.size() == 1) - insert_confirm->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query)); - else - insert_confirm->set_text(vformat(TTR("Create %d NEW tracks and insert keys?"), insert_data.size())); - - insert_confirm->get_ok()->set_text(TTR("Create")); - insert_confirm->popup_centered_minsize(); - insert_query = true; - } else { - call_deferred("_insert_delay"); - insert_queue = true; - } - - } else { - if (!insert_query && !insert_queue) { - call_deferred("_insert_delay"); - insert_queue = true; - } - } -} - -void AnimationKeyEditor::insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform) { - - if (!keying) - return; - if (!animation.is_valid()) - return; - - ERR_FAIL_COND(!root); - //let's build a node path - String path = root->get_path_to(p_node); - if (p_sub != "") - path += ":" + p_sub; - - NodePath np = path; - - int track_idx = -1; - - for (int i = 0; i < animation->get_track_count(); i++) { - - if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM) - continue; - if (animation->track_get_path(i) != np) - continue; - - track_idx = i; - break; - } - - InsertData id; - Dictionary val; - - id.path = np; - id.track_idx = track_idx; - id.value = p_xform; - id.type = Animation::TYPE_TRANSFORM; - id.query = "node '" + p_node->get_name() + "'"; - id.advance = false; - - //dialog insert - - _query_insert(id); -} - -void AnimationKeyEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) { - - ERR_FAIL_COND(!root); - //let's build a node path - - Node *node = p_node; - - String path = root->get_path_to(node); - - for (int i = 1; i < history->get_path_size(); i++) { - - String prop = history->get_path_property(i); - ERR_FAIL_COND(prop == ""); - path += ":" + prop; - } - - path += ":" + p_property; - - NodePath np = path; - - //locate track - - int track_idx = -1; - - for (int i = 0; i < animation->get_track_count(); i++) { - - if (animation->track_get_type(i) != Animation::TYPE_VALUE) - continue; - if (animation->track_get_path(i) != np) - continue; - - track_idx = i; - break; - } - - if (p_only_if_exists && track_idx == -1) - return; - InsertData id; - id.path = np; - id.track_idx = track_idx; - id.value = p_value; - id.type = Animation::TYPE_VALUE; - id.query = "property '" + p_property + "'"; - id.advance = false; - //dialog insert - _query_insert(id); -} - -void AnimationKeyEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) { - - ERR_FAIL_COND(!root); - //let's build a node path - ERR_FAIL_COND(history->get_path_size() == 0); - Object *obj = ObjectDB::get_instance(history->get_path_object(0)); - ERR_FAIL_COND(!Object::cast_to<Node>(obj)); - - Node *node = Object::cast_to<Node>(obj); - - String path = root->get_path_to(node); - - for (int i = 1; i < history->get_path_size(); i++) { - - String prop = history->get_path_property(i); - ERR_FAIL_COND(prop == ""); - path += ":" + prop; - } - - path += ":" + p_property; - - NodePath np = path; - - //locate track - - int track_idx = -1; - - for (int i = 0; i < animation->get_track_count(); i++) { - - if (animation->track_get_type(i) != Animation::TYPE_VALUE) - continue; - if (animation->track_get_path(i) != np) - continue; - - track_idx = i; - break; - } - - InsertData id; - id.path = np; - id.track_idx = track_idx; - id.value = p_value; - id.type = Animation::TYPE_VALUE; - id.query = "property '" + p_property + "'"; - id.advance = p_advance; - //dialog insert - _query_insert(id); -} - -void AnimationKeyEditor::_confirm_insert_list() { - - undo_redo->create_action(TTR("Anim Create & Insert")); - - int last_track = animation->get_track_count(); - while (insert_data.size()) { - - last_track = _confirm_insert(insert_data.front()->get(), last_track); - insert_data.pop_front(); - } - - undo_redo->commit_action(); -} - -int AnimationKeyEditor::_confirm_insert(InsertData p_id, int p_last_track) { - - if (p_last_track == -1) - p_last_track = animation->get_track_count(); - - bool created = false; - if (p_id.track_idx < 0) { - - created = true; - undo_redo->create_action(TTR("Anim Insert Track & Key")); - Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE; - - if (p_id.type == Animation::TYPE_VALUE) { - //wants a new tack - - { - //hack - NodePath np; - animation->add_track(p_id.type); - animation->track_set_path(animation->get_track_count() - 1, p_id.path); - PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); - animation->remove_track(animation->get_track_count() - 1); //hack - - if (h.type == Variant::REAL || - h.type == Variant::VECTOR2 || - h.type == Variant::RECT2 || - h.type == Variant::VECTOR3 || - h.type == Variant::AABB || - h.type == Variant::QUAT || - h.type == Variant::COLOR || - h.type == Variant::TRANSFORM) { - - update_mode = Animation::UPDATE_CONTINUOUS; - } - - if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) { - update_mode = Animation::UPDATE_TRIGGER; - } - } - } - - p_id.track_idx = p_last_track; - - undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type); - undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path); - if (p_id.type == Animation::TYPE_VALUE) - undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", p_id.track_idx, update_mode); - - } else { - undo_redo->create_action(TTR("Anim Insert Key")); - } - - float time = timeline_pos; - Variant value; - - switch (p_id.type) { - - case Animation::TYPE_VALUE: { - - value = p_id.value; - - } break; - case Animation::TYPE_TRANSFORM: { - - Transform tr = p_id.value; - Dictionary d; - d["location"] = tr.origin; - d["scale"] = tr.basis.get_scale(); - d["rotation"] = Quat(tr.basis); //.orthonormalized(); - value = d; - } break; - default: {} - } - - undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value); - - if (created) { - - //just remove the track - undo_redo->add_undo_method(animation.ptr(), "remove_track", p_last_track); - p_last_track++; - } else { - - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_id.track_idx, time); - int existing = animation->track_find_key(p_id.track_idx, time, true); - if (existing != -1) { - Variant v = animation->track_get_key_value(p_id.track_idx, existing); - float trans = animation->track_get_key_transition(p_id.track_idx, existing); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, v, trans); - } - } - - undo_redo->add_do_method(this, "update"); - undo_redo->add_undo_method(this, "update"); - undo_redo->add_do_method(track_editor, "update"); - undo_redo->add_undo_method(track_editor, "update"); - undo_redo->add_do_method(track_pos, "update"); - undo_redo->add_undo_method(track_pos, "update"); - - undo_redo->commit_action(); - - return p_last_track; -} - -Ref<Animation> AnimationKeyEditor::get_current_animation() const { - - return animation; -} - -void AnimationKeyEditor::_animation_len_changed(float p_len) { - - if (updating) - return; - - if (!animation.is_null()) { - - undo_redo->create_action(TTR("Change Anim Len")); - undo_redo->add_do_method(animation.ptr(), "set_length", p_len); - undo_redo->add_undo_method(animation.ptr(), "set_length", animation->get_length()); - undo_redo->add_do_method(this, "_animation_len_update"); - undo_redo->add_undo_method(this, "_animation_len_update"); - undo_redo->commit_action(); - } -} - -void AnimationKeyEditor::_animation_len_update() { - - if (!animation.is_null()) - emit_signal(alc, animation->get_length()); -} - -void AnimationKeyEditor::_animation_changed() { - if (updating) - return; - _update_menu(); -} - -void AnimationKeyEditor::_animation_loop_changed() { - - if (updating) - return; - - if (!animation.is_null()) { - - undo_redo->create_action(TTR("Change Anim Loop")); - undo_redo->add_do_method(animation.ptr(), "set_loop", loop->is_pressed()); - undo_redo->add_undo_method(animation.ptr(), "set_loop", !loop->is_pressed()); - undo_redo->commit_action(); - } -} - -void AnimationKeyEditor::_create_value_item(int p_type) { - - undo_redo->create_action(TTR("Anim Create Typed Value Key")); - - Variant::CallError ce; - Variant v = Variant::construct(Variant::Type(p_type), NULL, 0, ce); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", cvi_track, cvi_pos, v); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", cvi_track, cvi_pos); - - int existing = animation->track_find_key(cvi_track, cvi_pos, true); - - if (existing != -1) { - Variant v = animation->track_get_key_value(cvi_track, existing); - float trans = animation->track_get_key_transition(cvi_track, existing); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", cvi_track, cvi_pos, v, trans); - } - - undo_redo->commit_action(); -} - -void AnimationKeyEditor::set_anim_pos(float p_pos) { - - if (animation.is_null()) - return; - timeline_pos = p_pos; - update(); - track_pos->update(); - track_editor->update(); -} - -void AnimationKeyEditor::_pane_drag(const Point2 &p_delta) { - - Size2 ecs = ec->get_custom_minimum_size(); - ecs.y -= p_delta.y; - if (ecs.y < 100) - ecs.y = 100; - ec->set_custom_minimum_size(ecs); -} - -void AnimationKeyEditor::_insert_delay() { - - if (insert_query) { - //discard since it's entered into query mode - insert_queue = false; - return; - } - - undo_redo->create_action(TTR("Anim Insert")); - - int last_track = animation->get_track_count(); - bool advance = false; - while (insert_data.size()) { - - if (insert_data.front()->get().advance) - advance = true; - last_track = _confirm_insert(insert_data.front()->get(), last_track); - insert_data.pop_front(); - } - - undo_redo->commit_action(); - - if (advance) { - float step = animation->get_step(); - if (step == 0) - step = 1; - - float pos = timeline_pos; - - pos = Math::stepify(pos + step, step); - if (pos > animation->get_length()) - pos = animation->get_length(); - timeline_pos = pos; - track_pos->update(); - emit_signal("timeline_changed", pos, true); - } - insert_queue = false; -} - -void AnimationKeyEditor::_step_changed(float p_len) { - - updating = true; - if (!animation.is_null()) { - animation->set_step(p_len); - emit_signal("animation_step_changed", animation->get_step()); - } - updating = false; -} - -void AnimationKeyEditor::_scale() { - - if (selection.empty()) - return; - - float from_t = 1e20; - float to_t = -1e20; - float len = -1e20; - float pivot = 0; - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { - float t = animation->track_get_key_time(E->key().track, E->key().key); - if (t < from_t) - from_t = t; - if (t > to_t) - to_t = t; - } - - len = to_t - from_t; - if (last_menu_track_opt == TRACK_MENU_SCALE_PIVOT) { - pivot = timeline_pos; - - } else { - - pivot = from_t; - } - - float s = scale->get_value(); - if (s == 0) { - ERR_PRINT("Can't scale to 0"); - } - - undo_redo->create_action(TTR("Anim Scale Keys")); - - List<_AnimMoveRestore> to_restore; - - // 1-remove the keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); - } - // 2- remove overlapped keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float newtime = (E->get().pos - from_t) * s + from_t; - int idx = animation->track_find_key(E->key().track, newtime, true); - if (idx == -1) - continue; - SelectedKey sk; - sk.key = idx; - sk.track = E->key().track; - if (selection.has(sk)) - continue; //already in selection, don't save - - undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime); - _AnimMoveRestore amr; - - amr.key = animation->track_get_key_value(E->key().track, idx); - amr.track = E->key().track; - amr.time = newtime; - amr.transition = animation->track_get_key_transition(E->key().track, idx); - - to_restore.push_back(amr); - } - -#define _NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * ABS(s) + from_t - // 3-move the keys (re insert them) - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float newpos = _NEW_POS(E->get().pos); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); - } - - // 4-(undo) remove inserted keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float newpos = _NEW_POS(E->get().pos); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos); - } - - // 5-(undo) reinsert keys - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); - } - - // 6-(undo) reinsert overlapped keys - for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { - - _AnimMoveRestore &amr = E->get(); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); - } - - // 6-(undo) reinsert overlapped keys - for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { - - _AnimMoveRestore &amr = E->get(); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); - } - - undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); - undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); - - // 7-reselect - - for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - - float oldpos = E->get().pos; - float newpos = _NEW_POS(oldpos); - if (newpos >= 0) - undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos); - undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos); - } -#undef _NEW_POS - undo_redo->commit_action(); -} - -void AnimationKeyEditor::_add_call_track(const NodePath &p_base) { - - Node *base = EditorNode::get_singleton()->get_edited_scene(); - if (!base) - return; - Node *from = base->get_node(p_base); - if (!from || !root) - return; - - NodePath path = root->get_path_to(from); - - //print_line("root: "+String(root->get_path())); - //print_line("path: "+String(path)); - - undo_redo->create_action(TTR("Anim Add Call Track")); - undo_redo->add_do_method(animation.ptr(), "add_track", Animation::TYPE_METHOD); - undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path); - undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); - undo_redo->commit_action(); -} - -void AnimationKeyEditor::cleanup() { - - set_animation(Ref<Animation>()); -} - -void AnimationKeyEditor::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_root_removed"), &AnimationKeyEditor::_root_removed); - ClassDB::bind_method(D_METHOD("_scale"), &AnimationKeyEditor::_scale); - ClassDB::bind_method(D_METHOD("set_root"), &AnimationKeyEditor::set_root); - - //ClassDB::bind_method(D_METHOD("_confirm_insert"),&AnimationKeyEditor::_confirm_insert); - ClassDB::bind_method(D_METHOD("_confirm_insert_list"), &AnimationKeyEditor::_confirm_insert_list); - - ClassDB::bind_method(D_METHOD("_update_paths"), &AnimationKeyEditor::_update_paths); - ClassDB::bind_method(D_METHOD("_track_editor_draw"), &AnimationKeyEditor::_track_editor_draw); - - ClassDB::bind_method(D_METHOD("_animation_changed"), &AnimationKeyEditor::_animation_changed); - ClassDB::bind_method(D_METHOD("_scroll_changed"), &AnimationKeyEditor::_scroll_changed); - ClassDB::bind_method(D_METHOD("_track_editor_gui_input"), &AnimationKeyEditor::_track_editor_gui_input); - ClassDB::bind_method(D_METHOD("_track_name_changed"), &AnimationKeyEditor::_track_name_changed); - ClassDB::bind_method(D_METHOD("_track_menu_selected"), &AnimationKeyEditor::_track_menu_selected); - ClassDB::bind_method(D_METHOD("_menu_add_track"), &AnimationKeyEditor::_menu_add_track); - ClassDB::bind_method(D_METHOD("_menu_track"), &AnimationKeyEditor::_menu_track); - ClassDB::bind_method(D_METHOD("_clear_selection_for_anim"), &AnimationKeyEditor::_clear_selection_for_anim); - ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationKeyEditor::_select_at_anim); - ClassDB::bind_method(D_METHOD("_track_position_draw"), &AnimationKeyEditor::_track_position_draw); - ClassDB::bind_method(D_METHOD("_insert_delay"), &AnimationKeyEditor::_insert_delay); - ClassDB::bind_method(D_METHOD("_step_changed"), &AnimationKeyEditor::_step_changed); - - ClassDB::bind_method(D_METHOD("_animation_loop_changed"), &AnimationKeyEditor::_animation_loop_changed); - ClassDB::bind_method(D_METHOD("_animation_len_changed"), &AnimationKeyEditor::_animation_len_changed); - ClassDB::bind_method(D_METHOD("_create_value_item"), &AnimationKeyEditor::_create_value_item); - ClassDB::bind_method(D_METHOD("_pane_drag"), &AnimationKeyEditor::_pane_drag); - - ClassDB::bind_method(D_METHOD("_animation_len_update"), &AnimationKeyEditor::_animation_len_update); - - ClassDB::bind_method(D_METHOD("set_animation"), &AnimationKeyEditor::set_animation); - ClassDB::bind_method(D_METHOD("_animation_optimize"), &AnimationKeyEditor::_animation_optimize); - ClassDB::bind_method(D_METHOD("_curve_transition_changed"), &AnimationKeyEditor::_curve_transition_changed); - ClassDB::bind_method(D_METHOD("_toggle_edit_curves"), &AnimationKeyEditor::_toggle_edit_curves); - ClassDB::bind_method(D_METHOD("_add_call_track"), &AnimationKeyEditor::_add_call_track); - - ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "res"), PropertyInfo(Variant::STRING, "prop"))); - ADD_SIGNAL(MethodInfo("keying_changed")); - ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag"))); - ADD_SIGNAL(MethodInfo("animation_len_changed", PropertyInfo(Variant::REAL, "len"))); - ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::REAL, "step"))); - ADD_SIGNAL(MethodInfo("key_edited", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "key"))); -} - -AnimationKeyEditor::AnimationKeyEditor() { - - alc = "animation_len_changed"; - editor_selection = EditorNode::get_singleton()->get_editor_selection(); - - selected_track = -1; - updating = false; - te_drawing = false; - undo_redo = EditorNode::get_singleton()->get_undo_redo(); - history = EditorNode::get_singleton()->get_editor_history(); - - ec = memnew(Control); - ec->set_custom_minimum_size(Size2(0, 150) * EDSCALE); - add_child(ec); - ec->set_v_size_flags(SIZE_EXPAND_FILL); - - h_scroll = memnew(HScrollBar); - h_scroll->connect("value_changed", this, "_scroll_changed"); - add_child(h_scroll); - h_scroll->set_value(0); - - HBoxContainer *hb = memnew(HBoxContainer); - add_child(hb); - - root = NULL; - //menu = memnew( MenuButton ); - //menu->set_flat(true); - //menu->set_position(Point2()); - //add_child(menu); - - zoomicon = memnew(TextureRect); - hb->add_child(zoomicon); - zoomicon->set_tooltip(TTR("Animation zoom.")); - - zoom = memnew(HSlider); - //hb->add_child(zoom); - zoom->set_step(0.01); - zoom->set_min(0.0); - zoom->set_max(2.0); - zoom->set_value(1.0); - zoom->set_h_size_flags(SIZE_EXPAND_FILL); - zoom->set_v_size_flags(SIZE_EXPAND_FILL); - zoom->set_stretch_ratio(2); - hb->add_child(zoom); - zoom->connect("value_changed", this, "_scroll_changed"); - zoom->set_tooltip(TTR("Animation zoom.")); - - hb->add_child(memnew(VSeparator)); - - Label *l = memnew(Label); - l->set_text(TTR("Length (s):")); - hb->add_child(l); - - length = memnew(SpinBox); - length->set_min(0.01); - length->set_max(10000); - length->set_step(0.01); - length->set_h_size_flags(SIZE_EXPAND_FILL); - length->set_stretch_ratio(1); - length->set_tooltip(TTR("Animation length (in seconds).")); - length->set_editable(false); - - hb->add_child(length); - length->connect("value_changed", this, "_animation_len_changed"); - - l = memnew(Label); - l->set_text(TTR("Step (s):")); - hb->add_child(l); - - step = memnew(SpinBox); - step->set_min(0.00); - step->set_max(128); - step->set_step(0.01); - step->set_value(0.0); - step->set_h_size_flags(SIZE_EXPAND_FILL); - step->set_stretch_ratio(1); - step->set_tooltip(TTR("Cursor step snap (in seconds).")); - step->set_editable(false); - - hb->add_child(step); - step->connect("value_changed", this, "_step_changed"); - - loop = memnew(ToolButton); - loop->set_toggle_mode(true); - loop->connect("pressed", this, "_animation_loop_changed"); - hb->add_child(loop); - loop->set_tooltip(TTR("Enable/Disable looping in animation.")); - loop->set_disabled(true); - - hb->add_child(memnew(VSeparator)); - - menu_add_track = memnew(MenuButton); - hb->add_child(menu_add_track); - menu_add_track->get_popup()->connect("id_pressed", this, "_menu_add_track"); - menu_add_track->set_tooltip(TTR("Add new tracks.")); - menu_add_track->get_popup()->add_icon_item(get_icon("KeyValue", "EditorIcons"), "Add Normal Track", ADD_TRACK_MENU_ADD_VALUE_TRACK); - menu_add_track->get_popup()->add_icon_item(get_icon("KeyXform", "EditorIcons"), "Add Transform Track", ADD_TRACK_MENU_ADD_TRANSFORM_TRACK); - menu_add_track->get_popup()->add_icon_item(get_icon("KeyCall", "EditorIcons"), "Add Call Func Track", ADD_TRACK_MENU_ADD_CALL_TRACK); - - move_up_button = memnew(ToolButton); - hb->add_child(move_up_button); - move_up_button->connect("pressed", this, "_menu_track", make_binds(TRACK_MENU_MOVE_UP)); - move_up_button->set_focus_mode(FOCUS_NONE); - move_up_button->set_disabled(true); - move_up_button->set_tooltip(TTR("Move current track up.")); - - move_down_button = memnew(ToolButton); - hb->add_child(move_down_button); - move_down_button->connect("pressed", this, "_menu_track", make_binds(TRACK_MENU_MOVE_DOWN)); - move_down_button->set_focus_mode(FOCUS_NONE); - move_down_button->set_disabled(true); - move_down_button->set_tooltip(TTR("Move current track down.")); - - remove_button = memnew(ToolButton); - hb->add_child(remove_button); - remove_button->connect("pressed", this, "_menu_track", make_binds(TRACK_MENU_REMOVE)); - remove_button->set_focus_mode(FOCUS_NONE); - remove_button->set_disabled(true); - remove_button->set_tooltip(TTR("Remove selected track.")); - - hb->add_child(memnew(VSeparator)); - - menu_track = memnew(MenuButton); - hb->add_child(menu_track); - menu_track->get_popup()->connect("id_pressed", this, "_menu_track"); - menu_track->set_tooltip(TTR("Track Tools")); - - edit_button = memnew(ToolButton); - edit_button->set_toggle_mode(true); - edit_button->set_focus_mode(FOCUS_NONE); - edit_button->set_disabled(true); - - hb->add_child(edit_button); - edit_button->set_tooltip(TTR("Enable editing of individual keys by clicking them.")); - - optimize_dialog = memnew(ConfirmationDialog); - add_child(optimize_dialog); - optimize_dialog->set_title(TTR("Anim. Optimizer")); - VBoxContainer *optimize_vb = memnew(VBoxContainer); - optimize_dialog->add_child(optimize_vb); - - optimize_linear_error = memnew(SpinBox); - optimize_linear_error->set_max(1.0); - optimize_linear_error->set_min(0.001); - optimize_linear_error->set_step(0.001); - optimize_linear_error->set_value(0.05); - optimize_vb->add_margin_child(TTR("Max. Linear Error:"), optimize_linear_error); - optimize_angular_error = memnew(SpinBox); - optimize_angular_error->set_max(1.0); - optimize_angular_error->set_min(0.001); - optimize_angular_error->set_step(0.001); - optimize_angular_error->set_value(0.01); - - optimize_vb->add_margin_child(TTR("Max. Angular Error:"), optimize_angular_error); - optimize_max_angle = memnew(SpinBox); - optimize_vb->add_margin_child(TTR("Max Optimizable Angle:"), optimize_max_angle); - optimize_max_angle->set_max(360.0); - optimize_max_angle->set_min(0.0); - optimize_max_angle->set_step(0.1); - optimize_max_angle->set_value(22); - - optimize_dialog->get_ok()->set_text(TTR("Optimize")); - - /*keying = memnew( Button ); - keying->set_toggle_mode(true); - //keying->set_text("Keys"); - keying->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,60); - keying->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,10); - keying->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,55); - keying->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,10); - //add_child(keying); - keying->connect("pressed",this,"_keying_toggled"); - */ - - /* l = memnew( Label ); - l->set_text("Base: "); - l->set_position(Point2(0,3)); - //dr_panel->add_child(l);*/ - - //menu->get_popup()->connect("id_pressed",this,"_menu_callback"); - - hb = memnew(HBoxContainer); - hb->set_anchors_and_margins_preset(Control::PRESET_WIDE); - ec->add_child(hb); - hb->set_v_size_flags(SIZE_EXPAND_FILL); - - track_editor = memnew(Control); - track_editor->connect("draw", this, "_track_editor_draw"); - hb->add_child(track_editor); - track_editor->connect("gui_input", this, "_track_editor_gui_input"); - track_editor->set_focus_mode(Control::FOCUS_ALL); - track_editor->set_h_size_flags(SIZE_EXPAND_FILL); - - track_pos = memnew(Control); - track_pos->set_anchors_and_margins_preset(Control::PRESET_WIDE); - track_pos->set_mouse_filter(MOUSE_FILTER_IGNORE); - track_editor->add_child(track_pos); - track_pos->connect("draw", this, "_track_position_draw"); - - select_anim_warning = memnew(Label); - track_editor->add_child(select_anim_warning); - select_anim_warning->set_anchors_and_margins_preset(Control::PRESET_WIDE); - select_anim_warning->set_text(TTR("Select an AnimationPlayer from the Scene Tree to edit animations.")); - select_anim_warning->set_autowrap(true); - select_anim_warning->set_align(Label::ALIGN_CENTER); - select_anim_warning->set_valign(Label::VALIGN_CENTER); - - v_scroll = memnew(VScrollBar); - hb->add_child(v_scroll); - v_scroll->connect("value_changed", this, "_scroll_changed"); - v_scroll->set_value(0); - - key_editor_tab = memnew(TabContainer); - key_editor_tab->set_tab_align(TabContainer::ALIGN_LEFT); - hb->add_child(key_editor_tab); - key_editor_tab->set_custom_minimum_size(Size2(200, 0) * EDSCALE); - - key_editor = memnew(PropertyEditor); - key_editor->hide_top_label(); - key_editor->set_name(TTR("Key")); - key_editor_tab->add_child(key_editor); - - key_edit = memnew(AnimationKeyEdit); - key_edit->undo_redo = undo_redo; - //key_edit->ke_dialog=key_edit_dialog; - - type_menu = memnew(PopupMenu); - type_menu->set_pass_on_modal_close_click(false); - add_child(type_menu); - for (int i = 0; i < Variant::VARIANT_MAX; i++) - type_menu->add_item(Variant::get_type_name(Variant::Type(i)), i); - type_menu->connect("id_pressed", this, "_create_value_item"); - - VBoxContainer *curve_vb = memnew(VBoxContainer); - curve_vb->set_name(TTR("Transition")); - HBoxContainer *curve_hb = memnew(HBoxContainer); - curve_vb->add_child(curve_hb); - - curve_linear = memnew(ToolButton); - curve_linear->set_focus_mode(FOCUS_NONE); - curve_hb->add_child(curve_linear); - curve_in = memnew(ToolButton); - curve_in->set_focus_mode(FOCUS_NONE); - curve_hb->add_child(curve_in); - curve_out = memnew(ToolButton); - curve_out->set_focus_mode(FOCUS_NONE); - curve_hb->add_child(curve_out); - curve_inout = memnew(ToolButton); - curve_inout->set_focus_mode(FOCUS_NONE); - curve_hb->add_child(curve_inout); - curve_outin = memnew(ToolButton); - curve_outin->set_focus_mode(FOCUS_NONE); - curve_hb->add_child(curve_outin); - curve_constant = memnew(ToolButton); - curve_constant->set_focus_mode(FOCUS_NONE); - curve_hb->add_child(curve_constant); - - curve_edit = memnew(AnimationCurveEdit); - curve_vb->add_child(curve_edit); - curve_edit->set_v_size_flags(SIZE_EXPAND_FILL); - key_editor_tab->add_child(curve_vb); - - track_name = memnew(LineEdit); - track_name->set_as_toplevel(true); - track_name->hide(); - add_child(track_name); - track_name->connect("text_entered", this, "_track_name_changed"); - track_menu = memnew(PopupMenu); - track_menu->set_pass_on_modal_close_click(false); - add_child(track_menu); - track_menu->connect("id_pressed", this, "_track_menu_selected"); - - key_editor_tab->hide(); - - last_idx = 1; - - _update_menu(); - - insert_confirm = memnew(ConfirmationDialog); - add_child(insert_confirm); - insert_confirm->connect("confirmed", this, "_confirm_insert_list"); - - click.click = ClickOver::CLICK_NONE; - - name_column_ratio = 0.3; - timeline_pos = 0; - - keying = false; - insert_frame = 0; - insert_query = false; - insert_queue = false; - - editor_selection->connect("selection_changed", track_editor, "update"); - - scale_dialog = memnew(ConfirmationDialog); - VBoxContainer *vbc = memnew(VBoxContainer); - scale_dialog->add_child(vbc); - - scale = memnew(SpinBox); - scale->set_min(-99999); - scale->set_max(99999); - scale->set_step(0.001); - vbc->add_margin_child(TTR("Scale Ratio:"), scale); - scale_dialog->connect("confirmed", this, "_scale"); - add_child(scale_dialog); - - call_select = memnew(SceneTreeDialog); - add_child(call_select); - call_select->set_title(TTR("Call Functions in Which Node?")); - - cleanup_dialog = memnew(ConfirmationDialog); - add_child(cleanup_dialog); - VBoxContainer *cleanup_vb = memnew(VBoxContainer); - cleanup_dialog->add_child(cleanup_vb); - - cleanup_keys = memnew(CheckButton); - cleanup_keys->set_text(TTR("Remove invalid keys")); - cleanup_keys->set_pressed(true); - cleanup_vb->add_child(cleanup_keys); - - cleanup_tracks = memnew(CheckButton); - cleanup_tracks->set_text(TTR("Remove unresolved and empty tracks")); - cleanup_tracks->set_pressed(true); - cleanup_vb->add_child(cleanup_tracks); - - cleanup_all = memnew(CheckButton); - cleanup_all->set_text(TTR("Clean-up all animations")); - cleanup_vb->add_child(cleanup_all); - - cleanup_dialog->set_title(TTR("Clean-Up Animation(s) (NO UNDO!)")); - cleanup_dialog->get_ok()->set_text(TTR("Clean-Up")); - - cleanup_dialog->connect("confirmed", this, "_menu_track", varray(TRACK_MENU_CLEAN_UP_CONFIRM)); - - track_editor->set_clip_contents(true); -} - -AnimationKeyEditor::~AnimationKeyEditor() { - - memdelete(key_edit); -} diff --git a/editor/animation_editor.h b/editor/animation_editor.h deleted file mode 100644 index 1e593f237c..0000000000 --- a/editor/animation_editor.h +++ /dev/null @@ -1,348 +0,0 @@ -/*************************************************************************/ -/* animation_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifndef ANIMATION_EDITOR_H -#define ANIMATION_EDITOR_H - -#include "scene/gui/control.h" -#include "scene/gui/file_dialog.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/scroll_bar.h" -#include "scene/gui/slider.h" -#include "scene/gui/spin_box.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/texture_rect.h" -#include "scene/gui/tool_button.h" - -#include "editor_data.h" -#include "property_editor.h" -#include "scene/animation/animation_cache.h" -#include "scene/resources/animation.h" -#include "scene_tree_editor.h" - -class AnimationKeyEdit; -class AnimationCurveEdit; - -class AnimationKeyEditor : public VBoxContainer { - - GDCLASS(AnimationKeyEditor, VBoxContainer); - - /* - enum { - - MENU_NEW_ANIMATION, - MENU_OPEN_ANIMATION, - MENU_EDIT_ANIMATION, - MENU_CLOSE_ANIMATION, - MENU_KEYING_ACTIVE, - MENU_SET_ROOT_NODE, - MENU_SYNC_TO_PLAYER, - MENU_ANIM_BASE=100, - }; - -*/ - - enum { - - ADD_TRACK_MENU_ADD_VALUE_TRACK, - ADD_TRACK_MENU_ADD_TRANSFORM_TRACK, - ADD_TRACK_MENU_ADD_CALL_TRACK, - TRACK_MENU_SCALE, - TRACK_MENU_SCALE_PIVOT, - TRACK_MENU_MOVE_UP, - TRACK_MENU_MOVE_DOWN, - TRACK_MENU_REMOVE, - TRACK_MENU_DUPLICATE, - TRACK_MENU_DUPLICATE_TRANSPOSE, - TRACK_MENU_SET_ALL_TRANS_LINEAR, - TRACK_MENU_SET_ALL_TRANS_CONSTANT, - TRACK_MENU_SET_ALL_TRANS_OUT, - TRACK_MENU_SET_ALL_TRANS_IN, - TRACK_MENU_SET_ALL_TRANS_INOUT, - TRACK_MENU_SET_ALL_TRANS_OUTIN, - TRACK_MENU_NEXT_STEP, - TRACK_MENU_PREV_STEP, - TRACK_MENU_OPTIMIZE, - TRACK_MENU_CLEAN_UP, - TRACK_MENU_CLEAN_UP_CONFIRM, - CURVE_SET_LINEAR, - CURVE_SET_IN, - CURVE_SET_OUT, - CURVE_SET_INOUT, - CURVE_SET_OUTIN, - CURVE_SET_CONSTANT - }; - - enum { - RIGHT_MENU_DUPLICATE, - RIGHT_MENU_DUPLICATE_TRANSPOSE, - RIGHT_MENU_REMOVE - }; - - struct MouseOver { - - enum Over { - OVER_NONE, - OVER_NAME, - OVER_KEY, - OVER_VALUE, - OVER_INTERP, - OVER_WRAP, - OVER_UP, - OVER_DOWN, - OVER_REMOVE, - OVER_ADD_KEY, - }; - - Over over; - int track; - int over_key; - - } mouse_over; - - struct SelectedKey { - - int track; - int key; - bool operator<(const SelectedKey &p_key) const { return track == p_key.track ? key < p_key.key : track < p_key.track; }; - }; - - struct KeyInfo { - - float pos; - }; - - Map<SelectedKey, KeyInfo> selection; - - struct ClickOver { - - enum Click { - - CLICK_NONE, - CLICK_RESIZE_NAMES, - CLICK_DRAG_TIMELINE, - CLICK_MOVE_KEYS, - CLICK_SELECT_KEYS - - }; - - SelectedKey selk; - bool shift; - Click click; - Point2 at; - Point2 to; - } click; - - float timeline_pos; - - float name_column_ratio; - - int track_name_editing; - int interp_editing; - int cont_editing; - int wrap_editing; - int selected_track; - int track_ofs[5]; - - int last_menu_track_opt; - LineEdit *track_name; - PopupMenu *track_menu; - PopupMenu *type_menu; - - Control *ec; - TextureRect *zoomicon; - HSlider *zoom; - //MenuButton *menu; - SpinBox *length; - Button *loop; - bool keying; - ToolButton *edit_button; - ToolButton *move_up_button; - ToolButton *move_down_button; - ToolButton *remove_button; - - ToolButton *curve_linear; - ToolButton *curve_in; - ToolButton *curve_out; - ToolButton *curve_inout; - ToolButton *curve_outin; - ToolButton *curve_constant; - - ConfirmationDialog *optimize_dialog; - SpinBox *optimize_linear_error; - SpinBox *optimize_angular_error; - SpinBox *optimize_max_angle; - - ConfirmationDialog *cleanup_dialog; - CheckButton *cleanup_keys; - CheckButton *cleanup_tracks; - CheckButton *cleanup_all; - - SpinBox *step; - - MenuButton *menu_add_track; - MenuButton *menu_track; - - HScrollBar *h_scroll; - VScrollBar *v_scroll; - - Control *track_editor; - Control *track_pos; - TabContainer *key_editor_tab; - - ConfirmationDialog *scale_dialog; - SpinBox *scale; - - PropertyEditor *key_editor; - - SceneTreeDialog *call_select; - - Ref<Animation> animation; - void _update_paths(); - - int last_idx; - - Node *root; - - UndoRedo *undo_redo; - EditorHistory *history; - ConfirmationDialog *insert_confirm; - - AnimationKeyEdit *key_edit; - AnimationCurveEdit *curve_edit; - - bool inserting; - - bool updating; - bool te_drawing; - - void _animation_len_changed(float p_len); - void _animation_loop_changed(); - void _step_changed(float p_len); - - struct InsertData { - - Animation::TrackType type; - NodePath path; - int track_idx; - Variant value; - String query; - bool advance; - }; /* insert_data;*/ - - bool insert_query; - List<InsertData> insert_data; - uint64_t insert_frame; - - int cvi_track; - float cvi_pos; - - int right_data_size_cache; - - EditorSelection *editor_selection; - - Label *select_anim_warning; - - float _get_zoom_scale() const; - - void _track_editor_draw(); - void _track_editor_gui_input(const Ref<InputEvent> &p_input); - void _track_position_draw(); - - void _track_name_changed(const String &p_name); - void _track_menu_selected(int p_idx); - void _confirm_insert_list(); - int _confirm_insert(InsertData p_id, int p_last_track = -1); - void _query_insert(const InsertData &p_id); - void _update_menu(); - bool insert_queue; - void _insert_delay(); - void _scale(); - - void _clear_selection(); - - //void _browse_path(); - - StringName alc; - - void _animation_changed(); - void _animation_optimize(); - void _cleanup_animation(Ref<Animation> p_animation); - - void _scroll_changed(double); - - void _menu_add_track(int p_type); - void _menu_track(int p_type); - - void _clear_selection_for_anim(const Ref<Animation> &p_anim); - void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos); - void _curve_transition_changed(float p_what); - - PropertyInfo _find_hint_for_track(int p_idx, NodePath &r_base_path); - - void _create_value_item(int p_type); - void _pane_drag(const Point2 &p_delta); - bool _edit_if_single_selection(); - - void _toggle_edit_curves(); - void _animation_len_update(); - - void _add_call_track(const NodePath &p_base); - - void _anim_duplicate_keys(bool transpose = false); - void _anim_delete_keys(); - - void _root_removed(); - -protected: - void _notification(int p_what); - static void _bind_methods(); - -public: - void set_animation(const Ref<Animation> &p_anim); - Ref<Animation> get_current_animation() const; - void set_root(Node *p_root); - Node *get_root() const; - void update_keying(); - bool has_keying() const; - - void cleanup(); - - void set_anim_pos(float p_pos); - void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false); - void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); - void insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform); - - void show_select_node_warning(bool p_show) { select_anim_warning->set_visible(p_show); } - AnimationKeyEditor(); - ~AnimationKeyEditor(); -}; - -#endif // ANIMATION_EDITOR_H diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp new file mode 100644 index 0000000000..02d667031a --- /dev/null +++ b/editor/animation_track_editor.cpp @@ -0,0 +1,5032 @@ +#include "animation_track_editor.h" +#include "animation_track_editor_plugins.h" +#include "editor/animation_bezier_editor.h" +#include "editor/plugins/animation_player_editor_plugin.h" +#include "editor_node.h" +#include "editor_scale.h" +#include "os/keyboard.h" +#include "scene/main/viewport.h" +#include "servers/audio/audio_stream.h" + +class AnimationTrackKeyEdit : public Object { + + GDCLASS(AnimationTrackKeyEdit, Object); + +public: + bool setting; + bool hidden; + + bool _hide_script_from_inspector() { + return true; + } + + static void _bind_methods() { + + ClassDB::bind_method("_update_obj", &AnimationTrackKeyEdit::_update_obj); + ClassDB::bind_method("_key_ofs_changed", &AnimationTrackKeyEdit::_key_ofs_changed); + ClassDB::bind_method("_hide_script_from_inspector", &AnimationTrackKeyEdit::_hide_script_from_inspector); + } + + //PopupDialog *ke_dialog; + + void _fix_node_path(Variant &value) { + + NodePath np = value; + + if (np == NodePath()) + return; + + Node *root = EditorNode::get_singleton()->get_tree()->get_root(); + + Node *np_node = root->get_node(np); + ERR_FAIL_COND(!np_node); + + Node *edited_node = root->get_node(base); + ERR_FAIL_COND(!edited_node); + + value = edited_node->get_path_to(np_node); + } + + void _update_obj(const Ref<Animation> &p_anim) { + if (setting) + return; + if (hidden) + return; + if (!(animation == p_anim)) + return; + notify_change(); + } + + void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to) { + if (hidden) + return; + if (!(animation == p_anim)) + return; + if (from != key_ofs) + return; + key_ofs = to; + if (setting) + return; + notify_change(); + } + + bool _set(const StringName &p_name, const Variant &p_value) { + + int key = animation->track_find_key(track, key_ofs, true); + ERR_FAIL_COND_V(key == -1, false); + + String name = p_name; + if (name == "time") { + + float new_time = p_value; + if (new_time == key_ofs) + return true; + + int existing = animation->track_find_key(track, new_time, true); + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Time"), UndoRedo::MERGE_ENDS); + + Variant val = animation->track_get_key_value(track, key); + float trans = animation->track_get_key_transition(track, key); + + undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, key); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, val, trans); + undo_redo->add_do_method(this, "_key_ofs_changed", animation, key_ofs, new_time); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, new_time); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_ofs, val, trans); + undo_redo->add_undo_method(this, "_key_ofs_changed", animation, new_time, key_ofs); + + if (existing != -1) { + Variant v = animation->track_get_key_value(track, existing); + float trans = animation->track_get_key_transition(track, existing); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, new_time, v, trans); + } + + undo_redo->commit_action(); + setting = false; + + return true; + } else if (name == "easing") { + + float val = p_value; + float prev_val = animation->track_get_key_transition(track, key); + setting = true; + undo_redo->create_action(TTR("Anim Change Transition"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + + switch (animation->track_get_type(track)) { + + case Animation::TYPE_TRANSFORM: { + + Dictionary d_old = animation->track_get_key_value(track, key); + Dictionary d_new = d_old; + d_new[p_name] = p_value; + setting = true; + undo_redo->create_action(TTR("Anim Change Transform")); + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + + } break; + case Animation::TYPE_VALUE: { + + if (name == "value") { + + Variant value = p_value; + + if (value.get_type() == Variant::NODE_PATH) { + + _fix_node_path(value); + } + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + Variant prev = animation->track_get_key_value(track, key); + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + + } break; + case Animation::TYPE_METHOD: { + + Dictionary d_old = animation->track_get_key_value(track, key); + Dictionary d_new = d_old; + + bool change_notify_deserved = false; + bool mergeable = false; + + if (name == "name") { + + d_new["method"] = p_value; + } + + if (name == "arg_count") { + + Vector<Variant> args = d_old["args"]; + args.resize(p_value); + d_new["args"] = args; + change_notify_deserved = true; + } + + if (name.begins_with("args/")) { + + Vector<Variant> args = d_old["args"]; + int idx = name.get_slice("/", 1).to_int(); + ERR_FAIL_INDEX_V(idx, args.size(), false); + + String what = name.get_slice("/", 2); + if (what == "type") { + Variant::Type t = Variant::Type(int(p_value)); + + if (t != args[idx].get_type()) { + Variant::CallError err; + if (Variant::can_convert(args[idx].get_type(), t)) { + Variant old = args[idx]; + Variant *ptrs[1] = { &old }; + args[idx] = Variant::construct(t, (const Variant **)ptrs, 1, err); + } else { + + args[idx] = Variant::construct(t, NULL, 0, err); + } + change_notify_deserved = true; + d_new["args"] = args; + } + } + if (what == "value") { + + Variant value = p_value; + if (value.get_type() == Variant::NODE_PATH) { + + _fix_node_path(value); + } + + args[idx] = value; + d_new["args"] = args; + mergeable = true; + } + } + + if (mergeable) + undo_redo->create_action(TTR("Anim Change Call"), UndoRedo::MERGE_ENDS); + else + undo_redo->create_action(TTR("Anim Change Call")); + + Variant prev = animation->track_get_key_value(track, key); + setting = true; + undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, d_new); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, d_old); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + if (change_notify_deserved) + notify_change(); + return true; + } break; + case Animation::TYPE_BEZIER: { + + if (name == "value") { + + Variant value = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + float prev = animation->bezier_track_get_key_value(track, key); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_value", track, key, value); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_value", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + if (name == "in_handle") { + + Variant value = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + Vector2 prev = animation->bezier_track_get_key_in_handle(track, key); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_in_handle", track, key, value); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_in_handle", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + if (name == "out_handle") { + + Variant value = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + Vector2 prev = animation->bezier_track_get_key_out_handle(track, key); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_out_handle", track, key, value); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_out_handle", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + + } break; + case Animation::TYPE_AUDIO: { + + if (name == "stream") { + + Ref<AudioStream> stream = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + RES prev = animation->audio_track_get_key_stream(track, key); + undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_stream", track, key, stream); + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_stream", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + if (name == "start_offset") { + + float value = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + float prev = animation->audio_track_get_key_start_offset(track, key); + undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, value); + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_start_offset", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + if (name == "end_offset") { + + float value = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + float prev = animation->audio_track_get_key_end_offset(track, key); + undo_redo->add_do_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, value); + undo_redo->add_undo_method(animation.ptr(), "audio_track_set_key_end_offset", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + + } break; + case Animation::TYPE_ANIMATION: { + + if (name == "animation") { + + StringName name = p_value; + + setting = true; + undo_redo->create_action(TTR("Anim Change Keyframe Value"), UndoRedo::MERGE_ENDS); + StringName prev = animation->animation_track_get_key_animation(track, key); + undo_redo->add_do_method(animation.ptr(), "animation_track_set_key_animation", track, key, name); + undo_redo->add_undo_method(animation.ptr(), "animation_track_set_key_animation", track, key, prev); + undo_redo->add_do_method(this, "_update_obj", animation); + undo_redo->add_undo_method(this, "_update_obj", animation); + undo_redo->commit_action(); + setting = false; + return true; + } + + } break; + } + + return false; + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + + int key = animation->track_find_key(track, key_ofs, true); + ERR_FAIL_COND_V(key == -1, false); + + String name = p_name; + if (name == "time") { + r_ret = key_ofs; + return true; + } else if (name == "easing") { + r_ret = animation->track_get_key_transition(track, key); + return true; + } + + switch (animation->track_get_type(track)) { + + case Animation::TYPE_TRANSFORM: { + + Dictionary d = animation->track_get_key_value(track, key); + ERR_FAIL_COND_V(!d.has(name), false); + r_ret = d[p_name]; + return true; + + } break; + case Animation::TYPE_VALUE: { + + if (name == "value") { + r_ret = animation->track_get_key_value(track, key); + return true; + } + + } break; + case Animation::TYPE_METHOD: { + + Dictionary d = animation->track_get_key_value(track, key); + + if (name == "name") { + + ERR_FAIL_COND_V(!d.has("method"), false); + r_ret = d["method"]; + return true; + } + + ERR_FAIL_COND_V(!d.has("args"), false); + + Vector<Variant> args = d["args"]; + + if (name == "arg_count") { + + r_ret = args.size(); + return true; + } + + if (name.begins_with("args/")) { + + int idx = name.get_slice("/", 1).to_int(); + ERR_FAIL_INDEX_V(idx, args.size(), false); + + String what = name.get_slice("/", 2); + if (what == "type") { + r_ret = args[idx].get_type(); + return true; + } + if (what == "value") { + r_ret = args[idx]; + return true; + } + } + + } break; + case Animation::TYPE_BEZIER: { + + if (name == "value") { + r_ret = animation->bezier_track_get_key_value(track, key); + return true; + } + if (name == "in_handle") { + r_ret = animation->bezier_track_get_key_in_handle(track, key); + return true; + } + if (name == "out_handle") { + r_ret = animation->bezier_track_get_key_out_handle(track, key); + return true; + } + + } break; + case Animation::TYPE_AUDIO: { + + if (name == "stream") { + r_ret = animation->audio_track_get_key_stream(track, key); + return true; + } + if (name == "start_offset") { + r_ret = animation->audio_track_get_key_start_offset(track, key); + return true; + } + if (name == "end_offset") { + r_ret = animation->audio_track_get_key_end_offset(track, key); + return true; + } + + } break; + case Animation::TYPE_ANIMATION: { + + if (name == "animation") { + r_ret = animation->animation_track_get_key_animation(track, key); + return true; + } + + } break; + } + + return false; + } + void _get_property_list(List<PropertyInfo> *p_list) const { + + if (animation.is_null()) + return; + + ERR_FAIL_INDEX(track, animation->get_track_count()); + int key = animation->track_find_key(track, key_ofs, true); + ERR_FAIL_COND(key == -1); + + p_list->push_back(PropertyInfo(Variant::REAL, "time", PROPERTY_HINT_RANGE, "0," + rtos(animation->get_length()) + ",0.01")); + + switch (animation->track_get_type(track)) { + + case Animation::TYPE_TRANSFORM: { + + p_list->push_back(PropertyInfo(Variant::VECTOR3, "location")); + p_list->push_back(PropertyInfo(Variant::QUAT, "rotation")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); + + } break; + case Animation::TYPE_VALUE: { + + Variant v = animation->track_get_key_value(track, key); + + if (hint.type != Variant::NIL) { + + PropertyInfo pi = hint; + pi.name = "value"; + p_list->push_back(pi); + } else { + + PropertyHint hint = PROPERTY_HINT_NONE; + String hint_string; + + if (v.get_type() == Variant::OBJECT) { + //could actually check the object property if exists..? yes i will! + Ref<Resource> res = v; + if (res.is_valid()) { + + hint = PROPERTY_HINT_RESOURCE_TYPE; + hint_string = res->get_class(); + } + } + + if (v.get_type() != Variant::NIL) + p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string)); + } + + } break; + case Animation::TYPE_METHOD: { + + p_list->push_back(PropertyInfo(Variant::STRING, "name")); + p_list->push_back(PropertyInfo(Variant::INT, "arg_count", PROPERTY_HINT_RANGE, "0,5,1")); + + Dictionary d = animation->track_get_key_value(track, key); + ERR_FAIL_COND(!d.has("args")); + Vector<Variant> args = d["args"]; + String vtypes; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + + if (i > 0) + vtypes += ","; + vtypes += Variant::get_type_name(Variant::Type(i)); + } + + for (int i = 0; i < args.size(); i++) { + + p_list->push_back(PropertyInfo(Variant::INT, "args/" + itos(i) + "/type", PROPERTY_HINT_ENUM, vtypes)); + if (args[i].get_type() != Variant::NIL) + p_list->push_back(PropertyInfo(args[i].get_type(), "args/" + itos(i) + "/value")); + } + + } break; + case Animation::TYPE_BEZIER: { + + p_list->push_back(PropertyInfo(Variant::REAL, "value")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "in_handle")); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "out_handle")); + + } break; + case Animation::TYPE_AUDIO: { + + p_list->push_back(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); + p_list->push_back(PropertyInfo(Variant::REAL, "start_offset", PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::REAL, "end_offset", PROPERTY_HINT_RANGE, "0,3600,0.01,or_greater")); + + } break; + case Animation::TYPE_ANIMATION: { + + String animations; + + if (root_path && root_path->has_node(animation->track_get_path(track))) { + + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(root_path->get_node(animation->track_get_path(track))); + if (ap) { + List<StringName> anims; + ap->get_animation_list(&anims); + for (List<StringName>::Element *E = anims.front(); E; E = E->next()) { + if (animations != String()) { + animations += ","; + } + + animations += String(E->get()); + } + } + } + + if (animations != String()) { + animations += ","; + } + animations += "[stop]"; + + p_list->push_back(PropertyInfo(Variant::STRING, "animation", PROPERTY_HINT_ENUM, animations)); + + } break; + } + + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + p_list->push_back(PropertyInfo(Variant::REAL, "easing", PROPERTY_HINT_EXP_EASING)); + } + } + + UndoRedo *undo_redo; + Ref<Animation> animation; + int track; + float key_ofs; + Node *root_path; + + PropertyInfo hint; + NodePath base; + + void notify_change() { + + _change_notify(); + } + + AnimationTrackKeyEdit() { + hidden = true; + key_ofs = 0; + track = -1; + setting = false; + root_path = NULL; + } +}; + +void AnimationTimelineEdit::_zoom_changed(double) { + + update(); + play_position->update(); + emit_signal("zoom_changed"); +} + +float AnimationTimelineEdit::get_zoom_scale() const { + + float zv = zoom->get_value(); + if (zv < 1) { + zv = 1.0 - zv; + return Math::pow(1.0f + zv, 8.0f) * 100; + } else { + return 1.0 / Math::pow(zv, 8.0f) * 100; + } +} + +void AnimationTimelineEdit::_anim_length_changed(double p_new_len) { + + if (editing) + return; + + p_new_len = MAX(0.001, p_new_len); + + editing = true; + *block_animation_update_ptr = true; + undo_redo->create_action("Change animation length"); + undo_redo->add_do_method(animation.ptr(), "set_length", p_new_len); + undo_redo->add_undo_method(animation.ptr(), "set_length", animation->get_length()); + undo_redo->commit_action(); + *block_animation_update_ptr = false; + editing = false; + update(); + + emit_signal("length_changed", p_new_len); +} + +void AnimationTimelineEdit::_anim_loop_pressed() { + + *block_animation_update_ptr = true; + undo_redo->create_action("Change animation loop"); + undo_redo->add_do_method(animation.ptr(), "set_loop", loop->is_pressed()); + undo_redo->add_undo_method(animation.ptr(), "set_loop", animation->has_loop()); + undo_redo->commit_action(); + *block_animation_update_ptr = false; +} + +int AnimationTimelineEdit::get_buttons_width() const { + + Ref<Texture> interp_mode = get_icon("TrackContinuous", "EditorIcons"); + Ref<Texture> interp_type = get_icon("InterpRaw", "EditorIcons"); + Ref<Texture> loop_type = get_icon("InterpWrapClamp", "EditorIcons"); + Ref<Texture> remove_icon = get_icon("Remove", "EditorIcons"); + Ref<Texture> down_icon = get_icon("select_arrow", "Tree"); + + int total_w = interp_mode->get_width() + interp_type->get_width() + loop_type->get_width() + remove_icon->get_width(); + total_w += (down_icon->get_width() + 4 * EDSCALE) * 4; + + return total_w; +} + +int AnimationTimelineEdit::get_name_limit() const { + + Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons"); + + int limit = MAX(name_limit, add_track->get_minimum_size().width + hsize_icon->get_width()); + + limit = MIN(limit, get_size().width - get_buttons_width() - 1); + + return limit; +} + +void AnimationTimelineEdit::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + add_track->set_icon(get_icon("Add", "EditorIcons")); + loop->set_icon(get_icon("Loop", "EditorIcons")); + time_icon->set_texture(get_icon("Time", "EditorIcons")); + + add_track->get_popup()->clear(); + add_track->get_popup()->add_icon_item(get_icon("KeyValue", "EditorIcons"), TTR("Property Track")); + add_track->get_popup()->add_icon_item(get_icon("KeyXform", "EditorIcons"), TTR("3D Transform Track")); + add_track->get_popup()->add_icon_item(get_icon("KeyCall", "EditorIcons"), TTR("Call Method Track")); + add_track->get_popup()->add_icon_item(get_icon("KeyBezier", "EditorIcons"), TTR("Bezier Curve Track")); + add_track->get_popup()->add_icon_item(get_icon("KeyAudio", "EditorIcons"), TTR("Audio Playback Track")); + add_track->get_popup()->add_icon_item(get_icon("KeyAnimation", "EditorIcons"), TTR("Animation Playback Track")); + } + + if (p_what == NOTIFICATION_RESIZED) { + len_hb->set_position(Vector2(get_size().width - get_buttons_width(), 0)); + len_hb->set_size(Size2(get_buttons_width(), get_size().height)); + } + if (p_what == NOTIFICATION_DRAW) { + + int key_range = get_size().width - get_buttons_width() - get_name_limit(); + + if (!animation.is_valid()) + return; + + Ref<Font> font = get_font("font", "Label"); + Color color = get_color("font_color", "Label"); + + int zoomw = key_range; + float scale = get_zoom_scale(); + int h = get_size().height; + + float l = animation->get_length(); + if (l <= 0) + l = 0.001; //avoid crashor + + Ref<Texture> hsize_icon = get_icon("Hsize", "EditorIcons"); + hsize_rect = Rect2(get_name_limit() - hsize_icon->get_width() - 2 * EDSCALE, (get_size().height - hsize_icon->get_height()) / 2, hsize_icon->get_width(), hsize_icon->get_height()); + draw_texture(hsize_icon, hsize_rect.position); + + float keys_from = get_value(); + float keys_to = keys_from + zoomw / scale; + + { + float time_min = 0; + float time_max = animation->get_length(); + for (int i = 0; i < animation->get_track_count(); i++) { + + if (animation->track_get_key_count(i) > 0) { + + float beg = animation->track_get_key_time(i, 0); + /*if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { + beg += animation->bezier_track_get_key_in_handle(i, 0).x; + }* not worth it since they have no use */ + + if (beg < time_min) + time_min = beg; + + float end = animation->track_get_key_time(i, animation->track_get_key_count(i) - 1); + /*if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { + end += animation->bezier_track_get_key_out_handle(i, animation->track_get_key_count(i) - 1).x; + } not worth it since they have no use */ + + if (end > time_max) + time_max = end; + } + } + + float extra = (zoomw / scale) * 0.5; + + //if (time_min < -0.001) + // time_min -= extra; + time_max += extra; + set_min(time_min); + set_max(time_max); + + if (zoomw / scale < (time_max - time_min)) { + hscroll->show(); + + } else { + + hscroll->hide(); + } + } + + set_page(zoomw / scale); + + int end_px = (l - get_value()) * scale; + int begin_px = -get_value() * scale; + Color notimecol = get_color("dark_color_2", "Editor"); + Color timecolor = color; + timecolor.a = 0.2; + Color linecolor = color; + linecolor.a = 0.2; + + { + + draw_rect(Rect2(Point2(get_name_limit(), 0), Point2(zoomw - 1, h)), notimecol); + + if (begin_px < zoomw && end_px > 0) { + + if (begin_px < 0) + begin_px = 0; + if (end_px > zoomw) + end_px = zoomw; + + draw_rect(Rect2(Point2(get_name_limit() + begin_px, 0), Point2(end_px - begin_px - 1, h)), timecolor); + } + } + + Color color_time_sec = color; + Color color_time_dec = color; + color_time_dec.a *= 0.5; +#define SC_ADJ 100 + int min = 30; + int dec = 1; + int step = 1; + int decimals = 2; + bool step_found = false; + + const int period_width = font->get_char_size('.').width; + int max_digit_width = font->get_char_size('0').width; + for (int i = 1; i <= 9; i++) { + const int digit_width = font->get_char_size('0' + i).width; + max_digit_width = MAX(digit_width, max_digit_width); + } + const int max_sc = int(Math::ceil(zoomw / scale)); + const int max_sc_width = String::num(max_sc).length() * max_digit_width; + + while (!step_found) { + + min = max_sc_width; + if (decimals > 0) + min += period_width + max_digit_width * decimals; + + static const int _multp[3] = { 1, 2, 5 }; + for (int i = 0; i < 3; i++) { + + step = (_multp[i] * dec); + if (step * scale / SC_ADJ > min) { + step_found = true; + break; + } + } + if (step_found) + break; + dec *= 10; + decimals--; + if (decimals < 0) + decimals = 0; + } + + for (int i = 0; i < zoomw; i++) { + + float pos = get_value() + double(i) / scale; + float prev = get_value() + (double(i) - 1.0) / scale; + + int sc = int(Math::floor(pos * SC_ADJ)); + int prev_sc = int(Math::floor(prev * SC_ADJ)); + bool sub = (sc % SC_ADJ); + + if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { + + int scd = sc < 0 ? prev_sc : sc; + draw_line(Point2(get_name_limit() + i, 0), Point2(get_name_limit() + i, h), linecolor); + draw_string(font, Point2(get_name_limit() + i + 3, (h - font->get_height()) / 2 + font->get_ascent()).floor(), String::num((scd - (scd % step)) / double(SC_ADJ), decimals), sub ? color_time_dec : color_time_sec, zoomw - i); + } + } + + draw_line(Vector2(0, get_size().height), get_size(), linecolor); + } +} + +void AnimationTimelineEdit::set_animation(const Ref<Animation> &p_animation) { + animation = p_animation; + if (animation.is_valid()) { + len_hb->show(); + add_track->show(); + } else { + len_hb->hide(); + add_track->hide(); + } + update(); + update_values(); +} + +Size2 AnimationTimelineEdit::get_minimum_size() const { + + Size2 ms = add_track->get_minimum_size(); + Ref<Font> font = get_font("font", "Label"); + ms.height = MAX(ms.height, font->get_height()); + ms.width = get_buttons_width() + add_track->get_minimum_size().width + get_icon("Hsize", "EditorIcons")->get_width() + 2; + return ms; +} + +void AnimationTimelineEdit::set_block_animation_update_ptr(bool *p_block_ptr) { + block_animation_update_ptr = p_block_ptr; +} + +void AnimationTimelineEdit::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +void AnimationTimelineEdit::set_zoom(Range *p_zoom) { + zoom = p_zoom; + zoom->connect("value_changed", this, "_zoom_changed"); +} + +void AnimationTimelineEdit::set_play_position(float p_pos) { + + play_position_pos = p_pos; + play_position->update(); +} + +float AnimationTimelineEdit::get_play_position() const { + return play_position_pos; +} + +void AnimationTimelineEdit::update_play_position() { + play_position->update(); +} + +void AnimationTimelineEdit::update_values() { + + if (!animation.is_valid() || editing) + return; + + editing = true; + length->set_value(animation->get_length()); + loop->set_pressed(animation->has_loop()); + editing = false; +} + +void AnimationTimelineEdit::_play_position_draw() { + + if (!animation.is_valid() || play_position_pos < 0) + return; + + float scale = get_zoom_scale(); + int h = play_position->get_size().height; + + int px = (-get_value() + play_position_pos) * scale + get_name_limit(); + + if (px >= get_name_limit() && px < (play_position->get_size().width - get_buttons_width())) { + Color color = get_color("accent_color", "Editor"); + play_position->draw_line(Point2(px, 0), Point2(px, h), color); + } +} + +void AnimationTimelineEdit::_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && hsize_rect.has_point(mb->get_position())) { + + dragging_hsize = true; + dragging_hsize_from = mb->get_position().x; + dragging_hsize_at = name_limit; + } + + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && dragging_hsize) { + dragging_hsize = false; + } + if (mb.is_valid() && mb->get_position().x > get_name_limit() && mb->get_position().x < (get_size().width - get_buttons_width())) { + + if (!panning_timeline && mb->get_button_index() == BUTTON_LEFT) { + int x = mb->get_position().x - get_name_limit(); + + float ofs = x / get_zoom_scale() + get_value(); + emit_signal("timeline_changed", ofs, false); + dragging_timeline = true; + } + if (!dragging_timeline && mb->get_button_index() == BUTTON_MIDDLE) { + int x = mb->get_position().x - get_name_limit(); + panning_timeline_from = x / get_zoom_scale(); + panning_timeline = true; + panning_timeline_at = get_value(); + } + } + + if (dragging_timeline && mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + dragging_timeline = false; + } + + if (panning_timeline && mb.is_valid() && mb->get_button_index() == BUTTON_MIDDLE && !mb->is_pressed()) { + panning_timeline = false; + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid()) { + + if (dragging_hsize) { + int ofs = mm->get_position().x - dragging_hsize_from; + name_limit = dragging_hsize_at + ofs; + update(); + emit_signal("name_limit_changed"); + play_position->update(); + } + if (dragging_timeline) { + int x = mm->get_position().x - get_name_limit(); + float ofs = x / get_zoom_scale() + get_value(); + emit_signal("timeline_changed", ofs, false); + } + if (panning_timeline) { + int x = mm->get_position().x - get_name_limit(); + float ofs = x / get_zoom_scale(); + float diff = ofs - panning_timeline_from; + set_value(panning_timeline_at - diff); + } + } +} + +void AnimationTimelineEdit::set_hscroll(HScrollBar *p_hscroll) { + + hscroll = p_hscroll; +} + +void AnimationTimelineEdit::_track_added(int p_track) { + emit_signal("track_added", p_track); +} + +void AnimationTimelineEdit::_bind_methods() { + ClassDB::bind_method("_zoom_changed", &AnimationTimelineEdit::_zoom_changed); + ClassDB::bind_method("_anim_length_changed", &AnimationTimelineEdit::_anim_length_changed); + ClassDB::bind_method("_anim_loop_pressed", &AnimationTimelineEdit::_anim_loop_pressed); + ClassDB::bind_method("_play_position_draw", &AnimationTimelineEdit::_play_position_draw); + ClassDB::bind_method("_gui_input", &AnimationTimelineEdit::_gui_input); + ClassDB::bind_method("_track_added", &AnimationTimelineEdit::_track_added); + + ADD_SIGNAL(MethodInfo("zoom_changed")); + ADD_SIGNAL(MethodInfo("name_limit_changed")); + ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag"))); + ADD_SIGNAL(MethodInfo("track_added", PropertyInfo(Variant::INT, "track"))); + ADD_SIGNAL(MethodInfo("length_changed", PropertyInfo(Variant::REAL, "size"))); +} + +AnimationTimelineEdit::AnimationTimelineEdit() { + + block_animation_update_ptr = NULL; + editing = false; + name_limit = 150; + zoom = NULL; + + play_position_pos = 0; + play_position = memnew(Control); + play_position->set_mouse_filter(MOUSE_FILTER_PASS); + add_child(play_position); + play_position->set_anchors_and_margins_preset(PRESET_WIDE); + play_position->connect("draw", this, "_play_position_draw"); + + add_track = memnew(MenuButton); + add_track->set_position(Vector2(0, 0)); + add_child(add_track); + add_track->set_text(TTR("Add Track")); + + len_hb = memnew(HBoxContainer); + + Control *expander = memnew(Control); + expander->set_h_size_flags(SIZE_EXPAND_FILL); + len_hb->add_child(expander); + time_icon = memnew(TextureRect); + time_icon->set_v_size_flags(SIZE_SHRINK_CENTER); + time_icon->set_tooltip(TTR("Animation Length Time (seconds)")); + len_hb->add_child(time_icon); + length = memnew(EditorSpinSlider); + length->set_min(0.001); + length->set_max(3600); + length->set_step(0.01); + length->set_allow_greater(true); + length->set_custom_minimum_size(Vector2(70 * EDSCALE, 0)); + length->set_hide_slider(true); + length->set_tooltip(TTR("Animation Length Time (seconds)")); + length->connect("value_changed", this, "_anim_length_changed"); + len_hb->add_child(length); + loop = memnew(ToolButton); + loop->set_tooltip(TTR("Animation Looping")); + loop->connect("pressed", this, "_anim_loop_pressed"); + loop->set_toggle_mode(true); + len_hb->add_child(loop); + add_child(len_hb); + + add_track->hide(); + add_track->get_popup()->connect("index_pressed", this, "_track_added"); + len_hb->hide(); + + panning_timeline = false; + dragging_timeline = false; + dragging_hsize = false; +} + +//////////////////////////////////// + +void AnimationTrackEdit::_notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + if (animation.is_null()) + return; + ERR_FAIL_INDEX(track, animation->get_track_count()); + + int limit = timeline->get_name_limit(); + + if (has_focus()) { + Color accent = get_color("accent_color", "Editor"); + accent.a *= 0.7; + draw_rect(Rect2(Point2(), get_size()), accent, false); + } + + Ref<Font> font = get_font("font", "Label"); + Color color = get_color("font_color", "Label"); + Ref<Texture> type_icons[6] = { + get_icon("KeyValue", "EditorIcons"), + get_icon("KeyXform", "EditorIcons"), + get_icon("KeyCall", "EditorIcons"), + get_icon("KeyBezier", "EditorIcons"), + get_icon("KeyAudio", "EditorIcons"), + get_icon("KeyAnimation", "EditorIcons") + }; + int hsep = get_constant("hseparation", "ItemList"); + Color linecolor = color; + linecolor.a = 0.2; + + // NAMES AND ICONS // + + { + + Ref<Texture> check = animation->track_is_enabled(track) ? get_icon("checked", "CheckBox") : get_icon("unchecked", "CheckBox"); + + int ofs = in_group ? check->get_width() : 0; //not the best reference for margin but.. + + check_rect = Rect2(Point2(ofs, int(get_size().height - check->get_height()) / 2), check->get_size()); + + draw_texture(check, check_rect.position); + + ofs += check->get_width() + hsep; + + Ref<Texture> type_icon = type_icons[animation->track_get_type(track)]; + + draw_texture(type_icon, Point2(ofs, int(get_size().height - type_icon->get_height()) / 2)); + ofs += type_icon->get_width() + hsep; + + NodePath path = animation->track_get_path(track); + + Node *node = NULL; + + if (root && root->has_node(path)) { + node = root->get_node(path); + } + + String text; + Color text_color = color; + if (node && EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + text_color = get_color("accent_color", "Editor"); + } + + if (in_group) { + + if (animation->track_get_type(track) == Animation::TYPE_METHOD) { + text = TTR("Functions:"); + } else if (animation->track_get_type(track) == Animation::TYPE_AUDIO) { + text = TTR("Audio Clips:"); + } else if (animation->track_get_type(track) == Animation::TYPE_ANIMATION) { + text = TTR("Anim Clips:"); + } else { + Vector<StringName> sn = path.get_subnames(); + for (int i = 0; i < sn.size(); i++) { + if (i > 0) { + text += "."; + } + text += sn[i]; + } + } + text_color.a *= 0.7; + } else if (node) { + Ref<Texture> icon; + if (has_icon(node->get_class(), "EditorIcons")) { + icon = get_icon(node->get_class(), "EditorIcons"); + } else { + icon = get_icon("Node", "EditorIcons"); + } + + draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2)); + icon_cache = icon; + + text = node->get_name(); + ofs += hsep; + ofs += icon->get_width(); + Vector<StringName> sn = path.get_subnames(); + for (int i = 0; i < sn.size(); i++) { + text += "."; + text += sn[i]; + } + } else { + icon_cache = type_icon; + + text = path; + } + + path_cache = text; + + path_rect = Rect2(ofs, 0, limit - ofs - hsep, get_size().height); + + Vector2 string_pos = Point2(ofs, (get_size().height - font->get_height()) / 2 + font->get_ascent()); + string_pos = string_pos.floor(); + draw_string(font, string_pos, text, text_color, limit - ofs - hsep); + + draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor); + } + + // KEYFAMES // + + draw_bg(limit, get_size().width - timeline->get_buttons_width()); + + { + + float scale = timeline->get_zoom_scale(); + int limit_end = get_size().width - timeline->get_buttons_width(); + + for (int i = 0; i < animation->track_get_key_count(track); i++) { + + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + if (editor->is_key_selected(track, i) && editor->is_moving_selection()) { + offset += editor->get_moving_selection_offset(); + } + offset = offset * scale + limit; + if (i < animation->track_get_key_count(track) - 1) { + + float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value(); + if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) { + offset_n += editor->get_moving_selection_offset(); + } + offset_n = offset_n * scale + limit; + + draw_key_link(i, scale, int(offset), int(offset_n), limit, limit_end); + } + + draw_key(i, scale, int(offset), editor->is_key_selected(track, i), limit, limit_end); + } + } + + draw_fg(limit, get_size().width - timeline->get_buttons_width()); + + // BUTTONS // + { + + Ref<Texture> wrap_icon[2] = { + get_icon("InterpWrapClamp", "EditorIcons"), + get_icon("InterpWrapLoop", "EditorIcons"), + }; + + Ref<Texture> interp_icon[3] = { + get_icon("InterpRaw", "EditorIcons"), + get_icon("InterpLinear", "EditorIcons"), + get_icon("InterpCubic", "EditorIcons") + }; + Ref<Texture> cont_icon[4] = { + get_icon("TrackContinuous", "EditorIcons"), + get_icon("TrackDiscrete", "EditorIcons"), + get_icon("TrackTrigger", "EditorIcons"), + get_icon("TrackCapture", "EditorIcons") + }; + + int ofs = get_size().width - timeline->get_buttons_width(); + + Ref<Texture> down_icon = get_icon("select_arrow", "Tree"); + + draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor); + + ofs += hsep; + { + //callmode + + Animation::UpdateMode update_mode; + + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + update_mode = animation->value_track_get_update_mode(track); + } else { + update_mode = Animation::UPDATE_CONTINUOUS; + } + + Ref<Texture> update_icon = cont_icon[update_mode]; + + update_mode_rect.position.x = ofs; + update_mode_rect.position.y = int(get_size().height - update_icon->get_height()) / 2; + update_mode_rect.size = update_icon->get_size(); + + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + draw_texture(update_icon, update_mode_rect.position); + } + //make it easier to click + update_mode_rect.position.y = 0; + update_mode_rect.size.y = get_size().height; + + ofs += update_icon->get_width() + hsep; + update_mode_rect.size.x += hsep; + + if (animation->track_get_type(track) == Animation::TYPE_VALUE) { + draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); + update_mode_rect.size.x += down_icon->get_width(); + bezier_edit_rect = Rect2(); + } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) { + Ref<Texture> bezier_icon = get_icon("EditBezier", "EditorIcons"); + update_mode_rect.size.x += down_icon->get_width(); + bezier_edit_rect.position = update_mode_rect.position + (update_mode_rect.size - bezier_icon->get_size()) / 2; + bezier_edit_rect.size = bezier_icon->get_size(); + draw_texture(bezier_icon, bezier_edit_rect.position); + update_mode_rect = Rect2(); + } else { + update_mode_rect = Rect2(); + bezier_edit_rect = Rect2(); + } + + ofs += down_icon->get_width(); + draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor); + ofs += hsep; + } + + { + //interp + + Animation::InterpolationType interp_mode = animation->track_get_interpolation_type(track); + + Ref<Texture> icon = interp_icon[interp_mode]; + + interp_mode_rect.position.x = ofs; + interp_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; + interp_mode_rect.size = icon->get_size(); + + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) { + draw_texture(icon, interp_mode_rect.position); + } + //make it easier to click + interp_mode_rect.position.y = 0; + interp_mode_rect.size.y = get_size().height; + + ofs += icon->get_width() + hsep; + interp_mode_rect.size.x += hsep; + + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) { + draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); + interp_mode_rect.size.x += down_icon->get_width(); + } else { + interp_mode_rect = Rect2(); + } + + ofs += down_icon->get_width(); + draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor); + ofs += hsep; + } + + { + //loop + + bool loop_wrap = animation->track_get_interpolation_loop_wrap(track); + + Ref<Texture> icon = wrap_icon[loop_wrap ? 1 : 0]; + + loop_mode_rect.position.x = ofs; + loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2; + loop_mode_rect.size = icon->get_size(); + + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) { + draw_texture(icon, loop_mode_rect.position); + } + + loop_mode_rect.position.y = 0; + loop_mode_rect.size.y = get_size().height; + + ofs += icon->get_width() + hsep; + loop_mode_rect.size.x += hsep; + + if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM) { + draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); + loop_mode_rect.size.x += down_icon->get_width(); + } else { + loop_mode_rect = Rect2(); + } + + ofs += down_icon->get_width(); + draw_line(Point2(ofs + hsep * 0.5, 0), Point2(ofs + hsep * 0.5, get_size().height), linecolor); + ofs += hsep; + } + + { + //erase + + Ref<Texture> icon = get_icon("Remove", "EditorIcons"); + + remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2; + remove_rect.position.y = int(get_size().height - icon->get_height()) / 2; + remove_rect.size = icon->get_size(); + + draw_texture(icon, remove_rect.position); + } + } + + if (in_group) { + draw_line(Vector2(timeline->get_name_limit(), get_size().height), get_size(), linecolor); + } else { + draw_line(Vector2(0, get_size().height), get_size(), linecolor); + } + + if (dropping_at != 0) { + Color drop_color = get_color("accent_color", "Editor"); + if (dropping_at < 0) { + draw_line(Vector2(0, 0), Vector2(get_size().width, 0), drop_color); + } else { + draw_line(Vector2(0, get_size().height), get_size(), drop_color); + } + } + } + + if (p_what == NOTIFICATION_MOUSE_EXIT || p_what == NOTIFICATION_DRAG_END) { + cancel_drop(); + } +} + +int AnimationTrackEdit::get_key_height() const { + if (!animation.is_valid()) + return 0; + + return type_icon->get_height(); +} +Rect2 AnimationTrackEdit::get_key_rect(int p_index, float p_pixels_sec) { + + if (!animation.is_valid()) + return Rect2(); + Rect2 rect = Rect2(-type_icon->get_width() / 2, 0, type_icon->get_width(), get_size().height); + + //make it a big easier to click + rect.position.x -= rect.size.x * 0.5; + rect.size.x *= 2; + return rect; +} + +bool AnimationTrackEdit::is_key_selectable_by_distance() const { + return true; +} + +void AnimationTrackEdit::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) { + if (p_next_x < p_clip_left) + return; + if (p_x > p_clip_right) + return; + + Variant current = animation->track_get_key_value(get_track(), p_index); + Variant next = animation->track_get_key_value(get_track(), p_index + 1); + if (current != next) + return; + + Color color = get_color("font_color", "Label"); + color.a = 0.5; + + int from_x = MAX(p_x, p_clip_left); + int to_x = MIN(p_next_x, p_clip_right); + + draw_line(Point2(from_x + 1, get_size().height / 2), Point2(to_x, get_size().height / 2), color, 2); +} + +void AnimationTrackEdit::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + if (!animation.is_valid()) + return; + + if (p_x < p_clip_left || p_x > p_clip_right) + return; + + Vector2 ofs(p_x - type_icon->get_width() / 2, int(get_size().height - type_icon->get_height()) / 2); + + if (animation->track_get_type(track) == Animation::TYPE_METHOD) { + Ref<Font> font = get_font("font", "Label"); + Color color = get_color("font_color", "Label"); + color.a = 0.5; + + Dictionary d = animation->track_get_key_value(track, p_index); + String text; + + if (d.has("method")) + text += String(d["method"]); + text += "("; + Vector<Variant> args; + if (d.has("args")) + args = d["args"]; + for (int i = 0; i < args.size(); i++) { + + if (i > 0) + text += ", "; + text += String(args[i]); + } + text += ")"; + + int limit = MAX(0, p_clip_right - p_x - type_icon->get_width()); + if (limit > 0) { + draw_string(font, Vector2(p_x + type_icon->get_width(), int(get_size().height - font->get_height()) / 2 + font->get_ascent()), text, color, limit); + } + } + if (p_selected) { + draw_texture(selected_icon, ofs); + } else { + draw_texture(type_icon, ofs); + } +} + +//helper +void AnimationTrackEdit::draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled) { + + int clip_left = timeline->get_name_limit(); + int clip_right = get_size().width - timeline->get_buttons_width(); + + if (p_rect.position.x > clip_right) + return; + if (p_rect.position.x + p_rect.size.x < clip_left) + return; + Rect2 clip = Rect2(clip_left, 0, clip_right - clip_left, get_size().height); + draw_rect(clip.clip(p_rect), p_color, p_filled); +} + +void AnimationTrackEdit::draw_bg(int p_clip_left, int p_clip_right) { +} + +void AnimationTrackEdit::draw_fg(int p_clip_left, int p_clip_right) { +} + +void AnimationTrackEdit::draw_texture_clipped(const Ref<Texture> &p_texture, const Vector2 &p_pos) { + + draw_texture_region_clipped(p_texture, Rect2(p_pos, p_texture->get_size()), Rect2(Point2(), p_texture->get_size())); +} + +void AnimationTrackEdit::draw_texture_region_clipped(const Ref<Texture> &p_texture, const Rect2 &p_rect, const Rect2 &p_region) { + + int clip_left = timeline->get_name_limit(); + int clip_right = get_size().width - timeline->get_buttons_width(); + + //clip left and right + if (clip_left > p_rect.position.x + p_rect.size.x) + return; + if (clip_right < p_rect.position.x) + return; + + Rect2 rect = p_rect; + Rect2 region = p_region; + + if (clip_left > rect.position.x) { + int rect_pixels = (clip_left - rect.position.x); + int region_pixels = rect_pixels * region.size.x / rect.size.x; + + rect.position.x += rect_pixels; + rect.size.x -= rect_pixels; + + region.position.x += region_pixels; + region.size.x -= region_pixels; + } + + if (clip_right < rect.position.x + rect.size.x) { + + int rect_pixels = rect.position.x + rect.size.x - clip_right; + int region_pixels = rect_pixels * region.size.x / rect.size.x; + + rect.size.x -= rect_pixels; + region.size.x -= region_pixels; + } + + draw_texture_rect_region(p_texture, rect, region); +} + +int AnimationTrackEdit::get_track() const { + return track; +} + +Ref<Animation> AnimationTrackEdit::get_animation() const { + return animation; +} + +void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) { + + animation = p_animation; + track = p_track; + update(); + + Ref<Texture> type_icons[6] = { + get_icon("KeyValue", "EditorIcons"), + get_icon("KeyXform", "EditorIcons"), + get_icon("KeyCall", "EditorIcons"), + get_icon("KeyBezier", "EditorIcons"), + get_icon("KeyAudio", "EditorIcons"), + get_icon("KeyAnimation", "EditorIcons") + }; + + ERR_FAIL_INDEX(track, animation->get_track_count()); + + type_icon = type_icons[animation->track_get_type(track)]; + selected_icon = get_icon("KeySelected", "EditorIcons"); +} + +Size2 AnimationTrackEdit::get_minimum_size() const { + + Ref<Texture> texture = get_icon("Object", "EditorIcons"); + Ref<Font> font = get_font("font", "Label"); + int separation = get_constant("vseparation", "ItemList"); + + int max_h = MAX(texture->get_height(), font->get_height()); + max_h = MAX(max_h, get_key_height()); + + return Vector2(1, max_h + separation); +} + +void AnimationTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) { + undo_redo = p_undo_redo; +} + +void AnimationTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) { + timeline = p_timeline; + timeline->connect("zoom_changed", this, "_zoom_changed"); + timeline->connect("name_limit_changed", this, "_zoom_changed"); +} +void AnimationTrackEdit::set_editor(AnimationTrackEditor *p_editor) { + editor = p_editor; +} + +void AnimationTrackEdit::_play_position_draw() { + + if (!animation.is_valid() || play_position_pos < 0) + return; + + float scale = timeline->get_zoom_scale(); + int h = get_size().height; + + int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit(); + + if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { + Color color = get_color("accent_color", "Editor"); + play_position->draw_line(Point2(px, 0), Point2(px, h), color); + } +} + +void AnimationTrackEdit::set_play_position(float p_pos) { + + play_position_pos = p_pos; + play_position->update(); +} + +void AnimationTrackEdit::update_play_position() { + play_position->update(); +} + +void AnimationTrackEdit::set_root(Node *p_root) { + root = p_root; +} +void AnimationTrackEdit::_zoom_changed() { + update(); + play_position->update(); +} + +void AnimationTrackEdit::_path_entered(const String &p_text) { + + *block_animation_update_ptr = true; + undo_redo->create_action("Change Track Path"); + undo_redo->add_do_method(animation.ptr(), "track_set_path", track, p_text); + undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track)); + undo_redo->commit_action(); + *block_animation_update_ptr = false; + update(); + path->hide(); +} + +String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { + + if (check_rect.has_point(p_pos)) { + return TTR("Toggle this track on/off"); + } + + if (path_rect.has_point(p_pos)) { + return animation->track_get_path(track); + } + + if (update_mode_rect.has_point(p_pos)) { + return TTR("Update Mode (How this property is set)."); + } + + if (interp_mode_rect.has_point(p_pos)) { + return TTR("Interpolation Mode"); + } + + if (loop_mode_rect.has_point(p_pos)) { + return TTR("Loop Wrap Mode (Interpolate end with beginning on loop"); + } + + if (remove_rect.has_point(p_pos)) { + return TTR("Remove this track"); + } + + if (p_pos.x >= timeline->get_name_limit() && p_pos.x <= (get_size().width - timeline->get_buttons_width())) { + + int key_idx = -1; + float key_distance = 1e20; + + for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { //select should happen in the opposite order of drawing for more accurate overlap select + + Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale()); + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + offset = offset * timeline->get_zoom_scale() + timeline->get_name_limit(); + rect.position.x += offset; + + if (rect.has_point(p_pos)) { + + if (const_cast<AnimationTrackEdit *>(this)->is_key_selectable_by_distance()) { + float distance = ABS(offset - p_pos.x); + if (key_idx == -1 || distance < key_distance) { + key_idx = i; + key_distance = distance; + } + } else { + //first one does it + break; + } + } + } + + if (key_idx != -1) { + + String text = TTR("Time (s): ") + rtos(animation->track_get_key_time(track, key_idx)) + "\n"; + switch (animation->track_get_type(track)) { + + case Animation::TYPE_TRANSFORM: { + + Dictionary d = animation->track_get_key_value(track, key_idx); + if (d.has("location")) + text += "Pos: " + String(d["location"]) + "\n"; + if (d.has("rotation")) + text += "Rot: " + String(d["rotation"]) + "\n"; + if (d.has("scale")) + text += "Scale: " + String(d["scale"]) + "\n"; + } break; + case Animation::TYPE_VALUE: { + + Variant v = animation->track_get_key_value(track, key_idx); + //text+="value: "+String(v)+"\n"; + + bool prop_exists = false; + Variant::Type valid_type = Variant::NIL; + Object *obj = NULL; + + RES res; + Vector<StringName> leftover_path; + Node *node = root->get_node_and_resource(animation->track_get_path(track), res, leftover_path); + + if (res.is_valid()) { + obj = res.ptr(); + } else if (node) { + obj = node; + } + + if (obj) { + valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists); + } + + text += "Type: " + Variant::get_type_name(v.get_type()) + "\n"; + if (prop_exists && !Variant::can_convert(v.get_type(), valid_type)) { + text += "Value: " + String(v) + " (Invalid, expected type: " + Variant::get_type_name(valid_type) + ")\n"; + } else { + text += "Value: " + String(v) + "\n"; + } + text += "Easing: " + rtos(animation->track_get_key_transition(track, key_idx)); + + } break; + case Animation::TYPE_METHOD: { + + Dictionary d = animation->track_get_key_value(track, key_idx); + if (d.has("method")) + text += String(d["method"]); + text += "("; + Vector<Variant> args; + if (d.has("args")) + args = d["args"]; + for (int i = 0; i < args.size(); i++) { + + if (i > 0) + text += ", "; + text += String(args[i]); + } + text += ")\n"; + + } break; + case Animation::TYPE_BEZIER: { + + float h = animation->bezier_track_get_key_value(track, key_idx); + text += "Value: " + rtos(h) + "\n"; + Vector2 ih = animation->bezier_track_get_key_in_handle(track, key_idx); + text += "In-Handle: " + ih + "\n"; + Vector2 oh = animation->bezier_track_get_key_out_handle(track, key_idx); + text += "Out-Handle: " + oh + "\n"; + } break; + case Animation::TYPE_AUDIO: { + + String stream_name = "null"; + RES stream = animation->audio_track_get_key_stream(track, key_idx); + if (stream.is_valid()) { + if (stream->get_path().is_resource_file()) { + stream_name = stream->get_path().get_file(); + } else if (stream->get_name() != "") { + stream_name = stream->get_name(); + } else { + stream_name = stream->get_class(); + } + } + + text += "Stream: " + stream_name + "\n"; + float so = animation->audio_track_get_key_start_offset(track, key_idx); + text += "Start (s): " + rtos(so) + "\n"; + float eo = animation->audio_track_get_key_end_offset(track, key_idx); + text += "End (s): " + rtos(eo) + "\n"; + } break; + case Animation::TYPE_ANIMATION: { + + String name = animation->animation_track_get_key_animation(track, key_idx); + text += "Animation Clip: " + name + "\n"; + } break; + } + return text; + } + } + + return Control::get_tooltip(p_pos); +} + +void AnimationTrackEdit::_gui_input(const Ref<InputEvent> &p_event) { + + if (p_event->is_pressed()) { + if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->is_shortcut(p_event)) { + emit_signal("duplicate_request"); + accept_event(); + } + + if (ED_GET_SHORTCUT("animation_editor/duplicate_selection_transposed")->is_shortcut(p_event)) { + emit_signal("duplicate_transpose_request"); + accept_event(); + } + + if (ED_GET_SHORTCUT("animation_editor/delete_selection")->is_shortcut(p_event)) { + emit_signal("delete_request"); + accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + Point2 pos = mb->get_position(); + + if (check_rect.has_point(pos)) { + *block_animation_update_ptr = true; + undo_redo->create_action("Toggle track enabled"); + undo_redo->add_do_method(animation.ptr(), "track_set_enabled", track, !animation->track_is_enabled(track)); + undo_redo->add_undo_method(animation.ptr(), "track_set_enabled", track, animation->track_is_enabled(track)); + undo_redo->commit_action(); + *block_animation_update_ptr = false; + update(); + accept_event(); + } + if (path_rect.has_point(pos)) { + + clicking_on_name = true; + accept_event(); + } + + if (update_mode_rect.has_point(pos)) { + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("id_pressed", this, "_menu_selected"); + } + menu->clear(); + menu->add_icon_item(get_icon("TrackContinuous", "EditorIcons"), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS); + menu->add_icon_item(get_icon("TrackDiscrete", "EditorIcons"), TTR("Discrete"), MENU_CALL_MODE_DISCRETE); + menu->add_icon_item(get_icon("TrackTrigger", "EditorIcons"), TTR("Trigger"), MENU_CALL_MODE_TRIGGER); + menu->add_icon_item(get_icon("TrackCapture", "EditorIcons"), TTR("Capture"), MENU_CALL_MODE_CAPTURE); + menu->set_as_minsize(); + + Vector2 popup_pos = get_global_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height); + menu->set_global_position(popup_pos); + menu->popup(); + accept_event(); + } + + if (interp_mode_rect.has_point(pos)) { + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("id_pressed", this, "_menu_selected"); + } + menu->clear(); + menu->add_icon_item(get_icon("InterpRaw", "EditorIcons"), TTR("Nearest"), MENU_INTERPOLATION_NEAREST); + menu->add_icon_item(get_icon("InterpLinear", "EditorIcons"), TTR("Linear"), MENU_INTERPOLATION_LINEAR); + menu->add_icon_item(get_icon("InterpCubic", "EditorIcons"), TTR("Cubic"), MENU_INTERPOLATION_CUBIC); + menu->set_as_minsize(); + + Vector2 popup_pos = get_global_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height); + menu->set_global_position(popup_pos); + menu->popup(); + accept_event(); + } + + if (loop_mode_rect.has_point(pos)) { + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("id_pressed", this, "_menu_selected"); + } + menu->clear(); + menu->add_icon_item(get_icon("InterpWrapClamp", "EditorIcons"), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP); + menu->add_icon_item(get_icon("InterpWrapLoop", "EditorIcons"), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP); + menu->set_as_minsize(); + + Vector2 popup_pos = get_global_position() + loop_mode_rect.position + Vector2(0, loop_mode_rect.size.height); + menu->set_global_position(popup_pos); + menu->popup(); + accept_event(); + } + + if (remove_rect.has_point(pos)) { + emit_signal("remove_request", track); + accept_event(); + } + + if (bezier_edit_rect.has_point(pos)) { + emit_signal("bezier_edit"); + accept_event(); + } + + //check keyframes + + float scale = timeline->get_zoom_scale(); + int limit = timeline->get_name_limit(); + int limit_end = get_size().width - timeline->get_buttons_width(); + + if (pos.x >= limit && pos.x <= limit_end) { + + int key_idx = -1; + float key_distance = 1e20; + + for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { //select should happen in the opposite order of drawing for more accurate overlap select + + Rect2 rect = get_key_rect(i, scale); + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + offset = offset * scale + limit; + rect.position.x += offset; + + if (rect.has_point(pos)) { + + if (is_key_selectable_by_distance()) { + float distance = ABS(offset - pos.x); + if (key_idx == -1 || distance < key_distance) { + key_idx = i; + key_distance = distance; + } + } else { + //first one does it + key_idx = i; + break; + } + } + } + + if (key_idx != -1) { + if (mb->get_command() || mb->get_shift()) { + if (editor->is_key_selected(track, key_idx)) { + emit_signal("deselect_key", key_idx); + + } else { + emit_signal("select_key", key_idx, false); + moving_selection_attempt = true; + select_single_attempt = -1; + moving_selection_from_ofs = (mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale(); + } + } else { + if (!editor->is_key_selected(track, key_idx)) { + emit_signal("select_key", key_idx, true); + select_single_attempt = -1; + } else { + select_single_attempt = key_idx; + } + + moving_selection_attempt = true; + moving_selection_from_ofs = (mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale(); + } + accept_event(); + } else { + emit_signal("clear_selection"); + } + } + + /*using focus instead + * if (!selected && pos.x >= timeline->get_name_limit() && pos.x < (get_size().width - timeline->get_buttons_width())) { + set_selected(true); + emit_signal("selected"); + } + */ + } + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + Point2 pos = mb->get_position(); + if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) { + //can do something with menu too! show insert key + float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale(); + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("id_pressed", this, "_menu_selected"); + } + + menu->clear(); + menu->add_icon_item(get_icon("Key", "EditorIcons"), TTR("Insert Key"), MENU_KEY_INSERT); + if (editor->is_selection_active()) { + menu->add_separator(); + menu->add_icon_item(get_icon("Duplicate", "EditorIcons"), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE); + menu->add_separator(); + menu->add_icon_item(get_icon("Remove", "EditorIcons"), TTR("Delete Key(s)"), MENU_KEY_DELETE); + } + menu->set_as_minsize(); + + Vector2 popup_pos = get_global_transform().xform(get_local_mouse_position()); + menu->set_global_position(popup_pos); + menu->popup(); + + insert_at_pos = offset + timeline->get_value(); + accept_event(); + } + } + + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && clicking_on_name) { + + if (!path) { + path = memnew(LineEdit); + add_child(path); + path->set_as_toplevel(true); + path->connect("text_entered", this, "_path_entered"); + } + + path->set_text(animation->track_get_path(track)); + Vector2 theme_ofs = path->get_stylebox("normal", "LineEdit")->get_offset(); + path->set_position(get_global_position() + path_rect.position - theme_ofs); + path->set_size(path_rect.size); + path->show_modal(); + path->grab_focus(); + path->set_cursor_position(path->get_text().length()); + clicking_on_name = false; + } + + if (mb.is_valid() && moving_selection_attempt) { + + if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + moving_selection_attempt = false; + if (moving_selection) { + emit_signal("move_selection_commit"); + } else if (select_single_attempt != -1) { + emit_signal("select_key", select_single_attempt, true); + } + moving_selection = false; + select_single_attempt = -1; + } + + if (moving_selection && mb->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) { + + moving_selection_attempt = false; + moving_selection = false; + emit_signal("move_selection_cancel"); + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT && moving_selection_attempt) { + + if (!moving_selection) { + moving_selection = true; + emit_signal("move_selection_begin"); + } + + float new_ofs = (mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale(); + emit_signal("move_selection", new_ofs - moving_selection_from_ofs); + } +} + +Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) { + + if (!clicking_on_name) + return Variant(); + + Dictionary drag_data; + drag_data["type"] = "animation_track"; + drag_data["index"] = track; + + ToolButton *tb = memnew(ToolButton); + tb->set_text(path_cache); + tb->set_icon(icon_cache); + set_drag_preview(tb); + + clicking_on_name = false; + + return drag_data; +} + +bool AnimationTrackEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + + Dictionary d = p_data; + if (!d.has("type")) { + return false; + } + + String type = d["type"]; + if (type != "animation_track") + return false; + + if (p_point.y < get_size().height / 2) { + dropping_at = -1; + } else { + dropping_at = 1; + } + + const_cast<AnimationTrackEdit *>(this)->update(); + const_cast<AnimationTrackEdit *>(this)->emit_signal("drop_attempted", track); + + return true; +} +void AnimationTrackEdit::drop_data(const Point2 &p_point, const Variant &p_data) { + + Dictionary d = p_data; + if (!d.has("type")) { + return; + } + + String type = d["type"]; + if (type != "animation_track") + return; + + int from_track = d["index"]; + + if (dropping_at < 0) { + emit_signal("dropped", from_track, track); + } else { + emit_signal("dropped", from_track, track + 1); + } +} + +void AnimationTrackEdit::_menu_selected(int p_index) { + + switch (p_index) { + case MENU_CALL_MODE_CONTINUOUS: + case MENU_CALL_MODE_DISCRETE: + case MENU_CALL_MODE_TRIGGER: + case MENU_CALL_MODE_CAPTURE: { + + Animation::UpdateMode update_mode = Animation::UpdateMode(p_index); + *block_animation_update_ptr = true; + undo_redo->create_action("Change animation update mode"); + undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode); + undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track)); + undo_redo->commit_action(); + *block_animation_update_ptr = false; + update(); + + } break; + case MENU_INTERPOLATION_NEAREST: + case MENU_INTERPOLATION_LINEAR: + case MENU_INTERPOLATION_CUBIC: { + + Animation::InterpolationType interp_mode = Animation::InterpolationType(p_index - MENU_INTERPOLATION_NEAREST); + *block_animation_update_ptr = true; + undo_redo->create_action("Change animation interpolation mode"); + undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode); + undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track)); + undo_redo->commit_action(); + *block_animation_update_ptr = false; + update(); + } break; + case MENU_LOOP_WRAP: + case MENU_LOOP_CLAMP: { + + bool loop_wrap = p_index == MENU_LOOP_WRAP; + *block_animation_update_ptr = true; + undo_redo->create_action("Change animation loop mode"); + undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap); + undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track)); + undo_redo->commit_action(); + *block_animation_update_ptr = false; + update(); + + } break; + case MENU_KEY_INSERT: { + emit_signal("insert_key", insert_at_pos); + } break; + case MENU_KEY_DUPLICATE: { + emit_signal("duplicate_request"); + + } break; + case MENU_KEY_DELETE: { + emit_signal("delete_request"); + + } break; + } +} + +void AnimationTrackEdit::set_block_animation_update_ptr(bool *p_block_ptr) { + block_animation_update_ptr = p_block_ptr; +} + +void AnimationTrackEdit::cancel_drop() { + if (dropping_at != 0) { + dropping_at = 0; + update(); + } +} +void AnimationTrackEdit::set_in_group(bool p_enable) { + in_group = p_enable; + update(); +} + +void AnimationTrackEdit::append_to_selection(const Rect2 &p_box) { + + Rect2 select_rect(timeline->get_name_limit(), 0, get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(), get_size().height); + select_rect = select_rect.clip(p_box); + + for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) { //select should happen in the opposite order of drawing for more accurate overlap select + + Rect2 rect = const_cast<AnimationTrackEdit *>(this)->get_key_rect(i, timeline->get_zoom_scale()); + float offset = animation->track_get_key_time(track, i) - timeline->get_value(); + offset = offset * timeline->get_zoom_scale() + timeline->get_name_limit(); + rect.position.x += offset; + + if (select_rect.intersects(rect)) { + emit_signal("select_key", i, false); + } + } +} + +void AnimationTrackEdit::_bind_methods() { + + ClassDB::bind_method("_zoom_changed", &AnimationTrackEdit::_zoom_changed); + ClassDB::bind_method("_menu_selected", &AnimationTrackEdit::_menu_selected); + ClassDB::bind_method("_gui_input", &AnimationTrackEdit::_gui_input); + ClassDB::bind_method("_path_entered", &AnimationTrackEdit::_path_entered); + ClassDB::bind_method("_play_position_draw", &AnimationTrackEdit::_play_position_draw); + + ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag"))); + ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); + ADD_SIGNAL(MethodInfo("dropped", PropertyInfo(Variant::INT, "from_track"), PropertyInfo(Variant::INT, "to_track"))); + ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::REAL, "ofs"))); + ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); + ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("clear_selection")); + ADD_SIGNAL(MethodInfo("bezier_edit")); + + ADD_SIGNAL(MethodInfo("move_selection_begin")); + ADD_SIGNAL(MethodInfo("move_selection", PropertyInfo(Variant::REAL, "ofs"))); + ADD_SIGNAL(MethodInfo("move_selection_commit")); + ADD_SIGNAL(MethodInfo("move_selection_cancel")); + + ADD_SIGNAL(MethodInfo("duplicate_request")); + ADD_SIGNAL(MethodInfo("duplicate_transpose_request")); + ADD_SIGNAL(MethodInfo("delete_request")); +} + +AnimationTrackEdit::AnimationTrackEdit() { + undo_redo = NULL; + timeline = NULL; + root = NULL; + path = NULL; + menu = NULL; + block_animation_update_ptr = NULL; + clicking_on_name = false; + dropping_at = 0; + + in_group = false; + + moving_selection_attempt = false; + moving_selection = false; + select_single_attempt = -1; + + play_position_pos = 0; + play_position = memnew(Control); + play_position->set_mouse_filter(MOUSE_FILTER_PASS); + add_child(play_position); + play_position->set_anchors_and_margins_preset(PRESET_WIDE); + play_position->connect("draw", this, "_play_position_draw"); + set_focus_mode(FOCUS_CLICK); + set_mouse_filter(MOUSE_FILTER_PASS); //scroll has to work too for selection +} + +////////////////////////////////////// + +AnimationTrackEdit *AnimationTrackEditPlugin::create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) { + if (get_script_instance()) { + Variant args[6] = { + p_object, + p_type, + p_property, + p_hint, + p_hint_string, + p_usage + }; + + Variant *argptrs[6] = { + &args[0], + &args[1], + &args[2], + &args[3], + &args[4], + &args[5] + }; + + Variant::CallError ce; + return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_value_track_edit", (const Variant **)&argptrs, 6, ce).operator Object *()); + } + return NULL; +} + +AnimationTrackEdit *AnimationTrackEditPlugin::create_audio_track_edit() { + + if (get_script_instance()) { + return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_audio_track_edit").operator Object *()); + } + return NULL; +} + +AnimationTrackEdit *AnimationTrackEditPlugin::create_animation_track_edit(Object *p_object) { + if (get_script_instance()) { + return Object::cast_to<AnimationTrackEdit>(get_script_instance()->call("create_animation_track_edit", p_object).operator Object *()); + } + return NULL; +} + +/////////////////////////////////////// + +void AnimationTrackEditGroup::_notification(int p_what) { + + if (p_what == NOTIFICATION_DRAW) { + Ref<Font> font = get_font("font", "Label"); + int separation = get_constant("hseparation", "ItemList"); + Color color = get_color("font_color", "Label"); + + if (root && root->has_node(node)) { + Node *n = root->get_node(node); + if (n && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { + color = get_color("accent_color", "Editor"); + } + } + + Color bgcol = get_color("dark_color_2", "Editor"); + bgcol.a *= 0.6; + draw_rect(Rect2(Point2(), get_size()), bgcol); + Color linecolor = color; + linecolor.a = 0.2; + + draw_line(Point2(), Point2(get_size().width, 0), linecolor); + draw_line(Point2(timeline->get_name_limit(), 0), Point2(timeline->get_name_limit(), get_size().height), linecolor); + draw_line(Point2(get_size().width - timeline->get_buttons_width(), 0), Point2(get_size().width - timeline->get_buttons_width(), get_size().height), linecolor); + + int ofs = 0; + draw_texture(icon, Point2(ofs, int(get_size().height - icon->get_height()) / 2)); + ofs += separation + icon->get_width(); + draw_string(font, Point2(ofs, int(get_size().height - font->get_height()) / 2 + font->get_ascent()), node_name, color, timeline->get_name_limit() - ofs); + + int px = (-timeline->get_value() + timeline->get_play_position()) * timeline->get_zoom_scale() + timeline->get_name_limit(); + + if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { + Color accent = get_color("accent_color", "Editor"); + draw_line(Point2(px, 0), Point2(px, get_size().height), accent); + } + } +} + +void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture> &p_type, const String &p_name, const NodePath &p_node) { + icon = p_type; + node_name = p_name; + node = p_node; + update(); + minimum_size_changed(); +} + +Size2 AnimationTrackEditGroup::get_minimum_size() const { + + Ref<Font> font = get_font("font", "Label"); + int separation = get_constant("vseparation", "ItemList"); + + return Vector2(0, MAX(font->get_height(), icon->get_height()) + separation); +} + +void AnimationTrackEditGroup::set_timeline(AnimationTimelineEdit *p_timeline) { + timeline = p_timeline; + timeline->connect("zoom_changed", this, "_zoom_changed"); + timeline->connect("name_limit_changed", this, "_zoom_changed"); +} + +void AnimationTrackEditGroup::set_root(Node *p_root) { + root = p_root; + update(); +} + +void AnimationTrackEditGroup::_zoom_changed() { + update(); +} + +void AnimationTrackEditGroup::_bind_methods() { + ClassDB::bind_method("_zoom_changed", &AnimationTrackEditGroup::_zoom_changed); +} + +AnimationTrackEditGroup::AnimationTrackEditGroup() { + set_mouse_filter(MOUSE_FILTER_PASS); +} + +////////////////////////////////////// + +void AnimationTrackEditor::add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) { + + if (track_edit_plugins.find(p_plugin) != -1) + return; + track_edit_plugins.push_back(p_plugin); +} + +void AnimationTrackEditor::remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin) { + + track_edit_plugins.erase(p_plugin); +} + +void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { + + if (animation != p_anim && _get_track_selected() >= 0) { + track_edits[_get_track_selected()]->release_focus(); + } + if (animation.is_valid()) { + animation->disconnect("changed", this, "_animation_changed"); + _clear_selection(); + } + animation = p_anim; + timeline->set_animation(p_anim); + + _cancel_bezier_edit(); + _update_tracks(); + + if (animation.is_valid()) { + animation->connect("changed", this, "_animation_changed"); + + step->set_block_signals(true); + step->set_value(animation->get_step()); + step->set_block_signals(false); + step->set_read_only(false); + snap->set_disabled(false); + } else { + step->set_block_signals(true); + step->set_value(0); + step->set_block_signals(false); + step->set_read_only(true); + snap->set_disabled(true); + } +} + +Ref<Animation> AnimationTrackEditor::get_current_animation() const { + + return animation; +} +void AnimationTrackEditor::_root_removed(Node *p_root) { + root = NULL; +} + +void AnimationTrackEditor::set_root(Node *p_root) { + if (root) { + root->disconnect("tree_exiting", this, "_root_removed"); + } + + root = p_root; + + if (root) { + root->connect("tree_exiting", this, "_root_removed", make_binds(), CONNECT_ONESHOT); + } + + _update_tracks(); +} + +Node *AnimationTrackEditor::get_root() const { + + return root; +} + +void AnimationTrackEditor::update_keying() { + bool keying_enabled = is_visible_in_tree() && animation.is_valid(); + + if (keying_enabled == keying) + return; + + keying = keying_enabled; + //_update_menu(); + emit_signal("keying_changed"); +} + +bool AnimationTrackEditor::has_keying() const { + return keying; +} + +void AnimationTrackEditor::cleanup() { + set_animation(Ref<Animation>()); +} + +void AnimationTrackEditor::_name_limit_changed() { + + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } +} + +void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_drag) { + + emit_signal("timeline_changed", p_new_pos, p_drag); +} + +void AnimationTrackEditor::_track_remove_request(int p_track) { + + int idx = p_track; + if (idx >= 0 && idx < animation->get_track_count()) { + _clear_selection(); + undo_redo->create_action(TTR("Remove Anim Track")); + undo_redo->add_do_method(animation.ptr(), "remove_track", idx); + undo_redo->add_undo_method(animation.ptr(), "add_track", animation->track_get_type(idx), idx); + undo_redo->add_undo_method(animation.ptr(), "track_set_path", idx, animation->track_get_path(idx)); + //todo interpolation + for (int i = 0; i < animation->track_get_key_count(idx); i++) { + + Variant v = animation->track_get_key_value(idx, i); + float time = animation->track_get_key_time(idx, i); + float trans = animation->track_get_key_transition(idx, i); + + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", idx, time, v); + undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", idx, i, trans); + } + + undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", idx, animation->track_get_interpolation_type(idx)); + if (animation->track_get_type(idx) == Animation::TYPE_VALUE) { + undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", idx, animation->value_track_get_update_mode(idx)); + } + + undo_redo->commit_action(); + } +} + +void AnimationTrackEditor::set_anim_pos(float p_pos) { + + timeline->set_play_position(p_pos); + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->set_play_position(p_pos); + } + for (int i = 0; i < groups.size(); i++) { + groups[i]->update(); + } + bezier_edit->set_play_position(p_pos); +} + +void AnimationTrackEditor::_query_insert(const InsertData &p_id) { + + if (insert_frame != Engine::get_singleton()->get_frames_drawn()) { + //clear insert list for the frame if frame changed + if (insert_confirm->is_visible_in_tree()) + return; //do nothing + insert_data.clear(); + insert_query = false; + } + insert_frame = Engine::get_singleton()->get_frames_drawn(); + + for (List<InsertData>::Element *E = insert_data.front(); E; E = E->next()) { + //prevent insertion of multiple tracks + if (E->get().path == p_id.path) + return; //already inserted a track for this on this frame + } + + insert_data.push_back(p_id); + + if (p_id.track_idx == -1) { + if (bool(EDITOR_DEF("editors/animation/confirm_insert_track", true))) { + //potential new key, does not exist + if (insert_data.size() == 1) + insert_confirm_text->set_text(vformat(TTR("Create NEW track for %s and insert key?"), p_id.query)); + else + insert_confirm_text->set_text(vformat(TTR("Create %d NEW tracks and insert keys?"), insert_data.size())); + + bool all_bezier = true; + for (int i = 0; i < insert_data.size(); i++) { + if (insert_data[i].type != Animation::TYPE_VALUE && insert_data[i].type != Animation::TYPE_BEZIER) { + all_bezier = false; + } + + if (insert_data[i].type != Animation::TYPE_VALUE) { + continue; + } + switch (insert_data[i].value.get_type()) { + case Variant::INT: + case Variant::REAL: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::QUAT: + case Variant::PLANE: + case Variant::COLOR: { + //good + } break; + default: { + all_bezier = false; + } + } + } + + insert_confirm_bezier->set_visible(all_bezier); + insert_confirm->get_ok()->set_text(TTR("Create")); + insert_confirm->popup_centered_minsize(); + insert_query = true; + } else { + call_deferred("_insert_delay"); + insert_queue = true; + } + + } else { + if (!insert_query && !insert_queue) { + call_deferred("_insert_delay"); + insert_queue = true; + } + } +} + +void AnimationTrackEditor::_insert_delay() { + + if (insert_query) { + //discard since it's entered into query mode + insert_queue = false; + return; + } + + undo_redo->create_action(TTR("Anim Insert")); + + int last_track = animation->get_track_count(); + bool advance = false; + while (insert_data.size()) { + + if (insert_data.front()->get().advance) + advance = true; + last_track = _confirm_insert(insert_data.front()->get(), last_track); + insert_data.pop_front(); + } + + undo_redo->commit_action(); + + if (advance) { + float step = animation->get_step(); + if (step == 0) + step = 1; + + float pos = timeline->get_play_position(); + + pos = Math::stepify(pos + step, step); + if (pos > animation->get_length()) + pos = animation->get_length(); + set_anim_pos(pos); + emit_signal("timeline_changed", pos, true); + } + insert_queue = false; +} + +void AnimationTrackEditor::insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform) { + + if (!keying) + return; + if (!animation.is_valid()) + return; + + ERR_FAIL_COND(!root); + //let's build a node path + String path = root->get_path_to(p_node); + if (p_sub != "") + path += ":" + p_sub; + + NodePath np = path; + + int track_idx = -1; + + for (int i = 0; i < animation->get_track_count(); i++) { + + if (animation->track_get_type(i) != Animation::TYPE_TRANSFORM) + continue; + if (animation->track_get_path(i) != np) + continue; + + track_idx = i; + break; + } + + InsertData id; + Dictionary val; + + id.path = np; + id.track_idx = track_idx; + id.value = p_xform; + id.type = Animation::TYPE_TRANSFORM; + id.query = "node '" + p_node->get_name() + "'"; + id.advance = false; + + //dialog insert + + _query_insert(id); +} + +void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) { + + String path = p_path; + + //animation property is a special case, always creates an animation track + for (int i = 0; i < animation->get_track_count(); i++) { + + String np = animation->track_get_path(i); + + if (path == np && animation->track_get_type(i) == Animation::TYPE_ANIMATION) { + //exists + InsertData id; + id.path = path; + id.track_idx = i; + id.value = p_value; + id.type = Animation::TYPE_ANIMATION; + id.query = "animation"; + id.advance = false; + //dialog insert + _query_insert(id); + return; + } + } + + InsertData id; + id.path = path; + id.track_idx = -1; + id.value = p_value; + id.type = Animation::TYPE_ANIMATION; + id.query = "animation"; + id.advance = false; + //dialog insert + _query_insert(id); +} + +void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) { + + ERR_FAIL_COND(!root); + //let's build a node path + + Node *node = p_node; + + String path = root->get_path_to(node); + + if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { + if (node == AnimationPlayerEditor::singleton->get_player()) { + EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); + return; + } + _insert_animation_key(path, p_value); + return; + } + + EditorHistory *history = EditorNode::get_singleton()->get_editor_history(); + for (int i = 1; i < history->get_path_size(); i++) { + + String prop = history->get_path_property(i); + ERR_FAIL_COND(prop == ""); + path += ":" + prop; + } + + path += ":" + p_property; + + NodePath np = path; + + //locate track + + bool inserted = false; + + for (int i = 0; i < animation->get_track_count(); i++) { + + if (animation->track_get_type(i) == Animation::TYPE_VALUE) { + if (animation->track_get_path(i) != np) + continue; + + InsertData id; + id.path = np; + id.track_idx = i; + id.value = p_value; + id.type = Animation::TYPE_VALUE; + id.query = "property '" + p_property + "'"; + id.advance = false; + //dialog insert + _query_insert(id); + inserted = true; + } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { + + Variant value; + if (animation->track_get_path(i) == np) { + value = p_value; //all good + } else { + String path = animation->track_get_path(i); + if (NodePath(path.get_basename()) == np) { + String subindex = path.get_extension(); + value = p_value.get(subindex); + } else { + continue; + } + } + + InsertData id; + id.path = animation->track_get_path(i); + id.track_idx = i; + id.value = value; + id.type = Animation::TYPE_BEZIER; + id.query = "property '" + p_property + "'"; + id.advance = false; + //dialog insert + _query_insert(id); + inserted = true; + } + } + + if (inserted || p_only_if_exists) + return; + InsertData id; + id.path = np; + id.track_idx = -1; + id.value = p_value; + id.type = Animation::TYPE_VALUE; + id.query = "property '" + p_property + "'"; + id.advance = false; + //dialog insert + _query_insert(id); +} + +void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) { + + EditorHistory *history = EditorNode::get_singleton()->get_editor_history(); + + ERR_FAIL_COND(!root); + //let's build a node path + ERR_FAIL_COND(history->get_path_size() == 0); + Object *obj = ObjectDB::get_instance(history->get_path_object(0)); + ERR_FAIL_COND(!Object::cast_to<Node>(obj)); + + Node *node = Object::cast_to<Node>(obj); + + String path = root->get_path_to(node); + + if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { + if (node == AnimationPlayerEditor::singleton->get_player()) { + EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); + return; + } + _insert_animation_key(path, p_value); + return; + } + + for (int i = 1; i < history->get_path_size(); i++) { + + String prop = history->get_path_property(i); + ERR_FAIL_COND(prop == ""); + path += ":" + prop; + } + + path += ":" + p_property; + + NodePath np = path; + + //locate track + + bool inserted = false; + + for (int i = 0; i < animation->get_track_count(); i++) { + + if (animation->track_get_type(i) == Animation::TYPE_VALUE) { + if (animation->track_get_path(i) != np) + continue; + + InsertData id; + id.path = np; + id.track_idx = i; + id.value = p_value; + id.type = Animation::TYPE_VALUE; + id.query = "property '" + p_property + "'"; + id.advance = p_advance; + //dialog insert + _query_insert(id); + inserted = true; + } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { + + Variant value; + if (animation->track_get_path(i) == np) { + value = p_value; //all good + } else { + String path = animation->track_get_path(i); + if (NodePath(path.get_basename()) == np) { + String subindex = path.get_extension(); + value = p_value.get(subindex); + } else { + continue; + } + } + + InsertData id; + id.path = animation->track_get_path(i); + id.track_idx = i; + id.value = value; + id.type = Animation::TYPE_BEZIER; + id.query = "property '" + p_property + "'"; + id.advance = p_advance; + //dialog insert + _query_insert(id); + inserted = true; + } + } + + if (!inserted) { + InsertData id; + id.path = np; + id.track_idx = -1; + id.value = p_value; + id.type = Animation::TYPE_VALUE; + id.query = "property '" + p_property + "'"; + id.advance = p_advance; + //dialog insert + _query_insert(id); + } +} + +void AnimationTrackEditor::_confirm_insert_list() { + + undo_redo->create_action(TTR("Anim Create & Insert")); + + int last_track = animation->get_track_count(); + while (insert_data.size()) { + + last_track = _confirm_insert(insert_data.front()->get(), last_track, insert_confirm_bezier->is_pressed()); + insert_data.pop_front(); + } + + undo_redo->commit_action(); +} + +PropertyInfo AnimationTrackEditor::_find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val) { + + r_base_path = NodePath(); + ERR_FAIL_COND_V(!animation.is_valid(), PropertyInfo()); + ERR_FAIL_INDEX_V(p_idx, animation->get_track_count(), PropertyInfo()); + + if (!root) { + return PropertyInfo(); + } + + NodePath path = animation->track_get_path(p_idx); + + if (!root->has_node_and_resource(path)) { + return PropertyInfo(); + } + + RES res; + Vector<StringName> leftover_path; + Node *node = root->get_node_and_resource(path, res, leftover_path, true); + + if (node) { + r_base_path = node->get_path(); + } + + if (leftover_path.empty()) { + if (r_current_val) { + if (res.is_valid()) { + *r_current_val = res; + } else if (node) { + *r_current_val = node; + } + } + return PropertyInfo(); + } + + Variant property_info_base; + if (res.is_valid()) { + property_info_base = res; + if (r_current_val) { + *r_current_val = res->get(leftover_path[leftover_path.size() - 1]); + } + } else if (node) { + property_info_base = node; + if (r_current_val) { + *r_current_val = node->get(leftover_path[leftover_path.size() - 1]); + } + } + + for (int i = 0; i < leftover_path.size() - 1; i++) { + property_info_base = property_info_base.get_named(leftover_path[i]); + } + + List<PropertyInfo> pinfo; + property_info_base.get_property_list(&pinfo); + + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + + if (E->get().name == leftover_path[leftover_path.size() - 1]) { + return E->get(); + } + } + + return PropertyInfo(); +} + +static Vector<String> _get_bezier_subindices_for_type(Variant::Type p_type, bool *r_valid = NULL) { + Vector<String> subindices; + if (r_valid) { + *r_valid = true; + } + switch (p_type) { + case Variant::INT: { + subindices.push_back(""); + } break; + case Variant::REAL: { + subindices.push_back(""); + } break; + case Variant::VECTOR2: { + subindices.push_back(".x"); + subindices.push_back(".y"); + } break; + case Variant::VECTOR3: { + subindices.push_back(".x"); + subindices.push_back(".y"); + subindices.push_back(".z"); + } break; + case Variant::QUAT: { + subindices.push_back(".x"); + subindices.push_back(".y"); + subindices.push_back(".z"); + subindices.push_back(".w"); + } break; + case Variant::COLOR: { + subindices.push_back(".r"); + subindices.push_back(".g"); + subindices.push_back(".b"); + subindices.push_back(".a"); + } break; + case Variant::PLANE: { + subindices.push_back(".x"); + subindices.push_back(".y"); + subindices.push_back(".z"); + subindices.push_back(".d"); + } break; + default: { + if (r_valid) { + *r_valid = false; + } + } + } + + return subindices; +} + +int AnimationTrackEditor::_confirm_insert(InsertData p_id, int p_last_track, bool p_create_beziers) { + + if (p_last_track == -1) + p_last_track = animation->get_track_count(); + + bool created = false; + if (p_id.track_idx < 0) { + + if (p_create_beziers && (p_id.value.get_type() == Variant::VECTOR2 || + p_id.value.get_type() == Variant::VECTOR3 || + p_id.value.get_type() == Variant::QUAT || + p_id.value.get_type() == Variant::COLOR || + p_id.value.get_type() == Variant::PLANE)) { + + Vector<String> subindices = _get_bezier_subindices_for_type(p_id.value.get_type()); + + for (int i = 0; i < subindices.size(); i++) { + InsertData id = p_id; + id.type = Animation::TYPE_BEZIER; + id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length())); + id.path = String(p_id.path) + subindices[i]; + _confirm_insert(id, p_last_track + i); + } + + return p_last_track + subindices.size() - 1; + } + created = true; + undo_redo->create_action(TTR("Anim Insert Track & Key")); + Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE; + + if (p_id.type == Animation::TYPE_VALUE || p_id.type == Animation::TYPE_BEZIER) { + //wants a new tack + + { + //hack + NodePath np; + animation->add_track(p_id.type); + animation->track_set_path(animation->get_track_count() - 1, p_id.path); + PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); + animation->remove_track(animation->get_track_count() - 1); //hack + + if (h.type == Variant::REAL || + h.type == Variant::VECTOR2 || + h.type == Variant::RECT2 || + h.type == Variant::VECTOR3 || + h.type == Variant::AABB || + h.type == Variant::QUAT || + h.type == Variant::COLOR || + h.type == Variant::PLANE || + h.type == Variant::TRANSFORM2D || + h.type == Variant::TRANSFORM) { + + update_mode = Animation::UPDATE_CONTINUOUS; + } + + if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) { + update_mode = Animation::UPDATE_TRIGGER; + } + } + } + + p_id.track_idx = p_last_track; + + undo_redo->add_do_method(animation.ptr(), "add_track", p_id.type); + undo_redo->add_do_method(animation.ptr(), "track_set_path", p_id.track_idx, p_id.path); + if (p_id.type == Animation::TYPE_VALUE) + undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", p_id.track_idx, update_mode); + + } else { + undo_redo->create_action(TTR("Anim Insert Key")); + } + + float time = timeline->get_play_position(); + Variant value; + + switch (p_id.type) { + + case Animation::TYPE_VALUE: { + + value = p_id.value; + + } break; + case Animation::TYPE_TRANSFORM: { + + Transform tr = p_id.value; + Dictionary d; + d["location"] = tr.origin; + d["scale"] = tr.basis.get_scale(); + d["rotation"] = Quat(tr.basis); //.orthonormalized(); + value = d; + } break; + case Animation::TYPE_BEZIER: { + Array array; + array.resize(5); + array[0] = p_id.value; + array[1] = -0.25; + array[2] = 0; + array[3] = 0.25; + array[4] = 0; + value = array; + + } break; + case Animation::TYPE_ANIMATION: { + value = p_id.value; + } break; + default: {} + } + + undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value); + + if (created) { + + //just remove the track + undo_redo->add_undo_method(animation.ptr(), "remove_track", p_last_track); + p_last_track++; + } else { + + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_id.track_idx, time); + int existing = animation->track_find_key(p_id.track_idx, time, true); + if (existing != -1) { + Variant v = animation->track_get_key_value(p_id.track_idx, existing); + float trans = animation->track_get_key_transition(p_id.track_idx, existing); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, v, trans); + } + } + + /* + undo_redo->add_do_method(this, "update_tracks"); + undo_redo->add_undo_method(this, "update"); + undo_redo->add_do_method(track_editor, "update"); + undo_redo->add_undo_method(track_editor, "update"); + undo_redo->add_do_method(track_pos, "update"); + undo_redo->add_undo_method(track_pos, "update"); +*/ + undo_redo->commit_action(); + + return p_last_track; +} + +void AnimationTrackEditor::show_select_node_warning(bool p_show) { +} + +bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const { + + SelectedKey sk; + sk.key = p_key; + sk.track = p_track; + + return selection.has(sk); +} + +bool AnimationTrackEditor::is_selection_active() const { + return selection.size(); +} + +void AnimationTrackEditor::_update_tracks() { + + int selected = _get_track_selected(); + + while (track_vbox->get_child_count()) { + memdelete(track_vbox->get_child(0)); + } + + track_edits.clear(); + groups.clear(); + + if (animation.is_null()) + return; + + Map<String, VBoxContainer *> group_sort; + + bool use_grouping = !view_group->is_pressed(); + bool use_filter = selected_filter->is_pressed(); + + for (int i = 0; i < animation->get_track_count(); i++) { + AnimationTrackEdit *track_edit = NULL; + + //find hint and info for plugin + + if (use_filter) { + NodePath path = animation->track_get_path(i); + + if (root && root->has_node(path)) { + Node *node = root->get_node(path); + if (!node) { + continue; // no node, no filter + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; //skip track due to not selected + } + } + } + + if (animation->track_get_type(i) == Animation::TYPE_VALUE) { + + NodePath path = animation->track_get_path(i); + + if (root && root->has_node_and_resource(path)) { + RES res; + Vector<StringName> leftover_path; + Node *node = root->get_node_and_resource(path, res, leftover_path, true); + + Object *object = node; + if (res.is_valid()) { + object = res.ptr(); + } else { + object = node; + } + + if (object && !leftover_path.empty()) { + //not a property (value track?) + PropertyInfo pinfo; + pinfo.name = leftover_path[leftover_path.size() - 1]; + //now let's see if we can get more info about it + + List<PropertyInfo> plist; + object->get_property_list(&plist); + + for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { + + if (E->get().name == leftover_path[leftover_path.size() - 1]) { + pinfo = E->get(); + break; + } + } + + for (int j = 0; j < track_edit_plugins.size(); j++) { + track_edit = track_edit_plugins[j]->create_value_track_edit(object, pinfo.type, pinfo.name, pinfo.hint, pinfo.hint_string, pinfo.usage); + if (track_edit) { + break; + } + } + } + } + } + if (animation->track_get_type(i) == Animation::TYPE_AUDIO) { + + for (int j = 0; j < track_edit_plugins.size(); j++) { + track_edit = track_edit_plugins[j]->create_audio_track_edit(); + if (track_edit) { + break; + } + } + } + + if (animation->track_get_type(i) == Animation::TYPE_ANIMATION) { + NodePath path = animation->track_get_path(i); + + Node *node = NULL; + if (root && root->has_node(path)) { + node = root->get_node(path); + } + + if (node && Object::cast_to<AnimationPlayer>(node)) { + for (int j = 0; j < track_edit_plugins.size(); j++) { + track_edit = track_edit_plugins[j]->create_animation_track_edit(node); + if (track_edit) { + break; + } + } + } + } + + if (track_edit == NULL) { + //no valid plugin_found + track_edit = memnew(AnimationTrackEdit); + } + + track_edits.push_back(track_edit); + + if (use_grouping) { + String base_path = animation->track_get_path(i); + base_path = base_path.get_slice(":", 0); // remove subpath + + if (!group_sort.has(base_path)) { + AnimationTrackEditGroup *g = memnew(AnimationTrackEditGroup); + Ref<Texture> icon = get_icon("Node", "EditorIcons"); + String name = base_path; + String tooltip; + if (root) { + Node *n = root->get_node(base_path); + if (n) { + if (has_icon(n->get_class(), "EditorIcons")) { + icon = get_icon(n->get_class(), "EditorIcons"); + } + name = n->get_name(); + tooltip = root->get_path_to(n); + } + } + + g->set_type_and_name(icon, name, animation->track_get_path(i)); + g->set_root(root); + g->set_tooltip(tooltip); + g->set_timeline(timeline); + groups.push_back(g); + VBoxContainer *vb = memnew(VBoxContainer); + vb->add_constant_override("separation", 0); + vb->add_child(g); + track_vbox->add_child(vb); + group_sort[base_path] = vb; + } + + track_edit->set_in_group(true); + group_sort[base_path]->add_child(track_edit); + + } else { + track_edit->set_in_group(false); + track_vbox->add_child(track_edit); + } + + track_edit->set_undo_redo(undo_redo); + track_edit->set_timeline(timeline); + track_edit->set_block_animation_update_ptr(&block_animation_update); + track_edit->set_root(root); + track_edit->set_animation_and_track(animation, i); + track_edit->set_play_position(timeline->get_play_position()); + track_edit->set_editor(this); + + if (selected == i) { + track_edit->grab_focus(); + } + + track_edit->connect("timeline_changed", this, "_timeline_changed"); + track_edit->connect("remove_request", this, "_track_remove_request", varray(), CONNECT_DEFERRED); + track_edit->connect("dropped", this, "_dropped_track", varray(), CONNECT_DEFERRED); + track_edit->connect("insert_key", this, "_insert_key_from_track", varray(i), CONNECT_DEFERRED); + track_edit->connect("select_key", this, "_key_selected", varray(i), CONNECT_DEFERRED); + track_edit->connect("deselect_key", this, "_key_deselected", varray(i), CONNECT_DEFERRED); + track_edit->connect("bezier_edit", this, "_bezier_edit", varray(i), CONNECT_DEFERRED); + track_edit->connect("clear_selection", this, "_clear_selection"); + track_edit->connect("move_selection_begin", this, "_move_selection_begin"); + track_edit->connect("move_selection", this, "_move_selection"); + track_edit->connect("move_selection_commit", this, "_move_selection_commit"); + track_edit->connect("move_selection_cancel", this, "_move_selection_cancel"); + + track_edit->connect("duplicate_request", this, "_edit_menu_pressed", varray(EDIT_DUPLICATE_SELECTION), CONNECT_DEFERRED); + track_edit->connect("duplicate_transpose_request", this, "_edit_menu_pressed", varray(EDIT_DUPLICATE_TRANSPOSED), CONNECT_DEFERRED); + track_edit->connect("delete_request", this, "_edit_menu_pressed", varray(EDIT_DELETE_SELECTION), CONNECT_DEFERRED); + } +} + +void AnimationTrackEditor::_animation_changed() { + + timeline->update(); + timeline->update_values(); + if (block_animation_update) { + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } + for (int i = 0; i < groups.size(); i++) { + groups[i]->update(); + } + } else { + _update_tracks(); + } + + bezier_edit->update(); + + step->set_block_signals(true); + step->set_value(animation->get_step()); + step->set_block_signals(false); +} + +MenuButton *AnimationTrackEditor::get_edit_menu() { + return edit; +} + +void AnimationTrackEditor::_notification(int p_what) { + if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { + + zoom_icon->set_texture(get_icon("Zoom", "EditorIcons")); + snap->set_icon(get_icon("Snap", "EditorIcons")); + view_group->set_icon(get_icon(view_group->is_pressed() ? "AnimationTrackList" : "AnimationTrackGroup", "EditorIcons")); + selected_filter->set_icon(get_icon("AnimationFilter", "EditorIcons")); + main_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + } + + if (p_what == NOTIFICATION_READY) { + EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", this, "_selection_changed"); + } + + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + + update_keying(); + EditorNode::get_singleton()->update_keying(); + emit_signal("keying_changed"); + } +} + +void AnimationTrackEditor::_update_scroll(double) { + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } + for (int i = 0; i < groups.size(); i++) { + groups[i]->update(); + } +} + +void AnimationTrackEditor::_update_step(double p_new_step) { + + undo_redo->create_action("Change animation step"); + undo_redo->add_do_method(animation.ptr(), "set_step", p_new_step); + undo_redo->add_undo_method(animation.ptr(), "set_step", animation->get_step()); + step->set_block_signals(true); + undo_redo->commit_action(); + step->set_block_signals(false); + emit_signal("animation_step_changed", p_new_step); +} + +void AnimationTrackEditor::_update_length(double p_new_len) { + + emit_signal("animation_len_changed", p_new_len); +} + +void AnimationTrackEditor::_dropped_track(int p_from_track, int p_to_track) { + if (p_to_track >= track_edits.size()) { + p_to_track = track_edits.size() - 1; + } + + if (p_from_track == p_to_track) + return; + + _clear_selection(); + undo_redo->create_action("Rearrange tracks"); + undo_redo->add_do_method(animation.ptr(), "track_swap", p_from_track, p_to_track); + undo_redo->add_undo_method(animation.ptr(), "track_swap", p_to_track, p_from_track); + undo_redo->commit_action(); +} + +void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { + + ERR_FAIL_COND(!root); + Node *node = get_node(p_path); + ERR_FAIL_COND(!node); + NodePath path_to = root->get_path_to(node); + + if (adding_track_type == Animation::TYPE_TRANSFORM && !node->is_class("Spatial")) { + EditorNode::get_singleton()->show_warning(TTR("Transform tracks only apply to Spatial-based nodes.")); + return; + } + + switch (adding_track_type) { + case Animation::TYPE_VALUE: { + adding_track_path = path_to; + prop_selector->set_type_filter(Vector<Variant::Type>()); + prop_selector->select_property_from_instance(node); + } break; + case Animation::TYPE_TRANSFORM: + case Animation::TYPE_METHOD: { + + undo_redo->create_action("Add Track"); + undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); + undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to); + undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_BEZIER: { + + Vector<Variant::Type> filter; + filter.push_back(Variant::INT); + filter.push_back(Variant::REAL); + filter.push_back(Variant::VECTOR2); + filter.push_back(Variant::VECTOR3); + filter.push_back(Variant::QUAT); + filter.push_back(Variant::PLANE); + filter.push_back(Variant::COLOR); + + adding_track_path = path_to; + prop_selector->set_type_filter(filter); + prop_selector->select_property_from_instance(node); + } break; + case Animation::TYPE_AUDIO: { + + if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) { + EditorNode::get_singleton()->show_warning(TTR("Audio tracks can only point to nodes of type:\n-AudioStreamPlayer\n-AudioStreamPlayer2D\n-AudioStreamPlayer3D")); + return; + } + + undo_redo->create_action("Add Track"); + undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); + undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to); + undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_ANIMATION: { + + if (!node->is_class("AnimationPlayer")) { + EditorNode::get_singleton()->show_warning(TTR("Animation tracks can only point to AnimationPlayer nodes.")); + return; + } + + if (node == AnimationPlayerEditor::singleton->get_player()) { + EditorNode::get_singleton()->show_warning(TTR("An animation player can't animate itself, only other players.")); + return; + } + + undo_redo->create_action("Add Track"); + undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); + undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), path_to); + undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); + undo_redo->commit_action(); + + } break; + } +} + +void AnimationTrackEditor::_add_track(int p_type) { + if (!root) { + EditorNode::get_singleton()->show_warning(TTR("Not possible to add a new track without a root")); + return; + } + adding_track_type = p_type; + pick_track->popup_centered_ratio(); +} + +void AnimationTrackEditor::_new_track_property_selected(String p_name) { + + String full_path = String(adding_track_path) + ":" + p_name; + + if (adding_track_type == Animation::TYPE_VALUE) { + + Animation::UpdateMode update_mode = Animation::UPDATE_DISCRETE; + { + //hack + NodePath np; + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(animation->get_track_count() - 1, full_path); + PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); + animation->remove_track(animation->get_track_count() - 1); //hack + if (h.type == Variant::REAL || + h.type == Variant::VECTOR2 || + h.type == Variant::RECT2 || + h.type == Variant::VECTOR3 || + h.type == Variant::AABB || + h.type == Variant::QUAT || + h.type == Variant::COLOR || + h.type == Variant::PLANE || + h.type == Variant::TRANSFORM2D || + h.type == Variant::TRANSFORM) { + + update_mode = Animation::UPDATE_CONTINUOUS; + } + + if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) { + update_mode = Animation::UPDATE_TRIGGER; + } + } + + undo_redo->create_action("Add Track"); + undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); + undo_redo->add_do_method(animation.ptr(), "track_set_path", animation->get_track_count(), full_path); + undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", animation->get_track_count(), update_mode); + undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); + undo_redo->commit_action(); + } else { + Vector<String> subindices; + { + //hack + NodePath np; + animation->add_track(Animation::TYPE_VALUE); + animation->track_set_path(animation->get_track_count() - 1, full_path); + PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); + animation->remove_track(animation->get_track_count() - 1); //hack + bool valid; + subindices = _get_bezier_subindices_for_type(h.type, &valid); + if (!valid) { + EditorNode::get_singleton()->show_warning("Invalid track for Bezier (no suitable sub-properties)"); + return; + } + } + + undo_redo->create_action("Add Bezier Track"); + int base_track = animation->get_track_count(); + for (int i = 0; i < subindices.size(); i++) { + undo_redo->add_do_method(animation.ptr(), "add_track", adding_track_type); + undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track + i, full_path + subindices[i]); + undo_redo->add_undo_method(animation.ptr(), "remove_track", base_track + i); + } + undo_redo->commit_action(); + } +} + +void AnimationTrackEditor::_timeline_value_changed(double) { + + timeline->update_play_position(); + + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + track_edits[i]->update_play_position(); + } + + for (int i = 0; i < groups.size(); i++) { + groups[i]->update(); + } + + bezier_edit->update(); + bezier_edit->update_play_position(); +} + +int AnimationTrackEditor::_get_track_selected() { + + for (int i = 0; i < track_edits.size(); i++) { + if (track_edits[i]->has_focus()) + return i; + } + + return -1; +} + +void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { + + ERR_FAIL_INDEX(p_track, animation->get_track_count()); + + if (snap->is_pressed() && step->get_value() != 0) { + p_ofs = Math::stepify(p_ofs, step->get_value()); + } + while (animation->track_find_key(p_track, p_ofs, true) != -1) { //make sure insertion point is valid + p_ofs += 0.001; + } + + switch (animation->track_get_type(p_track)) { + case Animation::TYPE_TRANSFORM: { + if (!root->has_node(animation->track_get_path(p_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a key.")); + return; + } + Spatial *base = Object::cast_to<Spatial>(root->get_node(animation->track_get_path(p_track))); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Track is not of type Spatial, can't insert key")); + return; + } + + Transform xf = base->get_transform(); + + Vector3 loc = xf.get_origin(); + Vector3 scale = xf.basis.get_scale_local(); + Quat rot = xf.basis; + + undo_redo->create_action("Add Transform Track Key"); + undo_redo->add_do_method(animation.ptr(), "transform_track_insert_key", p_track, p_ofs, loc, rot, scale); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_VALUE: { + + NodePath bp; + Variant value; + _find_hint_for_track(p_track, bp, &value); + + undo_redo->create_action("Add Track Key"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, value); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_METHOD: { + if (!root->has_node(animation->track_get_path(p_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); + return; + } + Node *base = root->get_node(animation->track_get_path(p_track)); + + method_selector->select_method_from_instance(base); + + insert_key_from_track_call_ofs = p_ofs; + insert_key_from_track_call_track = p_track; + + } break; + case Animation::TYPE_BEZIER: { + + NodePath bp; + Variant value; + _find_hint_for_track(p_track, bp, &value); + Array arr; + arr.resize(5); + arr[0] = value; + arr[1] = -0.25; + arr[2] = 0; + arr[3] = 0.25; + arr[4] = 0; + + undo_redo->create_action("Add Track Key"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); + undo_redo->commit_action(); + + } break; + case Animation::TYPE_AUDIO: { + + Dictionary ak; + ak["stream"] = RES(); + ak["start_offset"] = 0; + ak["end_offset"] = 0; + + undo_redo->create_action("Add Track Key"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, ak); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); + undo_redo->commit_action(); + } break; + case Animation::TYPE_ANIMATION: { + + StringName anim = "[stop]"; + + undo_redo->create_action("Add Track Key"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, anim); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); + undo_redo->commit_action(); + } break; + } +} + +void AnimationTrackEditor::_add_method_key(const String &p_method) { + + if (!root->has_node(animation->track_get_path(insert_key_from_track_call_track))) { + EditorNode::get_singleton()->show_warning(TTR("Track path is invalid, so can't add a method key.")); + return; + } + Node *base = root->get_node(animation->track_get_path(insert_key_from_track_call_track)); + + List<MethodInfo> minfo; + base->get_method_list(&minfo); + + for (List<MethodInfo>::Element *E = minfo.front(); E; E = E->next()) { + if (E->get().name == p_method) { + + Dictionary d; + d["method"] = p_method; + Array params; + int first_defarg = E->get().arguments.size() - E->get().default_arguments.size(); + + for (int i = 0; i < E->get().arguments.size(); i++) { + + if (i >= first_defarg) { + Variant arg = E->get().default_arguments[i - first_defarg]; + params.push_back(arg); + } else { + Variant::CallError ce; + Variant arg = Variant::construct(E->get().arguments[i].type, NULL, 0, ce); + params.push_back(arg); + } + } + d["args"] = params; + + undo_redo->create_action("Add Method Track Key"); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", insert_key_from_track_call_track, insert_key_from_track_call_ofs); + undo_redo->commit_action(); + + return; + } + } + + EditorNode::get_singleton()->show_warning(TTR("Method not found in object: ") + p_method); +} + +void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) { + + ERR_FAIL_INDEX(p_track, animation->get_track_count()); + ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track)); + + SelectedKey sk; + sk.key = p_key; + sk.track = p_track; + + if (p_single) { + _clear_selection(); + } + + KeyInfo ki; + ki.pos = animation->track_get_key_time(p_track, p_key); + selection[sk] = ki; + + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } + + _update_key_edit(); +} + +void AnimationTrackEditor::_key_deselected(int p_key, int p_track) { + + ERR_FAIL_INDEX(p_track, animation->get_track_count()); + ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track)); + + SelectedKey sk; + sk.key = p_key; + sk.track = p_track; + + selection.erase(sk); + + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } + + _update_key_edit(); +} + +void AnimationTrackEditor::_move_selection_begin() { + moving_selection = true; + moving_selection_offset = 0; +} + +void AnimationTrackEditor::_move_selection(float p_offset) { + moving_selection_offset = p_offset; + if (snap->is_pressed() && step->get_value() != 0) { + moving_selection_offset = Math::stepify(moving_selection_offset, step->get_value()); + } + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } +} + +struct _AnimMoveRestore { + + int track; + float time; + Variant key; + float transition; +}; +//used for undo/redo + +void AnimationTrackEditor::_clear_key_edit() { + if (key_edit) { + +#if 0 + // going back seems like the most comfortable thing to do, but it results + // in weird behaviors and crashes, because going back to animation editor + // triggers the editor setting up again itself + + bool go_back = false; + if (EditorNode::get_singleton()->get_inspector()->get_edited_object() == key_edit) { + EditorNode::get_singleton()->push_item(NULL); + go_back = true; + } + + memdelete(key_edit); + key_edit = NULL; + + if (go_back) { + EditorNode::get_singleton()->get_inspector_dock()->go_back(); + } +#else + //if key edit is the object being inspected, remove it first + if (EditorNode::get_singleton()->get_inspector()->get_edited_object() == key_edit) { + EditorNode::get_singleton()->push_item(NULL); + } + //then actually delete it + memdelete(key_edit); + key_edit = NULL; +#endif + } +} + +void AnimationTrackEditor::_clear_selection() { + selection.clear(); + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } + _clear_key_edit(); +} + +void AnimationTrackEditor::_update_key_edit() { + + _clear_key_edit(); + if (!animation.is_valid()) + return; + if (selection.size() != 1) { + return; + } + + key_edit = memnew(AnimationTrackKeyEdit); + key_edit->animation = animation; + key_edit->track = selection.front()->key().track; + + float ofs = animation->track_get_key_time(key_edit->track, selection.front()->key().key); + key_edit->key_ofs = ofs; + key_edit->root_path = root; + + NodePath np; + key_edit->hint = _find_hint_for_track(key_edit->track, np); + key_edit->undo_redo = undo_redo; + key_edit->base = np; + + EditorNode::get_singleton()->push_item(key_edit); +} + +void AnimationTrackEditor::_clear_selection_for_anim(const Ref<Animation> &p_anim) { + + if (!(animation == p_anim)) + return; + //selection.clear(); + _clear_selection(); +} + +void AnimationTrackEditor::_select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos) { + + if (!(animation == p_anim)) + return; + + int idx = animation->track_find_key(p_track, p_pos, true); + ERR_FAIL_COND(idx < 0); + + SelectedKey sk; + sk.track = p_track; + sk.key = idx; + KeyInfo ki; + ki.pos = p_pos; + + selection.insert(sk, ki); +} + +void AnimationTrackEditor::_move_selection_commit() { + + undo_redo->create_action(TTR("Anim Move Keys")); + + List<_AnimMoveRestore> to_restore; + + float motion = moving_selection_offset; + // 1-remove the keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); + } + // 2- remove overlapped keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float newtime = E->get().pos + motion; + int idx = animation->track_find_key(E->key().track, newtime, true); + if (idx == -1) + continue; + SelectedKey sk; + sk.key = idx; + sk.track = E->key().track; + if (selection.has(sk)) + continue; //already in selection, don't save + + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime); + _AnimMoveRestore amr; + + amr.key = animation->track_get_key_value(E->key().track, idx); + amr.track = E->key().track; + amr.time = newtime; + amr.transition = animation->track_get_key_transition(E->key().track, idx); + + to_restore.push_back(amr); + } + + // 3-move the keys (re insert them) + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float newpos = E->get().pos + motion; + /* + if (newpos<0) + continue; //no add at the beginning + */ + undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); + } + + // 4-(undo) remove inserted keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float newpos = E->get().pos + motion; + /* + if (newpos<0) + continue; //no remove what no inserted + */ + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos); + } + + // 5-(undo) reinsert keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); + } + + // 6-(undo) reinsert overlapped keys + for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { + + _AnimMoveRestore &amr = E->get(); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); + } + + // 6-(undo) reinsert overlapped keys + for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { + + _AnimMoveRestore &amr = E->get(); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); + } + + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + + // 7-reselect + + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float oldpos = E->get().pos; + float newpos = oldpos + motion; + //if (newpos>=0) + undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos); + undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos); + } + + undo_redo->commit_action(); + + moving_selection = false; + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } +} +void AnimationTrackEditor::_move_selection_cancel() { + + moving_selection = false; + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } +} + +bool AnimationTrackEditor::is_moving_selection() const { + return moving_selection; +} +float AnimationTrackEditor::get_moving_selection_offset() const { + return moving_selection_offset; +} + +void AnimationTrackEditor::_box_selection_draw() { + + Color color = get_color("accent_color", "Editor"); + color.a = 0.2; + Rect2 rect = Rect2(Point2(), box_selection->get_size()); + box_selection->draw_rect(rect, color); +} + +void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + + timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05); + scroll->accept_event(); + } + + if (mb.is_valid() && mb->is_pressed() && mb->get_command() && mb->get_button_index() == BUTTON_WHEEL_UP) { + + timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05); + scroll->accept_event(); + } + + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + if (mb->is_pressed()) { + box_selecting = true; + box_selecting_from = scroll->get_global_transform().xform(mb->get_position()); + box_select_rect = Rect2(); + } else if (box_selecting) { + + if (box_selection->is_visible_in_tree()) { + //only if moved + for (int i = 0; i < track_edits.size(); i++) { + + Rect2 local_rect = box_select_rect; + local_rect.position -= track_edits[i]->get_global_position(); + track_edits[i]->append_to_selection(local_rect); + } + + if (_get_track_selected() == -1 && track_edits.size() > 0) { //minimal hack to make shortcuts work + track_edits[track_edits.size() - 1]->grab_focus(); + } + } else { + _clear_selection(); //clear it + } + + box_selection->hide(); + box_selecting = false; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) { + + timeline->set_value(timeline->get_value() - mm->get_relative().x / timeline->get_zoom_scale()); + } + + if (mm.is_valid() && box_selecting) { + + if (!(mm->get_button_mask() & BUTTON_MASK_LEFT)) { + //no longer + box_selection->hide(); + box_selecting = false; + return; + } + + if (!box_selection->is_visible_in_tree()) { + if (!mm->get_shift()) { + _clear_selection(); //only append if shift is pressed + } + box_selection->show(); + } + + Vector2 from = box_selecting_from; + Vector2 to = scroll->get_global_transform().xform(mm->get_position()); + + if (from.x > to.x) { + SWAP(from.x, to.x); + } + + if (from.y > to.y) { + SWAP(from.y, to.y); + } + + Rect2 rect(from, to - from); + Rect2 scroll_rect = Rect2(scroll->get_global_position(), scroll->get_size()); + rect = scroll_rect.clip(rect); + box_selection->set_position(rect.position); + box_selection->set_size(rect.size); + + box_select_rect = rect; + + if (get_local_mouse_position().y < 0) { + //avoid box selection from going up and lose focus to viewport + warp_mouse(Vector2(mm->get_position().x, 0)); + } + } +} + +void AnimationTrackEditor::_cancel_bezier_edit() { + bezier_edit->hide(); + scroll->show(); +} + +void AnimationTrackEditor::_bezier_edit(int p_for_track) { + + _clear_selection(); //bezier probably wants to use a separate selection mode + bezier_edit->set_root(root); + bezier_edit->set_animation_and_track(animation, p_for_track); + scroll->hide(); + bezier_edit->show(); + //search everything within the track and curve- edit it +} + +void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) { + //duplicait! + if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) { + + int top_track = 0x7FFFFFFF; + float top_time = 1e10; + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + const SelectedKey &sk = E->key(); + + float t = animation->track_get_key_time(sk.track, sk.key); + if (t < top_time) + top_time = t; + if (sk.track < top_track) + top_track = sk.track; + } + ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10); + + // + + int start_track = transpose ? _get_track_selected() : top_track; + + undo_redo->create_action(TTR("Anim Duplicate Keys")); + + List<Pair<int, float> > new_selection_values; + + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + const SelectedKey &sk = E->key(); + + float t = animation->track_get_key_time(sk.track, sk.key); + + float dst_time = t + (timeline->get_play_position() - top_time); + int dst_track = sk.track + (start_track - top_track); + + if (dst_track < 0 || dst_track >= animation->get_track_count()) + continue; + + if (animation->track_get_type(dst_track) != animation->track_get_type(sk.track)) + continue; + + int existing_idx = animation->track_find_key(dst_track, dst_time, true); + + undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", dst_track, dst_time); + + Pair<int, float> p; + p.first = dst_track; + p.second = dst_time; + new_selection_values.push_back(p); + + if (existing_idx != -1) { + + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(dst_track, existing_idx), animation->track_get_key_transition(dst_track, existing_idx)); + } + } + + undo_redo->commit_action(); + + //reselect duplicated + + Map<SelectedKey, KeyInfo> new_selection; + for (List<Pair<int, float> >::Element *E = new_selection_values.front(); E; E = E->next()) { + + int track = E->get().first; + float time = E->get().second; + + int existing_idx = animation->track_find_key(track, time, true); + + if (existing_idx == -1) + continue; + SelectedKey sk2; + sk2.track = track; + sk2.key = existing_idx; + + KeyInfo ki; + ki.pos = time; + + new_selection[sk2] = ki; + } + + selection = new_selection; + _update_tracks(); + _update_key_edit(); + } +} +void AnimationTrackEditor::_edit_menu_pressed(int p_option) { + + last_menu_track_opt = p_option; + switch (p_option) { + case EDIT_COPY_TRACKS: { + track_copy_select->clear(); + TreeItem *troot = track_copy_select->create_item(); + + for (int i = 0; i < animation->get_track_count(); i++) { + + NodePath path = animation->track_get_path(i); + Node *node = NULL; + + if (root && root->has_node(path)) { + node = root->get_node(path); + } + + String text; + Ref<Texture> icon = get_icon("Node", "EditorIcons"); + if (node) { + if (has_icon(node->get_class(), "EditorIcons")) { + icon = get_icon(node->get_class(), "EditorIcons"); + } + + text = node->get_name(); + Vector<StringName> sn = path.get_subnames(); + for (int i = 0; i < sn.size(); i++) { + text += "."; + text += sn[i]; + } + + path = NodePath(node->get_path().get_names(), path.get_subnames(), true); //store full path instead for copying + } else { + text = path; + int sep = text.find(":"); + if (sep != -1) { + text = text.substr(sep + 1, text.length()); + } + } + + switch (animation->track_get_type(i)) { + case Animation::TYPE_TRANSFORM: text += " (Transform)"; break; + case Animation::TYPE_METHOD: text += " (Methods)"; break; + case Animation::TYPE_BEZIER: text += " (Bezier)"; break; + case Animation::TYPE_AUDIO: text += " (Audio)"; break; + default: {}; + } + + TreeItem *it = track_copy_select->create_item(troot); + it->set_editable(0, true); + it->set_selectable(0, true); + it->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + it->set_icon(0, icon); + it->set_text(0, text); + Dictionary md; + md["track_idx"] = i; + md["path"] = path; + it->set_metadata(0, md); + } + + track_copy_dialog->popup_centered_minsize(Size2(300, 500) * EDSCALE); + } break; + case EDIT_COPY_TRACKS_CONFIRM: { + + track_clipboard.clear(); + TreeItem *root = track_copy_select->get_root(); + if (root) { + + TreeItem *it = root->get_children(); + while (it) { + Dictionary md = it->get_metadata(0); + int idx = md["track_idx"]; + if (it->is_checked(0) && idx >= 0 && idx < animation->get_track_count()) { + TrackClipboard tc; + tc.base_path = animation->track_get_path(idx); + tc.full_path = md["path"]; + tc.track_type = animation->track_get_type(idx); + tc.interp_type = animation->track_get_interpolation_type(idx); + if (tc.track_type == Animation::TYPE_VALUE) { + tc.update_mode = animation->value_track_get_update_mode(idx); + } + tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx); + tc.enabled = animation->track_is_enabled(idx); + for (int i = 0; i < animation->track_get_key_count(idx); i++) { + TrackClipboard::Key k; + k.time = animation->track_get_key_time(idx, i); + k.value = animation->track_get_key_value(idx, i); + k.transition = animation->track_get_key_transition(idx, i); + tc.keys.push_back(k); + } + track_clipboard.push_back(tc); + } + it = it->get_next(); + } + } + } break; + case EDIT_PASTE_TRACKS: { + + if (track_clipboard.size() == 0) { + EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty")); + break; + } + + int base_track = animation->get_track_count(); + undo_redo->create_action("Paste Tracks"); + for (int i = 0; i < track_clipboard.size(); i++) { + undo_redo->add_do_method(animation.ptr(), "add_track", track_clipboard[i].track_type); + Node *exists = NULL; + NodePath path = track_clipboard[i].base_path; + + if (root) { + NodePath np = track_clipboard[i].full_path; + exists = root->get_node(np); + if (exists) { + path = NodePath(root->get_path_to(exists).get_names(), track_clipboard[i].full_path.get_subnames(), false); + } + } + + undo_redo->add_do_method(animation.ptr(), "track_set_path", base_track, path); + undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", base_track, track_clipboard[i].interp_type); + undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", base_track, track_clipboard[i].loop_wrap); + undo_redo->add_do_method(animation.ptr(), "track_set_enabled", base_track, track_clipboard[i].enabled); + if (track_clipboard[i].track_type == Animation::TYPE_VALUE) { + undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode); + } + + for (int j = 0; j < track_clipboard[i].keys.size(); j++) { + undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition); + } + + undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count()); + + base_track++; + } + + undo_redo->commit_action(); + } break; + + case EDIT_SCALE_SELECTION: + case EDIT_SCALE_FROM_CURSOR: { + scale_dialog->popup_centered(Size2(200, 100) * EDSCALE); + } break; + case EDIT_SCALE_CONFIRM: { + if (selection.empty()) + return; + + float from_t = 1e20; + float to_t = -1e20; + float len = -1e20; + float pivot = 0; + + for (Map<SelectedKey, KeyInfo>::Element *E = selection.front(); E; E = E->next()) { + float t = animation->track_get_key_time(E->key().track, E->key().key); + if (t < from_t) + from_t = t; + if (t > to_t) + to_t = t; + } + + len = to_t - from_t; + if (last_menu_track_opt == EDIT_SCALE_FROM_CURSOR) { + pivot = timeline->get_play_position(); + + } else { + + pivot = from_t; + } + + float s = scale->get_value(); + if (s == 0) { + ERR_PRINT("Can't scale to 0"); + } + + undo_redo->create_action(TTR("Anim Scale Keys")); + + List<_AnimMoveRestore> to_restore; + + // 1-remove the keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); + } + // 2- remove overlapped keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float newtime = (E->get().pos - from_t) * s + from_t; + int idx = animation->track_find_key(E->key().track, newtime, true); + if (idx == -1) + continue; + SelectedKey sk; + sk.key = idx; + sk.track = E->key().track; + if (selection.has(sk)) + continue; //already in selection, don't save + + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime); + _AnimMoveRestore amr; + + amr.key = animation->track_get_key_value(E->key().track, idx); + amr.track = E->key().track; + amr.time = newtime; + amr.transition = animation->track_get_key_transition(E->key().track, idx); + + to_restore.push_back(amr); + } + +#define _NEW_POS(m_ofs) (((s > 0) ? m_ofs : from_t + (len - (m_ofs - from_t))) - pivot) * ABS(s) + from_t + // 3-move the keys (re insert them) + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float newpos = _NEW_POS(E->get().pos); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); + } + + // 4-(undo) remove inserted keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float newpos = _NEW_POS(E->get().pos); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos); + } + + // 5-(undo) reinsert keys + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); + } + + // 6-(undo) reinsert overlapped keys + for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { + + _AnimMoveRestore &amr = E->get(); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); + } + + // 6-(undo) reinsert overlapped keys + for (List<_AnimMoveRestore>::Element *E = to_restore.front(); E; E = E->next()) { + + _AnimMoveRestore &amr = E->get(); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, amr.transition); + } + + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + + // 7-reselect + + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + float oldpos = E->get().pos; + float newpos = _NEW_POS(oldpos); + if (newpos >= 0) + undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos); + undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos); + } +#undef _NEW_POS + undo_redo->commit_action(); + } break; + case EDIT_DUPLICATE_SELECTION: { + + if (bezier_edit->is_visible()) { + bezier_edit->duplicate_selection(); + break; + } + _anim_duplicate_keys(false); + } break; + case EDIT_DUPLICATE_TRANSPOSED: { + if (bezier_edit->is_visible()) { + EditorNode::get_singleton()->show_warning(TTR("This option does not work for Bezier editing, as it's only a single track.")); + break; + } + _anim_duplicate_keys(true); + } break; + case EDIT_DELETE_SELECTION: { + + if (bezier_edit->is_visible()) { + bezier_edit->delete_selection(); + break; + } + + if (selection.size()) { + undo_redo->create_action(TTR("Anim Delete Keys")); + + for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { + + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->key().track, E->key().key); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->key().track, E->get().pos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); + } + undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); + undo_redo->commit_action(); + //selection.clear(); + _update_key_edit(); + } + } break; + case EDIT_GOTO_NEXT_STEP: { + + if (animation.is_null()) + break; + float step = animation->get_step(); + if (step == 0) + step = 1; + + float pos = timeline->get_play_position(); + + pos = Math::stepify(pos + step, step); + if (pos > animation->get_length()) + pos = animation->get_length(); + set_anim_pos(pos); + + emit_signal("timeline_changed", pos, true); + + } break; + case EDIT_GOTO_PREV_STEP: { + if (animation.is_null()) + break; + float step = animation->get_step(); + if (step == 0) + step = 1; + + float pos = timeline->get_play_position(); + pos = Math::stepify(pos - step, step); + if (pos < 0) + pos = 0; + set_anim_pos(pos); + emit_signal("timeline_changed", pos, true); + + } break; + case EDIT_OPTIMIZE_ANIMATION: { + optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE); + + } break; + case EDIT_OPTIMIZE_ANIMATION_CONFIRM: { + animation->optimize(optimize_linear_error->get_value(), optimize_angular_error->get_value(), optimize_max_angle->get_value()); + _update_tracks(); + undo_redo->clear_history(); + + } break; + case EDIT_CLEAN_UP_ANIMATION: { + cleanup_dialog->popup_centered_minsize(Size2(300, 0) * EDSCALE); + + } break; + case EDIT_CLEAN_UP_ANIMATION_CONFIRM: { + if (cleanup_all->is_pressed()) { + List<StringName> names; + AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E->get())); + } + } else { + _cleanup_animation(animation); + } + + } break; + } +} + +void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { + + for (int i = 0; i < p_animation->get_track_count(); i++) { + + bool prop_exists = false; + Variant::Type valid_type = Variant::NIL; + Object *obj = NULL; + + RES res; + Vector<StringName> leftover_path; + + Node *node = root->get_node_and_resource(p_animation->track_get_path(i), res, leftover_path); + + if (res.is_valid()) { + obj = res.ptr(); + } else if (node) { + obj = node; + } + + if (obj && p_animation->track_get_type(i) == Animation::TYPE_VALUE) { + valid_type = obj->get_static_property_type_indexed(leftover_path, &prop_exists); + } + + if (!obj && cleanup_tracks->is_pressed()) { + + p_animation->remove_track(i); + i--; + continue; + } + + if (!prop_exists || p_animation->track_get_type(i) != Animation::TYPE_VALUE || cleanup_keys->is_pressed() == false) + continue; + + for (int j = 0; j < p_animation->track_get_key_count(i); j++) { + + Variant v = p_animation->track_get_key_value(i, j); + + if (!Variant::can_convert(v.get_type(), valid_type)) { + p_animation->track_remove_key(i, j); + j--; + } + } + + if (p_animation->track_get_key_count(i) == 0 && cleanup_tracks->is_pressed()) { + p_animation->remove_track(i); + i--; + } + } + + undo_redo->clear_history(); + _update_tracks(); +} + +void AnimationTrackEditor::_view_group_toggle() { + _update_tracks(); + view_group->set_icon(get_icon(view_group->is_pressed() ? "AnimationTrackList" : "AnimationTrackGroup", "EditorIcons")); +} + +void AnimationTrackEditor::_selection_changed() { + + if (selected_filter->is_pressed()) { + _update_tracks(); //needs updatin + } else { + for (int i = 0; i < track_edits.size(); i++) { + track_edits[i]->update(); + } + + for (int i = 0; i < groups.size(); i++) { + groups[i]->update(); + } + } +} + +float AnimationTrackEditor::snap_time(float p_value) { + + if (snap->is_pressed()) { + p_value = Math::stepify(p_value, step->get_value()); + } + + return p_value; +} + +void AnimationTrackEditor::_bind_methods() { + + ClassDB::bind_method("_animation_changed", &AnimationTrackEditor::_animation_changed); + ClassDB::bind_method("_timeline_changed", &AnimationTrackEditor::_timeline_changed); + ClassDB::bind_method("_track_remove_request", &AnimationTrackEditor::_track_remove_request); + ClassDB::bind_method("_name_limit_changed", &AnimationTrackEditor::_name_limit_changed); + ClassDB::bind_method("_update_scroll", &AnimationTrackEditor::_update_scroll); + ClassDB::bind_method("_update_step", &AnimationTrackEditor::_update_step); + ClassDB::bind_method("_update_length", &AnimationTrackEditor::_update_length); + ClassDB::bind_method("_dropped_track", &AnimationTrackEditor::_dropped_track); + ClassDB::bind_method("_add_track", &AnimationTrackEditor::_add_track); + ClassDB::bind_method("_new_track_node_selected", &AnimationTrackEditor::_new_track_node_selected); + ClassDB::bind_method("_new_track_property_selected", &AnimationTrackEditor::_new_track_property_selected); + ClassDB::bind_method("_root_removed", &AnimationTrackEditor::_root_removed); + ClassDB::bind_method("_confirm_insert_list", &AnimationTrackEditor::_confirm_insert_list); + ClassDB::bind_method("_insert_delay", &AnimationTrackEditor::_insert_delay); + ClassDB::bind_method("_timeline_value_changed", &AnimationTrackEditor::_timeline_value_changed); + ClassDB::bind_method("_insert_key_from_track", &AnimationTrackEditor::_insert_key_from_track); + ClassDB::bind_method("_add_method_key", &AnimationTrackEditor::_add_method_key); + ClassDB::bind_method("_key_selected", &AnimationTrackEditor::_key_selected); + ClassDB::bind_method("_key_deselected", &AnimationTrackEditor::_key_deselected); + ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection); + ClassDB::bind_method("_move_selection_begin", &AnimationTrackEditor::_move_selection_begin); + ClassDB::bind_method("_move_selection", &AnimationTrackEditor::_move_selection); + ClassDB::bind_method("_move_selection_commit", &AnimationTrackEditor::_move_selection_commit); + ClassDB::bind_method("_move_selection_cancel", &AnimationTrackEditor::_move_selection_cancel); + ClassDB::bind_method("_clear_selection_for_anim", &AnimationTrackEditor::_clear_selection_for_anim); + ClassDB::bind_method("_select_at_anim", &AnimationTrackEditor::_select_at_anim); + ClassDB::bind_method("_scroll_input", &AnimationTrackEditor::_scroll_input); + ClassDB::bind_method("_box_selection_draw", &AnimationTrackEditor::_box_selection_draw); + ClassDB::bind_method("_bezier_edit", &AnimationTrackEditor::_bezier_edit); + ClassDB::bind_method("_cancel_bezier_edit", &AnimationTrackEditor::_cancel_bezier_edit); + ClassDB::bind_method("_edit_menu_pressed", &AnimationTrackEditor::_edit_menu_pressed); + ClassDB::bind_method("_view_group_toggle", &AnimationTrackEditor::_view_group_toggle); + ClassDB::bind_method("_selection_changed", &AnimationTrackEditor::_selection_changed); + + ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::REAL, "position"), PropertyInfo(Variant::BOOL, "drag"))); + ADD_SIGNAL(MethodInfo("keying_changed")); + ADD_SIGNAL(MethodInfo("animation_len_changed", PropertyInfo(Variant::REAL, "len"))); + ADD_SIGNAL(MethodInfo("animation_step_changed", PropertyInfo(Variant::REAL, "step"))); +} + +AnimationTrackEditor::AnimationTrackEditor() { + root = NULL; + block_animation_update = false; + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + main_panel = memnew(PanelContainer); + add_child(main_panel); + main_panel->set_v_size_flags(SIZE_EXPAND_FILL); + HBoxContainer *timeline_scroll = memnew(HBoxContainer); + main_panel->add_child(timeline_scroll); + timeline_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + + VBoxContainer *timeline_vbox = memnew(VBoxContainer); + timeline_scroll->add_child(timeline_vbox); + timeline_vbox->set_v_size_flags(SIZE_EXPAND_FILL); + timeline_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + timeline_vbox->add_constant_override("separation", 0); + + timeline = memnew(AnimationTimelineEdit); + timeline->set_block_animation_update_ptr(&block_animation_update); + timeline->set_undo_redo(undo_redo); + timeline_vbox->add_child(timeline); + timeline->connect("timeline_changed", this, "_timeline_changed"); + timeline->connect("name_limit_changed", this, "_name_limit_changed"); + timeline->connect("track_added", this, "_add_track"); + timeline->connect("value_changed", this, "_timeline_value_changed"); + timeline->connect("length_changed", this, "_update_length"); + + scroll = memnew(ScrollContainer); + timeline_vbox->add_child(scroll); + scroll->set_v_size_flags(SIZE_EXPAND_FILL); + VScrollBar *sb = scroll->get_v_scrollbar(); + scroll->remove_child(sb); + timeline_scroll->add_child(sb); //move here so timeline and tracks are always aligned + scroll->connect("gui_input", this, "_scroll_input"); + + bezier_edit = memnew(AnimationBezierTrackEdit); + timeline_vbox->add_child(bezier_edit); + bezier_edit->set_block_animation_update_ptr(&block_animation_update); + bezier_edit->set_undo_redo(undo_redo); + bezier_edit->set_editor(this); + bezier_edit->set_timeline(timeline); + bezier_edit->hide(); + bezier_edit->set_v_size_flags(SIZE_EXPAND_FILL); + bezier_edit->connect("close_request", this, "_cancel_bezier_edit"); + + timeline_vbox->set_custom_minimum_size(Size2(0, 150) * EDSCALE); + + hscroll = memnew(HScrollBar); + timeline_vbox->add_child(hscroll); + hscroll->share(timeline); + hscroll->connect("value_changed", this, "_update_scroll"); + timeline->set_hscroll(hscroll); + + track_vbox = memnew(VBoxContainer); + scroll->add_child(track_vbox); + track_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + scroll->set_enable_h_scroll(false); + scroll->set_enable_v_scroll(true); + track_vbox->add_constant_override("separation", 0); + + //timeline_vbox->add_child(memnew(HSeparator)); + HBoxContainer *bottom_hb = memnew(HBoxContainer); + add_child(bottom_hb); + bottom_hb->add_spacer(); + + selected_filter = memnew(ToolButton); + selected_filter->connect("pressed", this, "_view_group_toggle"); //same function works the same + selected_filter->set_toggle_mode(true); + selected_filter->set_tooltip(TTR("Only show tracks from nodes selected in tree.")); + + bottom_hb->add_child(selected_filter); + + view_group = memnew(ToolButton); + view_group->connect("pressed", this, "_view_group_toggle"); + view_group->set_toggle_mode(true); + view_group->set_tooltip(TTR("Group tracks by node or display them as plain list.")); + + bottom_hb->add_child(view_group); + bottom_hb->add_child(memnew(VSeparator)); + + snap = memnew(ToolButton); + snap->set_text(TTR("Snap (s): ")); + bottom_hb->add_child(snap); + snap->set_disabled(true); + snap->set_toggle_mode(true); + snap->set_pressed(true); + + step = memnew(EditorSpinSlider); + step->set_min(0); + step->set_max(1000); + step->set_step(0.01); + step->set_hide_slider(true); + step->set_custom_minimum_size(Size2(100, 0) * EDSCALE); + bottom_hb->add_child(step); + step->connect("value_changed", this, "_update_step"); + step->set_read_only(true); + + bottom_hb->add_child(memnew(VSeparator)); + + zoom_icon = memnew(TextureRect); + zoom_icon->set_v_size_flags(SIZE_SHRINK_CENTER); + bottom_hb->add_child(zoom_icon); + zoom = memnew(HSlider); + zoom->set_step(0.01); + zoom->set_min(0.0); + zoom->set_max(2.0); + zoom->set_value(1.0); + zoom->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + zoom->set_v_size_flags(SIZE_SHRINK_CENTER); + bottom_hb->add_child(zoom); + timeline->set_zoom(zoom); + + edit = memnew(MenuButton); + edit->set_text(TTR("Edit")); + edit->set_flat(false); + edit->get_popup()->add_item(TTR("Copy Tracks"), EDIT_COPY_TRACKS); + edit->get_popup()->add_item(TTR("Paste Tracks"), EDIT_PASTE_TRACKS); + edit->get_popup()->add_separator(); + edit->get_popup()->add_item(TTR("Scale Selection"), EDIT_SCALE_SELECTION); + edit->get_popup()->add_item(TTR("Scale From Cursor"), EDIT_SCALE_FROM_CURSOR); + edit->get_popup()->add_separator(); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_CMD | KEY_D), EDIT_DUPLICATE_SELECTION); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection_transposed", TTR("Duplicate Transposed"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_D), EDIT_DUPLICATE_TRANSPOSED); + edit->get_popup()->set_item_shortcut_disabled(edit->get_popup()->get_item_index(EDIT_DUPLICATE_SELECTION), true); + edit->get_popup()->set_item_shortcut_disabled(edit->get_popup()->get_item_index(EDIT_DUPLICATE_TRANSPOSED), true); + edit->get_popup()->add_separator(); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/delete_selection", TTR("Delete Selection"), KEY_DELETE), EDIT_DELETE_SELECTION); + edit->get_popup()->set_item_shortcut_disabled(edit->get_popup()->get_item_index(EDIT_DELETE_SELECTION), true); + //this shortcut will be checked from the track itself. so no need to enable it here (will conflict with scenetree dock) + + edit->get_popup()->add_separator(); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTR("Goto Next Step"), KEY_MASK_CMD | KEY_RIGHT), EDIT_GOTO_NEXT_STEP); + edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTR("Goto Prev Step"), KEY_MASK_CMD | KEY_LEFT), EDIT_GOTO_PREV_STEP); + edit->get_popup()->add_separator(); + edit->get_popup()->add_item(TTR("Optimize Animation"), EDIT_OPTIMIZE_ANIMATION); + edit->get_popup()->add_item(TTR("Clean-Up Animation"), EDIT_CLEAN_UP_ANIMATION); + + edit->get_popup()->connect("id_pressed", this, "_edit_menu_pressed"); + + pick_track = memnew(SceneTreeDialog); + add_child(pick_track); + pick_track->set_title(TTR("Pick the node that will be animated:")); + pick_track->connect("selected", this, "_new_track_node_selected"); + prop_selector = memnew(PropertySelector); + add_child(prop_selector); + prop_selector->connect("selected", this, "_new_track_property_selected"); + + method_selector = memnew(PropertySelector); + add_child(method_selector); + method_selector->connect("selected", this, "_add_method_key"); + + inserting = false; + insert_query = false; + insert_frame = 0; + insert_queue = false; + + insert_confirm = memnew(ConfirmationDialog); + add_child(insert_confirm); + insert_confirm->connect("confirmed", this, "_confirm_insert_list"); + VBoxContainer *icvb = memnew(VBoxContainer); + insert_confirm->add_child(icvb); + insert_confirm_text = memnew(Label); + icvb->add_child(insert_confirm_text); + insert_confirm_bezier = memnew(CheckBox); + insert_confirm_bezier->set_text(TTR("Use Bezier Curves")); + icvb->add_child(insert_confirm_bezier); + keying = false; + moving_selection = 0; + key_edit = NULL; + + box_selection = memnew(Control); + add_child(box_selection); + box_selection->set_as_toplevel(true); + box_selection->set_mouse_filter(MOUSE_FILTER_IGNORE); + box_selection->hide(); + box_selection->connect("draw", this, "_box_selection_draw"); + box_selecting = false; + + //default plugins + + Ref<AnimationTrackEditDefaultPlugin> def_plugin; + def_plugin.instance(); + add_track_edit_plugin(def_plugin); + + //dialogs + + optimize_dialog = memnew(ConfirmationDialog); + add_child(optimize_dialog); + optimize_dialog->set_title(TTR("Anim. Optimizer")); + VBoxContainer *optimize_vb = memnew(VBoxContainer); + optimize_dialog->add_child(optimize_vb); + + optimize_linear_error = memnew(SpinBox); + optimize_linear_error->set_max(1.0); + optimize_linear_error->set_min(0.001); + optimize_linear_error->set_step(0.001); + optimize_linear_error->set_value(0.05); + optimize_vb->add_margin_child(TTR("Max. Linear Error:"), optimize_linear_error); + optimize_angular_error = memnew(SpinBox); + optimize_angular_error->set_max(1.0); + optimize_angular_error->set_min(0.001); + optimize_angular_error->set_step(0.001); + optimize_angular_error->set_value(0.01); + + optimize_vb->add_margin_child(TTR("Max. Angular Error:"), optimize_angular_error); + optimize_max_angle = memnew(SpinBox); + optimize_vb->add_margin_child(TTR("Max Optimizable Angle:"), optimize_max_angle); + optimize_max_angle->set_max(360.0); + optimize_max_angle->set_min(0.0); + optimize_max_angle->set_step(0.1); + optimize_max_angle->set_value(22); + + optimize_dialog->get_ok()->set_text(TTR("Optimize")); + optimize_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_CLEAN_UP_ANIMATION_CONFIRM)); + + // + + cleanup_dialog = memnew(ConfirmationDialog); + add_child(cleanup_dialog); + VBoxContainer *cleanup_vb = memnew(VBoxContainer); + cleanup_dialog->add_child(cleanup_vb); + + cleanup_keys = memnew(CheckButton); + cleanup_keys->set_text(TTR("Remove invalid keys")); + cleanup_keys->set_pressed(true); + cleanup_vb->add_child(cleanup_keys); + + cleanup_tracks = memnew(CheckButton); + cleanup_tracks->set_text(TTR("Remove unresolved and empty tracks")); + cleanup_tracks->set_pressed(true); + cleanup_vb->add_child(cleanup_tracks); + + cleanup_all = memnew(CheckButton); + cleanup_all->set_text(TTR("Clean-up all animations")); + cleanup_vb->add_child(cleanup_all); + + cleanup_dialog->set_title(TTR("Clean-Up Animation(s) (NO UNDO!)")); + cleanup_dialog->get_ok()->set_text(TTR("Clean-Up")); + + cleanup_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_CLEAN_UP_ANIMATION_CONFIRM)); + + // + scale_dialog = memnew(ConfirmationDialog); + VBoxContainer *vbc = memnew(VBoxContainer); + scale_dialog->add_child(vbc); + + scale = memnew(SpinBox); + scale->set_min(-99999); + scale->set_max(99999); + scale->set_step(0.001); + vbc->add_margin_child(TTR("Scale Ratio:"), scale); + scale_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_SCALE_CONFIRM)); + add_child(scale_dialog); + + track_copy_dialog = memnew(ConfirmationDialog); + add_child(track_copy_dialog); + track_copy_dialog->set_title(TTR("Select tracks to copy:")); + track_copy_dialog->get_ok()->set_text(TTR("Copy")); + + track_copy_select = memnew(Tree); + track_copy_select->set_hide_root(true); + track_copy_dialog->add_child(track_copy_select); + track_copy_dialog->connect("confirmed", this, "_edit_menu_pressed", varray(EDIT_COPY_TRACKS_CONFIRM)); +} + +AnimationTrackEditor::~AnimationTrackEditor() { + if (key_edit) { + memdelete(key_edit); + } +} diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h new file mode 100644 index 0000000000..0692c88bea --- /dev/null +++ b/editor/animation_track_editor.h @@ -0,0 +1,484 @@ +#ifndef ANIMATION_TRACK_EDITOR_H +#define ANIMATION_TRACK_EDITOR_H + +#include "scene/gui/control.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/scroll_bar.h" +#include "scene/gui/slider.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/tab_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/gui/tool_button.h" + +#include "editor/property_selector.h" +#include "editor_data.h" +#include "editor_spin_slider.h" +#include "property_editor.h" +#include "scene/animation/animation_cache.h" +#include "scene/resources/animation.h" +#include "scene_tree_editor.h" + +class AnimationTimelineEdit : public Range { + GDCLASS(AnimationTimelineEdit, Range) + + Ref<Animation> animation; + int name_limit; + Range *zoom; + Range *h_scroll; + float play_position_pos; + + HBoxContainer *len_hb; + EditorSpinSlider *length; + ToolButton *loop; + TextureRect *time_icon; + + MenuButton *add_track; + Control *play_position; //separate control used to draw so updates for only position changed are much faster + HScrollBar *hscroll; + + void _zoom_changed(double); + void _anim_length_changed(double p_new_len); + void _anim_loop_pressed(); + + void _play_position_draw(); + UndoRedo *undo_redo; + Rect2 hsize_rect; + + bool editing; + bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up) + + bool panning_timeline; + float panning_timeline_from; + float panning_timeline_at; + bool dragging_timeline; + bool dragging_hsize; + float dragging_hsize_from; + float dragging_hsize_at; + + void _gui_input(const Ref<InputEvent> &p_event); + void _track_added(int p_track); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + int get_name_limit() const; + int get_buttons_width() const; + + float get_zoom_scale() const; + + virtual Size2 get_minimum_size() const; + void set_animation(const Ref<Animation> &p_animation); + void set_zoom(Range *p_zoom); + Range *get_zoom() const { return zoom; } + void set_undo_redo(UndoRedo *p_undo_redo); + void set_block_animation_update_ptr(bool *p_block_ptr); + + void set_play_position(float p_pos); + float get_play_position() const; + void update_play_position(); + + void update_values(); + + void set_hscroll(HScrollBar *p_hscroll); + + AnimationTimelineEdit(); +}; + +class AnimationTrackEditor; + +class AnimationTrackEdit : public Control { + + GDCLASS(AnimationTrackEdit, Control) + + enum { + MENU_CALL_MODE_CONTINUOUS, + MENU_CALL_MODE_DISCRETE, + MENU_CALL_MODE_TRIGGER, + MENU_CALL_MODE_CAPTURE, + MENU_INTERPOLATION_NEAREST, + MENU_INTERPOLATION_LINEAR, + MENU_INTERPOLATION_CUBIC, + MENU_LOOP_WRAP, + MENU_LOOP_CLAMP, + MENU_KEY_INSERT, + MENU_KEY_DUPLICATE, + MENU_KEY_DELETE + }; + AnimationTimelineEdit *timeline; + UndoRedo *undo_redo; + LineEdit *path; + Node *root; + Control *play_position; //separate control used to draw so updates for only position changed are much faster + float play_position_pos; + + Ref<Animation> animation; + int track; + + Rect2 check_rect; + Rect2 path_rect; + + Rect2 update_mode_rect; + Rect2 interp_mode_rect; + Rect2 loop_mode_rect; + Rect2 remove_rect; + Rect2 bezier_edit_rect; + + Ref<Texture> type_icon; + Ref<Texture> selected_icon; + + PopupMenu *menu; + + bool clicking_on_name; + + void _zoom_changed(); + + Ref<Texture> icon_cache; + String path_cache; + + void _menu_selected(int p_index); + + bool *block_animation_update_ptr; //used to block all tracks re-gen (speed up) + + void _path_entered(const String &p_text); + void _play_position_draw(); + mutable int dropping_at; + + float insert_at_pos; + bool moving_selection_attempt; + int select_single_attempt; + bool moving_selection; + float moving_selection_from_ofs; + + bool in_group; + AnimationTrackEditor *editor; + +protected: + static void _bind_methods(); + void _notification(int p_what); + + virtual void _gui_input(const Ref<InputEvent> &p_event); + +public: + virtual Variant get_drag_data(const Point2 &p_point); + virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; + virtual void drop_data(const Point2 &p_point, const Variant &p_data); + + virtual String get_tooltip(const Point2 &p_pos) const; + + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right); + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + virtual void draw_bg(int p_clip_left, int p_clip_right); + virtual void draw_fg(int p_clip_left, int p_clip_right); + + //helper + void draw_texture_clipped(const Ref<Texture> &p_texture, const Vector2 &p_pos); + void draw_texture_region_clipped(const Ref<Texture> &p_texture, const Rect2 &p_rect, const Rect2 &p_region); + void draw_rect_clipped(const Rect2 &p_rect, const Color &p_color, bool p_filled = true); + + int get_track() const; + Ref<Animation> get_animation() const; + AnimationTimelineEdit *get_timeline() const { return timeline; } + AnimationTrackEditor *get_editor() const { return editor; } + UndoRedo *get_undo_redo() const { return undo_redo; } + bool *get_block_animation_update_ptr() { return block_animation_update_ptr; } + + void set_animation_and_track(const Ref<Animation> &p_animation, int p_track); + virtual Size2 get_minimum_size() const; + + void set_undo_redo(UndoRedo *p_undo_redo); + void set_timeline(AnimationTimelineEdit *p_timeline); + void set_editor(AnimationTrackEditor *p_editor); + void set_root(Node *p_root); + + void set_block_animation_update_ptr(bool *p_block_ptr); + + void set_play_position(float p_pos); + void update_play_position(); + void cancel_drop(); + + void set_in_group(bool p_enable); + void append_to_selection(const Rect2 &p_box); + + AnimationTrackEdit(); +}; + +class AnimationTrackEditPlugin : public Reference { + GDCLASS(AnimationTrackEditPlugin, Reference) +public: + virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage); + virtual AnimationTrackEdit *create_audio_track_edit(); + virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object); +}; + +class AnimationTrackKeyEdit; +class AnimationBezierTrackEdit; + +class AnimationTrackEditGroup : public Control { + GDCLASS(AnimationTrackEditGroup, Control) + Ref<Texture> icon; + String node_name; + NodePath node; + Node *root; + AnimationTimelineEdit *timeline; + + void _zoom_changed(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void set_type_and_name(const Ref<Texture> &p_type, const String &p_name, const NodePath &p_node); + virtual Size2 get_minimum_size() const; + void set_timeline(AnimationTimelineEdit *p_timeline); + void set_root(Node *p_root); + + AnimationTrackEditGroup(); +}; + +class AnimationTrackEditor : public VBoxContainer { + GDCLASS(AnimationTrackEditor, VBoxContainer) + + enum { + EDIT_COPY_TRACKS, + EDIT_COPY_TRACKS_CONFIRM, + EDIT_PASTE_TRACKS, + EDIT_SCALE_SELECTION, + EDIT_SCALE_FROM_CURSOR, + EDIT_SCALE_CONFIRM, + EDIT_DUPLICATE_SELECTION, + EDIT_DUPLICATE_TRANSPOSED, + EDIT_DELETE_SELECTION, + EDIT_GOTO_NEXT_STEP, + EDIT_GOTO_PREV_STEP, + EDIT_OPTIMIZE_ANIMATION, + EDIT_OPTIMIZE_ANIMATION_CONFIRM, + EDIT_CLEAN_UP_ANIMATION, + EDIT_CLEAN_UP_ANIMATION_CONFIRM + }; + + Ref<Animation> animation; + Node *root; + + MenuButton *edit; + + PanelContainer *main_panel; + HScrollBar *hscroll; + ScrollContainer *scroll; + VBoxContainer *track_vbox; + AnimationBezierTrackEdit *bezier_edit; + + AnimationTimelineEdit *timeline; + HSlider *zoom; + EditorSpinSlider *step; + TextureRect *zoom_icon; + ToolButton *snap; + + Vector<AnimationTrackEdit *> track_edits; + Vector<AnimationTrackEditGroup *> groups; + + bool block_animation_update; + + int _get_track_selected(); + void _animation_changed(); + void _update_tracks(); + + void _name_limit_changed(); + void _timeline_changed(float p_new_pos, bool p_drag); + void _track_remove_request(int p_track); + + UndoRedo *undo_redo; + + void _update_scroll(double); + void _update_step(double p_new_step); + void _update_length(double p_new_step); + void _dropped_track(int p_from_track, int p_to_track); + + void _add_track(int p_type); + void _new_track_node_selected(NodePath p_path); + void _new_track_property_selected(String p_name); + + PropertySelector *prop_selector; + PropertySelector *method_selector; + SceneTreeDialog *pick_track; + int adding_track_type; + NodePath adding_track_path; + + bool keying; + + struct InsertData { + + Animation::TrackType type; + NodePath path; + int track_idx; + Variant value; + String query; + bool advance; + }; /* insert_data;*/ + + Label *insert_confirm_text; + CheckBox *insert_confirm_bezier; + ConfirmationDialog *insert_confirm; + bool insert_queue; + bool inserting; + bool insert_query; + List<InsertData> insert_data; + uint64_t insert_frame; + + void _query_insert(const InsertData &p_id); + void _confirm_insert_list(); + int _confirm_insert(InsertData p_id, int p_last_track, bool p_create_beziers = false); + void _insert_delay(); + + void _root_removed(Node *p_root); + + PropertyInfo _find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val = NULL); + + void _timeline_value_changed(double); + + float insert_key_from_track_call_ofs; + int insert_key_from_track_call_track; + void _insert_key_from_track(float p_ofs, int p_track); + void _add_method_key(const String &p_method); + + void _clear_selection(); + void _clear_selection_for_anim(const Ref<Animation> &p_anim); + void _select_at_anim(const Ref<Animation> &p_anim, int p_track, float p_pos); + + //selection + + struct SelectedKey { + + int track; + int key; + bool operator<(const SelectedKey &p_key) const { return track == p_key.track ? key < p_key.key : track < p_key.track; }; + }; + + struct KeyInfo { + + float pos; + }; + + Map<SelectedKey, KeyInfo> selection; + + void _key_selected(int p_key, bool p_single, int p_track); + void _key_deselected(int p_key, int p_track); + + bool moving_selection; + float moving_selection_offset; + void _move_selection_begin(); + void _move_selection(float p_offset); + void _move_selection_commit(); + void _move_selection_cancel(); + + AnimationTrackKeyEdit *key_edit; + void _update_key_edit(); + + void _clear_key_edit(); + + Control *box_selection; + void _box_selection_draw(); + bool box_selecting; + Vector2 box_selecting_from; + Rect2 box_select_rect; + void _scroll_input(const Ref<InputEvent> &p_event); + + Vector<Ref<AnimationTrackEditPlugin> > track_edit_plugins; + + void _cancel_bezier_edit(); + void _bezier_edit(int p_for_track); + + ////////////// edit menu stuff + + ConfirmationDialog *optimize_dialog; + SpinBox *optimize_linear_error; + SpinBox *optimize_angular_error; + SpinBox *optimize_max_angle; + + ConfirmationDialog *cleanup_dialog; + CheckButton *cleanup_keys; + CheckButton *cleanup_tracks; + CheckButton *cleanup_all; + + ConfirmationDialog *scale_dialog; + SpinBox *scale; + + void _edit_menu_pressed(int p_option); + int last_menu_track_opt; + + void _cleanup_animation(Ref<Animation> p_animation); + + void _anim_duplicate_keys(bool transpose); + + void _view_group_toggle(); + ToolButton *view_group; + ToolButton *selected_filter; + + void _selection_changed(); + + ConfirmationDialog *track_copy_dialog; + Tree *track_copy_select; + struct TrackClipboard { + NodePath full_path; + NodePath base_path; + Animation::TrackType track_type; + Animation::InterpolationType interp_type; + Animation::UpdateMode update_mode; + bool loop_wrap; + bool enabled; + + struct Key { + float time; + float transition; + Variant value; + }; + Vector<Key> keys; + }; + + Vector<TrackClipboard> track_clipboard; + + void _insert_animation_key(NodePath p_path, const Variant &p_value); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin); + void remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin); + + void set_animation(const Ref<Animation> &p_anim); + Ref<Animation> get_current_animation() const; + void set_root(Node *p_root); + Node *get_root() const; + void update_keying(); + bool has_keying() const; + + void cleanup(); + + void set_anim_pos(float p_pos); + void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false); + void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); + void insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform); + + void show_select_node_warning(bool p_show); + + bool is_key_selected(int p_track, int p_key) const; + bool is_selection_active() const; + bool is_moving_selection() const; + float get_moving_selection_offset() const; + bool is_snap_enabled(); + float snap_time(float p_value); + + MenuButton *get_edit_menu(); + AnimationTrackEditor(); + ~AnimationTrackEditor(); +}; + +#endif // ANIMATION_TRACK_EDITOR_H diff --git a/editor/animation_track_editor_plugins.cpp b/editor/animation_track_editor_plugins.cpp new file mode 100644 index 0000000000..d0c91f10d9 --- /dev/null +++ b/editor/animation_track_editor_plugins.cpp @@ -0,0 +1,1317 @@ +#include "animation_track_editor_plugins.h" +#include "editor/audio_stream_preview.h" +#include "editor_resource_preview.h" +#include "editor_scale.h" +#include "scene/2d/animated_sprite.h" +#include "scene/2d/sprite.h" +#include "scene/3d/sprite_3d.h" +#include "scene/animation/animation_player.h" +#include "servers/audio/audio_stream.h" +/// BOOL /// +int AnimationTrackEditBool::get_key_height() const { + + Ref<Texture> checked = get_icon("checked", "CheckBox"); + return checked->get_height(); +} +Rect2 AnimationTrackEditBool::get_key_rect(int p_index, float p_pixels_sec) { + + Ref<Texture> checked = get_icon("checked", "CheckBox"); + return Rect2(0, 0, checked->get_width(), get_size().height); +} + +bool AnimationTrackEditBool::is_key_selectable_by_distance() const { + + return false; +} +void AnimationTrackEditBool::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Ref<Texture> icon; + bool checked = get_animation()->track_get_key_value(get_track(), p_index); + + if (checked) + icon = get_icon("checked", "CheckBox"); + else + icon = get_icon("unchecked", "CheckBox"); + + Vector2 ofs(p_x, int(get_size().height - icon->get_height()) / 2); + + draw_texture_clipped(icon, ofs); + + if (p_selected) { + Color color = get_color("accent_color", "Editor"); + draw_rect_clipped(Rect2(ofs, icon->get_size()), color, false); + } +} + +/// COLOR /// + +int AnimationTrackEditColor::get_key_height() const { + + Ref<Font> font = get_font("font", "Label"); + return font->get_height() * 0.8; +} +Rect2 AnimationTrackEditColor::get_key_rect(int p_index, float p_pixels_sec) { + + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + return Rect2(0, 0, fh, get_size().height); +} + +bool AnimationTrackEditColor::is_key_selectable_by_distance() const { + + return false; +} + +void AnimationTrackEditColor::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) { + + int x_from = p_x; + int x_to = p_next_x; + + Ref<Font> font = get_font("font", "Label"); + int fh = (font->get_height() * 0.8); + + x_from += fh - 1; + x_to += 1; + fh /= 3; + + if (x_from > p_clip_right) + return; + + if (x_to < p_clip_left) + return; + + Color color = get_animation()->track_get_key_value(get_track(), p_index); + Color color_next = get_animation()->track_get_key_value(get_track(), p_index + 1); + + if (x_from < p_clip_left) { + float c = float(p_clip_left - x_from) / (x_to - x_from); + color = color.linear_interpolate(color_next, c); + x_from = p_clip_left; + } + + if (x_to > p_clip_right) { + float c = float(p_clip_right - x_from) / (x_to - x_from); + color_next = color.linear_interpolate(color_next, c); + x_to = p_clip_right; + } + + int y_from = (get_size().height - fh) / 2; + + Vector<Vector2> points; + Vector<Color> colors; + + points.push_back(Vector2(x_from, y_from)); + colors.push_back(color); + + points.push_back(Vector2(x_to, y_from)); + colors.push_back(color_next); + + points.push_back(Vector2(x_to, y_from + fh)); + colors.push_back(color_next); + + points.push_back(Vector2(x_from, y_from + fh)); + colors.push_back(color); + + draw_primitive(points, colors, Vector<Vector2>()); +} + +void AnimationTrackEditColor::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Color color = get_animation()->track_get_key_value(get_track(), p_index); + + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + + Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh)); + + draw_rect_clipped(Rect2(rect.position, rect.size / 2), Color(0.4, 0.4, 0.4)); + draw_rect_clipped(Rect2(rect.position + rect.size / 2, rect.size / 2), Color(0.4, 0.4, 0.4)); + draw_rect_clipped(Rect2(rect.position + Vector2(rect.size.x / 2, 0), rect.size / 2), Color(0.6, 0.6, 0.6)); + draw_rect_clipped(Rect2(rect.position + Vector2(0, rect.size.y / 2), rect.size / 2), Color(0.6, 0.6, 0.6)); + draw_rect_clipped(rect, color); + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect_clipped(rect, accent, false); + } +} + +/// AUDIO /// + +void AnimationTrackEditAudio::_preview_changed(ObjectID p_which) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) + return; + + Ref<AudioStream> stream = object->call("get_stream"); + + if (stream.is_valid() && stream->get_instance_id() == p_which) { + update(); + } +} + +int AnimationTrackEditAudio::get_key_height() const { + + if (!ObjectDB::get_instance(id)) { + return AnimationTrackEdit::get_key_height(); + } + + Ref<Font> font = get_font("font", "Label"); + return int(font->get_height() * 1.5); +} +Rect2 AnimationTrackEditAudio::get_key_rect(int p_index, float p_pixels_sec) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + Ref<AudioStream> stream = object->call("get_stream"); + + if (!stream.is_valid()) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + bool play = get_animation()->track_get_key_value(get_track(), p_index); + if (play) { + float len = stream->get_length(); + + if (len == 0) { + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + len = preview->get_length(); + } + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + } + + return Rect2(0, 0, len * p_pixels_sec, get_size().height); + } else { + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + return Rect2(0, 0, fh, get_size().height); + } +} + +bool AnimationTrackEditAudio::is_key_selectable_by_distance() const { + + return false; +} +void AnimationTrackEditAudio::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + Ref<AudioStream> stream = object->call("get_stream"); + + if (!stream.is_valid()) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + Ref<Font> font = get_font("font", "Label"); + float fh = int(font->get_height() * 1.5); + + bool play = get_animation()->track_get_key_value(get_track(), p_index); + if (play) { + float len = stream->get_length(); + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + + float preview_len = preview->get_length(); + + if (len == 0) { + len = preview_len; + } + + int pixel_len = len * p_pixels_sec; + + int pixel_begin = p_x; + int pixel_end = p_x + pixel_len; + + if (pixel_end < p_clip_left) + return; + + if (pixel_begin > p_clip_right) + return; + + int from_x = MAX(pixel_begin, p_clip_left); + int to_x = MIN(pixel_end, p_clip_right); + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + float limit = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + int limit_x = pixel_begin + limit * p_pixels_sec; + to_x = MIN(limit_x, to_x); + } + + if (to_x <= from_x) + return; + + int h = get_size().height; + Rect2 rect = Rect2(from_x, (h - fh) / 2, to_x - from_x, fh); + draw_rect(rect, Color(0.25, 0.25, 0.25)); + + Vector<Vector2> lines; + lines.resize((to_x - from_x + 1) * 2); + preview_len = preview->get_length(); + + for (int i = from_x; i < to_x; i++) { + + float ofs = (i - pixel_begin) * preview_len / pixel_len; + float ofs_n = ((i + 1) - pixel_begin) * preview_len / pixel_len; + float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; + float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; + + int idx = i - from_x; + lines[idx * 2 + 0] = Vector2(i, rect.position.y + min * rect.size.y); + lines[idx * 2 + 1] = Vector2(i, rect.position.y + max * rect.size.y); + } + + Vector<Color> color; + color.push_back(Color(0.75, 0.75, 0.75)); + + VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, color); + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } + } else { + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh)); + + Color color = get_color("font_color", "Label"); + draw_rect(rect, color); + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } + } +} + +void AnimationTrackEditAudio::set_node(Object *p_object) { + + id = p_object->get_instance_id(); +} + +void AnimationTrackEditAudio::_bind_methods() { + ClassDB::bind_method("_preview_changed", &AnimationTrackEditAudio::_preview_changed); +} + +AnimationTrackEditAudio::AnimationTrackEditAudio() { + AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", this, "_preview_changed"); +} + +/// SPRITE FRAME /// + +int AnimationTrackEditSpriteFrame::get_key_height() const { + + if (!ObjectDB::get_instance(id)) { + return AnimationTrackEdit::get_key_height(); + } + + Ref<Font> font = get_font("font", "Label"); + return int(font->get_height() * 2); +} +Rect2 AnimationTrackEditSpriteFrame::get_key_rect(int p_index, float p_pixels_sec) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + Size2 size; + + if (Object::cast_to<Sprite>(object) || Object::cast_to<Sprite3D>(object)) { + + Ref<Texture> texture = object->call("get_texture"); + if (!texture.is_valid()) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + size = texture->get_size(); + + if (bool(object->call("is_region"))) { + size = Rect2(object->call("get_region_rect")).size; + } + + int hframes = object->call("get_hframes"); + int vframes = object->call("get_vframes"); + + if (hframes > 1) { + size.x /= hframes; + } + if (vframes > 1) { + size.y /= vframes; + } + } else if (Object::cast_to<AnimatedSprite>(object) || Object::cast_to<AnimatedSprite3D>(object)) { + + Ref<SpriteFrames> sf = object->call("get_sprite_frames"); + if (sf.is_null()) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + List<StringName> animations; + sf->get_animation_list(&animations); + + int frame = get_animation()->track_get_key_value(get_track(), p_index); + String animation; + if (animations.size() == 1) { + animation = animations.front()->get(); + } else { + // Go through other track to find if animation is set + String animation_path = get_animation()->track_get_path(get_track()); + animation_path = animation_path.replace(":frame", ":animation"); + int animation_track = get_animation()->find_track(animation_path); + float track_time = get_animation()->track_get_key_time(get_track(), p_index); + int animaiton_index = get_animation()->track_find_key(animation_track, track_time); + animation = get_animation()->track_get_key_value(animation_track, animaiton_index); + } + + Ref<Texture> texture = sf->get_frame(animation, frame); + if (!texture.is_valid()) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + size = texture->get_size(); + } + + size = size.floor(); + + Ref<Font> font = get_font("font", "Label"); + int height = int(font->get_height() * 2); + int width = height * size.width / size.height; + + return Rect2(0, 0, width, get_size().height); +} + +bool AnimationTrackEditSpriteFrame::is_key_selectable_by_distance() const { + + return false; +} +void AnimationTrackEditSpriteFrame::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + int frame = get_animation()->track_get_key_value(get_track(), p_index); + + Ref<Texture> texture; + Rect2 region; + + if (Object::cast_to<Sprite>(object) || Object::cast_to<Sprite3D>(object)) { + + texture = object->call("get_texture"); + if (!texture.is_valid()) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + region.size = texture->get_size(); + + if (bool(object->call("is_region"))) { + + region = Rect2(object->call("get_region_rect")); + } + + int hframes = object->call("get_hframes"); + int vframes = object->call("get_vframes"); + + if (hframes > 1) { + region.size.x /= hframes; + } + if (vframes > 1) { + region.size.y /= vframes; + } + + region.position.x += region.size.x * (frame % hframes); + region.position.y += region.size.y * (frame / hframes); + + } else if (Object::cast_to<AnimatedSprite>(object) || Object::cast_to<AnimatedSprite3D>(object)) { + + Ref<SpriteFrames> sf = object->call("get_sprite_frames"); + if (sf.is_null()) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + List<StringName> animations; + sf->get_animation_list(&animations); + + int frame = get_animation()->track_get_key_value(get_track(), p_index); + String animation; + if (animations.size() == 1) { + animation = animations.front()->get(); + } else { + // Go through other track to find if animation is set + String animation_path = get_animation()->track_get_path(get_track()); + animation_path = animation_path.replace(":frame", ":animation"); + int animation_track = get_animation()->find_track(animation_path); + float track_time = get_animation()->track_get_key_time(get_track(), p_index); + int animaiton_index = get_animation()->track_find_key(animation_track, track_time); + animation = get_animation()->track_get_key_value(animation_track, animaiton_index); + } + + texture = sf->get_frame(animation, frame); + if (!texture.is_valid()) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + region.size = texture->get_size(); + } + + Ref<Font> font = get_font("font", "Label"); + int height = int(font->get_height() * 2); + + int width = height * region.size.width / region.size.height; + + Rect2 rect(p_x, int(get_size().height - height) / 2, width, height); + + if (rect.position.x + rect.size.x < p_clip_left) + return; + + if (rect.position.x > p_clip_right) + return; + + Color accent = get_color("accent_color", "Editor"); + Color bg = accent; + bg.a = 0.15; + + draw_rect_clipped(rect, bg); + + draw_texture_region_clipped(texture, rect, region); + + if (p_selected) { + draw_rect_clipped(rect, accent, false); + } +} + +void AnimationTrackEditSpriteFrame::set_node(Object *p_object) { + + id = p_object->get_instance_id(); +} + +/// SUB ANIMATION /// + +int AnimationTrackEditSubAnim::get_key_height() const { + + if (!ObjectDB::get_instance(id)) { + return AnimationTrackEdit::get_key_height(); + } + + Ref<Font> font = get_font("font", "Label"); + return int(font->get_height() * 1.5); +} +Rect2 AnimationTrackEditSubAnim::get_key_rect(int p_index, float p_pixels_sec) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object); + + if (!ap) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + String anim = get_animation()->track_get_key_value(get_track(), p_index); + + if (anim != "[stop]" && ap->has_animation(anim)) { + + float len = ap->get_animation(anim)->get_length(); + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + } + + return Rect2(0, 0, len * p_pixels_sec, get_size().height); + } else { + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + return Rect2(0, 0, fh, get_size().height); + } +} + +bool AnimationTrackEditSubAnim::is_key_selectable_by_distance() const { + + return false; +} +void AnimationTrackEditSubAnim::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object); + + if (!ap) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + String anim = get_animation()->track_get_key_value(get_track(), p_index); + + if (anim != "[stop]" && ap->has_animation(anim)) { + + float len = ap->get_animation(anim)->get_length(); + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + } + + int pixel_len = len * p_pixels_sec; + + int pixel_begin = p_x; + int pixel_end = p_x + pixel_len; + + if (pixel_end < p_clip_left) + return; + + if (pixel_begin > p_clip_right) + return; + + int from_x = MAX(pixel_begin, p_clip_left); + int to_x = MIN(pixel_end, p_clip_right); + + if (to_x <= from_x) + return; + + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 1.5; + + Rect2 rect(from_x, int(get_size().height - fh) / 2, to_x - from_x, fh); + + Color color = get_color("font_color", "Label"); + Color bg = color; + bg.r = 1 - color.r; + bg.g = 1 - color.g; + bg.b = 1 - color.b; + draw_rect(rect, bg); + + Vector<Vector2> lines; + Vector<Color> colorv; + { + Ref<Animation> animation = ap->get_animation(anim); + + for (int i = 0; i < animation->get_track_count(); i++) { + + float h = (rect.size.height - 2) / animation->get_track_count(); + + int y = 2 + h * i + h / 2; + + for (int j = 0; j < animation->track_get_key_count(i); j++) { + + float ofs = animation->track_get_key_time(i, j); + int x = p_x + ofs * p_pixels_sec + 2; + + if (x < from_x || x >= (to_x - 4)) + continue; + + lines.push_back(Point2(x, y)); + lines.push_back(Point2(x + 1, y)); + } + } + + colorv.push_back(color); + } + + if (lines.size() > 2) { + VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, colorv); + } + + int limit = to_x - from_x - 4; + if (limit > 0) { + draw_string(font, Point2(from_x + 2, int(get_size().height - font->get_height()) / 2 + font->get_ascent()), anim, color); + } + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } + } else { + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh)); + + Color color = get_color("font_color", "Label"); + draw_rect(rect, color); + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } + } +} + +void AnimationTrackEditSubAnim::set_node(Object *p_object) { + + id = p_object->get_instance_id(); +} + +//// VOLUME DB //// + +int AnimationTrackEditVolumeDB::get_key_height() const { + + Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons"); + return volume_texture->get_height() * 1.2; +} + +void AnimationTrackEditVolumeDB::draw_bg(int p_clip_left, int p_clip_right) { + + Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons"); + int tex_h = volume_texture->get_height(); + + int y_from = (get_size().height - tex_h) / 2; + int y_size = tex_h; + + Color color(1, 1, 1, 0.3); + draw_texture_rect(volume_texture, Rect2(p_clip_left, y_from, p_clip_right - p_clip_left, y_from + y_size), false, color); +} + +void AnimationTrackEditVolumeDB::draw_fg(int p_clip_left, int p_clip_right) { + + Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons"); + int tex_h = volume_texture->get_height(); + int y_from = (get_size().height - tex_h) / 2; + int db0 = y_from + (24 / 80.0) * tex_h; + + draw_line(Vector2(p_clip_left, db0), Vector2(p_clip_right, db0), Color(1, 1, 1, 0.3)); +} + +void AnimationTrackEditVolumeDB::draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right) { + + if (p_x > p_clip_right || p_next_x < p_clip_left) + return; + + float db = get_animation()->track_get_key_value(get_track(), p_index); + float db_n = get_animation()->track_get_key_value(get_track(), p_index + 1); + + db = CLAMP(db, -60, 24); + db_n = CLAMP(db_n, -60, 24); + + float h = 1.0 - ((db + 60) / 84.0); + float h_n = 1.0 - ((db_n + 60) / 84.0); + + int from_x = p_x; + int to_x = p_next_x; + + if (from_x < p_clip_left) { + h = Math::lerp(h, h_n, float(p_clip_left - from_x) / float(to_x - from_x)); + from_x = p_clip_left; + } + + if (to_x > p_clip_right) { + h_n = Math::lerp(h, h_n, float(p_clip_right - from_x) / float(to_x - from_x)); + to_x = p_clip_right; + } + + Ref<Texture> volume_texture = get_icon("ColorTrackVu", "EditorIcons"); + int tex_h = volume_texture->get_height(); + + int y_from = (get_size().height - tex_h) / 2; + + Color color = get_color("font_color", "Label"); + color.a *= 0.7; + + draw_line(Point2(from_x, y_from + h * tex_h), Point2(to_x, y_from + h_n * tex_h), color, 2); +} + +//////////////////////// + +/// AUDIO /// + +void AnimationTrackEditTypeAudio::_preview_changed(ObjectID p_which) { + + for (int i = 0; i < get_animation()->track_get_key_count(get_track()); i++) { + Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), i); + if (stream.is_valid() && stream->get_instance_id() == p_which) { + update(); + return; + } + } +} + +int AnimationTrackEditTypeAudio::get_key_height() const { + + Ref<Font> font = get_font("font", "Label"); + return int(font->get_height() * 1.5); +} +Rect2 AnimationTrackEditTypeAudio::get_key_rect(int p_index, float p_pixels_sec) { + + Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), p_index); + + if (!stream.is_valid()) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), p_index); + float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), p_index); + + float len = stream->get_length(); + + if (len == 0) { + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + len = preview->get_length(); + } + + len -= end_ofs; + len -= start_ofs; + if (len <= 0.001) { + len = 0.001; + } + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + } + + return Rect2(0, 0, len * p_pixels_sec, get_size().height); +} + +bool AnimationTrackEditTypeAudio::is_key_selectable_by_distance() const { + + return false; +} +void AnimationTrackEditTypeAudio::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), p_index); + + if (!stream.is_valid()) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), p_index); + float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), p_index); + + if (len_resizing && p_index == len_resizing_index) { + float ofs_local = -len_resizing_rel / get_timeline()->get_zoom_scale(); + if (len_resizing_start) { + start_ofs += ofs_local; + if (start_ofs < 0) + start_ofs = 0; + } else { + end_ofs += ofs_local; + if (end_ofs < 0) + end_ofs = 0; + } + } + + Ref<Font> font = get_font("font", "Label"); + float fh = int(font->get_height() * 1.5); + + float len = stream->get_length(); + + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + + float preview_len = preview->get_length(); + + if (len == 0) { + len = preview_len; + } + + int pixel_total_len = len * p_pixels_sec; + + len -= end_ofs; + len -= start_ofs; + + if (len <= 0.001) { + len = 0.001; + } + + int pixel_len = len * p_pixels_sec; + + int pixel_begin = p_x; + int pixel_end = p_x + pixel_len; + + if (pixel_end < p_clip_left) + return; + + if (pixel_begin > p_clip_right) + return; + + int from_x = MAX(pixel_begin, p_clip_left); + int to_x = MIN(pixel_end, p_clip_right); + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + float limit = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + int limit_x = pixel_begin + limit * p_pixels_sec; + to_x = MIN(limit_x, to_x); + } + + if (to_x <= from_x) { + to_x = from_x + 1; + } + + int h = get_size().height; + Rect2 rect = Rect2(from_x, (h - fh) / 2, to_x - from_x, fh); + draw_rect(rect, Color(0.25, 0.25, 0.25)); + + Vector<Vector2> lines; + lines.resize((to_x - from_x + 1) * 2); + preview_len = preview->get_length(); + + for (int i = from_x; i < to_x; i++) { + + float ofs = (i - pixel_begin) * preview_len / pixel_total_len; + float ofs_n = ((i + 1) - pixel_begin) * preview_len / pixel_total_len; + ofs += start_ofs; + ofs_n += start_ofs; + + float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5; + float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5; + + int idx = i - from_x; + lines[idx * 2 + 0] = Vector2(i, rect.position.y + min * rect.size.y); + lines[idx * 2 + 1] = Vector2(i, rect.position.y + max * rect.size.y); + } + + Vector<Color> color; + color.push_back(Color(0.75, 0.75, 0.75)); + + VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, color); + + Color cut_color = get_color("accent_color", "Editor"); + cut_color.a = 0.7; + if (start_ofs > 0 && pixel_begin > p_clip_left) { + draw_rect(Rect2(pixel_begin, rect.position.y, 1, rect.size.y), cut_color); + } + if (end_ofs > 0 && pixel_end < p_clip_right) { + draw_rect(Rect2(pixel_end, rect.position.y, 1, rect.size.y), cut_color); + } + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } +} + +void AnimationTrackEditTypeAudio::_bind_methods() { + ClassDB::bind_method("_preview_changed", &AnimationTrackEditTypeAudio::_preview_changed); +} + +AnimationTrackEditTypeAudio::AnimationTrackEditTypeAudio() { + AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", this, "_preview_changed"); + len_resizing = false; +} + +bool AnimationTrackEditTypeAudio::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + + if (p_point.x > get_timeline()->get_name_limit() && p_point.x < get_size().width - get_timeline()->get_buttons_width()) { + + Dictionary drag_data = p_data; + if (drag_data.has("type") && String(drag_data["type"]) == "resource") { + Ref<AudioStream> res = drag_data["resource"]; + if (res.is_valid()) { + return true; + } + } + + if (drag_data.has("type") && String(drag_data["type"]) == "files") { + + Vector<String> files = drag_data["files"]; + + if (files.size() == 1) { + String file = files[0]; + Ref<AudioStream> res = ResourceLoader::load(file); + if (res.is_valid()) { + return true; + } + } + } + } + + return AnimationTrackEdit::can_drop_data(p_point, p_data); +} +void AnimationTrackEditTypeAudio::drop_data(const Point2 &p_point, const Variant &p_data) { + + if (p_point.x > get_timeline()->get_name_limit() && p_point.x < get_size().width - get_timeline()->get_buttons_width()) { + + Ref<AudioStream> stream; + Dictionary drag_data = p_data; + if (drag_data.has("type") && String(drag_data["type"]) == "resource") { + stream = drag_data["resource"]; + } else if (drag_data.has("type") && String(drag_data["type"]) == "files") { + + Vector<String> files = drag_data["files"]; + + if (files.size() == 1) { + String file = files[0]; + stream = ResourceLoader::load(file); + } + } + + if (stream.is_valid()) { + + int x = p_point.x - get_timeline()->get_name_limit(); + float ofs = x / get_timeline()->get_zoom_scale(); + ofs += get_timeline()->get_value(); + + ofs = get_editor()->snap_time(ofs); + + while (get_animation()->track_find_key(get_track(), ofs, true) != -1) { //make sure insertion point is valid + ofs += 0.001; + } + + print_line("inserting"); + + *get_block_animation_update_ptr() = true; + get_undo_redo()->create_action("Add Audio Track Clip"); + get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_insert_key", get_track(), ofs, stream); + get_undo_redo()->add_undo_method(get_animation().ptr(), "track_remove_key_at_position", get_track(), ofs); + get_undo_redo()->commit_action(); + *get_block_animation_update_ptr() = false; + + update(); + return; + } + } + + return AnimationTrackEdit::drop_data(p_point, p_data); +} + +void AnimationTrackEditTypeAudio::_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventMouseMotion> mm = p_event; + if (!len_resizing && mm.is_valid()) { + bool use_hsize_cursor = false; + for (int i = 0; i < get_animation()->track_get_key_count(get_track()); i++) { + + Ref<AudioStream> stream = get_animation()->audio_track_get_key_stream(get_track(), i); + + if (!stream.is_valid()) { + continue; + } + + float start_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), i); + float end_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), i); + float len = stream->get_length(); + + if (len == 0) { + Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(stream); + float preview_len = preview->get_length(); + len = preview_len; + } + + len -= end_ofs; + len -= start_ofs; + if (len <= 0.001) { + len = 0.001; + } + + if (get_animation()->track_get_key_count(get_track()) > i + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), i + 1) - get_animation()->track_get_key_time(get_track(), i)); + } + + float ofs = get_animation()->track_get_key_time(get_track(), i); + + ofs -= get_timeline()->get_value(); + ofs *= get_timeline()->get_zoom_scale(); + ofs += get_timeline()->get_name_limit(); + + int end = ofs + len * get_timeline()->get_zoom_scale(); + + if (end >= get_timeline()->get_name_limit() && end <= get_size().width - get_timeline()->get_buttons_width() && ABS(mm->get_position().x - end) < 5 * EDSCALE) { + use_hsize_cursor = true; + len_resizing_index = i; + } + } + + if (use_hsize_cursor) { + set_default_cursor_shape(CURSOR_HSIZE); + } else { + set_default_cursor_shape(CURSOR_ARROW); + } + } + + if (len_resizing && mm.is_valid()) { + len_resizing_rel += mm->get_relative().x; + len_resizing_start = mm->get_shift(); + update(); + accept_event(); + return; + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT && get_default_cursor_shape() == CURSOR_HSIZE) { + + len_resizing = true; + len_resizing_start = mb->get_shift(); + len_resizing_from_px = mb->get_position().x; + len_resizing_rel = 0; + update(); + accept_event(); + return; + } + + if (len_resizing && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + float ofs_local = -len_resizing_rel / get_timeline()->get_zoom_scale(); + if (len_resizing_start) { + float prev_ofs = get_animation()->audio_track_get_key_start_offset(get_track(), len_resizing_index); + *get_block_animation_update_ptr() = true; + get_undo_redo()->create_action("Change Audio Track Clip Start Offset"); + get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_set_key_start_offset", get_track(), len_resizing_index, prev_ofs + ofs_local); + get_undo_redo()->add_undo_method(get_animation().ptr(), "audio_track_set_key_start_offset", get_track(), len_resizing_index, prev_ofs); + get_undo_redo()->commit_action(); + *get_block_animation_update_ptr() = false; + + } else { + float prev_ofs = get_animation()->audio_track_get_key_end_offset(get_track(), len_resizing_index); + *get_block_animation_update_ptr() = true; + get_undo_redo()->create_action("Change Audio Track Clip End Offset"); + get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_set_key_end_offset", get_track(), len_resizing_index, prev_ofs + ofs_local); + get_undo_redo()->add_undo_method(get_animation().ptr(), "audio_track_set_key_end_offset", get_track(), len_resizing_index, prev_ofs); + get_undo_redo()->commit_action(); + *get_block_animation_update_ptr() = false; + } + + len_resizing = false; + len_resizing_index = -1; + update(); + accept_event(); + return; + } + + AnimationTrackEdit::_gui_input(p_event); +} + +//////////////////// +/// SUB ANIMATION /// + +int AnimationTrackEditTypeAnimation::get_key_height() const { + + if (!ObjectDB::get_instance(id)) { + return AnimationTrackEdit::get_key_height(); + } + + Ref<Font> font = get_font("font", "Label"); + return int(font->get_height() * 1.5); +} +Rect2 AnimationTrackEditTypeAnimation::get_key_rect(int p_index, float p_pixels_sec) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object); + + if (!ap) { + return AnimationTrackEdit::get_key_rect(p_index, p_pixels_sec); + } + + String anim = get_animation()->animation_track_get_key_animation(get_track(), p_index); + print_line("anim " + anim + " has " + itos(ap->has_animation(anim))); + + if (anim != "[stop]" && ap->has_animation(anim)) { + + float len = ap->get_animation(anim)->get_length(); + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + } + + return Rect2(0, 0, len * p_pixels_sec, get_size().height); + } else { + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + return Rect2(0, 0, fh, get_size().height); + } +} + +bool AnimationTrackEditTypeAnimation::is_key_selectable_by_distance() const { + + return false; +} +void AnimationTrackEditTypeAnimation::draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right) { + + Object *object = ObjectDB::get_instance(id); + + if (!object) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(object); + + if (!ap) { + AnimationTrackEdit::draw_key(p_index, p_pixels_sec, p_x, p_selected, p_clip_left, p_clip_right); + return; + } + + String anim = get_animation()->animation_track_get_key_animation(get_track(), p_index); + + if (anim != "[stop]" && ap->has_animation(anim)) { + + float len = ap->get_animation(anim)->get_length(); + + if (get_animation()->track_get_key_count(get_track()) > p_index + 1) { + len = MIN(len, get_animation()->track_get_key_time(get_track(), p_index + 1) - get_animation()->track_get_key_time(get_track(), p_index)); + } + + int pixel_len = len * p_pixels_sec; + + int pixel_begin = p_x; + int pixel_end = p_x + pixel_len; + + if (pixel_end < p_clip_left) + return; + + if (pixel_begin > p_clip_right) + return; + + int from_x = MAX(pixel_begin, p_clip_left); + int to_x = MIN(pixel_end, p_clip_right); + + if (to_x <= from_x) + return; + + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 1.5; + + Rect2 rect(from_x, int(get_size().height - fh) / 2, to_x - from_x, fh); + + Color color = get_color("font_color", "Label"); + Color bg = color; + bg.r = 1 - color.r; + bg.g = 1 - color.g; + bg.b = 1 - color.b; + draw_rect(rect, bg); + + Vector<Vector2> lines; + Vector<Color> colorv; + { + Ref<Animation> animation = ap->get_animation(anim); + + for (int i = 0; i < animation->get_track_count(); i++) { + + float h = (rect.size.height - 2) / animation->get_track_count(); + + int y = 2 + h * i + h / 2; + + for (int j = 0; j < animation->track_get_key_count(i); j++) { + + float ofs = animation->track_get_key_time(i, j); + int x = p_x + ofs * p_pixels_sec + 2; + + if (x < from_x || x >= (to_x - 4)) + continue; + + lines.push_back(Point2(x, y)); + lines.push_back(Point2(x + 1, y)); + } + } + + colorv.push_back(color); + } + + if (lines.size() > 2) { + VS::get_singleton()->canvas_item_add_multiline(get_canvas_item(), lines, colorv); + } + + int limit = to_x - from_x - 4; + if (limit > 0) { + draw_string(font, Point2(from_x + 2, int(get_size().height - font->get_height()) / 2 + font->get_ascent()), anim, color); + } + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } + } else { + Ref<Font> font = get_font("font", "Label"); + int fh = font->get_height() * 0.8; + Rect2 rect(Vector2(p_x, int(get_size().height - fh) / 2), Size2(fh, fh)); + + Color color = get_color("font_color", "Label"); + draw_rect(rect, color); + + if (p_selected) { + Color accent = get_color("accent_color", "Editor"); + draw_rect(rect, accent, false); + } + } +} + +void AnimationTrackEditTypeAnimation::set_node(Object *p_object) { + + id = p_object->get_instance_id(); +} + +AnimationTrackEditTypeAnimation::AnimationTrackEditTypeAnimation() { +} + +///////// +AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage) { + + if (p_property == "playing" && (p_object->is_class("AudioStreamPlayer") || p_object->is_class("AudioStreamPlayer2D") || p_object->is_class("AudioStreamPlayer3D"))) { + + AnimationTrackEditAudio *audio = memnew(AnimationTrackEditAudio); + audio->set_node(p_object); + return audio; + } + + if (p_property == "frame" && (p_object->is_class("Sprite") || p_object->is_class("Sprite3D") || p_object->is_class("AnimatedSprite") || p_object->is_class("AnimatedSprite3D"))) { + + AnimationTrackEditSpriteFrame *sprite = memnew(AnimationTrackEditSpriteFrame); + sprite->set_node(p_object); + return sprite; + } + + if (p_property == "current_animation" && (p_object->is_class("AnimationPlayer"))) { + + AnimationTrackEditSubAnim *player = memnew(AnimationTrackEditSubAnim); + player->set_node(p_object); + return player; + } + + if (p_property == "volume_db") { + + AnimationTrackEditVolumeDB *vu = memnew(AnimationTrackEditVolumeDB); + return vu; + } + + if (p_type == Variant::BOOL) { + return memnew(AnimationTrackEditBool); + } + if (p_type == Variant::COLOR) { + return memnew(AnimationTrackEditColor); + } + + return NULL; +} + +AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_audio_track_edit() { + + return memnew(AnimationTrackEditTypeAudio); +} + +AnimationTrackEdit *AnimationTrackEditDefaultPlugin::create_animation_track_edit(Object *p_object) { + + AnimationTrackEditTypeAnimation *an = memnew(AnimationTrackEditTypeAnimation); + an->set_node(p_object); + return an; +} diff --git a/editor/animation_track_editor_plugins.h b/editor/animation_track_editor_plugins.h new file mode 100644 index 0000000000..59604412d9 --- /dev/null +++ b/editor/animation_track_editor_plugins.h @@ -0,0 +1,139 @@ +#ifndef ANIMATION_TRACK_EDITOR_PLUGINS_H +#define ANIMATION_TRACK_EDITOR_PLUGINS_H + +#include "editor/animation_track_editor.h" + +class AnimationTrackEditBool : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditBool, AnimationTrackEdit) + Ref<Texture> icon_checked; + Ref<Texture> icon_unchecked; + +public: + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); +}; + +class AnimationTrackEditColor : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditColor, AnimationTrackEdit) + +public: + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right); +}; + +class AnimationTrackEditAudio : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditAudio, AnimationTrackEdit) + + ObjectID id; + + void _preview_changed(ObjectID p_which); + +protected: + static void _bind_methods(); + +public: + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + + void set_node(Object *p_object); + + AnimationTrackEditAudio(); +}; + +class AnimationTrackEditSpriteFrame : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditSpriteFrame, AnimationTrackEdit) + + ObjectID id; + +public: + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + + void set_node(Object *p_object); +}; + +class AnimationTrackEditSubAnim : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditSubAnim, AnimationTrackEdit) + + ObjectID id; + +public: + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + + void set_node(Object *p_object); +}; + +class AnimationTrackEditTypeAudio : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditTypeAudio, AnimationTrackEdit) + + void _preview_changed(ObjectID p_which); + + bool len_resizing; + bool len_resizing_start; + int len_resizing_index; + float len_resizing_from_px; + float len_resizing_rel; + +protected: + static void _bind_methods(); + +public: + virtual void _gui_input(const Ref<InputEvent> &p_event); + + virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; + virtual void drop_data(const Point2 &p_point, const Variant &p_data); + + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + + AnimationTrackEditTypeAudio(); +}; + +class AnimationTrackEditTypeAnimation : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditTypeAnimation, AnimationTrackEdit) + + ObjectID id; + +public: + virtual int get_key_height() const; + virtual Rect2 get_key_rect(int p_index, float p_pixels_sec); + virtual bool is_key_selectable_by_distance() const; + virtual void draw_key(int p_index, float p_pixels_sec, int p_x, bool p_selected, int p_clip_left, int p_clip_right); + + void set_node(Object *p_object); + AnimationTrackEditTypeAnimation(); +}; + +class AnimationTrackEditVolumeDB : public AnimationTrackEdit { + GDCLASS(AnimationTrackEditVolumeDB, AnimationTrackEdit) + +public: + virtual void draw_bg(int p_clip_left, int p_clip_right); + virtual void draw_fg(int p_clip_left, int p_clip_right); + virtual int get_key_height() const; + virtual void draw_key_link(int p_index, float p_pixels_sec, int p_x, int p_next_x, int p_clip_left, int p_clip_right); +}; + +class AnimationTrackEditDefaultPlugin : public AnimationTrackEditPlugin { + GDCLASS(AnimationTrackEditDefaultPlugin, AnimationTrackEditPlugin) +public: + virtual AnimationTrackEdit *create_value_track_edit(Object *p_object, Variant::Type p_type, const String &p_property, PropertyHint p_hint, const String &p_hint_string, int p_usage); + virtual AnimationTrackEdit *create_audio_track_edit(); + virtual AnimationTrackEdit *create_animation_track_edit(Object *p_object); +}; + +#endif // ANIMATION_TRACK_EDITOR_PLUGINS_H diff --git a/editor/audio_stream_preview.cpp b/editor/audio_stream_preview.cpp new file mode 100644 index 0000000000..6ee4d7f4b0 --- /dev/null +++ b/editor/audio_stream_preview.cpp @@ -0,0 +1,211 @@ +#include "audio_stream_preview.h" + +///////////////////// + +float AudioStreamPreview::get_length() const { + return length; +} +float AudioStreamPreview::get_max(float p_time, float p_time_next) const { + + if (length == 0) + return 0; + + int max = preview.size() / 2; + int time_from = p_time / length * max; + int time_to = p_time_next / length * max; + time_from = CLAMP(time_from, 0, max - 1); + time_to = CLAMP(time_to, 0, max - 1); + + if (time_to <= time_from) { + time_to = time_from + 1; + } + + uint8_t vmax; + + for (int i = time_from; i < time_to; i++) { + + uint8_t v = preview[i * 2 + 1]; + if (i == 0 || v > vmax) { + vmax = v; + } + } + + return (vmax / 255.0) * 2.0 - 1.0; +} +float AudioStreamPreview::get_min(float p_time, float p_time_next) const { + + if (length == 0) + return 0; + + int max = preview.size() / 2; + int time_from = p_time / length * max; + int time_to = p_time_next / length * max; + time_from = CLAMP(time_from, 0, max - 1); + time_to = CLAMP(time_to, 0, max - 1); + + if (time_to <= time_from) { + time_to = time_from + 1; + } + + uint8_t vmin; + + for (int i = time_from; i < time_to; i++) { + + uint8_t v = preview[i * 2]; + if (i == 0 || v < vmin) { + vmin = v; + } + } + + return (vmin / 255.0) * 2.0 - 1.0; +} + +AudioStreamPreview::AudioStreamPreview() { + length = 0; +} + +//// + +void AudioStreamPreviewGenerator::_update_emit(ObjectID p_id) { + emit_signal("preview_updated", p_id); +} + +void AudioStreamPreviewGenerator::_preview_thread(void *p_preview) { + + Preview *preview = (Preview *)p_preview; + + float muxbuff_chunk_s = 0.25; + + int mixbuff_chunk_frames = AudioServer::get_singleton()->get_mix_rate() * muxbuff_chunk_s; + + Vector<AudioFrame> mix_chunk; + mix_chunk.resize(mixbuff_chunk_frames); + + int frames_total = AudioServer::get_singleton()->get_mix_rate() * preview->preview->length; + int frames_todo = frames_total; + + preview->playback->start(); + + while (frames_todo) { + + int ofs_write = uint64_t(frames_total - frames_todo) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total); + int to_read = MIN(frames_todo, mixbuff_chunk_frames); + int to_write = uint64_t(to_read) * uint64_t(preview->preview->preview.size() / 2) / uint64_t(frames_total); + to_write = MIN(to_write, (preview->preview->preview.size() / 2) - ofs_write); + + preview->playback->mix(mix_chunk.ptrw(), 1.0, to_read); + + for (int i = 0; i < to_write; i++) { + float max = -1000; + float min = 1000; + int from = uint64_t(i) * to_read / to_write; + int to = uint64_t(i + 1) * to_read / to_write; + to = MIN(to, to_read); + from = MIN(from, to_read - 1); + if (to == from) { + to = from + 1; + } + + for (int j = from; j < to; j++) { + + max = MAX(max, mix_chunk[j].l); + max = MAX(max, mix_chunk[j].r); + + min = MIN(min, mix_chunk[j].l); + min = MIN(min, mix_chunk[j].r); + } + + uint8_t pfrom = CLAMP((min * 0.5 + 0.5) * 255, 0, 255); + uint8_t pto = CLAMP((max * 0.5 + 0.5) * 255, 0, 255); + + preview->preview->preview[(ofs_write + i) * 2 + 0] = pfrom; + preview->preview->preview[(ofs_write + i) * 2 + 1] = pto; + } + + frames_todo -= to_read; + singleton->call_deferred("_update_emit", preview->id); + } + + preview->playback->stop(); + + preview->generating = false; +} + +Ref<AudioStreamPreview> AudioStreamPreviewGenerator::generate_preview(const Ref<AudioStream> &p_stream) { + ERR_FAIL_COND_V(p_stream.is_null(), Ref<AudioStreamPreview>()); + + if (previews.has(p_stream->get_instance_id())) { + return previews[p_stream->get_instance_id()].preview; + } + + //no preview exists + + previews[p_stream->get_instance_id()] = Preview(); + + Preview *preview = &previews[p_stream->get_instance_id()]; + preview->base_stream = p_stream; + preview->playback = preview->base_stream->instance_playback(); + preview->generating = true; + preview->id = p_stream->get_instance_id(); + + float len_s = preview->base_stream->get_length(); + if (len_s == 0) { + len_s = 60 * 5; //five minutes + } + + int frames = AudioServer::get_singleton()->get_mix_rate() * len_s; + + Vector<uint8_t> maxmin; + int pw = frames / 20; + maxmin.resize(pw * 2); + { + uint8_t *ptr = maxmin.ptrw(); + for (int i = 0; i < pw * 2; i++) { + ptr[i] = 127; + } + } + + preview->preview.instance(); + preview->preview->preview = maxmin; + preview->preview->length = len_s; + + preview->thread = Thread::create(_preview_thread, preview); + + return preview->preview; +} + +void AudioStreamPreviewGenerator::_bind_methods() { + ClassDB::bind_method("_update_emit", &AudioStreamPreviewGenerator::_update_emit); + ClassDB::bind_method(D_METHOD("generate_preview", "stream"), &AudioStreamPreviewGenerator::generate_preview); + + ADD_SIGNAL(MethodInfo("preview_updated", PropertyInfo(Variant::INT, "obj_id"))); +} + +AudioStreamPreviewGenerator *AudioStreamPreviewGenerator::singleton = NULL; + +void AudioStreamPreviewGenerator::_notification(int p_what) { + if (p_what == NOTIFICATION_PROCESS) { + List<ObjectID> to_erase; + for (Map<ObjectID, Preview>::Element *E = previews.front(); E; E = E->next()) { + if (!E->get().generating) { + if (E->get().thread) { + Thread::wait_to_finish(E->get().thread); + E->get().thread = NULL; + } + if (!ObjectDB::get_instance(E->key())) { //no longer in use, get rid of preview + to_erase.push_back(E->key()); + } + } + } + + while (to_erase.front()) { + previews.erase(to_erase.front()->get()); + to_erase.pop_front(); + } + } +} + +AudioStreamPreviewGenerator::AudioStreamPreviewGenerator() { + singleton = this; + set_process(true); +} diff --git a/editor/audio_stream_preview.h b/editor/audio_stream_preview.h new file mode 100644 index 0000000000..cfe1667e9d --- /dev/null +++ b/editor/audio_stream_preview.h @@ -0,0 +1,56 @@ +#ifndef AUDIO_STREAM_PREVIEW_H +#define AUDIO_STREAM_PREVIEW_H + +#include "os/thread.h" +#include "scene/main/node.h" +#include "servers/audio/audio_stream.h" + +class AudioStreamPreview : public Reference { + GDCLASS(AudioStreamPreview, Reference) + friend class AudioStream; + Vector<uint8_t> preview; + float length; + + friend class AudioStreamPreviewGenerator; + +public: + float get_length() const; + float get_max(float p_time, float p_time_next) const; + float get_min(float p_time, float p_time_next) const; + + AudioStreamPreview(); +}; + +class AudioStreamPreviewGenerator : public Node { + GDCLASS(AudioStreamPreviewGenerator, Node) + + static AudioStreamPreviewGenerator *singleton; + + struct Preview { + Ref<AudioStreamPreview> preview; + Ref<AudioStream> base_stream; + Ref<AudioStreamPlayback> playback; + volatile bool generating; + ObjectID id; + Thread *thread; + }; + + Map<ObjectID, Preview> previews; + + static void _preview_thread(void *p_preview); + + void _update_emit(ObjectID p_id); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + static AudioStreamPreviewGenerator *get_singleton() { return singleton; } + + Ref<AudioStreamPreview> generate_preview(const Ref<AudioStream> &p_preview); + + AudioStreamPreviewGenerator(); +}; + +#endif // AUDIO_STREAM_PREVIEW_H diff --git a/editor/doc/doc_data.cpp b/editor/doc/doc_data.cpp index c992ac5f16..542dca74e0 100644 --- a/editor/doc/doc_data.cpp +++ b/editor/doc/doc_data.cpp @@ -233,7 +233,12 @@ void DocData::generate(bool p_basic_types) { c.category = ClassDB::get_category(name); List<PropertyInfo> properties; - ClassDB::get_property_list(name, &properties, true); + if (name == "ProjectSettings") { + //special case for project settings, so settings can be documented + ProjectSettings::get_singleton()->get_property_list(&properties); + } else { + ClassDB::get_property_list(name, &properties, true); + } for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { if (E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_CATEGORY || E->get().usage & PROPERTY_USAGE_INTERNAL) @@ -810,9 +815,24 @@ Error DocData::_load(Ref<XMLParser> parser) { if (parser->get_node_type() == XMLParser::NODE_TEXT) c.description = parser->get_node_data(); } else if (name == "tutorials") { - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) - c.tutorials = parser->get_node_data(); + while (parser->read() == OK) { + + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + + String name = parser->get_node_name(); + + if (name == "link") { + + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) + c.tutorials.push_back(parser->get_node_data().strip_edges()); + } else { + ERR_EXPLAIN("Invalid tag in doc file: " + name); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "tutorials") + break; //end of <tutorials> + } } else if (name == "demos") { parser->read(); if (parser->get_node_type() == XMLParser::NODE_TEXT) @@ -987,7 +1007,9 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri _write_string(f, 2, c.description.strip_edges().xml_escape()); _write_string(f, 1, "</description>"); _write_string(f, 1, "<tutorials>"); - _write_string(f, 2, c.tutorials.strip_edges().xml_escape()); + for (int i = 0; i < c.tutorials.size(); i++) { + _write_string(f, 2, "<link>" + c.tutorials.get(i).xml_escape() + "</link>"); + } _write_string(f, 1, "</tutorials>"); _write_string(f, 1, "<demos>"); _write_string(f, 2, c.demos.strip_edges().xml_escape()); diff --git a/editor/doc/doc_data.h b/editor/doc/doc_data.h index 0461133f9f..c7b70b5fb9 100644 --- a/editor/doc/doc_data.h +++ b/editor/doc/doc_data.h @@ -85,7 +85,7 @@ public: String category; String brief_description; String description; - String tutorials; + Vector<String> tutorials; String demos; Vector<MethodDoc> methods; Vector<MethodDoc> signals; diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index 360ed620f6..4b09db0a9e 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -31,11 +31,11 @@ #include "editor_about.h" #include "editor_node.h" -#include "authors.gen.h" -#include "donors.gen.h" -#include "license.gen.h" -#include "version.h" -#include "version_hash.gen.h" +#include "core/authors.gen.h" +#include "core/donors.gen.h" +#include "core/license.gen.h" +#include "core/version.h" +#include "core/version_hash.gen.h" void EditorAbout::_notification(int p_what) { @@ -69,7 +69,7 @@ TextureRect *EditorAbout::get_logo() const { return _logo; } -ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char **p_src[], const int p_flag_single_column) { +ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column) { ScrollContainer *sc = memnew(ScrollContainer); sc->set_name(p_name); @@ -82,7 +82,7 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St for (int i = 0; i < p_sections.size(); i++) { bool single_column = p_flag_single_column & 1 << i; - const char **names_ptr = p_src[i]; + const char *const *names_ptr = p_src[i]; if (*names_ptr) { Label *lbl = memnew(Label); @@ -151,7 +151,8 @@ EditorAbout::EditorAbout() { dev_sections.push_back(TTR("Lead Developer")); dev_sections.push_back(TTR("Project Manager ")); // " " appended to distinguish between 'project supervisor' and 'project list' dev_sections.push_back(TTR("Developers")); - const char **dev_src[] = { dev_founders, dev_lead, dev_manager, dev_names }; + const char *const *dev_src[] = { AUTHORS_FOUNDERS, AUTHORS_LEAD_DEVELOPERS, + AUTHORS_PROJECT_MANAGERS, AUTHORS_DEVELOPERS }; tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 1)); // Donors @@ -163,7 +164,8 @@ EditorAbout::EditorAbout() { donor_sections.push_back(TTR("Gold Donors")); donor_sections.push_back(TTR("Silver Donors")); donor_sections.push_back(TTR("Bronze Donors")); - const char **donor_src[] = { donor_s_plat, donor_s_gold, donor_s_mini, donor_gold, donor_silver, donor_bronze }; + const char *const *donor_src[] = { DONORS_SPONSOR_PLAT, DONORS_SPONSOR_GOLD, + DONORS_SPONSOR_MINI, DONORS_GOLD, DONORS_SILVER, DONORS_BRONZE }; tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 3)); // License @@ -172,7 +174,7 @@ EditorAbout::EditorAbout() { _license_text->set_name(TTR("License")); _license_text->set_h_size_flags(Control::SIZE_EXPAND_FILL); _license_text->set_v_size_flags(Control::SIZE_EXPAND_FILL); - _license_text->set_text(String::utf8(about_license)); + _license_text->set_text(String::utf8(GODOT_LICENSE_TEXT)); tc->add_child(_license_text); // Thirdparty License @@ -186,6 +188,7 @@ EditorAbout::EditorAbout() { tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); tpl_label->set_autowrap(true); tpl_label->set_text(TTR("Godot Engine relies on a number of thirdparty free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such thirdparty components with their respective copyright statements and license terms.")); + tpl_label->set_size(Size2(630, 1) * EDSCALE); license_thirdparty->add_child(tpl_label); HSplitContainer *tpl_hbc = memnew(HSplitContainer); @@ -207,20 +210,27 @@ EditorAbout::EditorAbout() { tpl_ti_lc->set_selectable(0, false); int read_idx = 0; String long_text = ""; - for (int i = 0; i < THIRDPARTY_COUNT; i++) { + for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) { + const ComponentCopyright &component = COPYRIGHT_INFO[component_index]; TreeItem *ti = _tpl_tree->create_item(tpl_ti_tp); - String thirdparty = String(about_thirdparty[i]); - ti->set_text(0, thirdparty); - String text = thirdparty + "\n"; - long_text += "- " + thirdparty + "\n\n"; - for (int j = 0; j < about_tp_copyright_count[i]; j++) { - - text += "\n Files:\n " + String(about_tp_file[read_idx]).replace("\n", "\n ") + "\n"; - String copyright = String::utf8(" \xc2\xa9 ") + String::utf8(about_tp_copyright[read_idx]).replace("\n", String::utf8("\n \xc2\xa9 ")); + String component_name = component.name; + ti->set_text(0, component_name); + String text = component_name + "\n"; + long_text += "- " + component_name + "\n"; + for (int part_index = 0; part_index < component.part_count; part_index++) { + const ComponentCopyrightPart &part = component.parts[part_index]; + text += "\n Files:"; + for (int file_num = 0; file_num < part.file_count; file_num++) { + text += "\n " + String(part.files[file_num]); + } + String copyright; + for (int copyright_index = 0; copyright_index < part.copyright_count; copyright_index++) { + copyright += String::utf8("\n \xc2\xa9 ") + String::utf8(part.copyright_statements[copyright_index]); + } text += copyright; long_text += copyright; - String license = "\n License: " + String(about_tp_license[read_idx]) + "\n"; + String license = "\n License: " + String(part.license) + "\n"; text += license; long_text += license + "\n"; read_idx++; @@ -230,10 +240,10 @@ EditorAbout::EditorAbout() { for (int i = 0; i < LICENSE_COUNT; i++) { TreeItem *ti = _tpl_tree->create_item(tpl_ti_lc); - String licensename = String(about_license_name[i]); + String licensename = String(LICENSE_NAMES[i]); ti->set_text(0, licensename); long_text += "- " + licensename + "\n\n"; - String licensebody = String(about_license_body[i]); + String licensebody = String(LICENSE_BODIES[i]); ti->set_metadata(0, licensebody); long_text += " " + licensebody.replace("\n", "\n ") + "\n\n"; } diff --git a/editor/editor_about.h b/editor/editor_about.h index b32fdf6567..71d1c95188 100644 --- a/editor/editor_about.h +++ b/editor/editor_about.h @@ -53,7 +53,7 @@ class EditorAbout : public AcceptDialog { private: void _license_tree_selected(); - ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char **p_src[], const int p_flag_single_column = 0); + ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column = 0); Tree *_tpl_tree; RichTextLabel *_license_text; diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index de9203232c..2f0982e5d9 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -610,8 +610,8 @@ void EditorAutoloadSettings::drop_data_fw(const Point2 &p_point, const Variant & i = 0; for (List<AutoLoadInfo>::Element *E = autoload_cache.front(); E; E = E->next()) { - undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", E->get().name, orders[i++]); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", E->get().name, E->get().order); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + E->get().name, orders[i++]); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", "autoload/" + E->get().name, E->get().order); } orders.clear(); @@ -742,7 +742,21 @@ EditorAutoloadSettings::EditorAutoloadSettings() { info.name = name; info.path = path; info.order = ProjectSettings::get_singleton()->get_order(pi.name); - info.node = _create_autoload(path); + + if (info.is_singleton) { + // Make sure name references work before parsing scripts + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_named_global_constant(info.name, Variant()); + } + } + + autoload_cache.push_back(info); + } + + for (List<AutoLoadInfo>::Element *E = autoload_cache.front(); E; E = E->next()) { + AutoLoadInfo &info = E->get(); + + info.node = _create_autoload(info.path); if (info.node) { Ref<Script> scr = info.node->get_script(); @@ -760,8 +774,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() { memdelete(info.node); info.node = NULL; } - - autoload_cache.push_back(info); } autoload_changed = "autoload_changed"; diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index d41d5c929a..4dde893c6d 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -78,7 +78,7 @@ void EditorHistory::cleanup_history() { current = history.size() - 1; } -void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int p_level_change) { +void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int p_level_change, bool p_inspector_only) { Object *obj = ObjectDB::get_instance(p_object); ERR_FAIL_COND(!obj); @@ -88,6 +88,7 @@ void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int o.ref = REF(r); o.object = p_object; o.property = p_property; + o.inspector_only = p_inspector_only; History h; @@ -120,6 +121,11 @@ void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int current++; } +void EditorHistory::add_object_inspector_only(ObjectID p_object) { + + _add_object(p_object, "", -1, true); +} + void EditorHistory::add_object(ObjectID p_object) { _add_object(p_object, "", -1); @@ -142,6 +148,13 @@ int EditorHistory::get_history_pos() { return current; } +bool EditorHistory::is_history_obj_inspector_only(int p_obj) const { + + ERR_FAIL_INDEX_V(p_obj, history.size(), false); + ERR_FAIL_INDEX_V(history[p_obj].level, history[p_obj].path.size(), false); + return history[p_obj].path[history[p_obj].level].inspector_only; +} + ObjectID EditorHistory::get_history_obj(int p_obj) const { ERR_FAIL_INDEX_V(p_obj, history.size(), 0); ERR_FAIL_INDEX_V(history[p_obj].level, history[p_obj].path.size(), 0); @@ -180,6 +193,14 @@ bool EditorHistory::previous() { return true; } +bool EditorHistory::is_current_inspector_only() const { + + if (current < 0 || current >= history.size()) + return false; + + const History &h = history[current]; + return h.path[h.level].inspector_only; +} ObjectID EditorHistory::get_current() { if (current < 0 || current >= history.size()) diff --git a/editor/editor_data.h b/editor/editor_data.h index 0452867bf4..0ecef8ae31 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -50,6 +50,7 @@ class EditorHistory { REF ref; ObjectID object; String property; + bool inspector_only; }; struct History { @@ -70,7 +71,7 @@ class EditorHistory { Variant value; }; - void _add_object(ObjectID p_object, const String &p_property, int p_level_change); + void _add_object(ObjectID p_object, const String &p_property, int p_level_change, bool p_inspector_only = false); public: void cleanup_history(); @@ -78,6 +79,7 @@ public: bool is_at_beginning() const; bool is_at_end() const; + void add_object_inspector_only(ObjectID p_object); void add_object(ObjectID p_object); void add_object(ObjectID p_object, const String &p_subprop); void add_object(ObjectID p_object, int p_relevel); @@ -85,10 +87,12 @@ public: int get_history_len(); int get_history_pos(); ObjectID get_history_obj(int p_obj) const; + bool is_history_obj_inspector_only(int p_obj) const; bool next(); bool previous(); ObjectID get_current(); + bool is_current_inspector_only() const; int get_path_size() const; ObjectID get_path_object(int p_index) const; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index b49c2d26d0..65e50560bc 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1280,11 +1280,10 @@ Error EditorHelp::_goto_desc(const String &p_class, int p_vscr) { class_desc->add_newline(); // class_desc->add_newline(); - Vector<String> tutorials = cd.tutorials.split_spaces(); - if (tutorials.size() != 0) { + if (cd.tutorials.size() != 0) { - for (int i = 0; i < tutorials.size(); i++) { - String link = tutorials[i]; + for (int i = 0; i < cd.tutorials.size(); i++) { + String link = cd.tutorials[i]; String linktxt = link; int seppos = linktxt.find("//"); if (seppos != -1) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index f94b7cd6ee..79746dcb5a 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -842,9 +842,11 @@ void EditorInspectorPlugin::_bind_methods() { MethodInfo vm; vm.name = "can_handle"; + vm.return_val.type = Variant::BOOL; vm.arguments.push_back(PropertyInfo(Variant::OBJECT, "object")); BIND_VMETHOD(vm); vm.name = "parse_begin"; + vm.return_val.type = Variant::NIL; BIND_VMETHOD(vm); vm.name = "parse_category"; vm.arguments.push_back(PropertyInfo(Variant::STRING, "category")); @@ -859,8 +861,8 @@ void EditorInspectorPlugin::_bind_methods() { vm.arguments.push_back(PropertyInfo(Variant::INT, "usage")); BIND_VMETHOD(vm); vm.arguments.clear(); - vm.return_val.type = Variant::NIL; vm.name = "parse_end"; + vm.return_val.type = Variant::NIL; BIND_VMETHOD(vm); } @@ -1329,8 +1331,9 @@ void EditorInspector::update_tree() { } else if (!(p.usage & PROPERTY_USAGE_EDITOR)) continue; - if (hide_script && p.name == "script") + if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) { continue; + } String basename = p.name; if (group != "") { @@ -1461,7 +1464,8 @@ void EditorInspector::update_tree() { #endif for (List<Ref<EditorInspectorPlugin> >::Element *E = valid_plugins.front(); E; E = E->next()) { Ref<EditorInspectorPlugin> ped = E->get(); - ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage); + bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage); + List<EditorInspectorPlugin::AddedEditor> editors = ped->added_editors; //make a copy, since plugins may be used again in a sub-inspector ped->added_editors.clear(); @@ -1474,6 +1478,9 @@ void EditorInspector::update_tree() { ep->object = object; ep->connect("property_changed", this, "_property_changed"); + if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) { + ep->connect("property_changed", this, "_property_changed_update_all", varray(), CONNECT_DEFERRED); + } ep->connect("property_keyed", this, "_property_keyed"); ep->connect("property_keyed_with_value", this, "_property_keyed_with_value"); ep->connect("property_checked", this, "_property_checked"); @@ -1526,6 +1533,10 @@ void EditorInspector::update_tree() { } } } + + if (exclusive) { + break; + } } } @@ -1636,6 +1647,7 @@ void EditorInspector::register_text_enter(Node *p_line_edit) { void EditorInspector::_filter_changed(const String &p_text) { + _clear(); update_tree(); } @@ -1650,6 +1662,10 @@ void EditorInspector::set_use_folding(bool p_enable) { update_tree(); } +bool EditorInspector::is_using_folding() { + return use_folding; +} + void EditorInspector::collapse_all_folding() { for (List<EditorInspectorSection *>::Element *E = sections.front(); E; E = E->next()) { @@ -1774,6 +1790,10 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v _edit_set(p_path, p_value, false, ""); } +void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value) { + update_tree(); +} + void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) { ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0); @@ -1943,6 +1963,8 @@ void EditorInspector::_bind_methods() { ClassDB::bind_method("_multiple_properties_changed", &EditorInspector::_multiple_properties_changed); ClassDB::bind_method("_property_changed", &EditorInspector::_property_changed); + ClassDB::bind_method("_property_changed_update_all", &EditorInspector::_property_changed_update_all); + ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change); ClassDB::bind_method("_node_removed", &EditorInspector::_node_removed); ClassDB::bind_method("_filter_changed", &EditorInspector::_filter_changed); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index a6b183799f..2a88be656a 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -257,6 +257,7 @@ class EditorInspector : public ScrollContainer { void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field); void _property_changed(const String &p_path, const Variant &p_value); + void _property_changed_update_all(const String &p_path, const Variant &p_value); void _multiple_properties_changed(Vector<String> p_paths, Array p_values); void _property_keyed(const String &p_path); void _property_keyed_with_value(const String &p_path, const Variant &p_value); @@ -314,6 +315,7 @@ public: void set_property_selectable(bool p_selectable); void set_use_folding(bool p_enable); + bool is_using_folding(); void collapse_all_folding(); void expand_all_folding(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4b068f1000..6256856b40 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -51,7 +51,6 @@ #include "scene/resources/packed_scene.h" #include "servers/physics_2d_server.h" -#include "editor/animation_editor.h" #include "editor/editor_audio_buses.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" @@ -67,7 +66,11 @@ #include "editor/import/resource_importer_scene.h" #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_wav.h" +#include "editor/plugins/animation_blend_space_1d_editor.h" +#include "editor/plugins/animation_blend_space_2d_editor.h" +#include "editor/plugins/animation_blend_tree_editor_plugin.h" #include "editor/plugins/animation_player_editor_plugin.h" +#include "editor/plugins/animation_state_machine_editor.h" #include "editor/plugins/animation_tree_editor_plugin.h" #include "editor/plugins/asset_library_editor_plugin.h" #include "editor/plugins/baked_lightmap_editor_plugin.h" @@ -88,7 +91,6 @@ #include "editor/plugins/mesh_editor_plugin.h" #include "editor/plugins/mesh_instance_editor_plugin.h" #include "editor/plugins/multimesh_editor_plugin.h" -#include "editor/plugins/navigation_mesh_editor_plugin.h" #include "editor/plugins/navigation_polygon_editor_plugin.h" #include "editor/plugins/particles_2d_editor_plugin.h" #include "editor/plugins/particles_editor_plugin.h" @@ -97,6 +99,7 @@ #include "editor/plugins/physical_bone_plugin.h" #include "editor/plugins/polygon_2d_editor_plugin.h" #include "editor/plugins/resource_preloader_editor_plugin.h" +#include "editor/plugins/root_motion_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/script_text_editor.h" #include "editor/plugins/shader_editor_plugin.h" @@ -584,7 +587,6 @@ void EditorNode::edit_node(Node *p_node) { void EditorNode::save_resource_in_path(const Ref<Resource> &p_resource, const String &p_path) { editor_data.apply_changes_in_editors(); - int flg = 0; if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) flg |= ResourceSaver::FLAG_COMPRESS; @@ -1052,8 +1054,23 @@ void EditorNode::_save_scene(String p_file, int idx) { flg |= ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS; err = ResourceSaver::save(p_file, sdata, flg); - Map<RES, bool> processed; - _save_edited_subresources(scene, processed, flg); + //Map<RES, bool> processed; + //this method is slow and not always works, deprecating + //_save_edited_subresources(scene, processed, flg); + { //instead, just find globally unsaved subresources and save them + + List<Ref<Resource> > cached; + ResourceCache::get_cached_resources(&cached); + for (List<Ref<Resource> >::Element *E = cached.front(); E; E = E->next()) { + + Ref<Resource> res = E->get(); + if (res->is_edited() && res->get_path().is_resource_file()) { + ResourceSaver::save(res->get_path(), res, flg); + res->set_edited(false); + } + } + } + editor_data.save_editor_external_data(); if (err == OK) { scene->set_filename(ProjectSettings::get_singleton()->localize_path(p_file)); @@ -1071,8 +1088,7 @@ void EditorNode::_save_scene(String p_file, int idx) { void EditorNode::_save_all_scenes() { - int i = _next_unsaved_scene(true, 0); - while (i != -1) { + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { Node *scene = editor_data.get_edited_scene_root(i); if (scene && scene->get_filename() != "") { if (i != editor_data.get_edited_scene()) @@ -1080,7 +1096,6 @@ void EditorNode::_save_all_scenes() { else _save_scene_with_preview(scene->get_filename()); } // else: ignore new scenes - i = _next_unsaved_scene(true, ++i); } _save_default_environment(); @@ -1302,7 +1317,31 @@ void EditorNode::_dialog_action(String p_file) { } } -void EditorNode::push_item(Object *p_object, const String &p_property) { +bool EditorNode::item_has_editor(Object *p_object) { + + return editor_data.get_subeditors(p_object).size() > 0; +} + +void EditorNode::edit_item(Object *p_object) { + + Vector<EditorPlugin *> sub_plugins; + + if (p_object) { + sub_plugins = editor_data.get_subeditors(p_object); + } + + if (!sub_plugins.empty()) { + _display_top_editors(false); + + _set_top_editors(sub_plugins); + _set_editing_top_editors(p_object); + _display_top_editors(true); + } else { + _hide_top_editors(); + } +} + +void EditorNode::push_item(Object *p_object, const String &p_property, bool p_inspector_only) { if (!p_object) { get_inspector()->edit(NULL); @@ -1314,7 +1353,9 @@ void EditorNode::push_item(Object *p_object, const String &p_property) { uint32_t id = p_object->get_instance_id(); if (id != editor_history.get_current()) { - if (p_property == "") + if (p_inspector_only) { + editor_history.add_object_inspector_only(id); + } else if (p_property == "") editor_history.add_object(id); else editor_history.add_object(id, p_property); @@ -1330,8 +1371,7 @@ void EditorNode::_save_default_environment() { if (fallback.is_valid() && fallback->get_path().is_resource_file()) { Map<RES, bool> processed; _find_and_save_edited_subresources(fallback.ptr(), processed, 0); - if (fallback->get_last_modified_time() != fallback->get_import_last_modified_time()) - save_resource_in_path(fallback, fallback->get_path()); + save_resource_in_path(fallback, fallback->get_path()); } } @@ -1368,6 +1408,7 @@ void EditorNode::_edit_current() { uint32_t current = editor_history.get_current(); Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL; + bool inspector_only = editor_history.is_current_inspector_only(); this->current = current_obj; @@ -1383,7 +1424,8 @@ void EditorNode::_edit_current() { return; } - bool capitalize = bool(EDITOR_DEF("interface/editor/capitalize_properties", true)); + bool capitalize = bool(EDITOR_GET("interface/inspector/capitalize_properties")); + bool disable_folding = bool(EDITOR_GET("interface/inspector/disable_folding")); bool is_resource = current_obj->is_class("Resource"); bool is_node = current_obj->is_class("Node"); @@ -1439,6 +1481,7 @@ void EditorNode::_edit_current() { if (current_obj->is_class("ScriptEditorDebuggerInspectedObject")) { editable_warning = TTR("This is a remote object so changes to it will not be kept.\nPlease read the documentation relevant to debugging to better understand this workflow."); capitalize = false; + disable_folding = true; } get_inspector()->edit(current_obj); @@ -1451,59 +1494,66 @@ void EditorNode::_edit_current() { get_inspector()->set_enable_capitalize_paths(capitalize); } + if (get_inspector()->is_using_folding() == disable_folding) { + get_inspector()->set_use_folding(!disable_folding); + } + /* Take care of PLUGIN EDITOR */ - EditorPlugin *main_plugin = editor_data.get_editor(current_obj); + if (!inspector_only) { - if (main_plugin) { + EditorPlugin *main_plugin = editor_data.get_editor(current_obj); - // special case if use of external editor is true - if (main_plugin->get_name() == "Script" && (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor")) || overrides_external_editor(current_obj))) { - if (!changing_scene) - main_plugin->edit(current_obj); - } + if (main_plugin) { - else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) { - // update screen main_plugin + // special case if use of external editor is true + if (main_plugin->get_name() == "Script" && (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor")) || overrides_external_editor(current_obj))) { + if (!changing_scene) + main_plugin->edit(current_obj); + } - if (!changing_scene) { + else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) { + // update screen main_plugin - if (editor_plugin_screen) - editor_plugin_screen->make_visible(false); - editor_plugin_screen = main_plugin; - editor_plugin_screen->edit(current_obj); + if (!changing_scene) { - editor_plugin_screen->make_visible(true); + if (editor_plugin_screen) + editor_plugin_screen->make_visible(false); + editor_plugin_screen = main_plugin; + editor_plugin_screen->edit(current_obj); - int plugin_count = editor_data.get_editor_plugin_count(); - for (int i = 0; i < plugin_count; i++) { - editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name()); - } + editor_plugin_screen->make_visible(true); + + int plugin_count = editor_data.get_editor_plugin_count(); + for (int i = 0; i < plugin_count; i++) { + editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name()); + } - for (int i = 0; i < editor_table.size(); i++) { + for (int i = 0; i < editor_table.size(); i++) { - main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin); + main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin); + } } - } - } else { + } else { - editor_plugin_screen->edit(current_obj); + editor_plugin_screen->edit(current_obj); + } } - } - Vector<EditorPlugin *> sub_plugins = editor_data.get_subeditors(current_obj); + Vector<EditorPlugin *> sub_plugins = editor_data.get_subeditors(current_obj); - if (!sub_plugins.empty()) { - _display_top_editors(false); + if (!sub_plugins.empty()) { + _display_top_editors(false); - _set_top_editors(sub_plugins); - _set_editing_top_editors(current_obj); - _display_top_editors(true); + _set_top_editors(sub_plugins); + _set_editing_top_editors(current_obj); + _display_top_editors(true); - } else if (!editor_plugins_over->get_plugins_list().empty()) { + } else if (!editor_plugins_over->get_plugins_list().empty()) { - _hide_top_editors(); + _hide_top_editors(); + } } inspector_dock->update(current_obj); @@ -2163,7 +2213,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { export_template_manager->popup_manager(); } break; - case SETTINGS_TOGGLE_FULLSCREN: { + case SETTINGS_TOGGLE_FULLSCREEN: { OS::get_singleton()->set_window_fullscreen(!OS::get_singleton()->is_window_fullscreen()); @@ -3039,6 +3089,7 @@ void EditorNode::register_editor_types() { ClassDB::register_class<EditorInspector>(); ClassDB::register_class<EditorInspectorPlugin>(); ClassDB::register_class<EditorProperty>(); + ClassDB::register_class<AnimationTrackEditPlugin>(); // FIXME: Is this stuff obsolete, or should it be ported to new APIs? ClassDB::register_class<EditorScenePostImport>(); @@ -4596,6 +4647,10 @@ EditorNode::EditorNode() { Ref<EditorInspectorDefaultPlugin> eidp; eidp.instance(); EditorInspector::add_inspector_plugin(eidp); + + Ref<EditorInspectorRootMotionPlugin> rmp; + rmp.instance(); + EditorInspector::add_inspector_plugin(rmp); } _pvrtc_register_compressors(); @@ -4621,9 +4676,7 @@ EditorNode::EditorNode() { GLOBAL_DEF("editor/main_run_args", ""); - ClassDB::set_class_enabled("CollisionShape", true); - ClassDB::set_class_enabled("CollisionShape2D", true); - ClassDB::set_class_enabled("CollisionPolygon2D", true); + ClassDB::set_class_enabled("RootMotionView", true); //defs here, use EDITOR_GET in logic EDITOR_DEF("interface/scene_tabs/always_show_close_button", false); @@ -4638,6 +4691,7 @@ EditorNode::EditorNode() { EDITOR_DEF("interface/scene_tabs/restore_scenes_on_load", false); EDITOR_DEF("interface/scene_tabs/show_thumbnail_on_hover", true); EDITOR_DEF("interface/inspector/capitalize_properties", true); + EDITOR_DEF("interface/inspector/disable_folding", false); EDITOR_DEF("interface/inspector/open_resources_in_new_inspector", false); EDITOR_DEF("run/auto_save/save_before_running", true); @@ -4835,7 +4889,11 @@ EditorNode::EditorNode() { srt->add_child(tabbar_container); tabbar_container->add_child(scene_tabs); distraction_free = memnew(ToolButton); +#ifdef OSX_ENABLED + distraction_free->set_shortcut(ED_SHORTCUT("editor/distraction_free_mode", TTR("Distraction Free Mode"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_D)); +#else distraction_free->set_shortcut(ED_SHORTCUT("editor/distraction_free_mode", TTR("Distraction Free Mode"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F11)); +#endif distraction_free->set_tooltip(TTR("Toggle distraction-free mode.")); distraction_free->connect("pressed", this, "_toggle_distraction_free_mode"); distraction_free->set_icon(gui_base->get_icon("DistractionFree", "EditorIcons")); @@ -4942,7 +5000,7 @@ EditorNode::EditorNode() { p->add_shortcut(ED_SHORTCUT("editor/save_scene_as", TTR("Save Scene As..."), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_S), FILE_SAVE_AS_SCENE); p->add_shortcut(ED_SHORTCUT("editor/save_all_scenes", TTR("Save all Scenes"), KEY_MASK_ALT + KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_S), FILE_SAVE_ALL_SCENES); p->add_separator(); - p->add_shortcut(ED_SHORTCUT("editor/close_scene", TTR("Close Scene"), KEY_MASK_SHIFT + KEY_MASK_CTRL + KEY_W), FILE_CLOSE); + p->add_shortcut(ED_SHORTCUT("editor/close_scene", TTR("Close Scene"), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_W), FILE_CLOSE); p->add_separator(); p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT); p->add_separator(); @@ -4995,7 +5053,7 @@ EditorNode::EditorNode() { #ifdef OSX_ENABLED p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_Q); #else - p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_CTRL + KEY_Q); + p->add_item(TTR("Quit to Project List"), RUN_PROJECT_MANAGER, KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_Q); #endif PanelContainer *editor_region = memnew(PanelContainer); @@ -5044,7 +5102,11 @@ EditorNode::EditorNode() { p->add_child(editor_layouts); editor_layouts->connect("id_pressed", this, "_layout_menu_option"); p->add_submenu_item(TTR("Editor Layout"), "Layouts"); - p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_SHIFT | KEY_F11), SETTINGS_TOGGLE_FULLSCREN); +#ifdef OSX_ENABLED + p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_F), SETTINGS_TOGGLE_FULLSCREEN); +#else + p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_SHIFT | KEY_F11), SETTINGS_TOGGLE_FULLSCREEN); +#endif p->add_separator(); p->add_item(TTR("Manage Export Templates"), SETTINGS_MANAGE_EXPORT_TEMPLATES); @@ -5084,7 +5146,11 @@ EditorNode::EditorNode() { play_button->set_focus_mode(Control::FOCUS_NONE); play_button->connect("pressed", this, "_menu_option", make_binds(RUN_PLAY)); play_button->set_tooltip(TTR("Play the project.")); +#ifdef OSX_ENABLED + play_button->set_shortcut(ED_SHORTCUT("editor/play", TTR("Play"), KEY_MASK_CMD | KEY_B)); +#else play_button->set_shortcut(ED_SHORTCUT("editor/play", TTR("Play"), KEY_F5)); +#endif pause_button = memnew(ToolButton); pause_button->set_toggle_mode(true); @@ -5093,7 +5159,11 @@ EditorNode::EditorNode() { pause_button->set_tooltip(TTR("Pause the scene")); pause_button->set_disabled(true); play_hb->add_child(pause_button); +#ifdef OSX_ENABLED + pause_button->set_shortcut(ED_SHORTCUT("editor/pause_scene", TTR("Pause Scene"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_Y)); +#else pause_button->set_shortcut(ED_SHORTCUT("editor/pause_scene", TTR("Pause Scene"), KEY_F7)); +#endif stop_button = memnew(ToolButton); play_hb->add_child(stop_button); @@ -5102,7 +5172,11 @@ EditorNode::EditorNode() { stop_button->connect("pressed", this, "_menu_option", make_binds(RUN_STOP)); stop_button->set_tooltip(TTR("Stop the scene.")); stop_button->set_disabled(true); +#ifdef OSX_ENABLED + stop_button->set_shortcut(ED_SHORTCUT("editor/stop", TTR("Stop"), KEY_MASK_CMD | KEY_PERIOD)); +#else stop_button->set_shortcut(ED_SHORTCUT("editor/stop", TTR("Stop"), KEY_F8)); +#endif run_native = memnew(EditorRunNative); play_hb->add_child(run_native); @@ -5120,7 +5194,11 @@ EditorNode::EditorNode() { play_scene_button->set_icon(gui_base->get_icon("PlayScene", "EditorIcons")); play_scene_button->connect("pressed", this, "_menu_option", make_binds(RUN_PLAY_SCENE)); play_scene_button->set_tooltip(TTR("Play the edited scene.")); +#ifdef OSX_ENABLED + play_scene_button->set_shortcut(ED_SHORTCUT("editor/play_scene", TTR("Play Scene"), KEY_MASK_CMD | KEY_R)); +#else play_scene_button->set_shortcut(ED_SHORTCUT("editor/play_scene", TTR("Play Scene"), KEY_F6)); +#endif play_custom_scene_button = memnew(ToolButton); play_hb->add_child(play_custom_scene_button); @@ -5129,7 +5207,11 @@ EditorNode::EditorNode() { play_custom_scene_button->set_icon(gui_base->get_icon("PlayCustom", "EditorIcons")); play_custom_scene_button->connect("pressed", this, "_menu_option", make_binds(RUN_PLAY_CUSTOM_SCENE)); play_custom_scene_button->set_tooltip(TTR("Play custom scene")); +#ifdef OSX_ENABLED + play_custom_scene_button->set_shortcut(ED_SHORTCUT("editor/play_custom_scene", TTR("Play Custom Scene"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_R)); +#else play_custom_scene_button->set_shortcut(ED_SHORTCUT("editor/play_custom_scene", TTR("Play Custom Scene"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F5)); +#endif progress_hb = memnew(BackgroundProgress); @@ -5301,6 +5383,8 @@ EditorNode::EditorNode() { file->connect("file_selected", this, "_dialog_action"); file_templates->connect("file_selected", this, "_dialog_action"); + preview_gen = memnew(AudioStreamPreviewGenerator); + add_child(preview_gen); //plugin stuff file_server = memnew(EditorFileServer); @@ -5331,6 +5415,10 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(ShaderEditorPlugin(this))); // FIXME: Disabled for Godot 3.0 as made incompatible, it needs to be ported to the new API. //add_editor_plugin(memnew(ShaderGraphEditorPlugin(this))); + add_editor_plugin(memnew(AnimationNodeBlendTreeEditorPlugin(this))); + add_editor_plugin(memnew(AnimationNodeBlendSpace1DEditorPlugin(this))); + add_editor_plugin(memnew(AnimationNodeBlendSpace2DEditorPlugin(this))); + add_editor_plugin(memnew(AnimationNodeStateMachineEditorPlugin(this))); add_editor_plugin(memnew(CameraEditorPlugin(this))); add_editor_plugin(memnew(ThemeEditorPlugin(this))); @@ -5365,7 +5453,6 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(TextureEditorPlugin(this))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); - add_editor_plugin(memnew(NavigationMeshEditorPlugin(this))); add_editor_plugin(memnew(SkeletonEditorPlugin(this))); add_editor_plugin(memnew(PhysicalBonePlugin(this))); @@ -5384,8 +5471,7 @@ EditorNode::EditorNode() { resource_preview->add_preview_generator(Ref<EditorPackedScenePreviewPlugin>(memnew(EditorPackedScenePreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorMaterialPreviewPlugin>(memnew(EditorMaterialPreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorScriptPreviewPlugin>(memnew(EditorScriptPreviewPlugin))); - // FIXME: Needs to be rewritten for AudioStream in Godot 3.0+ - //resource_preview->add_preview_generator( Ref<EditorSamplePreviewPlugin>( memnew(EditorSamplePreviewPlugin ))); + resource_preview->add_preview_generator(Ref<EditorAudioStreamPreviewPlugin>(memnew(EditorAudioStreamPreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorMeshPreviewPlugin>(memnew(EditorMeshPreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorBitmapPreviewPlugin>(memnew(EditorBitmapPreviewPlugin))); resource_preview->add_preview_generator(Ref<EditorFontPreviewPlugin>(memnew(EditorFontPreviewPlugin))); @@ -5512,10 +5598,17 @@ EditorNode::EditorNode() { print_handler.userdata = this; add_print_handler(&print_handler); +#ifdef OSX_ENABLED + ED_SHORTCUT("editor/editor_2d", TTR("Open 2D Editor"), KEY_MASK_ALT | KEY_1); + ED_SHORTCUT("editor/editor_3d", TTR("Open 3D Editor"), KEY_MASK_ALT | KEY_2); + ED_SHORTCUT("editor/editor_script", TTR("Open Script Editor"), KEY_MASK_ALT | KEY_3); + ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_MASK_ALT | KEY_SPACE); +#else ED_SHORTCUT("editor/editor_2d", TTR("Open 2D Editor"), KEY_F1); ED_SHORTCUT("editor/editor_3d", TTR("Open 3D Editor"), KEY_F2); ED_SHORTCUT("editor/editor_script", TTR("Open Script Editor"), KEY_F3); //hack neded for script editor F3 search to work :) Assign like this or don't use F3 ED_SHORTCUT("editor/editor_help", TTR("Search Help"), KEY_F4); +#endif ED_SHORTCUT("editor/editor_assetlib", TTR("Open Asset Library")); ED_SHORTCUT("editor/editor_next", TTR("Open the next Editor")); ED_SHORTCUT("editor/editor_prev", TTR("Open the previous Editor")); diff --git a/editor/editor_node.h b/editor/editor_node.h index bef5bc816c..dedd947633 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -32,6 +32,7 @@ #define EDITOR_NODE_H #include "core/print_string.h" +#include "editor/audio_stream_preview.h" #include "editor/connections_dialog.h" #include "editor/create_dialog.h" #include "editor/editor_about.h" @@ -81,6 +82,7 @@ #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" #include "scene/gui/viewport_container.h" + /** @author Juan Linietsky <reduzio@gmail.com> */ @@ -169,7 +171,7 @@ private: SETTINGS_LAYOUT_DEFAULT, SETTINGS_MANAGE_EXPORT_TEMPLATES, SETTINGS_PICK_MAIN_SCENE, - SETTINGS_TOGGLE_FULLSCREN, + SETTINGS_TOGGLE_FULLSCREEN, SETTINGS_HELP, SCENE_TAB_CLOSE, @@ -298,6 +300,7 @@ private: Vector<ToolButton *> main_editor_buttons; Vector<EditorPlugin *> editor_table; + AudioStreamPreviewGenerator *preview_gen; ProgressDialog *progress_dialog; BackgroundProgress *progress_hb; @@ -631,7 +634,9 @@ public: static HBoxContainer *get_menu_hb() { return singleton->menu_hb; } - void push_item(Object *p_object, const String &p_property = ""); + void push_item(Object *p_object, const String &p_property = "", bool p_inspector_only = false); + void edit_item(Object *p_object); + bool item_has_editor(Object *p_object); void open_request(const String &p_path); diff --git a/editor/editor_profiler.cpp b/editor/editor_profiler.cpp index 34c9ca6630..d4a97b7095 100644 --- a/editor/editor_profiler.cpp +++ b/editor/editor_profiler.cpp @@ -68,13 +68,13 @@ void EditorProfiler::add_frame_metric(const Metric &p_metric, bool p_final) { } updating_frame = false; - if (!frame_delay->is_processing()) { + if (frame_delay->is_stopped()) { frame_delay->set_wait_time(p_final ? 0.1 : 1); frame_delay->start(); } - if (!plot_delay->is_processing()) { + if (plot_delay->is_stopped()) { plot_delay->set_wait_time(0.1); plot_delay->start(); } @@ -424,20 +424,25 @@ void EditorProfiler::_update_frame() { void EditorProfiler::_activate_pressed() { if (activate->is_pressed()) { - clear(); activate->set_icon(get_icon("Stop", "EditorIcons")); - activate->set_text(TTR("Stop Profiling")); + activate->set_text(TTR("Stop")); } else { activate->set_icon(get_icon("Play", "EditorIcons")); - activate->set_text(TTR("Start Profiling")); + activate->set_text(TTR("Start")); } emit_signal("enable_profiling", activate->is_pressed()); } +void EditorProfiler::_clear_pressed() { + + clear(); +} + void EditorProfiler::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { activate->set_icon(get_icon("Play", "EditorIcons")); + clear_button->set_icon(get_icon("Clear", "EditorIcons")); } } @@ -599,6 +604,7 @@ void EditorProfiler::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_frame"), &EditorProfiler::_update_frame); ClassDB::bind_method(D_METHOD("_update_plot"), &EditorProfiler::_update_plot); ClassDB::bind_method(D_METHOD("_activate_pressed"), &EditorProfiler::_activate_pressed); + ClassDB::bind_method(D_METHOD("_clear_pressed"), &EditorProfiler::_clear_pressed); ClassDB::bind_method(D_METHOD("_graph_tex_draw"), &EditorProfiler::_graph_tex_draw); ClassDB::bind_method(D_METHOD("_graph_tex_input"), &EditorProfiler::_graph_tex_input); ClassDB::bind_method(D_METHOD("_graph_tex_mouse_exit"), &EditorProfiler::_graph_tex_mouse_exit); @@ -625,10 +631,15 @@ EditorProfiler::EditorProfiler() { add_child(hb); activate = memnew(Button); activate->set_toggle_mode(true); - activate->set_text(TTR("Start Profiling")); + activate->set_text(TTR("Start")); activate->connect("pressed", this, "_activate_pressed"); hb->add_child(activate); + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", this, "_clear_pressed"); + hb->add_child(clear_button); + hb->add_child(memnew(Label(TTR("Measure:")))); display_mode = memnew(OptionButton); diff --git a/editor/editor_profiler.h b/editor/editor_profiler.h index d902a97c5d..cb451475e7 100644 --- a/editor/editor_profiler.h +++ b/editor/editor_profiler.h @@ -100,6 +100,7 @@ public: private: Button *activate; + Button *clear_button; TextureRect *graph; Ref<ImageTexture> graph_texture; PoolVector<uint8_t> graph_image; @@ -133,6 +134,7 @@ private: void _update_frame(); void _activate_pressed(); + void _clear_pressed(); String _get_time_as_text(Metric &m, float p_time, int p_calls); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index c6d3a43f4e..9902d8d3e7 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1023,8 +1023,8 @@ void EditorPropertyRect2::_value_changed(double val) { Rect2 r2; r2.position.x = spin[0]->get_value(); - r2.position.x = spin[1]->get_value(); - r2.size.y = spin[2]->get_value(); + r2.position.y = spin[1]->get_value(); + r2.size.x = spin[2]->get_value(); r2.size.y = spin[3]->get_value(); emit_signal("property_changed", get_edited_property(), r2); } @@ -1530,6 +1530,8 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) { void EditorPropertyNodePath::_node_assign() { if (!scene_tree) { scene_tree = memnew(SceneTreeDialog); + scene_tree->get_scene_tree()->set_show_enabled_subscene(true); + scene_tree->get_scene_tree()->set_valid_types(valid_types); add_child(scene_tree); scene_tree->connect("selected", this, "_node_selected"); } @@ -1584,9 +1586,10 @@ void EditorPropertyNodePath::update_property() { assign->set_icon(icon); } -void EditorPropertyNodePath::setup(const NodePath &p_base_hint) { +void EditorPropertyNodePath::setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types) { base_hint = p_base_hint; + valid_types = p_valid_types; } void EditorPropertyNodePath::_notification(int p_what) { @@ -1779,6 +1782,7 @@ void EditorPropertyResource::_menu_option(int p_which) { if (!scene_tree) { scene_tree = memnew(SceneTreeDialog); + scene_tree->get_scene_tree()->set_show_enabled_subscene(true); add_child(scene_tree); scene_tree->connect("selected", this, "_viewport_selected"); scene_tree->set_title(TTR("Pick a Viewport")); @@ -1986,6 +1990,13 @@ void EditorPropertyResource::_sub_inspector_object_id_selected(int p_id) { emit_signal("object_id_selected", get_edited_property(), p_id); } +void EditorPropertyResource::_open_editor_pressed() { + RES res = get_edited_object()->get(get_edited_property()); + if (res.is_valid()) { + EditorNode::get_singleton()->edit_item(res.ptr()); + } +} + void EditorPropertyResource::update_property() { RES res = get_edited_object()->get(get_edited_property()); @@ -2009,9 +2020,29 @@ void EditorPropertyResource::update_property() { sub_inspector->set_read_only(is_read_only()); sub_inspector->set_use_folding(is_using_folding()); - add_child(sub_inspector); - set_bottom_editor(sub_inspector); + sub_inspector_vbox = memnew(VBoxContainer); + add_child(sub_inspector_vbox); + set_bottom_editor(sub_inspector_vbox); + + sub_inspector_vbox->add_child(sub_inspector); assign->set_pressed(true); + + bool use_editor = false; + for (int i = 0; i < EditorNode::get_singleton()->get_editor_data().get_editor_plugin_count(); i++) { + EditorPlugin *ep = EditorNode::get_singleton()->get_editor_data().get_editor_plugin(i); + if (ep->handles(res.ptr())) { + use_editor = true; + } + } + + if (use_editor) { + Button *open_in_editor = memnew(Button); + open_in_editor->set_text(TTR("Open Editor")); + open_in_editor->set_icon(get_icon("Edit", "EditorIcons")); + sub_inspector_vbox->add_child(open_in_editor); + open_in_editor->connect("pressed", this, "_open_editor_pressed"); + open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER); + } } if (res.ptr() != sub_inspector->get_edited_object()) { @@ -2021,8 +2052,9 @@ void EditorPropertyResource::update_property() { } else { if (sub_inspector) { set_bottom_editor(NULL); - memdelete(sub_inspector); + memdelete(sub_inspector_vbox); sub_inspector = NULL; + sub_inspector_vbox = NULL; } } #endif @@ -2242,11 +2274,13 @@ void EditorPropertyResource::_bind_methods() { ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &EditorPropertyResource::can_drop_data_fw); ClassDB::bind_method(D_METHOD("drop_data_fw"), &EditorPropertyResource::drop_data_fw); ClassDB::bind_method(D_METHOD("_button_draw"), &EditorPropertyResource::_button_draw); + ClassDB::bind_method(D_METHOD("_open_editor_pressed"), &EditorPropertyResource::_open_editor_pressed); } EditorPropertyResource::EditorPropertyResource() { sub_inspector = NULL; + sub_inspector_vbox = NULL; use_sub_inspector = !bool(EDITOR_GET("interface/inspector/open_resources_in_new_inspector")); HBoxContainer *hbc = memnew(HBoxContainer); add_child(hbc); @@ -2635,7 +2669,12 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath); if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) { - editor->setup(p_hint_text); + editor->setup(p_hint_text, Vector<StringName>()); + } + if (p_hint == PROPERTY_HINT_NODE_PATH_VALID_TYPES && p_hint_text != String()) { + Vector<String> types = p_hint_text.split(",", false); + Vector<StringName> sn = Variant(types); //convert via variant + editor->setup(NodePath(), sn); } add_property_editor(p_path, editor); @@ -2654,34 +2693,42 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ } break; case Variant::ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::ARRAY); add_property_editor(p_path, editor); } break; case Variant::POOL_BYTE_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_BYTE_ARRAY); add_property_editor(p_path, editor); } break; // 20 case Variant::POOL_INT_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_INT_ARRAY); add_property_editor(p_path, editor); } break; case Variant::POOL_REAL_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_REAL_ARRAY); add_property_editor(p_path, editor); } break; case Variant::POOL_STRING_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_STRING_ARRAY); add_property_editor(p_path, editor); } break; case Variant::POOL_VECTOR2_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_VECTOR2_ARRAY); add_property_editor(p_path, editor); } break; case Variant::POOL_VECTOR3_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_VECTOR3_ARRAY); add_property_editor(p_path, editor); } break; // 25 case Variant::POOL_COLOR_ARRAY: { EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_COLOR_ARRAY); add_property_editor(p_path, editor); } break; default: {} diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 03e72b4ec2..c67eccb60e 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -453,6 +453,7 @@ class EditorPropertyNodePath : public EditorProperty { SceneTreeDialog *scene_tree; NodePath base_hint; + Vector<StringName> valid_types; void _node_selected(const NodePath &p_path); void _node_assign(); void _node_clear(); @@ -463,7 +464,7 @@ protected: public: virtual void update_property(); - void setup(const NodePath &p_base_hint); + void setup(const NodePath &p_base_hint, Vector<StringName> p_valid_types); EditorPropertyNodePath(); }; @@ -491,6 +492,7 @@ class EditorPropertyResource : public EditorProperty { EditorFileDialog *file; Vector<String> inheritors_array; EditorInspector *sub_inspector; + VBoxContainer *sub_inspector_vbox; bool use_sub_inspector; bool dropping; @@ -516,6 +518,8 @@ class EditorPropertyResource : public EditorProperty { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void _open_editor_pressed(); + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 90f8d0e157..2bd28170e7 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -172,28 +172,9 @@ void EditorPropertyArray::update_property() { Variant array = get_edited_object()->get(get_edited_property()); - if ((!array.is_array()) != edit->is_disabled()) { - - if (array.is_array()) { - edit->set_disabled(false); - edit->set_pressed(false); - - } else { - edit->set_disabled(true); - if (vbox) { - memdelete(vbox); - } - } - } - - if (!array.is_array()) { - return; - } - - String arrtype; - switch (array.get_type()) { + String arrtype = ""; + switch (array_type) { case Variant::ARRAY: { - arrtype = "Array"; } break; @@ -229,6 +210,15 @@ void EditorPropertyArray::update_property() { default: {} } + if (!array.is_array()) { + edit->set_text(arrtype + "[" + Variant::get_type_name(array.get_type()) + "]"); + edit->set_pressed(false); + if (vbox) { + memdelete(vbox); + } + return; + } + edit->set_text(arrtype + "[" + itos(array.call("size")) + "]"); #ifdef TOOLS_ENABLED @@ -419,40 +409,55 @@ void EditorPropertyArray::update_property() { prop = memnew(EditorPropertyDictionary); } break; - case Variant::ARRAY: { - prop = memnew(EditorPropertyArray); + // arrays + case Variant::ARRAY: { + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::ARRAY); + prop = editor; } break; - - // arrays case Variant::POOL_BYTE_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_BYTE_ARRAY); + prop = editor; } break; case Variant::POOL_INT_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_INT_ARRAY); + prop = editor; } break; case Variant::POOL_REAL_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_REAL_ARRAY); + prop = editor; } break; case Variant::POOL_STRING_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_STRING_ARRAY); + prop = editor; } break; case Variant::POOL_VECTOR2_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_VECTOR2_ARRAY); + prop = editor; } break; case Variant::POOL_VECTOR3_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_VECTOR3_ARRAY); + prop = editor; } break; case Variant::POOL_COLOR_ARRAY: { - prop = memnew(EditorPropertyArray); + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(Variant::POOL_COLOR_ARRAY); + prop = editor; } break; default: {} } @@ -496,6 +501,14 @@ void EditorPropertyArray::_notification(int p_what) { } void EditorPropertyArray::_edit_pressed() { + Variant array = get_edited_object()->get(get_edited_property()); + if (!array.is_array()) { + Variant::CallError ce; + array = Variant::construct(array_type, NULL, 0, ce); + + get_edited_object()->set(get_edited_property(), array); + } + get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed()); update_property(); } @@ -522,6 +535,11 @@ void EditorPropertyArray::_length_changed(double p_page) { update_property(); } +void EditorPropertyArray::setup(Variant::Type p_array_type) { + + array_type = p_array_type; +} + void EditorPropertyArray::_bind_methods() { ClassDB::bind_method("_edit_pressed", &EditorPropertyArray::_edit_pressed); ClassDB::bind_method("_page_changed", &EditorPropertyArray::_page_changed); diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index 7f6203ee88..75c67d280d 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -62,6 +62,7 @@ class EditorPropertyArray : public EditorProperty { EditorSpinSlider *length; EditorSpinSlider *page; HBoxContainer *page_hb; + Variant::Type array_type; void _page_changed(double p_page); void _length_changed(double p_page); @@ -75,6 +76,7 @@ protected: void _notification(int p_what); public: + void setup(Variant::Type p_array_type); virtual void update_property(); EditorPropertyArray(); }; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index a47605be15..4045d6c3d3 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -313,8 +313,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/save_each_scene_on_quit", true); // Regression _initial_set("interface/editor/quit_confirmation", true); - _initial_set("interface/theme/preset", 0); - hints["interface/theme/preset"] = PropertyInfo(Variant::INT, "interface/theme/preset", PROPERTY_HINT_ENUM, "Default,Grey,Godot 2,Arc,Light,Alien,Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + _initial_set("interface/theme/preset", "Default"); + hints["interface/theme/preset"] = PropertyInfo(Variant::STRING, "interface/theme/preset", PROPERTY_HINT_ENUM, "Default,Alien,Arc,Godot 2,Grey,Light,Solarized (Dark),Solarized (Light),Custom", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/theme/icon_and_font_color", 0); hints["interface/theme/icon_and_font_color"] = PropertyInfo(Variant::INT, "interface/theme/icon_and_font_color", PROPERTY_HINT_ENUM, "Auto,Dark,Light", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/theme/base_color", Color::html("#323b4f")); @@ -568,79 +568,55 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { } void EditorSettings::_load_default_text_editor_theme() { - _initial_set("text_editor/highlighting/background_color", Color::html("3b000000")); + + bool dark_theme = is_dark_theme(); + + _initial_set("text_editor/highlighting/symbol_color", Color::html("badfff")); + _initial_set("text_editor/highlighting/keyword_color", Color::html("ffffb3")); + _initial_set("text_editor/highlighting/base_type_color", Color::html("a4ffd4")); + _initial_set("text_editor/highlighting/engine_type_color", Color::html("83d3ff")); + _initial_set("text_editor/highlighting/comment_color", Color::html("676767")); + _initial_set("text_editor/highlighting/string_color", Color::html("ef6ebe")); + _initial_set("text_editor/highlighting/background_color", dark_theme ? Color::html("3b000000") : Color::html("#323b4f")); _initial_set("text_editor/highlighting/completion_background_color", Color::html("2C2A32")); _initial_set("text_editor/highlighting/completion_selected_color", Color::html("434244")); _initial_set("text_editor/highlighting/completion_existing_color", Color::html("21dfdfdf")); _initial_set("text_editor/highlighting/completion_scroll_color", Color::html("ffffff")); _initial_set("text_editor/highlighting/completion_font_color", Color::html("aaaaaa")); + _initial_set("text_editor/highlighting/text_color", Color::html("aaaaaa")); + _initial_set("text_editor/highlighting/line_number_color", Color::html("66aaaaaa")); _initial_set("text_editor/highlighting/caret_color", Color::html("aaaaaa")); _initial_set("text_editor/highlighting/caret_background_color", Color::html("000000")); - _initial_set("text_editor/highlighting/line_number_color", Color::html("66aaaaaa")); - _initial_set("text_editor/highlighting/text_color", Color::html("aaaaaa")); _initial_set("text_editor/highlighting/text_selected_color", Color::html("000000")); - _initial_set("text_editor/highlighting/keyword_color", Color::html("ffffb3")); - _initial_set("text_editor/highlighting/base_type_color", Color::html("a4ffd4")); - _initial_set("text_editor/highlighting/engine_type_color", Color::html("83d3ff")); - _initial_set("text_editor/highlighting/function_color", Color::html("66a2ce")); - _initial_set("text_editor/highlighting/member_variable_color", Color::html("e64e59")); - _initial_set("text_editor/highlighting/comment_color", Color::html("676767")); - _initial_set("text_editor/highlighting/string_color", Color::html("ef6ebe")); - _initial_set("text_editor/highlighting/number_color", Color::html("EB9532")); - _initial_set("text_editor/highlighting/symbol_color", Color::html("badfff")); _initial_set("text_editor/highlighting/selection_color", Color::html("6ca9c2")); _initial_set("text_editor/highlighting/brace_mismatch_color", Color(1, 0.2, 0.2)); _initial_set("text_editor/highlighting/current_line_color", Color(0.3, 0.5, 0.8, 0.15)); _initial_set("text_editor/highlighting/line_length_guideline_color", Color(0.3, 0.5, 0.8, 0.1)); + _initial_set("text_editor/highlighting/word_highlighted_color", Color(0.8, 0.9, 0.9, 0.15)); + _initial_set("text_editor/highlighting/number_color", Color::html("EB9532")); + _initial_set("text_editor/highlighting/function_color", Color::html("66a2ce")); + _initial_set("text_editor/highlighting/member_variable_color", Color::html("e64e59")); _initial_set("text_editor/highlighting/mark_color", Color(1.0, 0.4, 0.4, 0.4)); _initial_set("text_editor/highlighting/breakpoint_color", Color(0.8, 0.8, 0.4, 0.2)); _initial_set("text_editor/highlighting/code_folding_color", Color(0.8, 0.8, 0.8, 0.8)); - _initial_set("text_editor/highlighting/word_highlighted_color", Color(0.8, 0.9, 0.9, 0.15)); _initial_set("text_editor/highlighting/search_result_color", Color(0.05, 0.25, 0.05, 1)); _initial_set("text_editor/highlighting/search_result_border_color", Color(0.1, 0.45, 0.1, 1)); - - // GDScript highlighter - _initial_set("text_editor/highlighting/gdscript/function_definition_color", Color::html("#01e1ff")); - _initial_set("text_editor/highlighting/gdscript/node_path_color", Color::html("#64c15a")); } bool EditorSettings::_save_text_editor_theme(String p_file) { String theme_section = "color_theme"; Ref<ConfigFile> cf = memnew(ConfigFile); // hex is better? - cf->set_value(theme_section, "background_color", ((Color)get("text_editor/highlighting/background_color")).to_html()); - cf->set_value(theme_section, "completion_background_color", ((Color)get("text_editor/highlighting/completion_background_color")).to_html()); - cf->set_value(theme_section, "completion_selected_color", ((Color)get("text_editor/highlighting/completion_selected_color")).to_html()); - cf->set_value(theme_section, "completion_existing_color", ((Color)get("text_editor/highlighting/completion_existing_color")).to_html()); - cf->set_value(theme_section, "completion_scroll_color", ((Color)get("text_editor/highlighting/completion_scroll_color")).to_html()); - cf->set_value(theme_section, "completion_font_color", ((Color)get("text_editor/highlighting/completion_font_color")).to_html()); - cf->set_value(theme_section, "caret_color", ((Color)get("text_editor/highlighting/caret_color")).to_html()); - cf->set_value(theme_section, "caret_background_color", ((Color)get("text_editor/highlighting/caret_background_color")).to_html()); - cf->set_value(theme_section, "line_number_color", ((Color)get("text_editor/highlighting/line_number_color")).to_html()); - cf->set_value(theme_section, "text_color", ((Color)get("text_editor/highlighting/text_color")).to_html()); - cf->set_value(theme_section, "text_selected_color", ((Color)get("text_editor/highlighting/text_selected_color")).to_html()); - cf->set_value(theme_section, "keyword_color", ((Color)get("text_editor/highlighting/keyword_color")).to_html()); - cf->set_value(theme_section, "base_type_color", ((Color)get("text_editor/highlighting/base_type_color")).to_html()); - cf->set_value(theme_section, "engine_type_color", ((Color)get("text_editor/highlighting/engine_type_color")).to_html()); - cf->set_value(theme_section, "function_color", ((Color)get("text_editor/highlighting/function_color")).to_html()); - cf->set_value(theme_section, "member_variable_color", ((Color)get("text_editor/highlighting/member_variable_color")).to_html()); - cf->set_value(theme_section, "comment_color", ((Color)get("text_editor/highlighting/comment_color")).to_html()); - cf->set_value(theme_section, "string_color", ((Color)get("text_editor/highlighting/string_color")).to_html()); - cf->set_value(theme_section, "number_color", ((Color)get("text_editor/highlighting/number_color")).to_html()); - cf->set_value(theme_section, "symbol_color", ((Color)get("text_editor/highlighting/symbol_color")).to_html()); - cf->set_value(theme_section, "selection_color", ((Color)get("text_editor/highlighting/selection_color")).to_html()); - cf->set_value(theme_section, "brace_mismatch_color", ((Color)get("text_editor/highlighting/brace_mismatch_color")).to_html()); - cf->set_value(theme_section, "current_line_color", ((Color)get("text_editor/highlighting/current_line_color")).to_html()); - cf->set_value(theme_section, "line_length_guideline_color", ((Color)get("text_editor/highlighting/line_length_guideline_color")).to_html()); - cf->set_value(theme_section, "mark_color", ((Color)get("text_editor/highlighting/mark_color")).to_html()); - cf->set_value(theme_section, "breakpoint_color", ((Color)get("text_editor/highlighting/breakpoint_color")).to_html()); - cf->set_value(theme_section, "code_folding_color", ((Color)get("text_editor/highlighting/code_folding_color")).to_html()); - cf->set_value(theme_section, "word_highlighted_color", ((Color)get("text_editor/highlighting/word_highlighted_color")).to_html()); - cf->set_value(theme_section, "search_result_color", ((Color)get("text_editor/highlighting/search_result_color")).to_html()); - cf->set_value(theme_section, "search_result_border_color", ((Color)get("text_editor/highlighting/search_result_border_color")).to_html()); - - //GDScript highlighter - cf->set_value(theme_section, "gdscript/function_definition_color", ((Color)get("text_editor/highlighting/gdscript/function_definition_color")).to_html()); - cf->set_value(theme_section, "gdscript/node_path_color", ((Color)get("text_editor/highlighting/gdscript/node_path_color")).to_html()); + + List<String> keys; + props.get_key_list(&keys); + keys.sort(); + + for (const List<String>::Element *E = keys.front(); E; E = E->next()) { + String key = E->get(); + if (key.begins_with("text_editor/highlighting/") && key.find("color") >= 0) { + cf->set_value(theme_section, key.replace("text_editor/highlighting/", ""), ((Color)props[key].variant).to_html()); + } + } Error err = cf->save(p_file); @@ -1216,6 +1192,14 @@ void EditorSettings::load_favorites() { } } +bool EditorSettings::is_dark_theme() { + int AUTO_COLOR = 0; + int LIGHT_COLOR = 2; + Color base_color = get("interface/theme/base_color"); + int icon_font_color_setting = get("interface/theme/icon_and_font_color"); + return (icon_font_color_setting == AUTO_COLOR && ((base_color.r + base_color.g + base_color.b) / 3.0) < 0.5) || icon_font_color_setting == LIGHT_COLOR; +} + void EditorSettings::list_text_editor_themes() { String themes = "Adaptive,Default,Custom"; DirAccess *d = DirAccess::open(get_text_editor_themes_dir()); @@ -1402,33 +1386,9 @@ struct ShortCutMapping { Ref<ShortCut> ED_SHORTCUT(const String &p_path, const String &p_name, uint32_t p_keycode) { #ifdef OSX_ENABLED - static const ShortCutMapping macos_mappings[] = { - { "editor/play", KEY_MASK_CMD | KEY_B }, - { "editor/play_scene", KEY_MASK_CMD | KEY_R }, - { "editor/pause_scene", KEY_MASK_CMD | KEY_MASK_CTRL | KEY_Y }, - { "editor/stop", KEY_MASK_CMD | KEY_PERIOD }, - { "editor/play_custom_scene", KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_R }, - { "editor/editor_2d", KEY_MASK_ALT | KEY_1 }, - { "editor/editor_3d", KEY_MASK_ALT | KEY_2 }, - { "editor/editor_script", KEY_MASK_ALT | KEY_3 }, - { "editor/editor_help", KEY_MASK_ALT | KEY_SPACE }, - { "editor/fullscreen_mode", KEY_MASK_CMD | KEY_MASK_CTRL | KEY_F }, - { "editor/distraction_free_mode", KEY_MASK_CMD | KEY_MASK_CTRL | KEY_D }, - { "script_text_editor/contextual_help", KEY_MASK_ALT | KEY_MASK_SHIFT | KEY_SPACE }, - { "script_text_editor/find_next", KEY_MASK_CMD | KEY_G }, - { "script_text_editor/find_previous", KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G }, - { "script_text_editor/toggle_breakpoint", KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B } - }; - + // Use Cmd+Backspace as a general replacement for Delete shortcuts on macOS if (p_keycode == KEY_DELETE) { p_keycode = KEY_MASK_CMD | KEY_BACKSPACE; - } else { - for (int i = 0; i < sizeof(macos_mappings) / sizeof(ShortCutMapping); i++) { - if (p_path == macos_mappings[i].path) { - p_keycode = macos_mappings[i].keycode; - break; - } - } } #endif diff --git a/editor/editor_settings.h b/editor/editor_settings.h index b48aac89c7..420e067cad 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -175,6 +175,8 @@ public: Vector<String> get_recent_dirs() const; void load_favorites(); + bool is_dark_theme(); + void list_text_editor_themes(); void load_text_editor_theme(); bool import_text_editor_theme(String p_file); diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 087dcd649f..0852a42794 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -37,6 +37,9 @@ String EditorSpinSlider::get_text_value() const { } void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) { + if (read_only) + return; + Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { @@ -301,10 +304,23 @@ void EditorSpinSlider::_grabber_mouse_exited() { update(); } +void EditorSpinSlider::set_read_only(bool p_enable) { + + read_only = p_enable; + update(); +} + +bool EditorSpinSlider::is_read_only() const { + return read_only; +} + void EditorSpinSlider::_bind_methods() { ClassDB::bind_method(D_METHOD("set_label", "label"), &EditorSpinSlider::set_label); ClassDB::bind_method(D_METHOD("get_label"), &EditorSpinSlider::get_label); + ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorSpinSlider::set_read_only); + ClassDB::bind_method(D_METHOD("is_read_only"), &EditorSpinSlider::is_read_only); + ClassDB::bind_method(D_METHOD("_gui_input"), &EditorSpinSlider::_gui_input); ClassDB::bind_method(D_METHOD("_grabber_mouse_entered"), &EditorSpinSlider::_grabber_mouse_entered); ClassDB::bind_method(D_METHOD("_grabber_mouse_exited"), &EditorSpinSlider::_grabber_mouse_exited); @@ -313,6 +329,7 @@ void EditorSpinSlider::_bind_methods() { ClassDB::bind_method(D_METHOD("_value_input_entered"), &EditorSpinSlider::_value_input_entered); ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only"); } EditorSpinSlider::EditorSpinSlider() { @@ -342,4 +359,5 @@ EditorSpinSlider::EditorSpinSlider() { value_input->connect("modal_closed", this, "_value_input_closed"); value_input->connect("text_entered", this, "_value_input_entered"); hide_slider = false; + read_only = false; } diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h index 4956990dc2..37d8a5f128 100644 --- a/editor/editor_spin_slider.h +++ b/editor/editor_spin_slider.h @@ -55,6 +55,8 @@ class EditorSpinSlider : public Range { bool grabbing_spinner_attempt; bool grabbing_spinner; + + bool read_only; Vector2 grabbing_spinner_mouse_pos; LineEdit *value_input; @@ -80,6 +82,9 @@ public: void set_hide_slider(bool p_hide); bool is_hiding_slider() const; + void set_read_only(bool p_enable); + bool is_read_only() const; + virtual Size2 get_minimum_size() const; EditorSpinSlider(); }; diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 8d29e0d40b..98402d8df5 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -254,7 +254,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color base_color = EDITOR_DEF("interface/theme/base_color", Color::html("#323b4f")); float contrast = EDITOR_DEF("interface/theme/contrast", default_contrast); - int preset = EDITOR_DEF("interface/theme/preset", 0); + String preset = EDITOR_DEF("interface/theme/preset", "Default"); + int icon_font_color_setting = EDITOR_DEF("interface/theme/icon_and_font_color", 0); bool highlight_tabs = EDITOR_DEF("interface/theme/highlight_tabs", false); int border_size = EDITOR_DEF("interface/theme/border_size", 1); @@ -266,45 +267,52 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color preset_accent_color; Color preset_base_color; float preset_contrast; - switch (preset) { - case 0: { // Default - preset_accent_color = Color::html("#699ce8"); - preset_base_color = Color::html("#323b4f"); - preset_contrast = default_contrast; - } break; - case 1: { // Grey - preset_accent_color = Color::html("#b8e4ff"); - preset_base_color = Color::html("#3d3d3d"); - preset_contrast = 0.2; - } break; - case 2: { // Godot 2 - preset_accent_color = Color::html("#86ace2"); - preset_base_color = Color::html("#3C3A44"); - preset_contrast = 0.25; - } break; - case 3: { // Arc - preset_accent_color = Color::html("#5294e2"); - preset_base_color = Color::html("#383c4a"); - preset_contrast = 0.25; - } break; - case 4: { // Light - preset_accent_color = Color::html("#2070ff"); - preset_base_color = Color::html("#ffffff"); - preset_contrast = 0.08; - } break; - case 5: { // Alien - preset_accent_color = Color::html("#1bfe99"); - preset_base_color = Color::html("#2f373f"); - preset_contrast = 0.25; - } - default: { // Custom - accent_color = EDITOR_DEF("interface/theme/accent_color", Color::html("#699ce8")); - base_color = EDITOR_DEF("interface/theme/base_color", Color::html("#323b4f")); - contrast = EDITOR_DEF("interface/theme/contrast", default_contrast); - } + + // Please, use alphabet order if you've added new theme here(After "Default" and "Custom") + + if (preset == "Default") { + preset_accent_color = Color::html("#699ce8"); + preset_base_color = Color::html("#323b4f"); + preset_contrast = default_contrast; + } else if (preset == "Custom") { + accent_color = EDITOR_DEF("interface/theme/accent_color", Color::html("#699ce8")); + base_color = EDITOR_DEF("interface/theme/base_color", Color::html("#323b4f")); + contrast = EDITOR_DEF("interface/theme/contrast", default_contrast); + } else if (preset == "Alien") { + preset_accent_color = Color::html("#1bfe99"); + preset_base_color = Color::html("#2f373f"); + preset_contrast = 0.25; + } else if (preset == "Arc") { + preset_accent_color = Color::html("#5294e2"); + preset_base_color = Color::html("#383c4a"); + preset_contrast = 0.25; + } else if (preset == "Godot 2") { + preset_accent_color = Color::html("#86ace2"); + preset_base_color = Color::html("#3C3A44"); + preset_contrast = 0.25; + } else if (preset == "Grey") { + preset_accent_color = Color::html("#b8e4ff"); + preset_base_color = Color::html("#3d3d3d"); + preset_contrast = 0.2; + } else if (preset == "Light") { + preset_accent_color = Color::html("#2070ff"); + preset_base_color = Color::html("#ffffff"); + preset_contrast = 0.08; + } else if (preset == "Solarized (Dark)") { + preset_accent_color = Color::html("#268bd2"); + preset_base_color = Color::html("#073642"); + preset_contrast = 0.15; + } else if (preset == "Solarized (Light)") { + preset_accent_color = Color::html("#268bd2"); + preset_base_color = Color::html("#fdf6e3"); + preset_contrast = 0.06; + } else { // Default + preset_accent_color = Color::html("#699ce8"); + preset_base_color = Color::html("#323b4f"); + preset_contrast = default_contrast; } - if (preset != 6) { + if (preset != "Custom") { accent_color = preset_accent_color; base_color = preset_base_color; contrast = preset_contrast; @@ -318,9 +326,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { EditorSettings::get_singleton()->set_manually("interface/theme/contrast", contrast); //Colors - int AUTO_COLOR = 0; - int LIGHT_COLOR = 2; - bool dark_theme = (icon_font_color_setting == AUTO_COLOR && ((base_color.r + base_color.g + base_color.b) / 3.0) < 0.5) || icon_font_color_setting == LIGHT_COLOR; + bool dark_theme = EditorSettings::get_singleton()->is_dark_theme(); const Color dark_color_1 = base_color.linear_interpolate(Color(0, 0, 0, 1), contrast); const Color dark_color_2 = base_color.linear_interpolate(Color(0, 0, 0, 1), contrast * 1.5); @@ -940,6 +946,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("bg", "GraphEdit", style_tree_bg); theme->set_color("grid_major", "GraphEdit", grid_major_color); theme->set_color("grid_minor", "GraphEdit", grid_minor_color); + theme->set_color("activity", "GraphEdit", accent_color); theme->set_icon("minus", "GraphEdit", theme->get_icon("ZoomLess", "EditorIcons")); theme->set_icon("more", "GraphEdit", theme->get_icon("ZoomMore", "EditorIcons")); theme->set_icon("reset", "GraphEdit", theme->get_icon("ZoomReset", "EditorIcons")); @@ -1047,11 +1054,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color comment_color = dim_color; const Color string_color = Color::html(dark_theme ? "#ffd942" : "#ffd118").linear_interpolate(mono_color, dark_theme ? 0.5 : 0.3); - const Color function_definition_color = Color::html(dark_theme ? "#01e1ff" : "#00a5ba"); - const Color node_path_color = Color::html(dark_theme ? "64c15a" : "#518b4b"); - - const Color te_background_color = dark_theme ? background_color : Color::html("#ffffff"); - const Color completion_background_color = base_color; + const Color te_background_color = dark_theme ? background_color : base_color; + const Color completion_background_color = dark_theme ? base_color : background_color; const Color completion_selected_color = alpha1; const Color completion_existing_color = alpha2; const Color completion_scroll_color = alpha1; @@ -1064,7 +1068,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color selection_color = alpha2; const Color brace_mismatch_color = error_color; const Color current_line_color = alpha1; - const Color line_length_guideline_color = warning_color; + const Color line_length_guideline_color = dark_theme ? base_color : background_color; const Color word_highlighted_color = alpha1; const Color number_color = basetype_color.linear_interpolate(mono_color, dark_theme ? 0.5 : 0.3); const Color function_color = main_color; @@ -1108,43 +1112,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { setting->set_initial_value("text_editor/highlighting/code_folding_color", code_folding_color, true); setting->set_initial_value("text_editor/highlighting/search_result_color", search_result_color, true); setting->set_initial_value("text_editor/highlighting/search_result_border_color", search_result_border_color, true); - - setting->set_initial_value("text_editor/highlighting/gdscript/function_definition_color", function_definition_color, true); - setting->set_initial_value("text_editor/highlighting/gdscript/node_path_color", node_path_color, true); } else if (text_editor_color_theme == "Default") { - setting->set_initial_value("text_editor/highlighting/symbol_color", Color::html("badfff"), true); - setting->set_initial_value("text_editor/highlighting/keyword_color", Color::html("ffffb3"), true); - setting->set_initial_value("text_editor/highlighting/base_type_color", Color::html("a4ffd4"), true); - setting->set_initial_value("text_editor/highlighting/engine_type_color", Color::html("83d3ff"), true); - setting->set_initial_value("text_editor/highlighting/comment_color", Color::html("676767"), true); - setting->set_initial_value("text_editor/highlighting/string_color", Color::html("ef6ebe"), true); - setting->set_initial_value("text_editor/highlighting/background_color", dark_theme ? Color::html("3b000000") : Color::html("#323b4f"), true); - setting->set_initial_value("text_editor/highlighting/completion_background_color", Color::html("2C2A32"), true); - setting->set_initial_value("text_editor/highlighting/completion_selected_color", Color::html("434244"), true); - setting->set_initial_value("text_editor/highlighting/completion_existing_color", Color::html("21dfdfdf"), true); - setting->set_initial_value("text_editor/highlighting/completion_scroll_color", Color::html("ffffff"), true); - setting->set_initial_value("text_editor/highlighting/completion_font_color", Color::html("aaaaaa"), true); - setting->set_initial_value("text_editor/highlighting/text_color", Color::html("aaaaaa"), true); - setting->set_initial_value("text_editor/highlighting/line_number_color", Color::html("66aaaaaa"), true); - setting->set_initial_value("text_editor/highlighting/caret_color", Color::html("aaaaaa"), true); - setting->set_initial_value("text_editor/highlighting/caret_background_color", Color::html("000000"), true); - setting->set_initial_value("text_editor/highlighting/text_selected_color", Color::html("000000"), true); - setting->set_initial_value("text_editor/highlighting/selection_color", Color::html("6ca9c2"), true); - setting->set_initial_value("text_editor/highlighting/brace_mismatch_color", Color(1, 0.2, 0.2), true); - setting->set_initial_value("text_editor/highlighting/current_line_color", Color(0.3, 0.5, 0.8, 0.15), true); - setting->set_initial_value("text_editor/highlighting/line_length_guideline_color", Color(0.3, 0.5, 0.8, 0.1), true); - setting->set_initial_value("text_editor/highlighting/word_highlighted_color", Color(0.8, 0.9, 0.9, 0.15), true); - setting->set_initial_value("text_editor/highlighting/number_color", Color::html("EB9532"), true); - setting->set_initial_value("text_editor/highlighting/function_color", Color::html("66a2ce"), true); - setting->set_initial_value("text_editor/highlighting/member_variable_color", Color::html("e64e59"), true); - setting->set_initial_value("text_editor/highlighting/mark_color", Color(1.0, 0.4, 0.4, 0.4), true); - setting->set_initial_value("text_editor/highlighting/breakpoint_color", Color(0.8, 0.8, 0.4, 0.2), true); - setting->set_initial_value("text_editor/highlighting/code_folding_color", Color(0.8, 0.8, 0.8, 0.8), true); - setting->set_initial_value("text_editor/highlighting/search_result_color", Color(0.05, 0.25, 0.05, 1), true); - setting->set_initial_value("text_editor/highlighting/search_result_border_color", Color(0.1, 0.45, 0.1, 1), true); - - setting->set_initial_value("text_editor/highlighting/gdscript/function_definition_color", Color::html("#01e1ff"), true); - setting->set_initial_value("text_editor/highlighting/gdscript/node_path_color", Color::html("#64c15a"), true); + setting->load_text_editor_theme(); } return theme; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 297373d299..e15c876893 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1048,18 +1048,24 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path) { Map<String, String> file_renames; Map<String, String> folder_renames; + bool is_moved = false; for (int i = 0; i < to_move.size(); i++) { String old_path = to_move[i].path.ends_with("/") ? to_move[i].path.substr(0, to_move[i].path.length() - 1) : to_move[i].path; String new_path = p_to_path.plus_file(old_path.get_file()); - _try_move_item(to_move[i], new_path, file_renames, folder_renames); + if (old_path != new_path) { + _try_move_item(to_move[i], new_path, file_renames, folder_renames); + is_moved = true; + } } - _update_dependencies_after_move(file_renames); - _update_resource_paths_after_move(file_renames); - _update_favorite_dirs_list_after_move(folder_renames); + if (is_moved) { + _update_dependencies_after_move(file_renames); + _update_resource_paths_after_move(file_renames); + _update_favorite_dirs_list_after_move(folder_renames); - print_line("call rescan!"); - _rescan(); + print_line("call rescan!"); + _rescan(); + } } void FileSystemDock::_file_option(int p_option) { diff --git a/editor/icons/README.md b/editor/icons/README.md index f3aaa23666..3a2aba5b07 100644 --- a/editor/icons/README.md +++ b/editor/icons/README.md @@ -2,11 +2,11 @@ The icons here are optimized SVGs, because the editor renders the svgs at runtim to be small in size, so they can be efficiently parsed. The original icons can be found at: -https://github.com/djrm/godot-design/tree/master/assets/icons +https://github.com/godotengine/godot-design/tree/master/engine/icons There you can find the optimizer script. If you add a new icon, please make a pull request to this repo: -https://github.com/djrm/godot-design/ +https://github.com/godotengine/godot-design/ and store the the optimized SVG version here. diff --git a/editor/icons/icon_animation_filter.svg b/editor/icons/icon_animation_filter.svg new file mode 100644 index 0000000000..4f8e881ea8 --- /dev/null +++ b/editor/icons/icon_animation_filter.svg @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_animation_filter.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1089" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="10.429825" + inkscape:cx="-5.6414698" + inkscape:cy="10.961343" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g10" /> + <g + transform="matrix(0.02719109,0,0,0.02719109,1.3153462,1.0022864)" + id="g12"> + <g + id="g10"> + <path + inkscape:connector-curvature="0" + d="M 495.289,20.143 H 16.709 c -14.938,0 -22.344,18.205 -11.666,28.636 l 169.7,165.778 v 260.587 c 0,14.041 16.259,21.739 27.131,13.031 L 331.017,384.743 c 3.956,-3.169 6.258,-7.962 6.258,-13.031 V 214.556 L 506.955,48.779 c 10.688,-10.44 3.259,-28.636 -11.666,-28.636 z" + id="path8" + style="fill:#e0e0e0;fill-opacity:1" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_animation_track_group.svg b/editor/icons/icon_animation_track_group.svg new file mode 100644 index 0000000000..9c4748a528 --- /dev/null +++ b/editor/icons/icon_animation_track_group.svg @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_animation_track_group.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1089" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="10.429825" + inkscape:cx="6.2135985" + inkscape:cy="6.5622523" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + style="fill:#e0e0e0" + inkscape:connector-curvature="0" + id="path2" + d="M 5.0508475,2 V 4 H 14 V 2 Z m -3.322034,-0.016949 v 2 h 2 v -2 z M 8.9830508,7 V 9 H 14 V 7 Z m -3.5254237,5 v 2 h 2 v -2 z m 3.5254237,0 v 2 H 14 v -2 z" + sodipodi:nodetypes="ccccccccccccccccccccccccc" /> + <path + style="fill:#e0e0e0" + inkscape:connector-curvature="0" + id="path2-3" + d="m 5.4915255,6.9322039 v 1.999999 h 2 v -1.999999 z" + sodipodi:nodetypes="ccccc" /> +</svg> diff --git a/editor/icons/icon_animation_track_list.svg b/editor/icons/icon_animation_track_list.svg new file mode 100644 index 0000000000..40e8414598 --- /dev/null +++ b/editor/icons/icon_animation_track_list.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_animation_track_list.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1089" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <g + transform="translate(0 -1036.4)" + id="g4"> + <path + transform="translate(0 1036.4)" + d="m2 2v2h2v-2h-2zm4 0v2h8v-2h-8zm-4 5v2h2v-2h-2zm4 0v2h8v-2h-8zm-4 5v2h2v-2h-2zm4 0v2h8v-2h-8z" + fill="#e0e0e0" + id="path2" /> + </g> +</svg> diff --git a/editor/icons/icon_animation_tree.svg b/editor/icons/icon_animation_tree.svg new file mode 100644 index 0000000000..046506fa37 --- /dev/null +++ b/editor/icons/icon_animation_tree.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)"> +<path transform="translate(0 1036.4)" d="m1 1v14h1.166v-2h1.834v2h8v-2h2v2h1v-14h-1v2h-2v-2h-8v2h-1.834v-2h-1.166zm4 3h2v1 1h1 3v2h-2v1 1h1 1v2h-1-2a1.0001 1.0001 0 0 1 -1 -1v-1-2h-1a1.0001 1.0001 0 0 1 -1 -1v-1-1-1zm-2.834 1h1.834v2h-1.834v-2zm9.834 0h2v2h-2v-2zm-9.834 4h1.834v2h-1.834v-2zm9.834 0h2v2h-2v-2z" fill="#cea4f1"/> +</g> +</svg> diff --git a/editor/icons/icon_auto_end.svg b/editor/icons/icon_auto_end.svg new file mode 100644 index 0000000000..9e779c69f4 --- /dev/null +++ b/editor/icons/icon_auto_end.svg @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_auto_end.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1273" + inkscape:window-height="766" + id="namedview8" + showgrid="false" + inkscape:zoom="41.7193" + inkscape:cx="12.08616" + inkscape:cy="6.9898672" + inkscape:window-x="539" + inkscape:window-y="208" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + inkscape:connector-curvature="0" + id="path2" + style="color:#000000;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + d="m 13.999798,14 c 0.552262,-5.5e-5 0.999945,-0.447738 1,-1 V 3 c -5.5e-5,-0.5522619 -0.447738,-0.9999448 -1,-1 H 5.9997976 C 5.6959349,1.9998247 5.4084731,2.1378063 5.2185476,2.375 l -4,5 c -0.29139692,0.3649711 -0.29139692,0.8830289 0,1.248 l 4,5 c 0.189538,0.237924 0.4770584,0.376652 0.78125,0.37695 h 8.0000004 z m -1,-2 H 6.4802976 l -3.1992,-4 3.1992,-4 H 12.999798 Z M 6.9997976,10 V 6 l -2,2 z" + sodipodi:nodetypes="cccccccccccccccccccccc" /> + <g + aria-label="E" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#e0e0e0;fill-opacity:1;stroke:none" + id="text829" + transform="matrix(0.20475474,0,0,0.20475474,4.7903856,12.365563)"> + <path + d="M 15.129502,-36.414393 H 35.422471 V -30.7308 H 22.649034 v 5.429688 h 12.011718 v 5.683594 H 22.649034 v 6.679687 h 13.203125 v 5.6835938 H 15.129502 Z" + style="fill:#e0e0e0;fill-opacity:1" + id="path831" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/editor/icons/icon_auto_triangle.svg b/editor/icons/icon_auto_triangle.svg new file mode 100644 index 0000000000..631f259452 --- /dev/null +++ b/editor/icons/icon_auto_triangle.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_auto_triangle.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1853" + inkscape:window-height="1016" + id="namedview10" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="17.168167" + inkscape:cy="5.5708575" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <g + transform="translate(-26.001 -9.8683)" + id="g4"> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1.87616086;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 8.2324219 0.67773438 L 0.64453125 15.289062 L 15.355469 15.289062 L 8.2324219 0.67773438 z M 6.9414062 5.4433594 L 9.2109375 5.4433594 C 9.5561128 6.0670927 9.8954447 6.7088542 10.230469 7.3671875 C 10.565492 8.0167875 10.901304 8.703974 11.236328 9.4316406 C 11.581503 10.159241 11.931781 10.934946 12.287109 11.757812 C 12.642437 12.580746 13.018126 13.477066 13.414062 14.447266 L 10.871094 14.447266 C 10.75942 14.135399 10.632366 13.815528 10.490234 13.486328 C 10.358255 13.157195 10.225729 12.827247 10.09375 12.498047 L 5.9824219 12.498047 C 5.8504432 12.827247 5.7143976 13.157195 5.5722656 13.486328 C 5.440287 13.815528 5.3167521 14.135399 5.2050781 14.447266 L 2.7382812 14.447266 C 3.1342186 13.477066 3.5099064 12.580746 3.8652344 11.757812 C 4.2205624 10.934946 4.5673204 10.159241 4.9023438 9.4316406 C 5.2475197 8.703974 5.5813793 8.0167875 5.90625 7.3671875 C 6.2412733 6.7088542 6.5860782 6.0670927 6.9414062 5.4433594 z M 8.0234375 7.4824219 C 7.9726708 7.6123552 7.8964385 7.790425 7.7949219 8.015625 C 7.6933999 8.240825 7.5772912 8.5003885 7.4453125 8.7949219 C 7.3133332 9.0894552 7.1643891 9.4143979 7.0019531 9.7695312 C 6.8496698 10.124665 6.6936847 10.496919 6.53125 10.886719 L 9.53125 10.886719 C 9.368814 10.496919 9.2108764 10.124665 9.0585938 9.7695312 C 8.9063104 9.4143979 8.7593188 9.0894552 8.6171875 8.7949219 C 8.4852082 8.5003885 8.3691001 8.240825 8.2675781 8.015625 C 8.1660555 7.790425 8.0843508 7.6123552 8.0234375 7.4824219 z " + transform="translate(26.001,1046.2683)" + id="path821" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_bezier_handles_balanced.svg b/editor/icons/icon_bezier_handles_balanced.svg new file mode 100644 index 0000000000..8ab99d79bb --- /dev/null +++ b/editor/icons/icon_bezier_handles_balanced.svg @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_bezier_handles_balanced.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1417" + inkscape:window-height="685" + id="namedview8" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="4.2910315" + inkscape:cy="11.857644" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="m 1.7627119,13.627119 c 0,0 1.2881355,-6.847458 6.5762712,-8.1355935 5.0847459,0.9491522 5.9661009,8.1355925 5.9661009,8.1355925" + id="path4526" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccc" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846" + cx="1.8983043" + cy="13.491526" + rx="1.2675855" + ry="1.1997888" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3" + cx="14.237288" + cy="13.491526" + rx="1.2675855" + ry="1.1997888" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:0.61799997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 7.4559186,5.1473018 2.7203863,6.7014816" + id="path5878" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:0.61489719;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 10.790357,4.2063094 8.2893822,5.149623" + id="path5878-7" + inkscape:connector-curvature="0" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3-6" + cx="8.2711868" + cy="4.7796612" + rx="1.2675855" + ry="1.1997888" /> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="M 1.7157324,5.8754878 A 1.2675855,1.1997888 0 0 0 0.44815434,7.0747066 1.2675855,1.1997888 0 0 0 1.7157324,8.2739253 1.2675855,1.1997888 0 0 0 2.9833105,7.0747066 1.2675855,1.1997888 0 0 0 1.7157324,5.8754878 Z m 0.00195,0.4238282 A 0.84677333,0.80148375 0 0 1 2.5653417,7.1000972 0.84677333,0.80148375 0 0 1 1.7176855,7.9008784 0.84677333,0.80148375 0 0 1 0.87002934,7.1000972 0.84677333,0.80148375 0 0 1 1.7176855,6.299316 Z" + id="path5846-5" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.7567277;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="M 11.909414,2.4642073 A 1.2836218,1.231838 0 0 0 10.6258,3.6954601 1.2836218,1.231838 0 0 0 11.909414,4.9267128 1.2836218,1.231838 0 0 0 13.193028,3.6954601 1.2836218,1.231838 0 0 0 11.909414,2.4642073 Z m 0.002,0.4351497 a 0.85748593,0.82289328 0 0 1 0.858383,0.8221719 0.85748593,0.82289328 0 0 1 -0.85838,0.822172 0.85748593,0.82289328 0 0 1 -0.858379,-0.822172 0.85748593,0.82289328 0 0 1 0.858379,-0.8221719 z" + id="path5846-5-6" /> +</svg> diff --git a/editor/icons/icon_bezier_handles_free.svg b/editor/icons/icon_bezier_handles_free.svg new file mode 100644 index 0000000000..e5dfb8d0fc --- /dev/null +++ b/editor/icons/icon_bezier_handles_free.svg @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_bezier_handles_separate.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1417" + inkscape:window-height="685" + id="namedview8" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="4.2910315" + inkscape:cy="11.857644" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="m 1.7627119,13.627119 c 0,0 1.2881355,-6.847458 6.5762712,-8.1355935 5.0847459,0.9491522 5.9661009,8.1355925 5.9661009,8.1355925" + id="path4526" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccc" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846" + cx="1.8983043" + cy="13.491526" + rx="1.2675855" + ry="1.1997888" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3" + cx="14.237288" + cy="13.491526" + rx="1.2675855" + ry="1.1997888" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:0.80513805;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 7.6850253,4.7560401 3.9088983,5.4168" + id="path5878" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:0.73079807;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 11.695505,2.3941651 8.696384,4.6876729" + id="path5878-7" + inkscape:connector-curvature="0" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3-6" + cx="8.2711868" + cy="4.7796612" + rx="1.2675855" + ry="1.1997888" /> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="M 2.4961199,4.3976698 A 1.1997888,1.2675855 80.074672 0 0 1.4542161,5.7974257 1.1997888,1.2675855 80.074672 0 0 2.9095255,6.7602105 1.1997888,1.2675855 80.074672 0 0 3.9514292,5.3604547 1.1997888,1.2675855 80.074672 0 0 2.4961199,4.3976698 Z m 0.074974,0.4171488 A 0.80148375,0.84677333 80.074672 0 1 3.5440925,5.4575082 0.80148375,0.84677333 80.074672 0 1 2.8471493,6.3924102 0.80148375,0.84677333 80.074672 0 1 1.8741535,5.74972 0.80148375,0.84677333 80.074672 0 1 2.5710967,4.814818 Z" + id="path5846-5" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.7567277;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="m 11.838896,0.64428913 a 1.231838,1.2836218 52.593897 0 0 -0.271701,1.75779027 1.231838,1.2836218 52.593897 0 0 1.767576,0.1983008 1.231838,1.2836218 52.593897 0 0 0.271701,-1.75779027 1.231838,1.2836218 52.593897 0 0 -1.767576,-0.1983008 z m 0.265925,0.3444462 A 0.82289328,0.85748593 52.593897 0 1 13.286115,1.1203938 0.82289328,0.85748593 52.593897 0 1 13.103698,2.2949179 0.82289328,0.85748593 52.593897 0 1 11.922407,2.163257 0.82289328,0.85748593 52.593897 0 1 12.104824,0.98873353 Z" + id="path5846-5-6" /> +</svg> diff --git a/editor/icons/icon_bezier_handles_mirror.svg b/editor/icons/icon_bezier_handles_mirror.svg new file mode 100644 index 0000000000..682c898368 --- /dev/null +++ b/editor/icons/icon_bezier_handles_mirror.svg @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_bezier_handles_mirror.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1417" + inkscape:window-height="685" + id="namedview8" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="4.2910315" + inkscape:cy="11.857644" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:1.70000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="m 1.7627119,13.627119 c 0,0 1.2881355,-6.847458 6.5762712,-8.1355935 5.0847459,0.9491522 5.9661009,8.1355925 5.9661009,8.1355925" + id="path4526" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccc" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846" + cx="1.8983043" + cy="13.491526" + rx="1.2675855" + ry="1.1997888" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3" + cx="14.237288" + cy="13.491526" + rx="1.2675855" + ry="1.1997888" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:0.80513805;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 8.2033896,4.6779662 H 4.3698875" + id="path5878" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#84c2ff;stroke-width:0.71670938;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 11.931789,4.6440679 H 8.2033896" + id="path5878-7" + inkscape:connector-curvature="0" /> + <ellipse + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3-6" + cx="8.2711868" + cy="4.7796612" + rx="1.2675855" + ry="1.1997888" /> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="M 3.1539157,3.4305762 A 1.2675855,1.1997888 0 0 0 1.8863376,4.629795 1.2675855,1.1997888 0 0 0 3.1539157,5.8290137 1.2675855,1.1997888 0 0 0 4.4214938,4.629795 1.2675855,1.1997888 0 0 0 3.1539157,3.4305762 Z m 0.00195,0.4238282 A 0.84677333,0.80148375 0 0 1 4.003525,4.6551856 0.84677333,0.80148375 0 0 1 3.1558688,5.4559668 0.84677333,0.80148375 0 0 1 2.3082126,4.6551856 0.84677333,0.80148375 0 0 1 3.1558688,3.8544044 Z" + id="path5846-5" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="m 13.093969,3.3750567 a 1.2675855,1.1997888 0 0 0 -1.267578,1.1992188 1.2675855,1.1997888 0 0 0 1.267578,1.1992187 1.2675855,1.1997888 0 0 0 1.267578,-1.1992187 1.2675855,1.1997888 0 0 0 -1.267578,-1.1992188 z m 0.002,0.4238282 a 0.84677333,0.80148375 0 0 1 0.847659,0.8007812 0.84677333,0.80148375 0 0 1 -0.847656,0.8007812 0.84677333,0.80148375 0 0 1 -0.847656,-0.8007812 0.84677333,0.80148375 0 0 1 0.847656,-0.8007812 z" + id="path5846-5-6" /> +</svg> diff --git a/editor/icons/icon_color_track_vu.svg b/editor/icons/icon_color_track_vu.svg new file mode 100644 index 0000000000..cad76d0234 --- /dev/null +++ b/editor/icons/icon_color_track_vu.svg @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="24" + version="1.1" + viewBox="0 0 16 24" + id="svg6" + sodipodi:docname="icon_color_track_vu.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10"> + <linearGradient + id="linearGradient4583" + inkscape:collect="always"> + <stop + id="stop4579" + offset="0" + style="stop-color:#f70000;stop-opacity:1" /> + <stop + id="stop4581" + offset="1" + style="stop-color:#eec315;stop-opacity:1" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient4549"> + <stop + style="stop-color:#288027;stop-opacity:1" + offset="0" + id="stop4545" /> + <stop + style="stop-color:#dbee15;stop-opacity:1" + offset="1" + id="stop4547" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4549" + id="linearGradient4551" + x1="7.7288136" + y1="16.474577" + x2="7.7288136" + y2="3.8644071" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0931873,0,0,1.4762899,-0.98021429,0.08553021)" /> + <linearGradient + gradientTransform="matrix(1.1036585,0,0,0.47778193,-16.507235,-7.9018165)" + inkscape:collect="always" + xlink:href="#linearGradient4583" + id="linearGradient4551-7" + x1="7.7288136" + y1="16.474577" + x2="7.7288136" + y2="3.8644071" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1170" + inkscape:window-height="712" + id="namedview8" + showgrid="false" + showguides="false" + inkscape:zoom="14.75" + inkscape:cx="5.3261277" + inkscape:cy="13.681053" + inkscape:window-x="397" + inkscape:window-y="233" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <rect + style="fill:url(#linearGradient4551);fill-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke" + id="rect822" + width="18.232145" + height="18.416088" + x="-1.3507863" + y="5.9906898" + ry="0.84580106" /> + <rect + style="fill:url(#linearGradient4551-7);fill-opacity:1;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke" + id="rect822-5" + width="18.406782" + height="5.9601259" + x="-16.881357" + y="-5.9906898" + ry="0.27373245" + transform="scale(-1)" /> +</svg> diff --git a/editor/icons/icon_edit_bezier.svg b/editor/icons/icon_edit_bezier.svg new file mode 100644 index 0000000000..542ff52aac --- /dev/null +++ b/editor/icons/icon_edit_bezier.svg @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_edit_bezier.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1039" + inkscape:window-height="585" + id="namedview8" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="11.65471" + inkscape:cy="9.0988062" + inkscape:window-x="277" + inkscape:window-y="113" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g4"> + <path + style="fill:none;stroke:#84c2ff;stroke-width:2.20000005;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + d="m 1.4758015,1050.3064 c 11.6492855,0.7191 3.1098343,-11.4976 12.2331255,-11.3475" + id="path4526" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> + <circle + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" + id="path5846-3" + cy="1038.7133" + cx="13.470984" + r="1.8230016" /> + <circle + r="1.8230016" + cx="2.4449117" + cy="1050.1708" + id="circle1374" + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:5.64574671;stroke-miterlimit:4.9000001;stroke-dasharray:none;stroke-opacity:1" /> + </g> +</svg> diff --git a/editor/icons/icon_key_animation.svg b/editor/icons/icon_key_animation.svg new file mode 100644 index 0000000000..a09567498f --- /dev/null +++ b/editor/icons/icon_key_animation.svg @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_animation.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1852" + inkscape:window-height="781" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="-10.271186" + inkscape:cy="3.4149032" + inkscape:window-x="68" + inkscape:window-y="117" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#ea686c" + id="rect2" + style="fill:#b76ef0;fill-opacity:1" /> + </g> +</svg> diff --git a/editor/icons/icon_key_audio.svg b/editor/icons/icon_key_audio.svg new file mode 100644 index 0000000000..7c728bfd01 --- /dev/null +++ b/editor/icons/icon_key_audio.svg @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_audio.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1053" + inkscape:window-height="591" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="4" + inkscape:cy="4" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#ea686c" + id="rect2" + style="fill:#eae668;fill-opacity:1" /> + </g> +</svg> diff --git a/editor/icons/icon_key_bezier.svg b/editor/icons/icon_key_bezier.svg new file mode 100644 index 0000000000..62af6fdb34 --- /dev/null +++ b/editor/icons/icon_key_bezier.svg @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_bezier.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1852" + inkscape:window-height="781" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="-17.152542" + inkscape:cy="3.4149032" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#ea686c" + id="rect2" + style="fill:#5792f6;fill-opacity:1" /> + </g> +</svg> diff --git a/editor/icons/icon_key_bezier_handle.svg b/editor/icons/icon_key_bezier_handle.svg new file mode 100644 index 0000000000..d7b22d0905 --- /dev/null +++ b/editor/icons/icon_key_bezier_handle.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_bezier_handle.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1853" + inkscape:window-height="1016" + id="namedview8" + showgrid="false" + inkscape:zoom="59" + inkscape:cx="2.0952442" + inkscape:cy="4.6061633" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <path + style="fill:#e0e0e0;fill-opacity:1" + d="M 3.9960938 -0.037109375 C 3.8010931 -0.037109375 3.6064535 0.038077731 3.4570312 0.1875 L 0.22070312 3.4238281 C -0.078134343 3.7226656 -0.078141414 4.2050617 0.22070312 4.5039062 L 3.4570312 7.7402344 C 3.7558687 8.0390718 4.2382719 8.0390718 4.5371094 7.7402344 L 7.7734375 4.5039062 C 8.072282 4.2050617 8.072275 3.7226656 7.7734375 3.4238281 L 4.5371094 0.1875 C 4.3876871 0.038077731 4.1910944 -0.037109375 3.9960938 -0.037109375 z M 4.0253906 0.81445312 C 4.1770098 0.81445312 4.3291322 0.87241756 4.4453125 0.98828125 L 6.9609375 3.4960938 C 7.193298 3.7278211 7.193298 4.102257 6.9609375 4.3339844 L 4.4453125 6.84375 C 4.212952 7.0754774 3.8378293 7.0754774 3.6054688 6.84375 L 1.0898438 4.3339844 C 0.85748323 4.102257 0.85748323 3.7278211 1.0898438 3.4960938 L 3.6054688 0.98828125 C 3.721649 0.87241756 3.8737714 0.81445312 4.0253906 0.81445312 z " + transform="translate(0,1044.4)" + id="rect2" /> + </g> +</svg> diff --git a/editor/icons/icon_key_bezier_point.svg b/editor/icons/icon_key_bezier_point.svg new file mode 100644 index 0000000000..aa33063c95 --- /dev/null +++ b/editor/icons/icon_key_bezier_point.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_bezier_point.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="836" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="4" + inkscape:cy="4" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#e0e0e0" + id="rect2" /> + </g> +</svg> diff --git a/editor/icons/icon_key_bezier_selected.svg b/editor/icons/icon_key_bezier_selected.svg new file mode 100644 index 0000000000..e3f967707a --- /dev/null +++ b/editor/icons/icon_key_bezier_selected.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_bezier_selected.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="836" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="4" + inkscape:cy="4" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#84c2ff" + id="rect2" /> + </g> +</svg> diff --git a/editor/icons/icon_key_call.svg b/editor/icons/icon_key_call.svg index 7fcc65801a..e702898288 100644 --- a/editor/icons/icon_key_call.svg +++ b/editor/icons/icon_key_call.svg @@ -1,5 +1,64 @@ -<svg width="8" height="8" version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1044.4)"> -<rect transform="rotate(-45)" x="-741.53" y="741.08" width="6.1027" height="6.1027" ry=".76286" fill="#adf18f"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_call.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="836" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="4" + inkscape:cy="4" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#adf18f" + id="rect2" + style="fill:#66f376;fill-opacity:1" /> + </g> </svg> diff --git a/editor/icons/icon_key_selected.svg b/editor/icons/icon_key_selected.svg index c73d31981d..2842fd93eb 100644 --- a/editor/icons/icon_key_selected.svg +++ b/editor/icons/icon_key_selected.svg @@ -1,5 +1,76 @@ -<svg width="8" height="8" version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1044.4)"> -<rect transform="rotate(-45)" x="-741.53" y="741.08" width="6.1027" height="6.1027" ry=".76286" fill="#84c2ff"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_selected.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1568" + inkscape:window-height="767" + id="namedview8" + showgrid="false" + inkscape:zoom="41.7193" + inkscape:cx="-0.48848775" + inkscape:cy="3.5639274" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4-3" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#84c2ff" + id="rect2" /> + </g> + <g + transform="translate(0,-1044.4)" + id="g4-3"> + <rect + style="fill:#003e7a;fill-opacity:1;stroke-width:0.56281364" + transform="matrix(0.71728847,-0.69677633,0.71728847,0.69677633,0,0)" + x="-751.20953" + y="753.42743" + width="3.4346831" + height="3.4346831" + ry="0.42934799" + id="rect2-6" /> + </g> </svg> diff --git a/editor/icons/icon_key_xform.svg b/editor/icons/icon_key_xform.svg index 7b73715771..fd22b67f52 100644 --- a/editor/icons/icon_key_xform.svg +++ b/editor/icons/icon_key_xform.svg @@ -1,5 +1,64 @@ -<svg width="8" height="8" version="1.1" viewBox="0 0 8 8" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1044.4)"> -<rect transform="rotate(-45)" x="-741.53" y="741.08" width="6.1027" height="6.1027" ry=".76286" fill="#ea686c"/> -</g> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="8" + height="8" + version="1.1" + viewBox="0 0 8 8" + id="svg6" + sodipodi:docname="icon_key_xform.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="836" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="4" + inkscape:cy="4" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1044.4)" + id="g4"> + <rect + transform="rotate(-45)" + x="-741.53" + y="741.08" + width="6.1027" + height="6.1027" + ry=".76286" + fill="#ea686c" + id="rect2" + style="fill:#ea9568;fill-opacity:1" /> + </g> </svg> diff --git a/editor/icons/icon_play_travel.svg b/editor/icons/icon_play_travel.svg new file mode 100644 index 0000000000..5cd3e07e20 --- /dev/null +++ b/editor/icons/icon_play_travel.svg @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_play_travel.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1446" + inkscape:window-height="646" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8.2818541" + inkscape:cy="5.7694884" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="matrix(0.59321602,0,0,0.59321602,-1.2203136,-611.14809)" + id="g6"> + <g + id="g4"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <g + transform="matrix(0.59321602,0,0,0.59321602,7.5254716,-610.94451)" + id="g6-3"> + <g + id="g4-6"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2-7" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect842" + width="9.5593224" + height="0.54237264" + x="3.0058463" + y="8.1280737" + ry="0.27118632" /> +</svg> diff --git a/editor/icons/icon_time.svg b/editor/icons/icon_time.svg new file mode 100644 index 0000000000..d50c9570b3 --- /dev/null +++ b/editor/icons/icon_time.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg6" + sodipodi:docname="icon_time.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="836" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="7.375" + inkscape:cx="4.4999435" + inkscape:cy="13.04848" + inkscape:window-x="744" + inkscape:window-y="280" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g4"> + <g + id="g8" + style="fill:#e0e0e0;fill-opacity:1" + transform="matrix(0.0279396,0,0,0.02755726,0.91401567,1037.1343)"> + <g + id="g6" + style="fill:#e0e0e0;fill-opacity:1"> + <path + d="M 276.193,58.507 V 40.389 h 14.578 c 11.153,0 20.194,-9.042 20.194,-20.194 C 310.965,9.043 301.923,0 290.771,0 h -69.544 c -11.153,0 -20.194,9.042 -20.194,20.194 0,11.152 9.042,20.194 20.194,20.194 h 14.578 V 58.506 C 119.952,68.76 28.799,166.327 28.799,284.799 28.799,410.078 130.721,512 256,512 381.279,512 483.201,410.078 483.201,284.799 483.2,166.327 392.046,68.76 276.193,58.507 Z m 0,412.009 v -20.124 c 0,-11.153 -9.042,-20.194 -20.194,-20.194 -11.153,0 -20.194,9.042 -20.194,20.194 v 20.124 C 148.895,461.131 79.668,391.902 70.283,304.994 h 20.124 c 11.153,0 20.194,-9.042 20.194,-20.194 0,-11.152 -9.042,-20.194 -20.194,-20.194 H 70.282 c 9.385,-86.91 78.614,-156.137 165.522,-165.523 v 20.124 c 0,11.153 9.042,20.194 20.194,20.194 11.153,0 20.194,-9.042 20.194,-20.194 V 99.081 c 86.91,9.385 156.137,78.614 165.522,165.523 H 421.59 c -11.153,0 -20.194,9.042 -20.194,20.194 0,11.152 9.042,20.194 20.194,20.194 h 20.126 c -9.385,86.911 -78.613,156.14 -165.523,165.524 z" + id="path2" + style="fill:#e0e0e0;fill-opacity:1" + inkscape:connector-curvature="0" /> + <path + d="m 317.248,194.99 -58.179,58.18 c -1.011,-0.097 -2.034,-0.151 -3.071,-0.151 -17.552,0 -31.779,14.229 -31.779,31.779 0,17.552 14.228,31.779 31.779,31.779 17.551,0 31.779,-14.229 31.779,-31.779 0,-1.037 -0.054,-2.06 -0.151,-3.07 l 58.178,-58.18 c 7.887,-7.885 7.887,-20.672 0,-28.559 -7.882,-7.886 -20.669,-7.886 -28.556,0.001 z" + id="path4" + style="fill:#e0e0e0;fill-opacity:1" + inkscape:connector-curvature="0" /> + </g> + </g> + </g> +</svg> diff --git a/editor/icons/icon_tool_add_node.svg b/editor/icons/icon_tool_add_node.svg new file mode 100644 index 0000000000..a4ff4d08a0 --- /dev/null +++ b/editor/icons/icon_tool_add_node.svg @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_tool_add_node.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1516" + inkscape:window-height="747" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <g + transform="translate(-26.001 -9.8683)" + id="g4"> + <path + style="fill:#e0e0e0;fill-opacity:1" + d="m 27.917081,1047.5557 c -0.422624,0 -0.763672,0.3411 -0.763672,0.7637 v 11.8301 c 0,0.4226 0.341048,0.7637 0.763672,0.7637 h 12.507813 c 0.422624,0 0.761719,-0.3411 0.761719,-0.7637 v -11.8301 c 0,-0.4226 -0.339095,-0.7637 -0.761719,-0.7637 z m 1.898438,1.6954 h 8.642578 c 0.422624,0 0.763672,0.341 0.763672,0.7636 v 8.5078 c 0,0.4227 -0.341048,0.7618 -0.763672,0.7618 h -8.642578 c -0.422625,0 -0.763672,-0.3391 -0.763672,-0.7618 v -8.5078 c 0,-0.4226 0.341047,-0.7636 0.763672,-0.7636 z" + id="rect821" + inkscape:connector-curvature="0" /> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect826" + width="7.7966104" + height="2.3728814" + x="30.20439" + y="1052.9802" + ry="0.76286" /> + <rect + style="fill:#e0e0e0;fill-opacity:1;stroke-width:0.88253576" + id="rect828" + width="2.3728814" + height="7.5254235" + x="32.916256" + y="1050.3361" + ry="0.72997814" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_tool_connect.svg b/editor/icons/icon_tool_connect.svg new file mode 100644 index 0000000000..91d5893163 --- /dev/null +++ b/editor/icons/icon_tool_connect.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_tool_connect.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1516" + inkscape:window-height="747" + id="namedview10" + showgrid="false" + inkscape:zoom="3.6875" + inkscape:cx="8.5909556" + inkscape:cy="7.8012075" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <g + transform="translate(-26.001 -9.8683)" + id="g4"> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect849" + width="14.305085" + height="2.1694915" + x="26.766621" + y="1053.1389" + ry="0.76286" /> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1.16725671px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 30.596131,1046.927 v 14.8861 l 8.228847,-7.5722 z" + id="path853" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_tool_triangle.svg b/editor/icons/icon_tool_triangle.svg new file mode 100644 index 0000000000..5696008767 --- /dev/null +++ b/editor/icons/icon_tool_triangle.svg @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_tool_triangle.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1204" + inkscape:window-height="703" + id="namedview10" + showgrid="false" + inkscape:zoom="29.5" + inkscape:cx="8.0650451" + inkscape:cy="7.0341257" + inkscape:window-x="542" + inkscape:window-y="205" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(0 -1036.4)" + id="g6"> + <g + transform="translate(-26.001 -9.8683)" + id="g4"> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 27.695915,1056.3022 c 0,0 7.457627,-8.0678 7.118644,-7.8644 -0.338983,0.2034 5.830509,11.7288 5.830509,11.7288 z" + id="path821" + inkscape:connector-curvature="0" /> + <circle + style="fill:#4b4b4b;fill-opacity:1;stroke:#e0e0e0;stroke-width:0.51200002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path825" + cx="34.662014" + cy="1048.5903" + r="1.607564" /> + <circle + style="fill:#4b4b4b;fill-opacity:1;stroke:#e0e0e0;stroke-width:0.51200002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path825-3" + cx="39.933205" + cy="1059.6581" + r="1.607564" /> + <circle + style="fill:#4b4b4b;fill-opacity:1;stroke:#e0e0e0;stroke-width:0.51200002;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path825-3-6" + cx="28.17049" + cy="1056.2683" + r="1.607564" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_track_capture.svg b/editor/icons/icon_track_capture.svg new file mode 100644 index 0000000000..da6a662746 --- /dev/null +++ b/editor/icons/icon_track_capture.svg @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="8" + version="1.1" + viewBox="0 0 16 8" + id="svg6" + sodipodi:docname="icon_track_capture.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1350" + inkscape:window-height="593" + id="namedview8" + showgrid="false" + inkscape:zoom="27.577164" + inkscape:cx="8.347146" + inkscape:cy="4.4012076" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + style="fill:#e0e0e0;fill-opacity:1" + d="m 2.1665128,0.99764963 c -0.422625,0 -0.763672,0.34104737 -0.763672,0.76367187 v 4.5742187 c 0,0.4226242 0.341047,0.7617192 0.763672,0.7617192 h 4.472656 c 0.422625,0 0.763672,-0.339095 0.763672,-0.7617192 V 5.347259 h -3.300781 c -0.1662,0 -0.298828,-0.3390943 -0.298828,-0.7617188 V 3.3609308 c 0,-0.4226244 0.132628,-0.7636718 0.298828,-0.7636718 h 3.300781 V 1.7613215 c 0,-0.4226245 -0.341047,-0.76367187 -0.763672,-0.76367187 z" + id="rect1389" + inkscape:connector-curvature="0" /> + <path + style="fill:#e0e0e0;fill-opacity:1;stroke:#e0e0e0;stroke-width:0.80299997;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 9.1827441,4.7953408 C 9.6993662,3.7537783 10.278269,2.5835979 10.469195,2.1949398 l 0.347137,-0.7066511 0.679654,0.00665 0.679654,0.00665 0.956945,2.3125 c 0.526319,1.271875 1.007254,2.4334375 1.068744,2.5812497 l 0.1118,0.26875 H 13.715914 13.1187 L 12.785851,6.0203387 12.453002,5.3765887 H 11.319176 10.185351 L 9.8066761,6.032702 9.4280014,6.6888154 l -0.5922856,1.37e-4 -0.592285,1.36e-4 z m 3.1779349,-0.369483 c 0.0042,-0.00346 -0.233487,-0.4884588 -0.528245,-1.0777779 l -0.535922,-1.0714891 -0.03691,0.0875 c -0.0203,0.048125 -0.183516,0.425 -0.362699,0.8375 -0.179182,0.4125 -0.355738,0.85125 -0.392346,0.975 -0.03661,0.12375 -0.07127,0.2390723 -0.07703,0.2562715 -0.0083,0.024853 0.188215,0.027989 0.957503,0.015278 0.532385,-0.0088 0.971429,-0.018823 0.975651,-0.022283 z" + id="path1424" + inkscape:connector-curvature="0" /> +</svg> diff --git a/editor/icons/icon_transition_end.svg b/editor/icons/icon_transition_end.svg new file mode 100644 index 0000000000..8a1937670a --- /dev/null +++ b/editor/icons/icon_transition_end.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_transition_end.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="10.204146" + inkscape:cy="5.3391396" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="translate(-2,-1036.4)" + id="g6"> + <g + id="g4"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect862" + width="3.0681243" + height="10.067283" + x="11.16989" + y="3.0084109" + ry="0.76286" /> +</svg> diff --git a/editor/icons/icon_transition_end_auto.svg b/editor/icons/icon_transition_end_auto.svg new file mode 100644 index 0000000000..18927bc4ef --- /dev/null +++ b/editor/icons/icon_transition_end_auto.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_transition_automatic.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="0.56831798" + inkscape:cy="5.1473818" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="translate(-2,-1036.4)" + id="g6" + style="fill:#77ce57;fill-opacity:1"> + <g + id="g4" + style="fill:#77ce57;fill-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#77ce57;fill-opacity:1" + id="rect862" + width="3.0681243" + height="10.067283" + x="11.16989" + y="3.0084109" + ry="0.76286" /> +</svg> diff --git a/editor/icons/icon_transition_end_auto_big.svg b/editor/icons/icon_transition_end_auto_big.svg new file mode 100644 index 0000000000..aaedafaf04 --- /dev/null +++ b/editor/icons/icon_transition_end_auto_big.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="20" + height="20" + version="1.1" + viewBox="0 0 20 20" + id="svg8" + sodipodi:docname="icon_transition_automatic_big.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="0.3064671" + inkscape:cy="14.348448" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="matrix(1.4099529,0,0,1.4099529,-4.1975887,-1462.5094)" + id="g6" + style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1"> + <g + id="g4" + style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-width:1.409953;stroke-opacity:1" + id="rect862" + width="4.3259106" + height="14.194397" + x="14.371336" + y="3.0076122" + ry="1.0755967" /> +</svg> diff --git a/editor/icons/icon_transition_end_big.svg b/editor/icons/icon_transition_end_big.svg new file mode 100644 index 0000000000..46d42e95e3 --- /dev/null +++ b/editor/icons/icon_transition_end_big.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="20" + height="20" + version="1.1" + viewBox="0 0 20 20" + id="svg8" + sodipodi:docname="icon_transition_end_big.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="-1.1122019" + inkscape:cy="10.839132" + inkscape:window-x="517" + inkscape:window-y="261" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="matrix(1.4203458,0,0,1.4203458,-4.29479,-1473.1325)" + id="g6" + style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <g + id="g4" + style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#e0e0e0;fill-opacity:1;stroke:#424242;stroke-width:1.42026603;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect862" + width="4.3577976" + height="14.299023" + x="14.411009" + y="3.1868868" + ry="1.0835251" /> +</svg> diff --git a/editor/icons/icon_transition_immediate.svg b/editor/icons/icon_transition_immediate.svg new file mode 100644 index 0000000000..ba16a33c91 --- /dev/null +++ b/editor/icons/icon_transition_immediate.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_transition_immediate.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="4.0199579" + inkscape:cy="5.3391396" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(-2,-1036.4)" + id="g6"> + <g + id="g4"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_transition_immediate_auto.svg b/editor/icons/icon_transition_immediate_auto.svg new file mode 100644 index 0000000000..c127560145 --- /dev/null +++ b/editor/icons/icon_transition_immediate_auto.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_transition_immediate_auto.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="-9.2592678" + inkscape:cy="5.3391396" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="translate(-2,-1036.4)" + id="g6"> + <g + id="g4"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_transition_immediate_auto_big.svg b/editor/icons/icon_transition_immediate_auto_big.svg new file mode 100644 index 0000000000..80d35a36f3 --- /dev/null +++ b/editor/icons/icon_transition_immediate_auto_big.svg @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="20" + height="20" + version="1.1" + viewBox="0 0 20 20" + id="svg8" + sodipodi:docname="icon_transition_immediate_auto_big.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="11.321237" + inkscape:cy="3.5752171" + inkscape:window-x="517" + inkscape:window-y="261" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="matrix(1.571031,0,0,1.571031,-2.7257681,-1630.6239)" + id="g6" + style="stroke:#404040;stroke-opacity:1"> + <g + id="g4" + style="stroke:#404040;stroke-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_transition_immediate_big.svg b/editor/icons/icon_transition_immediate_big.svg new file mode 100644 index 0000000000..108dcdd500 --- /dev/null +++ b/editor/icons/icon_transition_immediate_big.svg @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="20" + height="20" + version="1.1" + viewBox="0 0 20 20" + id="svg8" + sodipodi:docname="icon_transition_immediate_big.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="0.19928629" + inkscape:cy="4.534006" + inkscape:window-x="517" + inkscape:window-y="261" + inkscape:window-maximized="0" + inkscape:current-layer="g4" /> + <g + transform="matrix(1.571031,0,0,1.571031,-2.7257681,-1630.6239)" + id="g6" + style="stroke:#404040;stroke-opacity:1"> + <g + id="g4" + style="stroke:#404040;stroke-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#404040;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/editor/icons/icon_transition_sync.svg b/editor/icons/icon_transition_sync.svg new file mode 100644 index 0000000000..267d806615 --- /dev/null +++ b/editor/icons/icon_transition_sync.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_transition_sync.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="10.204146" + inkscape:cy="5.3391396" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="translate(2.5542471,-1036.4)" + id="g6"> + <g + id="g4"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#e0e0e0;fill-opacity:1" + id="rect862" + width="3.0681243" + height="10.067283" + x="1.9655174" + y="3.0084109" + ry="0.76286" /> +</svg> diff --git a/editor/icons/icon_transition_sync_auto.svg b/editor/icons/icon_transition_sync_auto.svg new file mode 100644 index 0000000000..5ce61e3a6a --- /dev/null +++ b/editor/icons/icon_transition_sync_auto.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg8" + sodipodi:docname="icon_transition_sync_auto.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="20.85965" + inkscape:cx="0.56831798" + inkscape:cy="5.1473818" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="translate(3.0815809,-1036.4)" + id="g6" + style="fill:#77ce57;fill-opacity:1"> + <g + id="g4" + style="fill:#77ce57;fill-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#77ce57;fill-opacity:1" + id="rect862" + width="3.0681243" + height="10.067283" + x="1.9655174" + y="3.0084109" + ry="0.76286" /> +</svg> diff --git a/editor/icons/icon_transition_sync_auto_big.svg b/editor/icons/icon_transition_sync_auto_big.svg new file mode 100644 index 0000000000..3e84d76398 --- /dev/null +++ b/editor/icons/icon_transition_sync_auto_big.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="20" + height="20" + version="1.1" + viewBox="0 0 20 20" + id="svg8" + sodipodi:docname="icon_transition_sync_auto_big.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="0.3064671" + inkscape:cy="14.348448" + inkscape:window-x="67" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="matrix(1.4099529,0,0,1.4099529,2.1752927,-1462.5094)" + id="g6" + style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1"> + <g + id="g4" + style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-width:1.409953;stroke-opacity:1" + id="rect862" + width="4.3259106" + height="14.194397" + x="1.6255733" + y="3.0076122" + ry="1.0755967" /> +</svg> diff --git a/editor/icons/icon_transition_sync_big.svg b/editor/icons/icon_transition_sync_big.svg new file mode 100644 index 0000000000..e7cf63e0b3 --- /dev/null +++ b/editor/icons/icon_transition_sync_big.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="20" + height="20" + version="1.1" + viewBox="0 0 20 20" + id="svg8" + sodipodi:docname="icon_transition_sync_big.svg" + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + <metadata + id="metadata14"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs12" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1403" + inkscape:window-height="782" + id="namedview10" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="19.226781" + inkscape:cy="9.27981" + inkscape:window-x="302" + inkscape:window-y="226" + inkscape:window-maximized="0" + inkscape:current-layer="svg8" /> + <g + transform="matrix(1.4203458,0,0,1.4203458,1.8747015,-1473.1325)" + id="g6" + style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <g + id="g4" + style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"> + <path + d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z" + dominant-baseline="auto" + style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto" + id="path2" + inkscape:connector-curvature="0" /> + </g> + </g> + <rect + style="fill:#e0e0e0;fill-opacity:1;stroke:#424242;stroke-width:1.42026603;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect862" + width="4.3577976" + height="14.299023" + x="1.4618562" + y="3.1868868" + ry="1.0835251" /> +</svg> diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index 2fb3bf7b1e..a13f741ee7 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -1785,8 +1785,7 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones } } - Quat q = xform.basis; - q.normalize(); + Quat q = xform.basis.get_rotation_quat(); Vector3 s = xform.basis.get_scale(); Vector3 l = xform.origin; @@ -1838,8 +1837,7 @@ void ColladaImport::create_animation(int p_clip, bool p_make_tracks_in_all_bones xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; - Quat q = xform.basis; - q.normalize(); + Quat q = xform.basis.get_rotation_quat(); Vector3 s = xform.basis.get_scale(); Vector3 l = xform.origin; diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp index f4be6e8d59..eb0bc0f782 100644 --- a/editor/import/editor_scene_importer_gltf.cpp +++ b/editor/import/editor_scene_importer_gltf.cpp @@ -863,6 +863,7 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { ERR_FAIL_COND_V(!d.has("primitives"), ERR_PARSE_ERROR); Array primitives = d["primitives"]; + Dictionary extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); for (int j = 0; j < primitives.size(); j++) { @@ -1000,8 +1001,10 @@ Error EditorSceneImporterGLTF::_parse_meshes(GLTFState &state) { Array targets = p["targets"]; if (j == 0) { + Array target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array(); for (int k = 0; k < targets.size(); k++) { - mesh.mesh->add_blend_shape(String("morph_") + itos(k)); + String name = k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k); + mesh.mesh->add_blend_shape(name); } } @@ -1253,12 +1256,15 @@ Error EditorSceneImporterGLTF::_parse_materials(GLTFState &state) { } if (mr.has("metallicFactor")) { - material->set_metallic(mr["metallicFactor"]); + } else { + material->set_metallic(1.0); } - if (mr.has("roughnessFactor")) { + if (mr.has("roughnessFactor")) { material->set_roughness(mr["roughnessFactor"]); + } else { + material->set_roughness(1.0); } if (mr.has("metallicRoughnessTexture")) { @@ -1983,8 +1989,7 @@ void EditorSceneImporterGLTF::_import_animation(GLTFState &state, AnimationPlaye int bone = node->joints[i].godot_bone_index; xform = skeleton->get_bone_rest(bone).affine_inverse() * xform; - rot = xform.basis; - rot.normalize(); + rot = xform.basis.get_rotation_quat(); scale = xform.basis.get_scale(); pos = xform.origin; } diff --git a/editor/import/resource_importer_obj.cpp b/editor/import/resource_importer_obj.cpp index 21803a2184..b8dd4a87b7 100644 --- a/editor/import/resource_importer_obj.cpp +++ b/editor/import/resource_importer_obj.cpp @@ -188,7 +188,7 @@ static Error _parse_material_library(const String &p_path, Map<String, Ref<Spati return OK; } -static Error _parse_obj(const String &p_path, List<Ref<Mesh> > &r_meshes, bool p_single_mesh, bool p_generate_tangents, Vector3 p_scale_mesh, List<String> *r_missing_deps) { +static Error _parse_obj(const String &p_path, List<Ref<Mesh> > &r_meshes, bool p_single_mesh, bool p_generate_tangents, bool p_optimize, Vector3 p_scale_mesh, List<String> *r_missing_deps) { FileAccessRef f = FileAccess::open(p_path, FileAccess::READ); @@ -200,6 +200,8 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh> > &r_meshes, bool p bool generate_tangents = p_generate_tangents; Vector3 scale_mesh = p_scale_mesh; bool flip_faces = false; + int mesh_flags = p_optimize ? Mesh::ARRAY_COMPRESS_DEFAULT : 0; + //bool flip_faces = p_options["force/flip_faces"]; //bool force_smooth = p_options["force/smooth_shading"]; //bool weld_vertices = p_options["force/weld_vertices"]; @@ -331,7 +333,7 @@ static Error _parse_obj(const String &p_path, List<Ref<Mesh> > &r_meshes, bool p surf_tool->set_material(material_map[current_material_library][current_material]); } - mesh = surf_tool->commit(mesh); + mesh = surf_tool->commit(mesh, mesh_flags); if (current_material != String()) { mesh->surface_set_name(mesh->get_surface_count() - 1, current_material.get_basename()); @@ -402,7 +404,7 @@ Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, in List<Ref<Mesh> > meshes; - Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, Vector3(1, 1, 1), r_missing_deps); + Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, p_flags & IMPORT_USE_COMPRESSION, Vector3(1, 1, 1), r_missing_deps); if (err != OK) { if (r_err) { @@ -470,6 +472,7 @@ void ResourceImporterOBJ::get_import_options(List<ImportOption> *r_options, int r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_tangents"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "scale_mesh"), Vector3(1, 1, 1))); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimize_mesh"), true)); } bool ResourceImporterOBJ::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { @@ -480,7 +483,7 @@ Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_s List<Ref<Mesh> > meshes; - Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["scale_mesh"], NULL); + Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["optimize_mesh"], p_options["scale_mesh"], NULL); ERR_FAIL_COND_V(err != OK, err); ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG); diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index fdbf66f656..91644492c3 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -224,24 +224,42 @@ String ResourceImporterScene::get_preset_name(int p_idx) const { static bool _teststr(const String &p_what, const String &p_str) { - if (p_what.findn("$" + p_str) != -1) //blender and other stuff + String what = p_what; + + //remove trailing spaces and numbers, some apps like blender add ".number" to duplicates so also compensate for this + while (what.length() && ((what[what.length() - 1] >= '0' && what[what.length() - 1] <= '9') || what[what.length() - 1] <= 32 || what[what.length() - 1] == '.')) { + + what = what.substr(0, what.length() - 1); + } + + if (what.findn("$" + p_str) != -1) //blender and other stuff return true; - if (p_what.to_lower().ends_with("-" + p_str)) //collada only supports "_" and "-" besides letters + if (what.to_lower().ends_with("-" + p_str)) //collada only supports "_" and "-" besides letters return true; - if (p_what.to_lower().ends_with("_" + p_str)) //collada only supports "_" and "-" besides letters + if (what.to_lower().ends_with("_" + p_str)) //collada only supports "_" and "-" besides letters return true; return false; } static String _fixstr(const String &p_what, const String &p_str) { - if (p_what.findn("$" + p_str) != -1) //blender and other stuff - return p_what.replace("$" + p_str, ""); - if (p_what.to_lower().ends_with("-" + p_str)) //collada only supports "_" and "-" besides letters - return p_what.substr(0, p_what.length() - (p_str.length() + 1)); - if (p_what.to_lower().ends_with("_" + p_str)) //collada only supports "_" and "-" besides letters - return p_what.substr(0, p_what.length() - (p_str.length() + 1)); - return p_what; + String what = p_what; + + //remove trailing spaces and numbers, some apps like blender add ".number" to duplicates so also compensate for this + while (what.length() && ((what[what.length() - 1] >= '0' && what[what.length() - 1] <= '9') || what[what.length() - 1] <= 32 || what[what.length() - 1] == '.')) { + + what = what.substr(0, what.length() - 1); + } + + String end = p_what.substr(what.length(), p_what.length() - what.length()); + + if (what.findn("$" + p_str) != -1) //blender and other stuff + return what.replace("$" + p_str, "") + end; + if (what.to_lower().ends_with("-" + p_str)) //collada only supports "_" and "-" besides letters + return what.substr(0, what.length() - (p_str.length() + 1)) + end; + if (what.to_lower().ends_with("_" + p_str)) //collada only supports "_" and "-" besides letters + return what.substr(0, what.length() - (p_str.length() + 1)) + end; + return what; } Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<ArrayMesh>, Ref<Shape> > &collision_map, LightBakeMode p_light_bake_mode) { @@ -437,13 +455,19 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Array Node *col; if (_teststr(name, "col")) { - mi->set_name(_fixstr(name, "col")); + String new_name = _fixstr(name, "col"); + if (mi->get_parent() && !mi->get_parent()->has_node(new_name)) { + mi->set_name(new_name); + } col = mi->create_trimesh_collision_node(); ERR_FAIL_COND_V(!col, NULL); col->set_name("col"); } else { - mi->set_name(_fixstr(name, "convcol")); + String new_name = _fixstr(name, "convcol"); + if (mi->get_parent() && !mi->get_parent()->has_node(new_name)) { + mi->set_name(new_name); + } col = mi->create_convex_collision_node(); ERR_FAIL_COND_V(!col, NULL); @@ -893,7 +917,6 @@ void ResourceImporterScene::_make_external_resources(Node *p_node, const String } String ext_name = p_base_path.plus_file(_make_extname(E->get()) + ".anim"); - if (FileAccess::exists(ext_name) && p_keep_animations) { //try to keep custom animation tracks Ref<Animation> old_anim = ResourceLoader::load(ext_name, "Animation", true); @@ -907,6 +930,7 @@ void ResourceImporterScene::_make_external_resources(Node *p_node, const String } } + anim->set_path(ext_name, true); //if not set, then its never saved externally ResourceSaver::save(ext_name, anim, ResourceSaver::FLAG_CHANGE_PATH); p_animations[anim] = anim; } diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index beaa8d9600..17a9394b51 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -395,6 +395,10 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); } + + if (normal) { + image->normalize(); + } } if (fix_alpha_border) { diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 4159a3658e..0d0b12c911 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -292,14 +292,14 @@ void InspectorDock::_menu_expandall() { } void InspectorDock::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) { - AnimationPlayerEditor::singleton->get_key_editor()->insert_value_key(p_keyed, p_value, p_advance); + AnimationPlayerEditor::singleton->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance); } void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Transform &p_key) { Spatial *s = Object::cast_to<Spatial>(sp); if (!s) return; - AnimationPlayerEditor::singleton->get_key_editor()->insert_transform_key(s, p_sub, p_key); + AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(s, p_sub, p_key); } void InspectorDock::_warning_pressed() { @@ -435,10 +435,14 @@ void InspectorDock::update(Object *p_object) { } } +void InspectorDock::go_back() { + _edit_back(); +} + void InspectorDock::update_keying() { bool valid = false; - if (AnimationPlayerEditor::singleton->get_key_editor()->has_keying()) { + if (AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history(); if (editor_history->get_path_size() >= 1) { @@ -549,8 +553,8 @@ InspectorDock::InspectorDock(EditorNode *p_editor, EditorData &p_editor_data) { inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); inspector->set_use_doc_hints(true); inspector->set_hide_script(false); - inspector->set_enable_capitalize_paths(bool(EDITOR_DEF("interface/editor/capitalize_properties", true))); - inspector->set_use_folding(!bool(EDITOR_DEF("interface/editor/disable_inspector_folding", false))); + inspector->set_enable_capitalize_paths(bool(EDITOR_GET("interface/inspector/capitalize_properties"))); + inspector->set_use_folding(!bool(EDITOR_GET("interface/inspector/disable_folding"))); inspector->register_text_enter(search); inspector->set_undo_redo(&editor_data->get_undo_redo()); diff --git a/editor/inspector_dock.h b/editor/inspector_dock.h index 688c8beed7..f347056158 100644 --- a/editor/inspector_dock.h +++ b/editor/inspector_dock.h @@ -31,7 +31,7 @@ #ifndef INSPECTOR_DOCK_H #define INSPECTOR_DOCK_H -#include "editor/animation_editor.h" +#include "editor/animation_track_editor.h" #include "editor/connections_dialog.h" #include "editor/create_dialog.h" #include "editor/editor_data.h" @@ -121,6 +121,7 @@ protected: static void _bind_methods(); public: + void go_back(); void update_keying(); void edit_resource(const Ref<Resource> &p_resource); void open_resource(const String &p_type); diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp new file mode 100644 index 0000000000..2e128db883 --- /dev/null +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -0,0 +1,741 @@ +#include "animation_blend_space_1d_editor.h" + +#include "os/keyboard.h" +#include "scene/animation/animation_blend_tree.h" + +void AnimationNodeBlendSpace1DEditorPlugin::edit(Object *p_object) { + anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace1D>(p_object)); +} + +bool AnimationNodeBlendSpace1DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("AnimationNodeBlendSpace1D"); +} + +void AnimationNodeBlendSpace1DEditorPlugin::make_visible(bool p_visible) { + + if (p_visible) { + button->show(); + editor->make_bottom_panel_item_visible(anim_tree_editor); + anim_tree_editor->set_process(true); + } else { + if (anim_tree_editor->is_visible_in_tree()) { + editor->hide_bottom_panel(); + } + + button->hide(); + anim_tree_editor->set_process(false); + } +} + +AnimationNodeBlendSpace1DEditorPlugin::AnimationNodeBlendSpace1DEditorPlugin(EditorNode *p_node) { + editor = p_node; + anim_tree_editor = memnew(AnimationNodeBlendSpace1DEditor); + anim_tree_editor->set_custom_minimum_size(Size2(0, 150 * EDSCALE)); + + button = editor->add_bottom_panel_item(TTR("BlendSpace1D"), anim_tree_editor); + button->hide(); +} + +AnimationNodeBlendSpace1DEditorPlugin::~AnimationNodeBlendSpace1DEditorPlugin() { +} + +void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventKey> k = p_event; + + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) { + if (selected_point != -1) { + _erase_selected(); + accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); + + List<StringName> classes; + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + classes.sort_custom<StringName::AlphCompare>(); + + menu->add_submenu_item(TTR("Add Animation"), "animations"); + + AnimationTree *gp = blend_space->get_tree(); + ERR_FAIL_COND(!gp); + + if (gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + animations_menu->add_icon_item(get_icon("Animation", "Editoricons"), E->get()); + animations_to_add.push_back(E->get()); + } + } + } + + for (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + String name = String(E->get()).replace_first("AnimationNode", ""); + if (name == "Animation") + continue; + + int idx = menu->get_item_count(); + menu->add_item(vformat("Add %s", name)); + menu->set_item_metadata(idx, E->get()); + } + + menu->set_global_position(blend_space_draw->get_global_transform().xform(mb->get_position())); + menu->popup(); + + add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x; + add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + add_point_pos += blend_space->get_min_space(); + + if (snap->is_pressed()) { + add_point_pos = Math::stepify(add_point_pos, blend_space->get_snap()); + } + } + + if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + blend_space_draw->update(); // why not + + // try to see if a point can be selected + selected_point = -1; + _update_tool_erase(); + + for (int i = 0; i < points.size(); i++) { + + if (Math::abs(float(points[i] - mb->get_position().x)) < 10 * EDSCALE) { + selected_point = i; + + Ref<AnimationNode> node = blend_space->get_blend_point_node(i); + EditorNode::get_singleton()->push_item(node.ptr(), "", true); + dragging_selected_attempt = true; + drag_from = mb->get_position(); + _update_tool_erase(); + _update_edited_point_pos(); + return; + } + } + } + + if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) { + if (dragging_selected) { + // move + float point = blend_space->get_blend_point_position(selected_point); + point += drag_ofs.x; + + if (snap->is_pressed()) { + point = Math::stepify(point, blend_space->get_snap()); + } + + updating = true; + undo_redo->create_action("Move Node Point"); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + _update_edited_point_pos(); + } + + dragging_selected_attempt = false; + dragging_selected = false; + blend_space_draw->update(); + } + + // *set* the blend + if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + float blend_pos = mb->get_position().x / blend_space_draw->get_size().x; + blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); + blend_pos += blend_space->get_min_space(); + + blend_space->set_blend_pos(blend_pos); + blend_space_draw->update(); + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid() && !blend_space_draw->has_focus()) { + blend_space_draw->grab_focus(); + blend_space_draw->update(); + } + + if (mm.is_valid() && dragging_selected_attempt) { + dragging_selected = true; + drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * ((blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, 0)); + blend_space_draw->update(); + _update_edited_point_pos(); + } + + if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + float blend_pos = mm->get_position().x / blend_space_draw->get_size().x; + blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); + blend_pos += blend_space->get_min_space(); + + blend_space->set_blend_pos(blend_pos); + blend_space_draw->update(); + } +} + +void AnimationNodeBlendSpace1DEditor::_blend_space_draw() { + + Color linecolor = get_color("font_color", "Label"); + Color linecolor_soft = linecolor; + linecolor_soft.a *= 0.5; + + Ref<Font> font = get_font("font", "Label"); + Ref<Texture> icon = get_icon("KeyValue", "EditorIcons"); + Ref<Texture> icon_selected = get_icon("KeySelected", "EditorIcons"); + + Size2 s = blend_space_draw->get_size(); + + if (blend_space_draw->has_focus()) { + Color color = get_color("accent_color", "Editor"); + blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); + } + + blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor); + + if (blend_space->get_min_space() < 0) { + float point = 0.0; + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s.width; + + float x = point; + + blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor); + blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height() + font->get_ascent()), "0", linecolor); + blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft); + } + + if (snap->is_pressed()) { + + linecolor_soft.a = linecolor.a * 0.1; + + if (blend_space->get_snap() > 0) { + int prev_idx = -1; + + for (int i = 0; i < s.x; i++) { + float v = blend_space->get_min_space() + i * (blend_space->get_max_space() - blend_space->get_min_space()) / s.x; + int idx = int(v / blend_space->get_snap()); + + if (i > 0 && prev_idx != idx) { + blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft); + } + + prev_idx = idx; + } + } + } + + points.clear(); + + for (int i = 0; i < blend_space->get_blend_point_count(); i++) { + float point = blend_space->get_blend_point_position(i); + + if (dragging_selected && selected_point == i) { + point += drag_ofs.x; + if (snap->is_pressed()) { + point = Math::stepify(point, blend_space->get_snap()); + } + } + + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s.width; + + points.push_back(point); + + Vector2 gui_point = Vector2(point, s.height / 2.0); + + gui_point -= (icon->get_size() / 2.0); + + gui_point = gui_point.floor(); + + if (i == selected_point) { + blend_space_draw->draw_texture(icon_selected, gui_point); + } else { + blend_space_draw->draw_texture(icon, gui_point); + } + } + + // blend position + { + Color color; + if (tool_blend->is_pressed()) { + color = get_color("accent_color", "Editor"); + } else { + color = linecolor; + color.a *= 0.5; + } + + float point = blend_space->get_blend_pos(); + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s.width; + + Vector2 gui_point = Vector2(point, s.height / 2.0); + + float mind = 5 * EDSCALE; + float maxd = 15 * EDSCALE; + blend_space_draw->draw_line(gui_point + Vector2(mind, 0), gui_point + Vector2(maxd, 0), color, 2); + blend_space_draw->draw_line(gui_point + Vector2(-mind, 0), gui_point + Vector2(-maxd, 0), color, 2); + blend_space_draw->draw_line(gui_point + Vector2(0, mind), gui_point + Vector2(0, maxd), color, 2); + blend_space_draw->draw_line(gui_point + Vector2(0, -mind), gui_point + Vector2(0, -maxd), color, 2); + } +} + +void AnimationNodeBlendSpace1DEditor::_update_space() { + + if (updating) + return; + + updating = true; + + if (blend_space->get_parent().is_valid()) { + goto_parent_hb->show(); + } else { + goto_parent_hb->hide(); + } + + max_value->set_value(blend_space->get_max_space()); + min_value->set_value(blend_space->get_min_space()); + + label_value->set_text(blend_space->get_value_label()); + + snap_value->set_value(blend_space->get_snap()); + + blend_space_draw->update(); + + updating = false; +} + +void AnimationNodeBlendSpace1DEditor::_config_changed(double) { + if (updating) + return; + + updating = true; + undo_redo->create_action("Change BlendSpace1D Limits"); + undo_redo->add_do_method(blend_space.ptr(), "set_max_space", max_value->get_value()); + undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space()); + undo_redo->add_do_method(blend_space.ptr(), "set_min_space", min_value->get_value()); + undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space()); + undo_redo->add_do_method(blend_space.ptr(), "set_snap", snap_value->get_value()); + undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace1DEditor::_labels_changed(String) { + if (updating) + return; + + updating = true; + undo_redo->create_action("Change BlendSpace1D Labels", UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(blend_space.ptr(), "set_value_label", label_value->get_text()); + undo_redo->add_undo_method(blend_space.ptr(), "set_value_label", blend_space->get_value_label()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; +} + +void AnimationNodeBlendSpace1DEditor::_snap_toggled() { + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { + String type = menu->get_item_metadata(p_index); + + Object *obj = ClassDB::instance(type); + ERR_FAIL_COND(!obj); + AnimationNode *an = Object::cast_to<AnimationNode>(obj); + ERR_FAIL_COND(!an); + + Ref<AnimationNode> node(an); + + updating = true; + undo_redo->create_action("Add Node Point"); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos); + undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) { + Ref<AnimationNodeAnimation> anim; + anim.instance(); + + anim->set_animation(animations_to_add[p_index]); + + updating = true; + undo_redo->create_action("Add Animation Point"); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos); + undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace1DEditor::_tool_switch(int p_tool) { + + if (p_tool == 0) { + tool_erase->show(); + tool_erase_sep->show(); + } else { + tool_erase->hide(); + tool_erase_sep->hide(); + } + + _update_tool_erase(); + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() { + if (updating) + return; + + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { + float pos = blend_space->get_blend_point_position(selected_point); + + if (dragging_selected) { + pos += drag_ofs.x; + + if (snap->is_pressed()) { + pos = Math::stepify(pos, blend_space->get_snap()); + } + } + + updating = true; + edit_value->set_value(pos); + updating = false; + } +} + +void AnimationNodeBlendSpace1DEditor::_update_tool_erase() { + + bool point_valid = selected_point >= 0 && selected_point < blend_space->get_blend_point_count(); + tool_erase->set_disabled(!point_valid); + + if (point_valid) { + Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); + + if (EditorNode::get_singleton()->item_has_editor(an.ptr())) { + open_editor->show(); + } else { + open_editor->hide(); + } + + edit_hb->show(); + } else { + edit_hb->hide(); + } +} + +void AnimationNodeBlendSpace1DEditor::_erase_selected() { + if (selected_point != -1) { + updating = true; + + undo_redo->create_action("Remove BlendSpace1D Point"); + undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point); + undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + + updating = false; + + blend_space_draw->update(); + } +} + +void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) { + if (updating) + return; + + updating = true; + undo_redo->create_action("Move BlendSpace1D Node Point"); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, edit_value->get_value()); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace1DEditor::_open_editor() { + + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { + Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); + ERR_FAIL_COND(an.is_null()); + EditorNode::get_singleton()->edit_item(an.ptr()); + } +} + +void AnimationNodeBlendSpace1DEditor::_goto_parent() { + EditorNode::get_singleton()->edit_item(blend_space->get_parent().ptr()); +} + +void AnimationNodeBlendSpace1DEditor::_removed_from_graph() { + EditorNode::get_singleton()->edit_item(NULL); +} + +void AnimationNodeBlendSpace1DEditor::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + error_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + panel->add_style_override("panel", get_stylebox("bg", "Tree")); + tool_blend->set_icon(get_icon("EditPivot", "EditorIcons")); + tool_select->set_icon(get_icon("ToolSelect", "EditorIcons")); + tool_create->set_icon(get_icon("EditKey", "EditorIcons")); + tool_erase->set_icon(get_icon("Remove", "EditorIcons")); + snap->set_icon(get_icon("SnapGrid", "EditorIcons")); + open_editor->set_icon(get_icon("Edit", "EditorIcons")); + goto_parent->set_icon(get_icon("MoveUp", "EditorIcons")); + } + + if (p_what == NOTIFICATION_PROCESS) { + String error; + + if (!blend_space->get_tree()) { + error = TTR("BlendSpace1D does not belong to an AnimationTree node."); + } else if (!blend_space->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (blend_space->get_tree()->is_state_invalid()) { + error = blend_space->get_tree()->get_invalid_state_reason(); + } + + if (error != error_label->get_text()) { + error_label->set_text(error); + if (error != String()) { + error_panel->show(); + } else { + error_panel->hide(); + } + } + } +} + +void AnimationNodeBlendSpace1DEditor::_bind_methods() { + ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpace1DEditor::_blend_space_gui_input); + ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpace1DEditor::_blend_space_draw); + ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpace1DEditor::_config_changed); + ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpace1DEditor::_labels_changed); + ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace1DEditor::_update_space); + ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpace1DEditor::_snap_toggled); + ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpace1DEditor::_tool_switch); + ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpace1DEditor::_erase_selected); + ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace1DEditor::_update_tool_erase); + ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpace1DEditor::_edit_point_pos); + + ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpace1DEditor::_add_menu_type); + ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpace1DEditor::_add_animation_type); + + ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace1DEditor::_update_edited_point_pos); + + ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpace1DEditor::_open_editor); + ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpace1DEditor::_goto_parent); + + ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace1DEditor::_removed_from_graph); +} + +void AnimationNodeBlendSpace1DEditor::edit(AnimationNodeBlendSpace1D *p_blend_space) { + + if (blend_space.is_valid()) { + blend_space->disconnect("removed_from_graph", this, "_removed_from_graph"); + } + + if (p_blend_space) { + blend_space = Ref<AnimationNodeBlendSpace1D>(p_blend_space); + } else { + blend_space.unref(); + } + + if (blend_space.is_null()) { + hide(); + } else { + blend_space->connect("removed_from_graph", this, "_removed_from_graph"); + + _update_space(); + } +} + +AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = NULL; + +AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { + singleton = this; + updating = false; + + HBoxContainer *top_hb = memnew(HBoxContainer); + add_child(top_hb); + + Ref<ButtonGroup> bg; + bg.instance(); + + goto_parent_hb = memnew(HBoxContainer); + top_hb->add_child(goto_parent_hb); + + goto_parent = memnew(ToolButton); + goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED); + goto_parent_hb->add_child(goto_parent); + goto_parent_hb->add_child(memnew(VSeparator)); + goto_parent_hb->hide(); + + tool_blend = memnew(ToolButton); + tool_blend->set_toggle_mode(true); + tool_blend->set_button_group(bg); + top_hb->add_child(tool_blend); + tool_blend->set_pressed(true); + tool_blend->set_tooltip(TTR("Set the blending position within the space")); + tool_blend->connect("pressed", this, "_tool_switch", varray(3)); + + tool_select = memnew(ToolButton); + tool_select->set_toggle_mode(true); + tool_select->set_button_group(bg); + top_hb->add_child(tool_select); + tool_select->set_tooltip(TTR("Select and move points, create points with RMB.")); + tool_select->connect("pressed", this, "_tool_switch", varray(0)); + + tool_create = memnew(ToolButton); + tool_create->set_toggle_mode(true); + tool_create->set_button_group(bg); + top_hb->add_child(tool_create); + tool_create->set_tooltip(TTR("Create points.")); + tool_create->connect("pressed", this, "_tool_switch", varray(1)); + + tool_erase_sep = memnew(VSeparator); + top_hb->add_child(tool_erase_sep); + tool_erase = memnew(ToolButton); + top_hb->add_child(tool_erase); + tool_erase->set_tooltip(TTR("Erase points.")); + tool_erase->connect("pressed", this, "_erase_selected"); + + top_hb->add_child(memnew(VSeparator)); + + snap = memnew(ToolButton); + snap->set_toggle_mode(true); + top_hb->add_child(snap); + snap->set_pressed(true); + snap->connect("pressed", this, "_snap_toggled"); + + snap_value = memnew(SpinBox); + top_hb->add_child(snap_value); + snap_value->set_min(0.01); + snap_value->set_step(0.01); + snap_value->set_max(1000); + + edit_hb = memnew(HBoxContainer); + top_hb->add_child(edit_hb); + edit_hb->add_child(memnew(VSeparator)); + edit_hb->add_child(memnew(Label(TTR("Point")))); + + edit_value = memnew(SpinBox); + edit_hb->add_child(edit_value); + edit_value->set_min(-1000); + edit_value->set_max(1000); + edit_value->set_step(0.01); + edit_value->connect("value_changed", this, "_edit_point_pos"); + + open_editor = memnew(Button); + edit_hb->add_child(open_editor); + open_editor->set_text(TTR("Open Editor")); + open_editor->connect("pressed", this, "_open_editor", varray(), CONNECT_DEFERRED); + + edit_hb->hide(); + open_editor->hide(); + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + main_vb->set_v_size_flags(SIZE_EXPAND_FILL); + + panel = memnew(PanelContainer); + panel->set_clip_contents(true); + main_vb->add_child(panel); + panel->set_h_size_flags(SIZE_EXPAND_FILL); + panel->set_v_size_flags(SIZE_EXPAND_FILL); + + blend_space_draw = memnew(Control); + blend_space_draw->connect("gui_input", this, "_blend_space_gui_input"); + blend_space_draw->connect("draw", this, "_blend_space_draw"); + blend_space_draw->set_focus_mode(FOCUS_ALL); + + panel->add_child(blend_space_draw); + + { + HBoxContainer *bottom_hb = memnew(HBoxContainer); + main_vb->add_child(bottom_hb); + bottom_hb->set_h_size_flags(SIZE_EXPAND_FILL); + + min_value = memnew(SpinBox); + min_value->set_max(0); + min_value->set_min(-10000); + min_value->set_step(0.01); + + max_value = memnew(SpinBox); + max_value->set_max(10000); + max_value->set_min(0.01); + max_value->set_step(0.01); + + label_value = memnew(LineEdit); + label_value->set_expand_to_text_length(true); + + // now add + + bottom_hb->add_child(min_value); + bottom_hb->add_spacer(); + bottom_hb->add_child(label_value); + bottom_hb->add_spacer(); + bottom_hb->add_child(max_value); + } + + snap_value->connect("value_changed", this, "_config_changed"); + min_value->connect("value_changed", this, "_config_changed"); + max_value->connect("value_changed", this, "_config_changed"); + label_value->connect("text_changed", this, "_labels_changed"); + + error_panel = memnew(PanelContainer); + add_child(error_panel); + + error_label = memnew(Label); + error_panel->add_child(error_label); + error_label->set_text("hmmm"); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("index_pressed", this, "_add_menu_type"); + + animations_menu = memnew(PopupMenu); + menu->add_child(animations_menu); + animations_menu->set_name("animations"); + animations_menu->connect("index_pressed", this, "_add_animation_type"); + + selected_point = -1; + dragging_selected = false; + dragging_selected_attempt = false; + + set_custom_minimum_size(Size2(0, 150 * EDSCALE)); +} diff --git a/editor/plugins/animation_blend_space_1d_editor.h b/editor/plugins/animation_blend_space_1d_editor.h new file mode 100644 index 0000000000..52139626e6 --- /dev/null +++ b/editor/plugins/animation_blend_space_1d_editor.h @@ -0,0 +1,117 @@ +#ifndef ANIMATION_BLEND_SPACE_1D_EDITOR_H +#define ANIMATION_BLEND_SPACE_1D_EDITOR_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/animation/animation_blend_space_1d.h" +#include "scene/gui/button.h" +#include "scene/gui/graph_edit.h" +#include "scene/gui/popup.h" +#include "scene/gui/tree.h" + +class AnimationNodeBlendSpace1DEditor : public VBoxContainer { + + GDCLASS(AnimationNodeBlendSpace1DEditor, VBoxContainer) + + Ref<AnimationNodeBlendSpace1D> blend_space; + + HBoxContainer *goto_parent_hb; + ToolButton *goto_parent; + + PanelContainer *panel; + ToolButton *tool_blend; + ToolButton *tool_select; + ToolButton *tool_create; + VSeparator *tool_erase_sep; + ToolButton *tool_erase; + ToolButton *snap; + SpinBox *snap_value; + + LineEdit *label_value; + SpinBox *max_value; + SpinBox *min_value; + + HBoxContainer *edit_hb; + SpinBox *edit_value; + Button *open_editor; + + int selected_point; + + Control *blend_space_draw; + + PanelContainer *error_panel; + Label *error_label; + + bool updating; + + UndoRedo *undo_redo; + + static AnimationNodeBlendSpace1DEditor *singleton; + + void _blend_space_gui_input(const Ref<InputEvent> &p_event); + void _blend_space_draw(); + + void _update_space(); + + void _config_changed(double); + void _labels_changed(String); + void _snap_toggled(); + + PopupMenu *menu; + PopupMenu *animations_menu; + Vector<String> animations_to_add; + float add_point_pos; + Vector<float> points; + + bool dragging_selected_attempt; + bool dragging_selected; + Vector2 drag_from; + Vector2 drag_ofs; + + void _add_menu_type(int p_index); + void _add_animation_type(int p_index); + + void _tool_switch(int p_tool); + void _update_edited_point_pos(); + void _update_tool_erase(); + void _erase_selected(); + void _edit_point_pos(double); + void _open_editor(); + + void _goto_parent(); + + void _removed_from_graph(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + static AnimationNodeBlendSpace1DEditor *get_singleton() { return singleton; } + void edit(AnimationNodeBlendSpace1D *p_blend_space); + AnimationNodeBlendSpace1DEditor(); +}; + +class AnimationNodeBlendSpace1DEditorPlugin : public EditorPlugin { + + GDCLASS(AnimationNodeBlendSpace1DEditorPlugin, EditorPlugin) + + AnimationNodeBlendSpace1DEditor *anim_tree_editor; + EditorNode *editor; + Button *button; + +public: + virtual String get_name() const { return "BlendSpace1D"; } + + bool has_main_screen() const { return false; } + + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + AnimationNodeBlendSpace1DEditorPlugin(EditorNode *p_node); + ~AnimationNodeBlendSpace1DEditorPlugin(); +}; + +#endif // ANIMATION_BLEND_SPACE_1D_EDITOR_H diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp new file mode 100644 index 0000000000..8d17062248 --- /dev/null +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -0,0 +1,1023 @@ +#include "animation_blend_space_2d_editor.h" + +#include "core/io/resource_loader.h" +#include "core/project_settings.h" +#include "math/delaunay.h" +#include "os/input.h" +#include "os/keyboard.h" +#include "scene/animation/animation_blend_tree.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/panel.h" +#include "scene/main/viewport.h" + +void AnimationNodeBlendSpace2DEditor::edit(AnimationNodeBlendSpace2D *p_blend_space) { + + if (blend_space.is_valid()) { + blend_space->disconnect("removed_from_graph", this, "_removed_from_graph"); + } + + if (p_blend_space) { + blend_space = Ref<AnimationNodeBlendSpace2D>(p_blend_space); + } else { + blend_space.unref(); + } + + if (blend_space.is_null()) { + hide(); + } else { + blend_space->connect("removed_from_graph", this, "_removed_from_graph"); + + _update_space(); + } +} + +void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventKey> k = p_event; + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) { + if (selected_point != -1 || selected_triangle != -1) { + _erase_selected(); + accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); + List<StringName> classes; + classes.sort_custom<StringName::AlphCompare>(); + + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + menu->add_submenu_item(TTR("Add Animation"), "animations"); + + AnimationTree *gp = blend_space->get_tree(); + ERR_FAIL_COND(!gp); + if (gp && gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + animations_menu->add_icon_item(get_icon("Animation", "EditorIcons"), E->get()); + animations_to_add.push_back(E->get()); + } + } + } + + for (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + + String name = String(E->get()).replace_first("AnimationNode", ""); + if (name == "Animation") + continue; // nope + int idx = menu->get_item_count(); + menu->add_item(vformat("Add %s", name)); + menu->set_item_metadata(idx, E->get()); + } + + menu->set_global_position(blend_space_draw->get_global_transform().xform(mb->get_position())); + menu->popup(); + add_point_pos = (mb->get_position() / blend_space_draw->get_size()); + add_point_pos.y = 1.0 - add_point_pos.y; + add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + add_point_pos += blend_space->get_min_space(); + + if (snap->is_pressed()) { + add_point_pos.x = Math::stepify(add_point_pos.x, blend_space->get_snap().x); + add_point_pos.y = Math::stepify(add_point_pos.y, blend_space->get_snap().y); + } + } + + if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + blend_space_draw->update(); //update anyway + //try to see if a point can be selected + selected_point = -1; + selected_triangle = -1; + _update_tool_erase(); + + for (int i = 0; i < points.size(); i++) { + + if (points[i].distance_to(mb->get_position()) < 10 * EDSCALE) { + selected_point = i; + Ref<AnimationNode> node = blend_space->get_blend_point_node(i); + EditorNode::get_singleton()->push_item(node.ptr(), "", true); + dragging_selected_attempt = true; + drag_from = mb->get_position(); + _update_tool_erase(); + _update_edited_point_pos(); + return; + } + } + + //then try to see if a triangle can be selected + if (!blend_space->get_auto_triangles()) { //if autotriangles use, disable this + for (int i = 0; i < blend_space->get_triangle_count(); i++) { + Vector<Vector2> triangle; + + for (int j = 0; j < 3; j++) { + int idx = blend_space->get_triangle_point(i, j); + ERR_FAIL_INDEX(idx, points.size()); + triangle.push_back(points[idx]); + } + + if (Geometry::is_point_in_triangle(mb->get_position(), triangle[0], triangle[1], triangle[2])) { + selected_triangle = i; + _update_tool_erase(); + return; + } + } + } + } + + if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + blend_space_draw->update(); //update anyway + //try to see if a point can be selected + selected_point = -1; + + for (int i = 0; i < points.size(); i++) { + + if (making_triangle.find(i) != -1) + continue; + + if (points[i].distance_to(mb->get_position()) < 10 * EDSCALE) { + making_triangle.push_back(i); + if (making_triangle.size() == 3) { + //add triangle! + if (blend_space->has_triangle(making_triangle[0], making_triangle[1], making_triangle[2])) { + making_triangle.clear(); + EditorNode::get_singleton()->show_warning(TTR("Triangle already exists")); + return; + } + + updating = true; + undo_redo->create_action("Add Triangle"); + undo_redo->add_do_method(blend_space.ptr(), "add_triangle", making_triangle[0], making_triangle[1], making_triangle[2]); + undo_redo->add_undo_method(blend_space.ptr(), "remove_triangle", blend_space->get_triangle_count()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + making_triangle.clear(); + } + return; + } + } + } + + if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) { + if (dragging_selected) { + //move + Vector2 point = blend_space->get_blend_point_position(selected_point); + point += drag_ofs; + if (snap->is_pressed()) { + point.x = Math::stepify(point.x, blend_space->get_snap().x); + point.y = Math::stepify(point.y, blend_space->get_snap().y); + } + + updating = true; + undo_redo->create_action("Move Node Point"); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + _update_edited_point_pos(); + } + dragging_selected_attempt = false; + dragging_selected = false; + blend_space_draw->update(); + } + + if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + Vector2 blend_pos = (mb->get_position() / blend_space_draw->get_size()); + blend_pos.y = 1.0 - blend_pos.y; + blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + blend_pos += blend_space->get_min_space(); + + blend_space->set_blend_position(blend_pos); + blend_space_draw->update(); + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid() && !blend_space_draw->has_focus()) { + blend_space_draw->grab_focus(); + blend_space_draw->update(); + } + + if (mm.is_valid() && dragging_selected_attempt) { + dragging_selected = true; + drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * (blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, -1); + blend_space_draw->update(); + _update_edited_point_pos(); + } + + if (mm.is_valid() && tool_triangle->is_pressed() && making_triangle.size()) { + blend_space_draw->update(); + } + + if (mm.is_valid() && !tool_triangle->is_pressed() && making_triangle.size()) { + making_triangle.clear(); + blend_space_draw->update(); + } + + if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + + Vector2 blend_pos = (mm->get_position() / blend_space_draw->get_size()); + blend_pos.y = 1.0 - blend_pos.y; + blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); + blend_pos += blend_space->get_min_space(); + + blend_space->set_blend_position(blend_pos); + blend_space_draw->update(); + } +} + +void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { + + String type = menu->get_item_metadata(p_index); + + Object *obj = ClassDB::instance(type); + ERR_FAIL_COND(!obj); + AnimationNode *an = Object::cast_to<AnimationNode>(obj); + ERR_FAIL_COND(!an); + + Ref<AnimationNode> node(an); + + updating = true; + undo_redo->create_action("Add Node Point"); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos); + undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { + + Ref<AnimationNodeAnimation> anim; + anim.instance(); + + anim->set_animation(animations_to_add[p_index]); + + updating = true; + undo_redo->create_action("Add Animation Point"); + undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos); + undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace2DEditor::_update_tool_erase() { + tool_erase->set_disabled(!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count())); + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { + Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); + if (EditorNode::get_singleton()->item_has_editor(an.ptr())) { + open_editor->show(); + } else { + open_editor->hide(); + } + edit_hb->show(); + } else { + edit_hb->hide(); + } +} + +void AnimationNodeBlendSpace2DEditor::_tool_switch(int p_tool) { + making_triangle.clear(); + + if (p_tool == 2) { + Vector<Vector2> points; + for (int i = 0; i < blend_space->get_blend_point_count(); i++) { + points.push_back(blend_space->get_blend_point_position(i)); + } + Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(points); + print_line("triangleS: " + itos(tr.size())); + for (int i = 0; i < tr.size(); i++) { + blend_space->add_triangle(tr[i].points[0], tr[i].points[1], tr[i].points[2]); + } + } + + if (p_tool == 0) { + tool_erase->show(); + tool_erase_sep->show(); + } else { + tool_erase->hide(); + tool_erase_sep->hide(); + } + _update_tool_erase(); + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { + + Color linecolor = get_color("font_color", "Label"); + Color linecolor_soft = linecolor; + linecolor_soft.a *= 0.5; + Ref<Font> font = get_font("font", "Label"); + Ref<Texture> icon = get_icon("KeyValue", "EditorIcons"); + Ref<Texture> icon_selected = get_icon("KeySelected", "EditorIcons"); + + Size2 s = blend_space_draw->get_size(); + + if (blend_space_draw->has_focus()) { + Color color = get_color("accent_color", "Editor"); + blend_space_draw->draw_rect(Rect2(Point2(), s), color, false); + } + blend_space_draw->draw_line(Point2(1, 0), Point2(1, s.height - 1), linecolor); + blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor); + + blend_space_draw->draw_line(Point2(0, 0), Point2(5 * EDSCALE, 0), linecolor); + if (blend_space->get_min_space().y < 0) { + int y = (blend_space->get_max_space().y / (blend_space->get_max_space().y - blend_space->get_min_space().y)) * s.height; + blend_space_draw->draw_line(Point2(0, y), Point2(5 * EDSCALE, y), linecolor); + blend_space_draw->draw_string(font, Point2(2 * EDSCALE, y - font->get_height() + font->get_ascent()), "0", linecolor); + blend_space_draw->draw_line(Point2(5 * EDSCALE, y), Point2(s.width, y), linecolor_soft); + } + + if (blend_space->get_min_space().x < 0) { + int x = (-blend_space->get_min_space().x / (blend_space->get_max_space().x - blend_space->get_min_space().x)) * s.width; + blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor); + blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height() + font->get_ascent()), "0", linecolor); + blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft); + } + + if (snap->is_pressed()) { + + linecolor_soft.a = linecolor.a * 0.1; + + if (blend_space->get_snap().x > 0) { + + int prev_idx; + for (int i = 0; i < s.x; i++) { + + float v = blend_space->get_min_space().x + i * (blend_space->get_max_space().x - blend_space->get_min_space().x) / s.x; + int idx = int(v / blend_space->get_snap().x); + + if (i > 0 && prev_idx != idx) { + blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft); + } + + prev_idx = idx; + } + } + + if (blend_space->get_snap().y > 0) { + + int prev_idx; + for (int i = 0; i < s.y; i++) { + + float v = blend_space->get_max_space().y - i * (blend_space->get_max_space().y - blend_space->get_min_space().y) / s.y; + int idx = int(v / blend_space->get_snap().y); + + if (i > 0 && prev_idx != idx) { + blend_space_draw->draw_line(Point2(0, i), Point2(s.width, i), linecolor_soft); + } + + prev_idx = idx; + } + } + } + + //triangles first + for (int i = 0; i < blend_space->get_triangle_count(); i++) { + + Vector<Vector2> points; + points.resize(3); + + for (int j = 0; j < 3; j++) { + int point_idx = blend_space->get_triangle_point(i, j); + Vector2 point = blend_space->get_blend_point_position(point_idx); + if (dragging_selected && selected_point == point_idx) { + point += drag_ofs; + if (snap->is_pressed()) { + point.x = Math::stepify(point.x, blend_space->get_snap().x); + point.y = Math::stepify(point.y, blend_space->get_snap().y); + } + } + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s; + point.y = s.height - point.y; + points[j] = point; + } + + for (int j = 0; j < 3; j++) { + blend_space_draw->draw_line(points[j], points[(j + 1) % 3], linecolor, 1, true); + } + + Color color; + if (i == selected_triangle) { + color = get_color("accent_color", "Editor"); + color.a *= 0.5; + } else { + color = linecolor; + color.a *= 0.2; + } + + Vector<Color> colors; + colors.push_back(color); + colors.push_back(color); + colors.push_back(color); + blend_space_draw->draw_primitive(points, colors, Vector<Vector2>()); + } + + points.clear(); + for (int i = 0; i < blend_space->get_blend_point_count(); i++) { + + Vector2 point = blend_space->get_blend_point_position(i); + if (dragging_selected && selected_point == i) { + point += drag_ofs; + if (snap->is_pressed()) { + point.x = Math::stepify(point.x, blend_space->get_snap().x); + point.y = Math::stepify(point.y, blend_space->get_snap().y); + } + } + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s; + point.y = s.height - point.y; + + points.push_back(point); + point -= (icon->get_size() / 2); + point = point.floor(); + + if (i == selected_point) { + blend_space_draw->draw_texture(icon_selected, point); + } else { + blend_space_draw->draw_texture(icon, point); + } + } + + if (making_triangle.size()) { + Vector<Vector2> points; + for (int i = 0; i < making_triangle.size(); i++) { + Vector2 point = blend_space->get_blend_point_position(making_triangle[i]); + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s; + point.y = s.height - point.y; + points.push_back(point); + } + + for (int i = 0; i < points.size() - 1; i++) { + blend_space_draw->draw_line(points[i], points[i + 1], linecolor, 2, true); + } + blend_space_draw->draw_line(points[points.size() - 1], blend_space_draw->get_local_mouse_position(), linecolor, 2, true); + } + + ///draw cursor position + + { + Color color; + if (tool_blend->is_pressed()) { + color = get_color("accent_color", "Editor"); + } else { + color = linecolor; + color.a *= 0.5; + } + + Vector2 point = blend_space->get_blend_position(); + point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + point *= s; + point.y = s.height - point.y; + + if (blend_space->get_triangle_count()) { + Vector2 closest = blend_space->get_closest_point(blend_space->get_blend_position()); + closest = (closest - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); + closest *= s; + closest.y = s.height - closest.y; + + Color lcol = color; + lcol.a *= 0.4; + blend_space_draw->draw_line(point, closest, lcol, 2); + } + + float mind = 5 * EDSCALE; + float maxd = 15 * EDSCALE; + blend_space_draw->draw_line(point + Vector2(mind, 0), point + Vector2(maxd, 0), color, 2); + blend_space_draw->draw_line(point + Vector2(-mind, 0), point + Vector2(-maxd, 0), color, 2); + blend_space_draw->draw_line(point + Vector2(0, mind), point + Vector2(0, maxd), color, 2); + blend_space_draw->draw_line(point + Vector2(0, -mind), point + Vector2(0, -maxd), color, 2); + } +} + +void AnimationNodeBlendSpace2DEditor::_snap_toggled() { + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace2DEditor::_update_space() { + + if (updating) + return; + + updating = true; + + if (blend_space->get_parent().is_valid()) { + goto_parent_hb->show(); + } else { + goto_parent_hb->hide(); + } + + if (blend_space->get_auto_triangles()) { + tool_triangle->hide(); + } else { + tool_triangle->show(); + } + + auto_triangles->set_pressed(blend_space->get_auto_triangles()); + + max_x_value->set_value(blend_space->get_max_space().x); + max_y_value->set_value(blend_space->get_max_space().y); + + min_x_value->set_value(blend_space->get_min_space().x); + min_y_value->set_value(blend_space->get_min_space().y); + + label_x->set_text(blend_space->get_x_label()); + label_y->set_text(blend_space->get_y_label()); + + snap_x->set_value(blend_space->get_snap().x); + snap_y->set_value(blend_space->get_snap().y); + + blend_space_draw->update(); + + updating = false; +} + +void AnimationNodeBlendSpace2DEditor::_config_changed(double) { + if (updating) + return; + + updating = true; + undo_redo->create_action("Change BlendSpace2D Limits"); + undo_redo->add_do_method(blend_space.ptr(), "set_max_space", Vector2(max_x_value->get_value(), max_y_value->get_value())); + undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space()); + undo_redo->add_do_method(blend_space.ptr(), "set_min_space", Vector2(min_x_value->get_value(), min_y_value->get_value())); + undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space()); + undo_redo->add_do_method(blend_space.ptr(), "set_snap", Vector2(snap_x->get_value(), snap_y->get_value())); + undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace2DEditor::_labels_changed(String) { + if (updating) + return; + + updating = true; + undo_redo->create_action("Change BlendSpace2D Labels", UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(blend_space.ptr(), "set_x_label", label_x->get_text()); + undo_redo->add_undo_method(blend_space.ptr(), "set_x_label", blend_space->get_x_label()); + undo_redo->add_do_method(blend_space.ptr(), "set_y_label", label_y->get_text()); + undo_redo->add_undo_method(blend_space.ptr(), "set_y_label", blend_space->get_y_label()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; +} + +void AnimationNodeBlendSpace2DEditor::_erase_selected() { + + if (selected_point != -1) { + + updating = true; + undo_redo->create_action("Remove BlendSpace2D Point"); + undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point); + undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point); + + //restore triangles using this point + for (int i = 0; i < blend_space->get_triangle_count(); i++) { + for (int j = 0; j < 3; j++) { + if (blend_space->get_triangle_point(i, j) == selected_point) { + undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(i, 0), blend_space->get_triangle_point(i, 1), blend_space->get_triangle_point(i, 2), i); + break; + } + } + } + + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); + } else if (selected_triangle != -1) { + + updating = true; + undo_redo->create_action("Remove BlendSpace2D Triangle"); + undo_redo->add_do_method(blend_space.ptr(), "remove_triangle", selected_triangle); + undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(selected_triangle, 0), blend_space->get_triangle_point(selected_triangle, 1), blend_space->get_triangle_point(selected_triangle, 2), selected_triangle); + + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); + } +} + +void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() { + if (updating) + return; + + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { + Vector2 pos = blend_space->get_blend_point_position(selected_point); + if (dragging_selected) { + pos += drag_ofs; + if (snap->is_pressed()) { + pos.x = Math::stepify(pos.x, blend_space->get_snap().x); + pos.y = Math::stepify(pos.y, blend_space->get_snap().y); + } + } + updating = true; + edit_x->set_value(pos.x); + edit_y->set_value(pos.y); + updating = false; + } +} + +void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { + if (updating) + return; + updating = true; + undo_redo->create_action("Move Node Point"); + undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, Vector2(edit_x->get_value(), edit_y->get_value())); + undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point)); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->add_do_method(this, "_update_edited_point_pos"); + undo_redo->add_undo_method(this, "_update_edited_point_pos"); + undo_redo->commit_action(); + updating = false; + + blend_space_draw->update(); +} + +void AnimationNodeBlendSpace2DEditor::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + error_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + panel->add_style_override("panel", get_stylebox("bg", "Tree")); + tool_blend->set_icon(get_icon("EditPivot", "EditorIcons")); + tool_select->set_icon(get_icon("ToolSelect", "EditorIcons")); + tool_create->set_icon(get_icon("EditKey", "EditorIcons")); + tool_triangle->set_icon(get_icon("ToolTriangle", "EditorIcons")); + tool_erase->set_icon(get_icon("Remove", "EditorIcons")); + snap->set_icon(get_icon("SnapGrid", "EditorIcons")); + open_editor->set_icon(get_icon("Edit", "EditorIcons")); + goto_parent->set_icon(get_icon("MoveUp", "EditorIcons")); + auto_triangles->set_icon(get_icon("AutoTriangle", "EditorIcons")); + } + + if (p_what == NOTIFICATION_PROCESS) { + + String error; + + if (!blend_space->get_tree()) { + error = TTR("BlendSpace2D does not belong to an AnimationTree node."); + } else if (!blend_space->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (blend_space->get_tree()->is_state_invalid()) { + error = blend_space->get_tree()->get_invalid_state_reason(); + } else if (blend_space->get_triangle_count() == 0) { + error = TTR("No triangles exist, so no blending can take place."); + } + + if (error != error_label->get_text()) { + error_label->set_text(error); + if (error != String()) { + error_panel->show(); + } else { + error_panel->hide(); + } + } + } +} + +void AnimationNodeBlendSpace2DEditor::_open_editor() { + + if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) { + Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point); + ERR_FAIL_COND(!an.is_valid()); + EditorNode::get_singleton()->edit_item(an.ptr()); + } +} + +void AnimationNodeBlendSpace2DEditor::_goto_parent() { + + EditorNode::get_singleton()->edit_item(blend_space->get_parent().ptr()); +} + +void AnimationNodeBlendSpace2DEditor::_removed_from_graph() { + EditorNode::get_singleton()->edit_item(NULL); +} + +void AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled() { + + undo_redo->create_action("Toggle Auto Triangles"); + undo_redo->add_do_method(blend_space.ptr(), "set_auto_triangles", auto_triangles->is_pressed()); + undo_redo->add_undo_method(blend_space.ptr(), "set_auto_triangles", blend_space->get_auto_triangles()); + undo_redo->add_do_method(this, "_update_space"); + undo_redo->add_undo_method(this, "_update_space"); + undo_redo->commit_action(); +} + +void AnimationNodeBlendSpace2DEditor::_bind_methods() { + + ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpace2DEditor::_blend_space_gui_input); + ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpace2DEditor::_blend_space_draw); + ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpace2DEditor::_config_changed); + ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpace2DEditor::_labels_changed); + ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace2DEditor::_update_space); + ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpace2DEditor::_snap_toggled); + ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpace2DEditor::_tool_switch); + ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpace2DEditor::_erase_selected); + ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace2DEditor::_update_tool_erase); + ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpace2DEditor::_edit_point_pos); + + ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpace2DEditor::_add_menu_type); + ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpace2DEditor::_add_animation_type); + + ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace2DEditor::_update_edited_point_pos); + + ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpace2DEditor::_open_editor); + ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpace2DEditor::_goto_parent); + + ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace2DEditor::_removed_from_graph); + + ClassDB::bind_method("_auto_triangles_toggled", &AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled); +} + +AnimationNodeBlendSpace2DEditor *AnimationNodeBlendSpace2DEditor::singleton = NULL; + +AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { + + singleton = this; + updating = false; + + HBoxContainer *top_hb = memnew(HBoxContainer); + add_child(top_hb); + + Ref<ButtonGroup> bg; + bg.instance(); + + goto_parent_hb = memnew(HBoxContainer); + top_hb->add_child(goto_parent_hb); + goto_parent = memnew(ToolButton); + goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED); + goto_parent_hb->add_child(goto_parent); + goto_parent_hb->add_child(memnew(VSeparator)); + goto_parent_hb->hide(); + + tool_blend = memnew(ToolButton); + tool_blend->set_toggle_mode(true); + tool_blend->set_button_group(bg); + top_hb->add_child(tool_blend); + tool_blend->set_pressed(true); + tool_blend->set_tooltip(TTR("Set the blending position within the space")); + tool_blend->connect("pressed", this, "_tool_switch", varray(3)); + + tool_select = memnew(ToolButton); + tool_select->set_toggle_mode(true); + tool_select->set_button_group(bg); + top_hb->add_child(tool_select); + tool_select->set_tooltip(TTR("Select and move points, create points with RMB.")); + tool_select->connect("pressed", this, "_tool_switch", varray(0)); + + tool_create = memnew(ToolButton); + tool_create->set_toggle_mode(true); + tool_create->set_button_group(bg); + top_hb->add_child(tool_create); + tool_create->set_tooltip(TTR("Create points.")); + tool_create->connect("pressed", this, "_tool_switch", varray(1)); + + tool_triangle = memnew(ToolButton); + tool_triangle->set_toggle_mode(true); + tool_triangle->set_button_group(bg); + top_hb->add_child(tool_triangle); + tool_triangle->set_tooltip(TTR("Create triangles by connecting points.")); + tool_triangle->connect("pressed", this, "_tool_switch", varray(2)); + + tool_erase_sep = memnew(VSeparator); + top_hb->add_child(tool_erase_sep); + tool_erase = memnew(ToolButton); + top_hb->add_child(tool_erase); + tool_erase->set_tooltip(TTR("Erase points and triangles.")); + tool_erase->connect("pressed", this, "_erase_selected"); + tool_erase->set_disabled(true); + + top_hb->add_child(memnew(VSeparator)); + + auto_triangles = memnew(ToolButton); + top_hb->add_child(auto_triangles); + auto_triangles->connect("pressed", this, "_auto_triangles_toggled"); + auto_triangles->set_toggle_mode(true); + auto_triangles->set_tooltip(TTR("Generate blend triangles automatically (instead of manually)")); + + top_hb->add_child(memnew(VSeparator)); + + snap = memnew(ToolButton); + snap->set_toggle_mode(true); + top_hb->add_child(snap); + //snap->set_text(TTR("Snap")); + snap->set_pressed(true); + snap->connect("pressed", this, "_snap_toggled"); + + snap_x = memnew(SpinBox); + top_hb->add_child(snap_x); + snap_x->set_prefix("x:"); + snap_x->set_min(0.01); + snap_x->set_step(0.01); + snap_x->set_max(1000); + + snap_y = memnew(SpinBox); + top_hb->add_child(snap_y); + snap_y->set_prefix("y:"); + snap_y->set_min(0.01); + snap_y->set_step(0.01); + snap_y->set_max(1000); + + edit_hb = memnew(HBoxContainer); + top_hb->add_child(edit_hb); + edit_hb->add_child(memnew(VSeparator)); + edit_hb->add_child(memnew(Label(TTR("Point")))); + edit_x = memnew(SpinBox); + edit_hb->add_child(edit_x); + edit_x->set_min(-1000); + edit_x->set_step(0.01); + edit_x->set_max(1000); + edit_x->connect("value_changed", this, "_edit_point_pos"); + edit_y = memnew(SpinBox); + edit_hb->add_child(edit_y); + edit_y->set_min(-1000); + edit_y->set_step(0.01); + edit_y->set_max(1000); + edit_y->connect("value_changed", this, "_edit_point_pos"); + open_editor = memnew(Button); + edit_hb->add_child(open_editor); + open_editor->set_text(TTR("Open Editor")); + open_editor->connect("pressed", this, "_open_editor", varray(), CONNECT_DEFERRED); + edit_hb->hide(); + open_editor->hide(); + + HBoxContainer *main_hb = memnew(HBoxContainer); + add_child(main_hb); + main_hb->set_v_size_flags(SIZE_EXPAND_FILL); + + GridContainer *main_grid = memnew(GridContainer); + main_grid->set_columns(2); + main_hb->add_child(main_grid); + main_grid->set_h_size_flags(SIZE_EXPAND_FILL); + { + VBoxContainer *left_vbox = memnew(VBoxContainer); + main_grid->add_child(left_vbox); + left_vbox->set_v_size_flags(SIZE_EXPAND_FILL); + max_y_value = memnew(SpinBox); + left_vbox->add_child(max_y_value); + left_vbox->add_spacer(); + label_y = memnew(LineEdit); + left_vbox->add_child(label_y); + label_y->set_expand_to_text_length(true); + left_vbox->add_spacer(); + min_y_value = memnew(SpinBox); + left_vbox->add_child(min_y_value); + + max_y_value->set_max(10000); + max_y_value->set_min(0.01); + max_y_value->set_step(0.01); + + min_y_value->set_min(-10000); + min_y_value->set_max(0); + min_y_value->set_step(0.01); + } + + panel = memnew(PanelContainer); + panel->set_clip_contents(true); + main_grid->add_child(panel); + panel->set_h_size_flags(SIZE_EXPAND_FILL); + + blend_space_draw = memnew(Control); + blend_space_draw->connect("gui_input", this, "_blend_space_gui_input"); + blend_space_draw->connect("draw", this, "_blend_space_draw"); + blend_space_draw->set_focus_mode(FOCUS_ALL); + + panel->add_child(blend_space_draw); + main_grid->add_child(memnew(Control)); //empty bottom left + + { + HBoxContainer *bottom_vbox = memnew(HBoxContainer); + main_grid->add_child(bottom_vbox); + bottom_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + min_x_value = memnew(SpinBox); + bottom_vbox->add_child(min_x_value); + bottom_vbox->add_spacer(); + label_x = memnew(LineEdit); + bottom_vbox->add_child(label_x); + label_x->set_expand_to_text_length(true); + bottom_vbox->add_spacer(); + max_x_value = memnew(SpinBox); + bottom_vbox->add_child(max_x_value); + + max_x_value->set_max(10000); + max_x_value->set_min(0.01); + max_x_value->set_step(0.01); + + min_x_value->set_min(-10000); + min_x_value->set_max(0); + min_x_value->set_step(0.01); + } + + snap_x->connect("value_changed", this, "_config_changed"); + snap_y->connect("value_changed", this, "_config_changed"); + max_x_value->connect("value_changed", this, "_config_changed"); + min_x_value->connect("value_changed", this, "_config_changed"); + max_y_value->connect("value_changed", this, "_config_changed"); + min_y_value->connect("value_changed", this, "_config_changed"); + label_x->connect("text_changed", this, "_labels_changed"); + label_y->connect("text_changed", this, "_labels_changed"); + + error_panel = memnew(PanelContainer); + add_child(error_panel); + error_label = memnew(Label); + error_panel->add_child(error_label); + error_label->set_text("eh"); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + set_custom_minimum_size(Size2(0, 300 * EDSCALE)); + + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("index_pressed", this, "_add_menu_type"); + + animations_menu = memnew(PopupMenu); + menu->add_child(animations_menu); + animations_menu->set_name("animations"); + animations_menu->connect("index_pressed", this, "_add_animation_type"); + + selected_point = -1; + selected_triangle = -1; + + dragging_selected = false; + dragging_selected_attempt = false; +} + +void AnimationNodeBlendSpace2DEditorPlugin::edit(Object *p_object) { + + anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace2D>(p_object)); +} + +bool AnimationNodeBlendSpace2DEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("AnimationNodeBlendSpace2D"); +} + +void AnimationNodeBlendSpace2DEditorPlugin::make_visible(bool p_visible) { + + if (p_visible) { + //editor->hide_animation_player_editors(); + //editor->animation_panel_make_visible(true); + button->show(); + editor->make_bottom_panel_item_visible(anim_tree_editor); + anim_tree_editor->set_process(true); + } else { + + if (anim_tree_editor->is_visible_in_tree()) + editor->hide_bottom_panel(); + button->hide(); + anim_tree_editor->set_process(false); + } +} + +AnimationNodeBlendSpace2DEditorPlugin::AnimationNodeBlendSpace2DEditorPlugin(EditorNode *p_node) { + + editor = p_node; + anim_tree_editor = memnew(AnimationNodeBlendSpace2DEditor); + anim_tree_editor->set_custom_minimum_size(Size2(0, 300)); + + button = editor->add_bottom_panel_item(TTR("BlendSpace2D"), anim_tree_editor); + button->hide(); +} + +AnimationNodeBlendSpace2DEditorPlugin::~AnimationNodeBlendSpace2DEditorPlugin() { +} diff --git a/editor/plugins/animation_blend_space_2d_editor.h b/editor/plugins/animation_blend_space_2d_editor.h new file mode 100644 index 0000000000..a0e497804e --- /dev/null +++ b/editor/plugins/animation_blend_space_2d_editor.h @@ -0,0 +1,130 @@ +#ifndef ANIMATION_BLEND_SPACE_2D_EDITOR_H +#define ANIMATION_BLEND_SPACE_2D_EDITOR_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/animation/animation_blend_space_2d.h" +#include "scene/gui/button.h" +#include "scene/gui/graph_edit.h" +#include "scene/gui/popup.h" +#include "scene/gui/tree.h" +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ + +class AnimationNodeBlendSpace2DEditor : public VBoxContainer { + + GDCLASS(AnimationNodeBlendSpace2DEditor, VBoxContainer); + + Ref<AnimationNodeBlendSpace2D> blend_space; + + HBoxContainer *goto_parent_hb; + ToolButton *goto_parent; + + PanelContainer *panel; + ToolButton *tool_blend; + ToolButton *tool_select; + ToolButton *tool_create; + ToolButton *tool_triangle; + VSeparator *tool_erase_sep; + ToolButton *tool_erase; + ToolButton *snap; + SpinBox *snap_x; + SpinBox *snap_y; + + ToolButton *auto_triangles; + + LineEdit *label_x; + LineEdit *label_y; + SpinBox *max_x_value; + SpinBox *min_x_value; + SpinBox *max_y_value; + SpinBox *min_y_value; + + HBoxContainer *edit_hb; + SpinBox *edit_x; + SpinBox *edit_y; + Button *open_editor; + + int selected_point; + int selected_triangle; + + Control *blend_space_draw; + + PanelContainer *error_panel; + Label *error_label; + + bool updating; + + UndoRedo *undo_redo; + + static AnimationNodeBlendSpace2DEditor *singleton; + + void _blend_space_gui_input(const Ref<InputEvent> &p_event); + void _blend_space_draw(); + + void _update_space(); + + void _config_changed(double); + void _labels_changed(String); + void _snap_toggled(); + + PopupMenu *menu; + PopupMenu *animations_menu; + Vector<String> animations_to_add; + Vector2 add_point_pos; + Vector<Vector2> points; + + bool dragging_selected_attempt; + bool dragging_selected; + Vector2 drag_from; + Vector2 drag_ofs; + + Vector<int> making_triangle; + + void _add_menu_type(int p_index); + void _add_animation_type(int p_index); + + void _tool_switch(int p_tool); + void _update_edited_point_pos(); + void _update_tool_erase(); + void _erase_selected(); + void _edit_point_pos(double); + void _open_editor(); + + void _goto_parent(); + + void _removed_from_graph(); + + void _auto_triangles_toggled(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + static AnimationNodeBlendSpace2DEditor *get_singleton() { return singleton; } + void edit(AnimationNodeBlendSpace2D *p_blend_space); + AnimationNodeBlendSpace2DEditor(); +}; + +class AnimationNodeBlendSpace2DEditorPlugin : public EditorPlugin { + + GDCLASS(AnimationNodeBlendSpace2DEditorPlugin, EditorPlugin); + + AnimationNodeBlendSpace2DEditor *anim_tree_editor; + EditorNode *editor; + Button *button; + +public: + virtual String get_name() const { return "BlendSpace2D"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + AnimationNodeBlendSpace2DEditorPlugin(EditorNode *p_node); + ~AnimationNodeBlendSpace2DEditorPlugin(); +}; +#endif // ANIMATION_BLEND_SPACE_2D_EDITOR_H diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp new file mode 100644 index 0000000000..3efb2736b5 --- /dev/null +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -0,0 +1,848 @@ +#include "animation_blend_tree_editor_plugin.h" + +#include "core/io/resource_loader.h" +#include "core/project_settings.h" +#include "os/input.h" +#include "os/keyboard.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/panel.h" +#include "scene/main/viewport.h" + +void AnimationNodeBlendTreeEditor::edit(AnimationNodeBlendTree *p_blend_tree) { + + if (blend_tree.is_valid()) { + blend_tree->disconnect("removed_from_graph", this, "_removed_from_graph"); + } + + if (p_blend_tree) { + blend_tree = Ref<AnimationNodeBlendTree>(p_blend_tree); + } else { + blend_tree.unref(); + } + + if (blend_tree.is_null()) { + hide(); + } else { + blend_tree->connect("removed_from_graph", this, "_removed_from_graph"); + + _update_graph(); + } +} + +void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) { + + for (int i = 0; i < add_options.size(); i++) { + ERR_FAIL_COND(add_options[i].script == p_script); + } + + AddOption ao; + ao.name = p_name; + ao.script = p_script; + add_options.push_back(ao); + + _update_options_menu(); +} + +void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_script) { + + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].script == p_script) { + add_options.remove(i); + return; + } + } + + _update_options_menu(); +} + +void AnimationNodeBlendTreeEditor::_update_options_menu() { + + add_node->get_popup()->clear(); + for (int i = 0; i < add_options.size(); i++) { + add_node->get_popup()->add_item(add_options[i].name); + } +} + +Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const { + + return Size2(10, 200); +} + +void AnimationNodeBlendTreeEditor::_update_graph() { + + if (updating) + return; + + graph->set_scroll_ofs(blend_tree->get_graph_offset() * EDSCALE); + + if (blend_tree->get_parent().is_valid()) { + goto_parent->show(); + } else { + goto_parent->hide(); + } + graph->clear_connections(); + //erase all nodes + for (int i = 0; i < graph->get_child_count(); i++) { + + if (Object::cast_to<GraphNode>(graph->get_child(i))) { + memdelete(graph->get_child(i)); + i--; + } + } + + animations.clear(); + + List<StringName> nodes; + blend_tree->get_node_list(&nodes); + + for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { + + GraphNode *node = memnew(GraphNode); + graph->add_child(node); + + Ref<AnimationNode> agnode = blend_tree->get_node(E->get()); + + if (!agnode->is_connected("changed", this, "_node_changed")) { + agnode->connect("changed", this, "_node_changed", varray(agnode->get_instance_id()), CONNECT_DEFERRED); + } + + node->set_offset(agnode->get_position() * EDSCALE); + + node->set_title(agnode->get_caption()); + node->set_name(E->get()); + + int base = 0; + if (String(E->get()) != "output") { + LineEdit *name = memnew(LineEdit); + name->set_text(E->get()); + name->set_expand_to_text_length(true); + node->add_child(name); + node->set_slot(0, false, 0, Color(), true, 0, get_color("font_color", "Label")); + name->connect("text_entered", this, "_node_renamed", varray(agnode)); + name->connect("focus_exited", this, "_node_renamed_focus_out", varray(name, agnode)); + base = 1; + node->set_show_close_button(true); + node->connect("close_request", this, "_delete_request", varray(E->get()), CONNECT_DEFERRED); + } + + for (int i = 0; i < agnode->get_input_count(); i++) { + Label *in_name = memnew(Label); + node->add_child(in_name); + in_name->set_text(agnode->get_input_name(i)); + node->set_slot(base + i, true, 0, get_color("font_color", "Label"), false, 0, Color()); + } + + node->connect("dragged", this, "_node_dragged", varray(agnode)); + + if (EditorNode::get_singleton()->item_has_editor(agnode.ptr())) { + node->add_child(memnew(HSeparator)); + Button *open_in_editor = memnew(Button); + open_in_editor->set_text(TTR("Open Editor")); + open_in_editor->set_icon(get_icon("Edit", "EditorIcons")); + node->add_child(open_in_editor); + open_in_editor->connect("pressed", this, "_open_in_editor", varray(E->get()), CONNECT_DEFERRED); + open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER); + } + + if (agnode->has_filter()) { + + node->add_child(memnew(HSeparator)); + Button *edit_filters = memnew(Button); + edit_filters->set_text(TTR("Edit Filters")); + edit_filters->set_icon(get_icon("AnimationFilter", "EditorIcons")); + node->add_child(edit_filters); + edit_filters->connect("pressed", this, "_edit_filters", varray(E->get()), CONNECT_DEFERRED); + edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER); + } + + Ref<AnimationNodeAnimation> anim = agnode; + if (anim.is_valid()) { + + MenuButton *mb = memnew(MenuButton); + mb->set_text(anim->get_animation()); + mb->set_icon(get_icon("Animation", "EditorIcons")); + Array options; + + node->add_child(memnew(HSeparator)); + node->add_child(mb); + + ProgressBar *pb = memnew(ProgressBar); + + AnimationTree *player = anim->get_tree(); + if (player->has_node(player->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(player->get_node(player->get_animation_player())); + if (ap) { + List<StringName> anims; + ap->get_animation_list(&anims); + + for (List<StringName>::Element *F = anims.front(); F; F = F->next()) { + mb->get_popup()->add_item(F->get()); + options.push_back(F->get()); + } + + if (ap->has_animation(anim->get_animation())) { + pb->set_max(ap->get_animation(anim->get_animation())->get_length()); + } + } + } + + pb->set_percent_visible(false); + animations[E->get()] = pb; + node->add_child(pb); + + mb->get_popup()->connect("index_pressed", this, "_anim_selected", varray(options, E->get()), CONNECT_DEFERRED); + } + + Ref<AnimationNodeOneShot> oneshot = agnode; + if (oneshot.is_valid()) { + + HBoxContainer *play_stop = memnew(HBoxContainer); + play_stop->add_spacer(); + Button *play = memnew(Button); + play->set_icon(get_icon("Play", "EditorIcons")); + play->connect("pressed", this, "_oneshot_start", varray(E->get()), CONNECT_DEFERRED); + play_stop->add_child(play); + Button *stop = memnew(Button); + stop->set_icon(get_icon("Stop", "EditorIcons")); + stop->connect("pressed", this, "_oneshot_stop", varray(E->get()), CONNECT_DEFERRED); + play_stop->add_child(stop); + play_stop->add_spacer(); + node->add_child(play_stop); + } + } + + List<AnimationNodeBlendTree::NodeConnection> connections; + blend_tree->get_node_connections(&connections); + + for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = connections.front(); E; E = E->next()) { + + StringName from = E->get().output_node; + StringName to = E->get().input_node; + int to_idx = E->get().input_index; + + graph->connect_node(from, 0, to, to_idx); + } +} + +void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { + + ERR_FAIL_INDEX(p_idx, add_options.size()); + + Ref<AnimationNode> anode; + + if (add_options[p_idx].type != String()) { + AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(add_options[p_idx].type)); + ERR_FAIL_COND(!an); + anode = Ref<AnimationNode>(an); + } else { + ERR_FAIL_COND(add_options[p_idx].script.is_null()); + String base_type = add_options[p_idx].script->get_instance_base_type(); + AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(base_type)); + ERR_FAIL_COND(!an); + anode = Ref<AnimationNode>(an); + anode->set_script(add_options[p_idx].script.get_ref_ptr()); + } + + Point2 instance_pos = graph->get_scroll_ofs() + graph->get_size() * 0.5; + + anode->set_position(instance_pos); + + String base_name = add_options[p_idx].name; + int base = 1; + String name = base_name; + while (blend_tree->has_node(name)) { + base++; + name = base_name + " " + itos(base); + } + + undo_redo->create_action("Add Node to BlendTree"); + undo_redo->add_do_method(blend_tree.ptr(), "add_node", name, anode); + undo_redo->add_undo_method(blend_tree.ptr(), "remove_node", name); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, Ref<AnimationNode> p_node) { + + updating = true; + undo_redo->create_action("Node Moved"); + undo_redo->add_do_method(p_node.ptr(), "set_position", p_to / EDSCALE); + undo_redo->add_undo_method(p_node.ptr(), "set_position", p_from / EDSCALE); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; +} + +void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + + AnimationNodeBlendTree::ConnectionError err = blend_tree->can_connect_node(p_to, p_to_index, p_from); + + if (err != AnimationNodeBlendTree::CONNECTION_OK) { + EditorNode::get_singleton()->show_warning(TTR("Unable to connect, port may be in use or connection may be invalid.")); + return; + } + + undo_redo->create_action("Nodes Connected"); + undo_redo->add_do_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from); + undo_redo->add_undo_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index, p_from); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void AnimationNodeBlendTreeEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { + + graph->disconnect_node(p_from, p_from_index, p_to, p_to_index); + + updating = true; + undo_redo->create_action("Nodes Disconnected"); + undo_redo->add_do_method(blend_tree.ptr(), "disconnect_node", p_to, p_to_index); + undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", p_to, p_to_index, p_from); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; +} + +void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, const String &p_node) { + + String option = p_options[p_index]; + + Ref<AnimationNodeAnimation> anim = blend_tree->get_node(p_node); + ERR_FAIL_COND(!anim.is_valid()); + + undo_redo->create_action("Set Animation"); + undo_redo->add_do_method(anim.ptr(), "set_animation", option); + undo_redo->add_undo_method(anim.ptr(), "set_animation", anim->get_animation()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { + + undo_redo->create_action("Delete Node"); + undo_redo->add_do_method(blend_tree.ptr(), "remove_node", p_which); + undo_redo->add_undo_method(blend_tree.ptr(), "add_node", p_which, blend_tree->get_node(p_which)); + + List<AnimationNodeBlendTree::NodeConnection> conns; + blend_tree->get_node_connections(&conns); + + for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) { + if (E->get().output_node == p_which || E->get().input_node == p_which) { + undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", E->get().input_node, E->get().input_index, E->get().output_node); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); +} + +void AnimationNodeBlendTreeEditor::_oneshot_start(const StringName &p_name) { + + Ref<AnimationNodeOneShot> os = blend_tree->get_node(p_name); + ERR_FAIL_COND(!os.is_valid()); + os->start(); +} + +void AnimationNodeBlendTreeEditor::_oneshot_stop(const StringName &p_name) { + + Ref<AnimationNodeOneShot> os = blend_tree->get_node(p_name); + ERR_FAIL_COND(!os.is_valid()); + os->stop(); +} + +void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) { + + GraphNode *gn = Object::cast_to<GraphNode>(p_node); + ERR_FAIL_COND(!gn); + + String name = gn->get_name(); + + Ref<AnimationNode> anode = blend_tree->get_node(name); + ERR_FAIL_COND(!anode.is_valid()); + + EditorNode::get_singleton()->push_item(anode.ptr(), "", true); +} + +void AnimationNodeBlendTreeEditor::_open_in_editor(const String &p_which) { + + Ref<AnimationNode> an = blend_tree->get_node(p_which); + ERR_FAIL_COND(!an.is_valid()) + EditorNode::get_singleton()->edit_item(an.ptr()); +} + +void AnimationNodeBlendTreeEditor::_open_parent() { + if (blend_tree->get_parent().is_valid()) { + EditorNode::get_singleton()->edit_item(blend_tree->get_parent().ptr()); + } +} + +void AnimationNodeBlendTreeEditor::_filter_toggled() { + + updating = true; + undo_redo->create_action("Toggle filter on/off"); + undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_enabled", filter_enabled->is_pressed()); + undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_enabled", _filter_edit->is_filter_enabled()); + undo_redo->add_do_method(this, "_update_filters", _filter_edit); + undo_redo->add_undo_method(this, "_update_filters", _filter_edit); + undo_redo->commit_action(); + updating = false; +} + +void AnimationNodeBlendTreeEditor::_filter_edited() { + + TreeItem *edited = filters->get_edited(); + ERR_FAIL_COND(!edited); + + NodePath edited_path = edited->get_metadata(0); + bool filtered = edited->is_checked(0); + + updating = true; + undo_redo->create_action("Change filter"); + undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", edited_path, filtered); + undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", edited_path, _filter_edit->is_path_filtered(edited_path)); + undo_redo->add_do_method(this, "_update_filters", _filter_edit); + undo_redo->add_undo_method(this, "_update_filters", _filter_edit); + undo_redo->commit_action(); + updating = false; +} + +bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &anode) { + + if (updating || _filter_edit != anode) + return false; + + NodePath player_path = anode->get_tree()->get_animation_player(); + + if (!anode->get_tree()->has_node(player_path)) { + EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names.")); + return false; + } + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(anode->get_tree()->get_node(player_path)); + if (!player) { + EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names.")); + return false; + } + + Node *base = player->get_node(player->get_root()); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names.")); + return false; + } + + updating = true; + + Set<String> paths; + { + List<StringName> animations; + player->get_animation_list(&animations); + + for (List<StringName>::Element *E = animations.front(); E; E = E->next()) { + + Ref<Animation> anim = player->get_animation(E->get()); + for (int i = 0; i < anim->get_track_count(); i++) { + paths.insert(anim->track_get_path(i)); + } + } + } + + filter_enabled->set_pressed(anode->is_filter_enabled()); + filters->clear(); + TreeItem *root = filters->create_item(); + + Map<String, TreeItem *> parenthood; + + for (Set<String>::Element *E = paths.front(); E; E = E->next()) { + + NodePath path = E->get(); + TreeItem *ti = NULL; + String accum; + for (int i = 0; i < path.get_name_count(); i++) { + String name = path.get_name(i); + if (accum != String()) { + accum += "/"; + } + accum += name; + if (!parenthood.has(accum)) { + if (ti) { + ti = filters->create_item(ti); + } else { + ti = filters->create_item(root); + } + parenthood[accum] = ti; + ti->set_text(0, name); + ti->set_selectable(0, false); + ti->set_editable(0, false); + + if (base->has_node(accum)) { + Node *node = base->get_node(accum); + if (has_icon(node->get_class(), "EditorIcons")) { + ti->set_icon(0, get_icon(node->get_class(), "EditorIcons")); + } else { + ti->set_icon(0, get_icon("Node", "EditorIcons")); + } + } + + } else { + ti = parenthood[accum]; + } + } + + Node *node = NULL; + if (base->has_node(accum)) { + node = base->get_node(accum); + } + if (!node) + continue; //no node, cant edit + + if (path.get_subname_count()) { + + String concat = path.get_concatenated_subnames(); + + Skeleton *skeleton = Object::cast_to<Skeleton>(node); + if (skeleton && skeleton->find_bone(concat) != -1) { + //path in skeleton + String bone = concat; + int idx = skeleton->find_bone(bone); + List<String> bone_path; + while (idx != -1) { + bone_path.push_front(skeleton->get_bone_name(idx)); + idx = skeleton->get_bone_parent(idx); + } + + accum += ":"; + for (List<String>::Element *F = bone_path.front(); F; F = F->next()) { + if (F != bone_path.front()) { + accum += "/"; + } + + accum += F->get(); + if (!parenthood.has(accum)) { + ti = filters->create_item(ti); + parenthood[accum] = ti; + ti->set_text(0, F->get()); + ti->set_selectable(0, false); + ti->set_editable(0, false); + ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons")); + } else { + ti = parenthood[accum]; + } + } + + ti->set_editable(0, true); + ti->set_selectable(0, true); + ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + ti->set_text(0, concat); + ti->set_checked(0, anode->is_path_filtered(path)); + ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons")); + ti->set_metadata(0, path); + + } else { + //just a property + ti = filters->create_item(ti); + ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + ti->set_text(0, concat); + ti->set_editable(0, true); + ti->set_selectable(0, true); + ti->set_checked(0, anode->is_path_filtered(path)); + ti->set_metadata(0, path); + } + } else { + if (ti) { + //just a node, likely call or animation track + ti->set_editable(0, true); + ti->set_selectable(0, true); + ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + ti->set_checked(0, anode->is_path_filtered(path)); + ti->set_metadata(0, path); + } + } + } + + updating = false; + + return true; +} + +void AnimationNodeBlendTreeEditor::_edit_filters(const String &p_which) { + + Ref<AnimationNode> anode = blend_tree->get_node(p_which); + ERR_FAIL_COND(!anode.is_valid()); + + _filter_edit = anode; + if (!_update_filters(anode)) + return; + + filter_dialog->popup_centered_minsize(Size2(500, 500) * EDSCALE); +} + +void AnimationNodeBlendTreeEditor::_removed_from_graph() { + if (is_visible()) { + EditorNode::get_singleton()->edit_item(NULL); + } +} + +void AnimationNodeBlendTreeEditor::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + + goto_parent->set_icon(get_icon("MoveUp", "EditorIcons")); + + error_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + } + + if (p_what == NOTIFICATION_PROCESS) { + + String error; + + if (!blend_tree->get_tree()) { + error = TTR("BlendTree does not belong to an AnimationTree node."); + } else if (!blend_tree->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (blend_tree->get_tree()->is_state_invalid()) { + error = blend_tree->get_tree()->get_invalid_state_reason(); + } + + if (error != error_label->get_text()) { + error_label->set_text(error); + if (error != String()) { + error_panel->show(); + } else { + error_panel->hide(); + } + } + + List<AnimationNodeBlendTree::NodeConnection> conns; + blend_tree->get_node_connections(&conns); + for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) { + float activity = 0; + if (blend_tree->get_tree() && !blend_tree->get_tree()->is_state_invalid()) { + activity = blend_tree->get_connection_activity(E->get().input_node, E->get().input_index); + } + graph->set_connection_activity(E->get().output_node, 0, E->get().input_node, E->get().input_index, activity); + } + + AnimationTree *graph_player = blend_tree->get_tree(); + AnimationPlayer *player = NULL; + if (graph_player->has_node(graph_player->get_animation_player())) { + player = Object::cast_to<AnimationPlayer>(graph_player->get_node(graph_player->get_animation_player())); + } + + if (player) { + for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) { + Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key()); + if (an.is_valid()) { + if (player->has_animation(an->get_animation())) { + Ref<Animation> anim = player->get_animation(an->get_animation()); + if (anim.is_valid()) { + E->get()->set_max(anim->get_length()); + E->get()->set_value(an->get_playback_time()); + } + } + } + } + } + } +} + +void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) { + if (updating) + return; + updating = true; + blend_tree->set_graph_offset(p_scroll / EDSCALE); + updating = false; +} + +void AnimationNodeBlendTreeEditor::_node_changed(ObjectID p_node) { + + AnimationNode *an = Object::cast_to<AnimationNode>(ObjectDB::get_instance(p_node)); + if (an && an->get_parent() == blend_tree) { + _update_graph(); + } +} + +void AnimationNodeBlendTreeEditor::_bind_methods() { + + ClassDB::bind_method("_update_graph", &AnimationNodeBlendTreeEditor::_update_graph); + ClassDB::bind_method("_add_node", &AnimationNodeBlendTreeEditor::_add_node); + ClassDB::bind_method("_node_dragged", &AnimationNodeBlendTreeEditor::_node_dragged); + ClassDB::bind_method("_node_renamed", &AnimationNodeBlendTreeEditor::_node_renamed); + ClassDB::bind_method("_node_renamed_focus_out", &AnimationNodeBlendTreeEditor::_node_renamed_focus_out); + ClassDB::bind_method("_connection_request", &AnimationNodeBlendTreeEditor::_connection_request); + ClassDB::bind_method("_disconnection_request", &AnimationNodeBlendTreeEditor::_disconnection_request); + ClassDB::bind_method("_node_selected", &AnimationNodeBlendTreeEditor::_node_selected); + ClassDB::bind_method("_open_in_editor", &AnimationNodeBlendTreeEditor::_open_in_editor); + ClassDB::bind_method("_open_parent", &AnimationNodeBlendTreeEditor::_open_parent); + ClassDB::bind_method("_scroll_changed", &AnimationNodeBlendTreeEditor::_scroll_changed); + ClassDB::bind_method("_delete_request", &AnimationNodeBlendTreeEditor::_delete_request); + ClassDB::bind_method("_edit_filters", &AnimationNodeBlendTreeEditor::_edit_filters); + ClassDB::bind_method("_update_filters", &AnimationNodeBlendTreeEditor::_update_filters); + ClassDB::bind_method("_filter_edited", &AnimationNodeBlendTreeEditor::_filter_edited); + ClassDB::bind_method("_filter_toggled", &AnimationNodeBlendTreeEditor::_filter_toggled); + ClassDB::bind_method("_oneshot_start", &AnimationNodeBlendTreeEditor::_oneshot_start); + ClassDB::bind_method("_oneshot_stop", &AnimationNodeBlendTreeEditor::_oneshot_stop); + ClassDB::bind_method("_node_changed", &AnimationNodeBlendTreeEditor::_node_changed); + ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendTreeEditor::_removed_from_graph); + + ClassDB::bind_method("_anim_selected", &AnimationNodeBlendTreeEditor::_anim_selected); +} + +AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = NULL; + +void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<AnimationNode> p_node) { + + String prev_name = blend_tree->get_node_name(p_node); + ERR_FAIL_COND(prev_name == String()); + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(prev_name)); + ERR_FAIL_COND(!gn); + + String new_name = p_text; + + ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1) + + ERR_FAIL_COND(new_name == prev_name); + + String base_name = new_name; + int base = 1; + String name = base_name; + while (blend_tree->has_node(name)) { + base++; + name = base_name + " " + itos(base); + } + + updating = true; + undo_redo->create_action("Node Renamed"); + undo_redo->add_do_method(blend_tree.ptr(), "rename_node", prev_name, name); + undo_redo->add_undo_method(blend_tree.ptr(), "rename_node", name, prev_name); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + gn->set_name(new_name); + gn->set_size(gn->get_minimum_size()); +} + +void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node) { + _node_renamed(le->call("get_text"), p_node); +} + +AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { + + singleton = this; + updating = false; + + graph = memnew(GraphEdit); + add_child(graph); + graph->add_valid_right_disconnect_type(0); + graph->add_valid_left_disconnect_type(0); + graph->set_v_size_flags(SIZE_EXPAND_FILL); + graph->connect("connection_request", this, "_connection_request", varray(), CONNECT_DEFERRED); + graph->connect("disconnection_request", this, "_disconnection_request", varray(), CONNECT_DEFERRED); + graph->connect("node_selected", this, "_node_selected"); + graph->connect("scroll_offset_changed", this, "_scroll_changed"); + + VSeparator *vs = memnew(VSeparator); + graph->get_zoom_hbox()->add_child(vs); + graph->get_zoom_hbox()->move_child(vs, 0); + + add_node = memnew(MenuButton); + graph->get_zoom_hbox()->add_child(add_node); + add_node->set_text(TTR("Add Node..")); + graph->get_zoom_hbox()->move_child(add_node, 0); + add_node->get_popup()->connect("index_pressed", this, "_add_node"); + + goto_parent = memnew(Button); + graph->get_zoom_hbox()->add_child(goto_parent); + graph->get_zoom_hbox()->move_child(goto_parent, 0); + goto_parent->hide(); + goto_parent->connect("pressed", this, "_open_parent"); + + add_options.push_back(AddOption("Animation", "AnimationNodeAnimation")); + add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot")); + add_options.push_back(AddOption("Add2", "AnimationNodeAdd2")); + add_options.push_back(AddOption("Add3", "AnimationNodeAdd3")); + add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2")); + add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3")); + add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek")); + add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale")); + add_options.push_back(AddOption("Transition", "AnimationNodeTransition")); + add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree")); + add_options.push_back(AddOption("BlendSpace1D", "AnimationNodeBlendSpace1D")); + add_options.push_back(AddOption("BlendSpace2D", "AnimationNodeBlendSpace2D")); + add_options.push_back(AddOption("StateMachine", "AnimationNodeStateMachine")); + _update_options_menu(); + + error_panel = memnew(PanelContainer); + add_child(error_panel); + error_label = memnew(Label); + error_panel->add_child(error_label); + error_label->set_text("eh"); + + filter_dialog = memnew(AcceptDialog); + add_child(filter_dialog); + filter_dialog->set_title(TTR("Edit Filtered Tracks:")); + + VBoxContainer *filter_vbox = memnew(VBoxContainer); + filter_dialog->add_child(filter_vbox); + + filter_enabled = memnew(CheckBox); + filter_enabled->set_text(TTR("Enable filtering")); + filter_enabled->connect("pressed", this, "_filter_toggled"); + filter_vbox->add_child(filter_enabled); + + filters = memnew(Tree); + filter_vbox->add_child(filters); + filters->set_v_size_flags(SIZE_EXPAND_FILL); + filters->set_hide_root(true); + filters->connect("item_edited", this, "_filter_edited"); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); +} + +void AnimationNodeBlendTreeEditorPlugin::edit(Object *p_object) { + + anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendTree>(p_object)); +} + +bool AnimationNodeBlendTreeEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("AnimationNodeBlendTree"); +} + +void AnimationNodeBlendTreeEditorPlugin::make_visible(bool p_visible) { + + if (p_visible) { + //editor->hide_animation_player_editors(); + //editor->animation_panel_make_visible(true); + button->show(); + editor->make_bottom_panel_item_visible(anim_tree_editor); + anim_tree_editor->set_process(true); + } else { + + if (anim_tree_editor->is_visible_in_tree()) + editor->hide_bottom_panel(); + button->hide(); + anim_tree_editor->set_process(false); + } +} + +AnimationNodeBlendTreeEditorPlugin::AnimationNodeBlendTreeEditorPlugin(EditorNode *p_node) { + + editor = p_node; + anim_tree_editor = memnew(AnimationNodeBlendTreeEditor); + anim_tree_editor->set_custom_minimum_size(Size2(0, 300)); + + button = editor->add_bottom_panel_item(TTR("BlendTree"), anim_tree_editor); + button->hide(); +} + +AnimationNodeBlendTreeEditorPlugin::~AnimationNodeBlendTreeEditorPlugin() { +} diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h new file mode 100644 index 0000000000..deba3b2b0e --- /dev/null +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -0,0 +1,117 @@ +#ifndef ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H +#define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/animation/animation_blend_tree.h" +#include "scene/gui/button.h" +#include "scene/gui/graph_edit.h" +#include "scene/gui/popup.h" +#include "scene/gui/tree.h" +/** + @author Juan Linietsky <reduzio@gmail.com> +*/ + +class AnimationNodeBlendTreeEditor : public VBoxContainer { + + GDCLASS(AnimationNodeBlendTreeEditor, VBoxContainer); + + Ref<AnimationNodeBlendTree> blend_tree; + GraphEdit *graph; + MenuButton *add_node; + Button *goto_parent; + + PanelContainer *error_panel; + Label *error_label; + + UndoRedo *undo_redo; + + AcceptDialog *filter_dialog; + Tree *filters; + CheckBox *filter_enabled; + + Map<StringName, ProgressBar *> animations; + + void _update_graph(); + + struct AddOption { + String name; + String type; + Ref<Script> script; + AddOption(const String &p_name = String(), const String &p_type = String()) { + name = p_name; + type = p_type; + } + }; + + Vector<AddOption> add_options; + + void _add_node(int p_idx); + void _update_options_menu(); + + static AnimationNodeBlendTreeEditor *singleton; + + void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, Ref<AnimationNode> p_node); + void _node_renamed(const String &p_text, Ref<AnimationNode> p_node); + void _node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node); + + bool updating; + + void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); + void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); + + void _scroll_changed(const Vector2 &p_scroll); + void _node_selected(Object *p_node); + void _open_in_editor(const String &p_which); + void _open_parent(); + void _anim_selected(int p_index, Array p_options, const String &p_node); + void _delete_request(const String &p_which); + void _oneshot_start(const StringName &p_name); + void _oneshot_stop(const StringName &p_name); + + bool _update_filters(const Ref<AnimationNode> &anode); + void _edit_filters(const String &p_which); + void _filter_edited(); + void _filter_toggled(); + Ref<AnimationNode> _filter_edit; + + void _node_changed(ObjectID p_node); + + void _removed_from_graph(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + static AnimationNodeBlendTreeEditor *get_singleton() { return singleton; } + + void add_custom_type(const String &p_name, const Ref<Script> &p_script); + void remove_custom_type(const Ref<Script> &p_script); + + virtual Size2 get_minimum_size() const; + void edit(AnimationNodeBlendTree *p_blend_tree); + AnimationNodeBlendTreeEditor(); +}; + +class AnimationNodeBlendTreeEditorPlugin : public EditorPlugin { + + GDCLASS(AnimationNodeBlendTreeEditorPlugin, EditorPlugin); + + AnimationNodeBlendTreeEditor *anim_tree_editor; + EditorNode *editor; + Button *button; + +public: + virtual String get_name() const { return "BlendTree"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + AnimationNodeBlendTreeEditorPlugin(EditorNode *p_node); + ~AnimationNodeBlendTreeEditorPlugin(); +}; + +#endif // ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 23c5e36a92..248e386bf1 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -30,7 +30,7 @@ #include "animation_player_editor_plugin.h" -#include "editor/animation_editor.h" +#include "editor/animation_track_editor.h" #include "editor/editor_settings.h" #include "io/resource_loader.h" #include "io/resource_saver.h" @@ -50,9 +50,9 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) { set_process(false); - key_editor->set_animation(Ref<Animation>()); - key_editor->set_root(NULL); - key_editor->show_select_node_warning(true); + track_editor->set_animation(Ref<Animation>()); + track_editor->set_root(NULL); + track_editor->show_select_node_warning(true); _update_player(); //editor->animation_editor_make_visible(false); } @@ -84,7 +84,7 @@ void AnimationPlayerEditor::_notification(int p_what) { } } frame->set_value(player->get_current_animation_position()); - key_editor->set_anim_pos(player->get_current_animation_position()); + track_editor->set_anim_pos(player->get_current_animation_position()); EditorNode::get_singleton()->get_inspector()->refresh(); } else if (last_active) { @@ -101,8 +101,6 @@ void AnimationPlayerEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { - save_anim->get_popup()->connect("id_pressed", this, "_animation_save_menu"); - tool_anim->get_popup()->connect("id_pressed", this, "_animation_tool_menu"); onion_skinning->get_popup()->connect("id_pressed", this, "_onion_skinning_menu"); @@ -121,16 +119,8 @@ void AnimationPlayerEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { - add_anim->set_icon(get_icon("New", "EditorIcons")); - rename_anim->set_icon(get_icon("Rename", "EditorIcons")); - duplicate_anim->set_icon(get_icon("Duplicate", "EditorIcons")); autoplay->set_icon(get_icon("AutoPlay", "EditorIcons")); - load_anim->set_icon(get_icon("Folder", "EditorIcons")); - save_anim->set_icon(get_icon("Save", "EditorIcons")); - - remove_anim->set_icon(get_icon("Remove", "EditorIcons")); - blend_anim->set_icon(get_icon("Blend", "EditorIcons")); play->set_icon(get_icon("PlayStart", "EditorIcons")); play_from->set_icon(get_icon("Play", "EditorIcons")); play_bw->set_icon(get_icon("PlayStartBackwards", "EditorIcons")); @@ -138,11 +128,27 @@ void AnimationPlayerEditor::_notification(int p_what) { autoplay_icon = get_icon("AutoPlay", "EditorIcons"); stop->set_icon(get_icon("Stop", "EditorIcons")); - resource_edit_anim->set_icon(get_icon("EditResource", "EditorIcons")); + pin->set_icon(get_icon("Pin", "EditorIcons")); - tool_anim->set_icon(get_icon("Tools", "EditorIcons")); onion_skinning->set_icon(get_icon("Onion", "EditorIcons")); + tool_anim->add_style_override("normal", get_stylebox("normal", "Button")); + track_editor->get_edit_menu()->add_style_override("normal", get_stylebox("normal", "Button")); + +#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_icon(m_icon, "EditorIcons")) + + ITEM_ICON(TOOL_NEW_ANIM, "New"); + ITEM_ICON(TOOL_LOAD_ANIM, "Load"); + ITEM_ICON(TOOL_SAVE_ANIM, "Save"); + ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save"); + ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate"); + ITEM_ICON(TOOL_RENAME_ANIM, "Rename"); + ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend"); + ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit"); + ITEM_ICON(TOOL_REMOVE_ANIM, "Remove"); + //ITEM_ICON(TOOL_COPY_ANIM, "Copy"); + //ITEM_ICON(TOOL_PASTE_ANIM, "Paste"); + } break; } } @@ -304,10 +310,10 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { Ref<Animation> anim = player->get_animation(current); { - key_editor->set_animation(anim); + track_editor->set_animation(anim); Node *root = player->get_node(player->get_root()); if (root) { - key_editor->set_root(root); + track_editor->set_root(root); } } frame->set_max(anim->get_length()); @@ -317,8 +323,8 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { frame->set_step(0.00001); } else { - key_editor->set_animation(Ref<Animation>()); - key_editor->set_root(NULL); + track_editor->set_animation(Ref<Animation>()); + track_editor->set_root(NULL); } autoplay->set_pressed(current == player->get_autoplay()); @@ -704,16 +710,16 @@ void AnimationPlayerEditor::_animation_edit() { if (animation->get_item_count()) { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); - key_editor->set_animation(anim); + track_editor->set_animation(anim); Node *root = player->get_node(player->get_root()); if (root) { - key_editor->set_root(root); + track_editor->set_root(root); } } else { - key_editor->set_animation(Ref<Animation>()); - key_editor->set_root(NULL); + track_editor->set_animation(Ref<Animation>()); + track_editor->set_root(NULL); } } void AnimationPlayerEditor::_dialog_action(String p_file) { @@ -810,8 +816,16 @@ void AnimationPlayerEditor::_update_player() { animation->clear(); - add_anim->set_disabled(player == NULL); - load_anim->set_disabled(player == NULL); +#define ITEM_DISABLED(m_item, m_disabled) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), m_disabled) + + ITEM_DISABLED(TOOL_SAVE_ANIM, animlist.size() == 0); + ITEM_DISABLED(TOOL_SAVE_AS_ANIM, animlist.size() == 0); + ITEM_DISABLED(TOOL_DUPLICATE_ANIM, animlist.size() == 0); + ITEM_DISABLED(TOOL_RENAME_ANIM, animlist.size() == 0); + ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0); + ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0); + ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0); + stop->set_disabled(animlist.size() == 0); play->set_disabled(animlist.size() == 0); play_bw->set_disabled(animlist.size() == 0); @@ -820,12 +834,6 @@ void AnimationPlayerEditor::_update_player() { frame->set_editable(animlist.size() != 0); animation->set_disabled(animlist.size() == 0); autoplay->set_disabled(animlist.size() == 0); - duplicate_anim->set_disabled(animlist.size() == 0); - rename_anim->set_disabled(animlist.size() == 0); - blend_anim->set_disabled(animlist.size() == 0); - remove_anim->set_disabled(animlist.size() == 0); - resource_edit_anim->set_disabled(animlist.size() == 0); - save_anim->set_disabled(animlist.size() == 0); tool_anim->set_disabled(player == NULL); onion_skinning->set_disabled(player == NULL); pin->set_disabled(player == NULL); @@ -863,10 +871,10 @@ void AnimationPlayerEditor::_update_player() { if (animation->get_item_count()) { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); - key_editor->set_animation(anim); + track_editor->set_animation(anim); Node *root = player->get_node(player->get_root()); if (root) { - key_editor->set_root(root); + track_editor->set_root(root); } } @@ -884,9 +892,9 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { if (player) { _update_player(); - key_editor->show_select_node_warning(false); + track_editor->show_select_node_warning(false); } else { - key_editor->show_select_node_warning(true); + track_editor->show_select_node_warning(true); //hide(); } @@ -1024,7 +1032,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) { player->seek(pos, true); } - key_editor->set_anim_pos(pos); + track_editor->set_anim_pos(pos); updating = true; }; @@ -1084,16 +1092,55 @@ void AnimationPlayerEditor::_hide_anim_editors() { hide(); set_process(false); - key_editor->set_animation(Ref<Animation>()); - key_editor->set_root(NULL); - key_editor->show_select_node_warning(true); + track_editor->set_animation(Ref<Animation>()); + track_editor->set_root(NULL); + track_editor->show_select_node_warning(true); //editor->animation_editor_make_visible(false); } +void AnimationPlayerEditor::_animation_about_to_show_menu() { +} + void AnimationPlayerEditor::_animation_tool_menu(int p_option) { + String current = animation->get_item_text(animation->get_selected()); + Ref<Animation> anim; + if (current != "") { + anim = player->get_animation(current); + } + switch (p_option) { + case TOOL_NEW_ANIM: { + _animation_new(); + } break; + + case TOOL_LOAD_ANIM: { + _animation_load(); + break; + } break; + case TOOL_SAVE_ANIM: { + if (anim.is_valid()) { + _animation_save(anim); + } + } break; + case TOOL_SAVE_AS_ANIM: { + if (anim.is_valid()) { + _animation_save_as(anim); + } + } break; + case TOOL_DUPLICATE_ANIM: { + _animation_duplicate(); + } break; + case TOOL_RENAME_ANIM: { + _animation_rename(); + } break; + case TOOL_EDIT_TRANSITIONS: { + _animation_blend(); + } break; + case TOOL_REMOVE_ANIM: { + _animation_remove(); + } break; case TOOL_COPY_ANIM: { if (!animation->get_item_count()) { @@ -1156,23 +1203,6 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { } } -void AnimationPlayerEditor::_animation_save_menu(int p_option) { - - String current = animation->get_item_text(animation->get_selected()); - if (current != "") { - Ref<Animation> anim = player->get_animation(current); - - switch (p_option) { - case ANIM_SAVE: - _animation_save(anim); - break; - case ANIM_SAVE_AS: - _animation_save_as(anim); - break; - } - } -} - void AnimationPlayerEditor::_onion_skinning_menu(int p_option) { PopupMenu *menu = onion_skinning->get_popup(); @@ -1431,7 +1461,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { float pos = cpos + step_off * anim->get_step(); - bool valid = anim->has_loop() || pos >= 0 && pos <= anim->get_length(); + bool valid = anim->has_loop() || (pos >= 0 && pos <= anim->get_length()); onion.captures_valid[cidx] = valid; if (valid) { player->seek(pos, true); @@ -1494,6 +1524,10 @@ void AnimationPlayerEditor::_stop_onion_skinning() { } } +void AnimationPlayerEditor::_pin_pressed() { + EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->update_tree(); +} + void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &AnimationPlayerEditor::_gui_input); @@ -1532,11 +1566,13 @@ void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_blend_editor_next_changed"), &AnimationPlayerEditor::_blend_editor_next_changed); ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &AnimationPlayerEditor::_unhandled_key_input); ClassDB::bind_method(D_METHOD("_animation_tool_menu"), &AnimationPlayerEditor::_animation_tool_menu); - ClassDB::bind_method(D_METHOD("_animation_save_menu"), &AnimationPlayerEditor::_animation_save_menu); + ClassDB::bind_method(D_METHOD("_onion_skinning_menu"), &AnimationPlayerEditor::_onion_skinning_menu); ClassDB::bind_method(D_METHOD("_editor_visibility_changed"), &AnimationPlayerEditor::_editor_visibility_changed); ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1); ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2); + + ClassDB::bind_method(D_METHOD("_pin_pressed"), &AnimationPlayerEditor::_pin_pressed); } AnimationPlayerEditor *AnimationPlayerEditor::singleton = NULL; @@ -1606,26 +1642,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay scale->set_tooltip(TTR("Scale animation playback globally for the node.")); scale->hide(); - add_anim = memnew(ToolButton); - ED_SHORTCUT("animation_player_editor/add_animation", TTR("Create new animation in player.")); - add_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/add_animation")); - add_anim->set_tooltip(TTR("Create new animation in player.")); - - hb->add_child(add_anim); - - load_anim = memnew(ToolButton); - ED_SHORTCUT("animation_player_editor/load_from_disk", TTR("Load animation from disk.")); - add_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/load_from_disk")); - load_anim->set_tooltip(TTR("Load an animation from disk.")); - hb->add_child(load_anim); - - save_anim = memnew(MenuButton); - save_anim->set_tooltip(TTR("Save the current animation.")); - save_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save", TTR("Save")), ANIM_SAVE); - save_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as", TTR("Save As")), ANIM_SAVE_AS); - save_anim->set_focus_mode(Control::FOCUS_NONE); - hb->add_child(save_anim); - accept = memnew(AcceptDialog); add_child(accept); accept->connect("confirmed", this, "_menu_confirm_current"); @@ -1634,23 +1650,28 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay add_child(delete_dialog); delete_dialog->connect("confirmed", this, "_animation_remove_confirmed"); - duplicate_anim = memnew(ToolButton); - hb->add_child(duplicate_anim); - ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate Animation")); - duplicate_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/duplicate_animation")); - duplicate_anim->set_tooltip(TTR("Duplicate Animation")); - - rename_anim = memnew(ToolButton); - hb->add_child(rename_anim); - ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename Animation")); - rename_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/rename_animation")); - rename_anim->set_tooltip(TTR("Rename Animation")); - - remove_anim = memnew(ToolButton); - hb->add_child(remove_anim); - ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove Animation")); - remove_anim->set_shortcut(ED_GET_SHORTCUT("animation_player_editor/remove_animation")); - remove_anim->set_tooltip(TTR("Remove Animation")); + tool_anim = memnew(MenuButton); + tool_anim->set_flat(false); + //tool_anim->set_flat(false); + tool_anim->set_tooltip(TTR("Animation Tools")); + tool_anim->set_text(TTR("Animation")); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM); + tool_anim->get_popup()->add_separator(); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation", TTR("Load")), TOOL_LOAD_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_animation", TTR("Save")), TOOL_SAVE_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as_animation", TTR("Save As...")), TOOL_SAVE_AS_ANIM); + tool_anim->get_popup()->add_separator(); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy")), TOOL_COPY_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste")), TOOL_PASTE_ANIM); + tool_anim->get_popup()->add_separator(); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate")), TOOL_DUPLICATE_ANIM); + tool_anim->get_popup()->add_separator(); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE); + tool_anim->get_popup()->add_separator(); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM); + hb->add_child(tool_anim); animation = memnew(OptionButton); hb->add_child(animation); @@ -1662,18 +1683,12 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay hb->add_child(autoplay); autoplay->set_tooltip(TTR("Autoplay on Load")); - blend_anim = memnew(ToolButton); - hb->add_child(blend_anim); - blend_anim->set_tooltip(TTR("Edit Target Blend Times")); - - tool_anim = memnew(MenuButton); - //tool_anim->set_flat(false); - tool_anim->set_tooltip(TTR("Animation Tools")); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy Animation")), TOOL_COPY_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste Animation")), TOOL_PASTE_ANIM); //tool_anim->get_popup()->add_separator(); //tool_anim->get_popup()->add_item("Edit Anim Resource",TOOL_PASTE_ANIM); - hb->add_child(tool_anim); + + track_editor = memnew(AnimationTrackEditor); + + hb->add_child(track_editor->get_edit_menu()); onion_skinning = memnew(MenuButton); //onion_skinning->set_flat(false); @@ -1702,10 +1717,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay pin->set_toggle_mode(true); pin->set_tooltip(TTR("Pin AnimationPlayer")); hb->add_child(pin); - - resource_edit_anim = memnew(Button); - hb->add_child(resource_edit_anim); - resource_edit_anim->hide(); + pin->connect("pressed", this, "_pin_pressed"); file = memnew(EditorFileDialog); add_child(file); @@ -1758,16 +1770,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay play_bw_from->connect("pressed", this, "_play_bw_from_pressed"); stop->connect("pressed", this, "_stop_pressed"); //pause->connect("pressed", this,"_pause_pressed"); - add_anim->connect("pressed", this, "_animation_new"); - rename_anim->connect("pressed", this, "_animation_rename"); - load_anim->connect("pressed", this, "_animation_load"); - duplicate_anim->connect("pressed", this, "_animation_duplicate"); //frame->connect("text_entered", this,"_seek_frame_changed"); - blend_anim->connect("pressed", this, "_animation_blend"); - remove_anim->connect("pressed", this, "_animation_remove"); animation->connect("item_selected", this, "_animation_selected", Vector<Variant>(), true); - resource_edit_anim->connect("pressed", this, "_animation_resource_edit"); + file->connect("file_selected", this, "_dialog_action"); frame->connect("value_changed", this, "_seek_value_changed", Vector<Variant>(), true); scale->connect("text_entered", this, "_scale_changed", Vector<Variant>(), true); @@ -1777,18 +1783,17 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay set_process_unhandled_key_input(true); - key_editor = memnew(AnimationKeyEditor); - add_child(key_editor); - key_editor->set_v_size_flags(SIZE_EXPAND_FILL); - key_editor->connect("timeline_changed", this, "_animation_key_editor_seek"); - key_editor->connect("animation_len_changed", this, "_animation_key_editor_anim_len_changed"); - key_editor->connect("animation_step_changed", this, "_animation_key_editor_anim_step_changed"); + add_child(track_editor); + track_editor->set_v_size_flags(SIZE_EXPAND_FILL); + track_editor->connect("timeline_changed", this, "_animation_key_editor_seek"); + track_editor->connect("animation_len_changed", this, "_animation_key_editor_anim_len_changed"); + track_editor->connect("animation_step_changed", this, "_animation_key_editor_anim_step_changed"); _update_player(); // Onion skinning - key_editor->connect("visibility_changed", this, "_editor_visibility_changed"); + track_editor->connect("visibility_changed", this, "_editor_visibility_changed"); onion.enabled = false; onion.past = true; diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index a7b7c6c465..5ac7b99903 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -42,8 +42,9 @@ /** @author Juan Linietsky <reduzio@gmail.com> */ -class AnimationKeyEditor; +class AnimationTrackEditor; class AnimationPlayerEditorPlugin; + class AnimationPlayerEditor : public VBoxContainer { GDCLASS(AnimationPlayerEditor, VBoxContainer); @@ -53,6 +54,14 @@ class AnimationPlayerEditor : public VBoxContainer { AnimationPlayer *player; enum { + TOOL_NEW_ANIM, + TOOL_LOAD_ANIM, + TOOL_SAVE_ANIM, + TOOL_SAVE_AS_ANIM, + TOOL_DUPLICATE_ANIM, + TOOL_RENAME_ANIM, + TOOL_EDIT_TRANSITIONS, + TOOL_REMOVE_ANIM, TOOL_COPY_ANIM, TOOL_PASTE_ANIM, TOOL_EDIT_RESOURCE @@ -72,6 +81,7 @@ class AnimationPlayerEditor : public VBoxContainer { }; enum { + ANIM_OPEN, ANIM_SAVE, ANIM_SAVE_AS }; @@ -89,16 +99,8 @@ class AnimationPlayerEditor : public VBoxContainer { Button *play_bw_from; //Button *pause; - Button *add_anim; Button *autoplay; - Button *rename_anim; - Button *duplicate_anim; - - Button *resource_edit_anim; - Button *load_anim; - MenuButton *save_anim; - Button *blend_anim; - Button *remove_anim; + MenuButton *tool_anim; MenuButton *onion_skinning; ToolButton *pin; @@ -130,7 +132,7 @@ class AnimationPlayerEditor : public VBoxContainer { bool updating; bool updating_blends; - AnimationKeyEditor *key_editor; + AnimationTrackEditor *track_editor; // Onion skinning struct { @@ -207,8 +209,8 @@ class AnimationPlayerEditor : public VBoxContainer { void _unhandled_key_input(const Ref<InputEvent> &p_ev); void _animation_tool_menu(int p_option); - void _animation_save_menu(int p_option); void _onion_skinning_menu(int p_option); + void _animation_about_to_show_menu(); void _editor_visibility_changed(); bool _are_onion_layers_valid(); @@ -219,6 +221,8 @@ class AnimationPlayerEditor : public VBoxContainer { void _start_onion_skinning(); void _stop_onion_skinning(); + void _pin_pressed(); + AnimationPlayerEditor(); ~AnimationPlayerEditor(); @@ -232,7 +236,9 @@ public: AnimationPlayer *get_player() const; static AnimationPlayerEditor *singleton; - AnimationKeyEditor *get_key_editor() { return key_editor; } + bool is_pinned() const { return pin->is_pressed(); } + void unpin() { pin->set_pressed(false); } + AnimationTrackEditor *get_track_editor() { return track_editor; } Dictionary get_state() const; void set_state(const Dictionary &p_state); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp new file mode 100644 index 0000000000..04bd5f0cec --- /dev/null +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -0,0 +1,1313 @@ +#include "animation_state_machine_editor.h" + +#include "core/io/resource_loader.h" +#include "core/project_settings.h" +#include "math/delaunay.h" +#include "os/input.h" +#include "os/keyboard.h" +#include "scene/animation/animation_blend_tree.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/panel.h" +#include "scene/main/viewport.h" + +void AnimationNodeStateMachineEditor::edit(AnimationNodeStateMachine *p_state_machine) { + + if (state_machine.is_valid()) { + state_machine->disconnect("removed_from_graph", this, "_removed_from_graph"); + } + + if (p_state_machine) { + state_machine = Ref<AnimationNodeStateMachine>(p_state_machine); + } else { + state_machine.unref(); + } + + if (state_machine.is_null()) { + hide(); + } else { + state_machine->connect("removed_from_graph", this, "_removed_from_graph"); + + selected_transition_from = StringName(); + selected_transition_to = StringName(); + selected_node = StringName(); + _update_mode(); + _update_graph(); + } +} + +void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) { + + Ref<InputEventKey> k = p_event; + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) { + if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) { + _erase_selected(); + accept_event(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + + //Add new node + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == BUTTON_LEFT))) { + menu->clear(); + animations_menu->clear(); + animations_to_add.clear(); + List<StringName> classes; + classes.sort_custom<StringName::AlphCompare>(); + + ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); + menu->add_submenu_item(TTR("Add Animation"), "animations"); + + AnimationTree *gp = state_machine->get_tree(); + ERR_FAIL_COND(!gp); + if (gp && gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + animations_menu->add_icon_item(get_icon("Animation", "EditorIcons"), E->get()); + animations_to_add.push_back(E->get()); + } + } + } + + for (List<StringName>::Element *E = classes.front(); E; E = E->next()) { + + String name = String(E->get()).replace_first("AnimationNode", ""); + if (name == "Animation") + continue; // nope + int idx = menu->get_item_count(); + menu->add_item(vformat("Add %s", name)); + menu->set_item_metadata(idx, E->get()); + } + + menu->set_global_position(state_machine_draw->get_global_transform().xform(mb->get_position())); + menu->popup(); + add_node_pos = mb->get_position() / EDSCALE + state_machine->get_graph_offset(); + } + + // select node or push a field inside + if (mb.is_valid() && !mb->get_shift() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + selected_transition_from = StringName(); + selected_transition_to = StringName(); + selected_node = StringName(); + + for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order + + if (node_rects[i].play.has_point(mb->get_position())) { //edit name + if (play_mode->get_selected() == 1 || !state_machine->is_playing()) { + //start + state_machine->start(node_rects[i].node_name); + } else { + //travel + if (!state_machine->travel(node_rects[i].node_name)) { + + state_machine->start(node_rects[i].node_name); + //removing this due to usability.. + //error_time = 5; + //error_text = vformat(TTR("No path found from '%s' to '%s'."), state_machine->get_current_node(), node_rects[i].node_name); + } + } + state_machine_draw->update(); + return; + } + + if (node_rects[i].name.has_point(mb->get_position())) { //edit name + + Ref<StyleBox> line_sb = get_stylebox("normal", "LineEdit"); + + Rect2 edit_rect = node_rects[i].name; + edit_rect.position -= line_sb->get_offset(); + edit_rect.size += line_sb->get_minimum_size(); + + name_edit->set_global_position(state_machine_draw->get_global_transform().xform(edit_rect.position)); + name_edit->set_size(edit_rect.size); + name_edit->set_text(node_rects[i].node_name); + name_edit->show_modal(); + name_edit->grab_focus(); + name_edit->select_all(); + + prev_name = node_rects[i].node_name; + return; + } + + if (node_rects[i].edit.has_point(mb->get_position())) { //edit name + call_deferred("_open_editor", node_rects[i].node_name); + return; + } + + if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected + selected_node = node_rects[i].node_name; + + Ref<AnimationNode> anode = state_machine->get_node(selected_node); + EditorNode::get_singleton()->push_item(anode.ptr(), "", true); + state_machine_draw->update(); + dragging_selected_attempt = true; + dragging_selected = false; + drag_from = mb->get_position(); + snap_x = StringName(); + snap_y = StringName(); + _update_mode(); + return; + } + } + + //test the lines now + int closest = -1; + float closest_d = 1e20; + for (int i = 0; i < transition_lines.size(); i++) { + + Vector2 s[2] = { + transition_lines[i].from, + transition_lines[i].to + }; + Vector2 cpoint = Geometry::get_closest_point_to_segment_2d(mb->get_position(), s); + float d = cpoint.distance_to(mb->get_position()); + if (d > transition_lines[i].width) { + continue; + } + + if (d < closest_d) { + closest = i; + closest_d = d; + } + } + + if (closest >= 0) { + selected_transition_from = transition_lines[closest].from_node; + selected_transition_to = transition_lines[closest].to_node; + + Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest); + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + } + + state_machine_draw->update(); + _update_mode(); + } + + //end moving node + if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + + if (dragging_selected) { + + Ref<AnimationNode> an = state_machine->get_node(selected_node); + updating = true; + undo_redo->create_action("Move Node"); + undo_redo->add_do_method(an.ptr(), "set_position", an->get_position() + drag_ofs / EDSCALE); + undo_redo->add_undo_method(an.ptr(), "set_position", an->get_position()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + } + snap_x = StringName(); + snap_y = StringName(); + + dragging_selected_attempt = false; + dragging_selected = false; + state_machine_draw->update(); + } + + //connect nodes + if (mb.is_valid() && ((tool_select->is_pressed() && mb->get_shift()) || tool_connect->is_pressed()) && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + + for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order + if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected + connecting = true; + connecting_from = node_rects[i].node_name; + connecting_to = mb->get_position(); + connecting_to_node = StringName(); + return; + } + } + } + + //end connecting nodes + if (mb.is_valid() && connecting && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + + if (connecting_to_node != StringName()) { + + if (state_machine->has_transition(connecting_from, connecting_to_node)) { + EditorNode::get_singleton()->show_warning("Transition exists!"); + + } else { + + Ref<AnimationNodeStateMachineTransition> tr; + tr.instance(); + tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected())); + + updating = true; + undo_redo->create_action("Add Transition"); + undo_redo->add_do_method(state_machine.ptr(), "add_transition", connecting_from, connecting_to_node, tr); + undo_redo->add_undo_method(state_machine.ptr(), "remove_transition", connecting_from, connecting_to_node); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + selected_transition_from = connecting_from; + selected_transition_to = connecting_to_node; + + EditorNode::get_singleton()->push_item(tr.ptr(), "", true); + _update_mode(); + } + } + connecting_to_node = StringName(); + connecting = false; + state_machine_draw->update(); + } + + Ref<InputEventMouseMotion> mm = p_event; + + //pan window + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) { + + h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x); + v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y); + } + + //move mouse while connecting + if (mm.is_valid() && connecting) { + + connecting_to = mm->get_position(); + connecting_to_node = StringName(); + state_machine_draw->update(); + + for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order + if (node_rects[i].node_name != connecting_from && node_rects[i].node.has_point(connecting_to)) { //select node since nothing else was selected + connecting_to_node = node_rects[i].node_name; + return; + } + } + } + + //move mouse while moving a node + if (mm.is_valid() && dragging_selected_attempt) { + + dragging_selected = true; + drag_ofs = mm->get_position() - drag_from; + snap_x = StringName(); + snap_y = StringName(); + { + //snap + Vector2 cpos = state_machine->get_node(selected_node)->get_position() + drag_ofs / EDSCALE; + List<StringName> nodes; + state_machine->get_node_list(&nodes); + + float best_d_x = 1e20; + float best_d_y = 1e20; + + for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { + if (E->get() == selected_node) + continue; + Vector2 npos = state_machine->get_node(E->get())->get_position(); + + float d_x = ABS(npos.x - cpos.x); + if (d_x < MIN(5, best_d_x)) { + drag_ofs.x -= cpos.x - npos.x; + best_d_x = d_x; + snap_x = E->get(); + } + + float d_y = ABS(npos.y - cpos.y); + if (d_y < MIN(5, best_d_y)) { + drag_ofs.y -= cpos.y - npos.y; + best_d_y = d_y; + snap_y = E->get(); + } + } + } + + state_machine_draw->update(); + } + + //put ibeam (text cursor) over names to make it clearer that they are editable + if (mm.is_valid()) { + + state_machine_draw->grab_focus(); + + bool over_text_now = false; + String new_over_node = StringName(); + int new_over_node_what = -1; + if (tool_select->is_pressed()) { + + for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order + + if (node_rects[i].name.has_point(mm->get_position())) { + over_text_now = true; + break; + } + + if (node_rects[i].node.has_point(mm->get_position())) { + new_over_node = node_rects[i].node_name; + if (node_rects[i].play.has_point(mm->get_position())) { + new_over_node_what = 0; + } + if (node_rects[i].edit.has_point(mm->get_position())) { + new_over_node_what = 1; + } + } + } + } + + if (new_over_node != over_node || new_over_node_what != over_node_what) { + over_node = new_over_node; + over_node_what = new_over_node_what; + state_machine_draw->update(); + } + + if (over_text != over_text_now) { + + if (over_text_now) { + state_machine_draw->set_default_cursor_shape(CURSOR_IBEAM); + } else { + state_machine_draw->set_default_cursor_shape(CURSOR_ARROW); + } + + over_text = over_text_now; + } + } +} + +void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) { + + String type = menu->get_item_metadata(p_index); + + Object *obj = ClassDB::instance(type); + ERR_FAIL_COND(!obj); + AnimationNode *an = Object::cast_to<AnimationNode>(obj); + ERR_FAIL_COND(!an); + + Ref<AnimationNode> node(an); + node->set_position(add_node_pos); + + String base_name = type.replace_first("AnimationNode", ""); + int base = 1; + String name = base_name; + while (state_machine->has_node(name)) { + base++; + name = base_name + " " + itos(base); + } + + updating = true; + undo_redo->create_action("Add Node"); + undo_redo->add_do_method(state_machine.ptr(), "add_node", name, node); + undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + state_machine_draw->update(); +} + +void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { + + Ref<AnimationNodeAnimation> anim; + anim.instance(); + + anim->set_animation(animations_to_add[p_index]); + + String base_name = animations_to_add[p_index]; + int base = 1; + String name = base_name; + while (state_machine->has_node(name)) { + base++; + name = base_name + " " + itos(base); + } + + anim->set_position(add_node_pos); + + updating = true; + undo_redo->create_action("Add Node"); + undo_redo->add_do_method(state_machine.ptr(), "add_node", name, anim); + undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + state_machine_draw->update(); +} + +void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance) { + + Color linecolor = get_color("font_color", "Label"); + Color icon_color(1, 1, 1); + Color accent = get_color("accent_color", "Editor"); + + if (!p_enabled) { + linecolor.a *= 0.2; + icon_color.a *= 0.2; + accent.a *= 0.6; + } + + Ref<Texture> icons[6] = { + get_icon("TransitionImmediateBig", "EditorIcons"), + get_icon("TransitionSyncBig", "EditorIcons"), + get_icon("TransitionEndBig", "EditorIcons"), + get_icon("TransitionImmediateAutoBig", "EditorIcons"), + get_icon("TransitionSyncAutoBig", "EditorIcons"), + get_icon("TransitionEndAutoBig", "EditorIcons") + }; + + if (p_selected) { + state_machine_draw->draw_line(p_from, p_to, accent, 6, true); + } + + if (p_travel) { + linecolor = accent; + linecolor.set_hsv(1.0, linecolor.get_s(), linecolor.get_v()); + } + state_machine_draw->draw_line(p_from, p_to, linecolor, 2, true); + + Ref<Texture> icon = icons[p_mode + (p_auto_advance ? 3 : 0)]; + + Transform2D xf; + xf.elements[0] = (p_to - p_from).normalized(); + xf.elements[1] = xf.elements[0].tangent(); + xf.elements[2] = (p_from + p_to) * 0.5 - xf.elements[1] * icon->get_height() * 0.5 - xf.elements[0] * icon->get_height() * 0.5; + + state_machine_draw->draw_set_transform_matrix(xf); + state_machine_draw->draw_texture(icon, Vector2(), icon_color); + state_machine_draw->draw_set_transform_matrix(Transform2D()); +} + +void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) { + + if (r_to == r_from) + return; + + //this could be optimized... + Vector2 n = (r_to - r_from).normalized(); + while (p_rect.has_point(r_from)) { + r_from += n; + } +} + +void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) { + + if (r_to == r_from) + return; + + //this could be optimized... + Vector2 n = (r_to - r_from).normalized(); + while (p_rect.has_point(r_to)) { + r_to -= n; + } +} + +void AnimationNodeStateMachineEditor::_state_machine_draw() { + + Ref<StyleBox> style = get_stylebox("frame", "GraphNode"); + Ref<StyleBox> style_selected = get_stylebox("selectedframe", "GraphNode"); + + Ref<Font> font = get_font("title_font", "GraphNode"); + Color font_color = get_color("title_color", "GraphNode"); + Ref<Texture> play = get_icon("Play", "EditorIcons"); + Ref<Texture> auto_play = get_icon("AutoPlay", "EditorIcons"); + Ref<Texture> edit = get_icon("Edit", "EditorIcons"); + Color accent = get_color("accent_color", "Editor"); + Color linecolor = get_color("font_color", "Label"); + linecolor.a *= 0.3; + Ref<StyleBox> playing_overlay = get_stylebox("position", "GraphNode"); + + bool playing = state_machine->is_playing(); + StringName current = state_machine->get_current_node(); + StringName blend_from = state_machine->get_blend_from_node(); + Vector<StringName> travel_path = state_machine->get_travel_path(); + + if (state_machine_draw->has_focus()) { + state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), accent, false); + } + int sep = 3 * EDSCALE; + + List<StringName> nodes; + state_machine->get_node_list(&nodes); + + node_rects.clear(); + Rect2 scroll_range(Point2(), state_machine_draw->get_size()); + + //snap lines + if (dragging_selected) { + + Vector2 from = (state_machine->get_node(selected_node)->get_position() * EDSCALE) + drag_ofs - state_machine->get_graph_offset() * EDSCALE; + if (snap_x != StringName()) { + Vector2 to = (state_machine->get_node(snap_x)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE; + state_machine_draw->draw_line(from, to, linecolor, 2); + } + if (snap_y != StringName()) { + Vector2 to = (state_machine->get_node(snap_y)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE; + state_machine_draw->draw_line(from, to, linecolor, 2); + } + } + + //pre pass nodes so we know the rectangles + for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { + + Ref<AnimationNode> anode = state_machine->get_node(E->get()); + String name = E->get(); + bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr()); + Ref<StyleBox> sb = E->get() == selected_node ? style_selected : style; + + Size2 s = sb->get_minimum_size(); + int strsize = font->get_string_size(name).width; + s.width += strsize; + s.height += MAX(font->get_height(), play->get_height()); + s.width += sep + play->get_width(); + if (needs_editor) { + s.width += sep + edit->get_width(); + } + + Vector2 offset; + offset += anode->get_position() * EDSCALE; + if (selected_node == E->get() && dragging_selected) { + offset += drag_ofs; + } + offset -= s / 2; + offset = offset.floor(); + + //prepre rect + + NodeRect nr; + nr.node = Rect2(offset, s); + nr.node_name = E->get(); + + scroll_range = scroll_range.merge(nr.node); //merge with range + + //now scroll it to draw + nr.node.position -= state_machine->get_graph_offset() * EDSCALE; + + node_rects.push_back(nr); + } + + transition_lines.clear(); + + //draw conecting line for potential new transition + if (connecting) { + Vector2 from = (state_machine->get_node(connecting_from)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE; + Vector2 to; + if (connecting_to_node != StringName()) { + to = (state_machine->get_node(connecting_to_node)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE; + } else { + to = connecting_to; + } + + for (int i = 0; i < node_rects.size(); i++) { + if (node_rects[i].node_name == connecting_from) { + _clip_src_line_to_rect(from, to, node_rects[i].node); + } + if (node_rects[i].node_name == connecting_to_node) { + _clip_dst_line_to_rect(from, to, node_rects[i].node); + } + } + + _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()), true, false, false, false); + } + + Ref<Texture> tr_reference_icon = get_icon("TransitionImmediateBig", "EditorIcons"); + float tr_bidi_offset = int(tr_reference_icon->get_height() * 0.8); + + //draw transition lines + for (int i = 0; i < state_machine->get_transition_count(); i++) { + + TransitionLine tl; + tl.from_node = state_machine->get_transition_from(i); + Vector2 ofs_from = (dragging_selected && tl.from_node == selected_node) ? drag_ofs : Vector2(); + tl.from = (state_machine->get_node(tl.from_node)->get_position() * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE; + + tl.to_node = state_machine->get_transition_to(i); + Vector2 ofs_to = (dragging_selected && tl.to_node == selected_node) ? drag_ofs : Vector2(); + tl.to = (state_machine->get_node(tl.to_node)->get_position() * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE; + + Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i); + tl.disabled = tr->is_disabled(); + tl.auto_advance = tr->has_auto_advance(); + tl.mode = tr->get_switch_mode(); + tl.width = tr_bidi_offset; + + if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists + Vector2 offset = -(tl.from - tl.to).normalized().tangent() * tr_bidi_offset; + tl.from += offset; + tl.to += offset; + } + + for (int i = 0; i < node_rects.size(); i++) { + if (node_rects[i].node_name == tl.from_node) { + _clip_src_line_to_rect(tl.from, tl.to, node_rects[i].node); + } + if (node_rects[i].node_name == tl.to_node) { + _clip_dst_line_to_rect(tl.from, tl.to, node_rects[i].node); + } + } + + bool selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node; + + bool travel = false; + + if (blend_from == tl.from_node && current == tl.to_node) { + travel = true; + } + + if (travel_path.size()) { + + if (current == tl.from_node && travel_path[0] == tl.to_node) { + travel = true; + } else { + for (int j = 0; j < travel_path.size() - 1; j++) { + if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) { + travel = true; + break; + } + } + } + } + _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, selected, travel, tl.auto_advance); + + transition_lines.push_back(tl); + } + + //draw actual nodes + for (int i = 0; i < node_rects.size(); i++) { + + String name = node_rects[i].node_name; + Ref<AnimationNode> anode = state_machine->get_node(name); + bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr()); + Ref<StyleBox> sb = name == selected_node ? style_selected : style; + int strsize = font->get_string_size(name).width; + + NodeRect &nr = node_rects[i]; + + Vector2 offset = nr.node.position; + int h = nr.node.size.height; + + //prepre rect + + //now scroll it to draw + state_machine_draw->draw_style_box(sb, nr.node); + + if (playing && (blend_from == name || current == name || travel_path.find(name) != -1)) { + state_machine_draw->draw_style_box(playing_overlay, nr.node); + } + + bool onstart = state_machine->get_start_node() == name; + if (onstart) { + state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("Start"), font_color); + } + + if (state_machine->get_end_node() == name) { + + int endofs = nr.node.size.x - font->get_string_size(TTR("End")).x; + state_machine_draw->draw_string(font, offset + Vector2(endofs, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("End"), font_color); + } + + offset.x += sb->get_offset().x; + + nr.play.position = offset + Vector2(0, (h - play->get_height()) / 2).floor(); + nr.play.size = play->get_size(); + + Ref<Texture> play_tex = onstart ? auto_play : play; + + if (over_node == name && over_node_what == 0) { + state_machine_draw->draw_texture(play_tex, nr.play.position, accent); + } else { + state_machine_draw->draw_texture(play_tex, nr.play.position); + } + offset.x += sep + play->get_width(); + + nr.name.position = offset + Vector2(0, (h - font->get_height()) / 2).floor(); + nr.name.size = Vector2(strsize, font->get_height()); + + state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent()), name, font_color); + offset.x += strsize + sep; + + if (needs_editor) { + nr.edit.position = offset + Vector2(0, (h - edit->get_height()) / 2).floor(); + nr.edit.size = edit->get_size(); + + if (over_node == name && over_node_what == 1) { + state_machine_draw->draw_texture(edit, nr.edit.position, accent); + } else { + state_machine_draw->draw_texture(edit, nr.edit.position); + } + offset.x += sep + edit->get_width(); + } + } + + scroll_range = scroll_range.grow(200 * EDSCALE); + + //adjust scrollbars + updating = true; + h_scroll->set_min(scroll_range.position.x); + h_scroll->set_max(scroll_range.position.x + scroll_range.size.x); + h_scroll->set_page(state_machine_draw->get_size().x); + h_scroll->set_value(state_machine->get_graph_offset().x); + + v_scroll->set_min(scroll_range.position.y); + v_scroll->set_max(scroll_range.position.y + scroll_range.size.y); + v_scroll->set_page(state_machine_draw->get_size().y); + v_scroll->set_value(state_machine->get_graph_offset().y); + updating = false; + + state_machine_play_pos->update(); +} + +void AnimationNodeStateMachineEditor::_state_machine_pos_draw() { + + if (!state_machine->is_playing()) + return; + + int idx = -1; + for (int i = 0; node_rects.size(); i++) { + if (node_rects[i].node_name == state_machine->get_current_node()) { + idx = i; + break; + } + } + + if (idx == -1) + return; + + NodeRect &nr = node_rects[idx]; + + Vector2 from; + from.x = nr.play.position.x; + from.y = (nr.play.position.y + nr.play.size.y + nr.node.position.y + nr.node.size.y) * 0.5; + + Vector2 to; + if (nr.edit.size.x) { + to.x = nr.edit.position.x + nr.edit.size.x; + } else { + to.x = nr.name.position.x + nr.name.size.x; + } + to.y = from.y; + + float len = MAX(0.0001, state_machine->get_current_length()); + + float pos = CLAMP(state_machine->get_current_play_pos(), 0, len); + float c = pos / len; + Color fg = get_color("font_color", "Label"); + Color bg = fg; + bg.a *= 0.3; + + state_machine_play_pos->draw_line(from, to, bg, 2); + + to = from.linear_interpolate(to, c); + + state_machine_play_pos->draw_line(from, to, fg, 2); +} + +void AnimationNodeStateMachineEditor::_update_graph() { + + if (updating) + return; + + updating = true; + + if (state_machine->get_parent().is_valid()) { + goto_parent_hbox->show(); + } else { + goto_parent_hbox->hide(); + } + + state_machine_draw->update(); + + updating = false; +} + +void AnimationNodeStateMachineEditor::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + error_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + error_label->add_color_override("font_color", get_color("error_color", "Editor")); + panel->add_style_override("panel", get_stylebox("bg", "Tree")); + goto_parent->set_icon(get_icon("MoveUp", "EditorIcons")); + + tool_select->set_icon(get_icon("ToolSelect", "EditorIcons")); + tool_create->set_icon(get_icon("ToolAddNode", "EditorIcons")); + tool_connect->set_icon(get_icon("ToolConnect", "EditorIcons")); + + transition_mode->clear(); + transition_mode->add_icon_item(get_icon("TransitionImmediate", "EditorIcons"), TTR("Immediate")); + transition_mode->add_icon_item(get_icon("TransitionSync", "EditorIcons"), TTR("Sync")); + transition_mode->add_icon_item(get_icon("TransitionEnd", "EditorIcons"), TTR("At End")); + + //force filter on those, so they deform better + get_icon("TransitionImmediateBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER); + get_icon("TransitionEndBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER); + get_icon("TransitionSyncBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER); + get_icon("TransitionImmediateAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER); + get_icon("TransitionEndAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER); + get_icon("TransitionSyncAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER); + + tool_erase->set_icon(get_icon("Remove", "EditorIcons")); + tool_autoplay->set_icon(get_icon("AutoPlay", "EditorIcons")); + tool_end->set_icon(get_icon("AutoEnd", "EditorIcons")); + + play_mode->clear(); + play_mode->add_icon_item(get_icon("PlayTravel", "EditorIcons"), TTR("Travel")); + play_mode->add_icon_item(get_icon("Play", "EditorIcons"), TTR("Immediate")); + } + + if (p_what == NOTIFICATION_PROCESS) { + + String error; + + if (error_time > 0) { + error = error_text; + error_time -= get_process_delta_time(); + } else if (!state_machine->get_tree()) { + error = TTR("StateMachine does not belong to an AnimationTree node."); + } else if (!state_machine->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (state_machine->get_tree()->is_state_invalid()) { + error = state_machine->get_tree()->get_invalid_state_reason(); + } else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) { + if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) { + error = TTR("Start and end nodes are needed for a sub-transition."); + } + } + + if (error != error_label->get_text()) { + error_label->set_text(error); + if (error != String()) { + error_panel->show(); + } else { + error_panel->hide(); + } + } + + for (int i = 0; i < transition_lines.size(); i++) { + int tidx = -1; + for (int j = 0; j < state_machine->get_transition_count(); j++) { + if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) { + tidx = j; + break; + } + } + + if (tidx == -1) { //missing transition, should redraw + state_machine_draw->update(); + break; + } + + if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) { + state_machine_draw->update(); + break; + } + + if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) { + state_machine_draw->update(); + break; + } + + if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) { + state_machine_draw->update(); + break; + } + } + + bool same_travel_path = true; + Vector<StringName> tp = state_machine->get_travel_path(); + + { + + if (last_travel_path.size() != tp.size()) { + same_travel_path = false; + } else { + for (int i = 0; i < last_travel_path.size(); i++) { + if (last_travel_path[i] != tp[i]) { + same_travel_path = false; + break; + } + } + } + } + + //update if travel state changed + if (!same_travel_path || last_active != state_machine->is_playing() || last_current_node != state_machine->get_current_node() || last_blend_from_node != state_machine->get_blend_from_node()) { + + state_machine_draw->update(); + last_travel_path = tp; + last_current_node = state_machine->get_current_node(); + last_active = state_machine->is_playing(); + last_blend_from_node = state_machine->get_blend_from_node(); + state_machine_play_pos->update(); + } + + if (last_play_pos != state_machine->get_current_play_pos()) { + + last_play_pos = state_machine->get_current_play_pos(); + state_machine_play_pos->update(); + } + } + + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + over_node = StringName(); + } +} + +void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) { + Ref<AnimationNode> an = state_machine->get_node(p_name); + ERR_FAIL_COND(!an.is_valid()); + EditorNode::get_singleton()->edit_item(an.ptr()); +} + +void AnimationNodeStateMachineEditor::_goto_parent() { + + EditorNode::get_singleton()->edit_item(state_machine->get_parent().ptr()); +} + +void AnimationNodeStateMachineEditor::_removed_from_graph() { + EditorNode::get_singleton()->edit_item(NULL); +} + +void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { + + String new_name = p_text; + + ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1) + + ERR_FAIL_COND(new_name == prev_name); + + String base_name = new_name; + int base = 1; + String name = base_name; + while (state_machine->has_node(name)) { + base++; + name = base_name + " " + itos(base); + } + + updating = true; + undo_redo->create_action("Node Renamed"); + undo_redo->add_do_method(state_machine.ptr(), "rename_node", prev_name, name); + undo_redo->add_undo_method(state_machine.ptr(), "rename_node", name, prev_name); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + + state_machine_draw->update(); + + name_edit->hide(); +} + +void AnimationNodeStateMachineEditor::_scroll_changed(double) { + if (updating) + return; + + state_machine->set_graph_offset(Vector2(h_scroll->get_value(), v_scroll->get_value())); + state_machine_draw->update(); +} + +void AnimationNodeStateMachineEditor::_erase_selected() { + + if (selected_node != StringName() && state_machine->has_node(selected_node)) { + updating = true; + undo_redo->create_action("Node Removed"); + undo_redo->add_do_method(state_machine.ptr(), "remove_node", selected_node); + undo_redo->add_undo_method(state_machine.ptr(), "add_node", selected_node, state_machine->get_node(selected_node)); + for (int i = 0; i < state_machine->get_transition_count(); i++) { + String from = state_machine->get_transition_from(i); + String to = state_machine->get_transition_to(i); + if (from == selected_node || to == selected_node) { + undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(i)); + } + } + if (String(state_machine->get_start_node()) == selected_node) { + undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", selected_node); + } + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + selected_node = StringName(); + } + + if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) { + + Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to)); + updating = true; + undo_redo->create_action("Transition Removed"); + undo_redo->add_do_method(state_machine.ptr(), "remove_transition", selected_transition_from, selected_transition_to); + undo_redo->add_undo_method(state_machine.ptr(), "add_transition", selected_transition_from, selected_transition_to, tr); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + selected_transition_from = StringName(); + selected_transition_to = StringName(); + } + + state_machine_draw->update(); +} + +void AnimationNodeStateMachineEditor::_autoplay_selected() { + + if (selected_node != StringName() && state_machine->has_node(selected_node)) { + + StringName new_start_node; + if (state_machine->get_start_node() == selected_node) { //toggle it + new_start_node = StringName(); + } else { + new_start_node = selected_node; + } + + updating = true; + undo_redo->create_action("Set Start Node (Autoplay)"); + undo_redo->add_do_method(state_machine.ptr(), "set_start_node", new_start_node); + undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", state_machine->get_start_node()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + state_machine_draw->update(); + } +} + +void AnimationNodeStateMachineEditor::_end_selected() { + + if (selected_node != StringName() && state_machine->has_node(selected_node)) { + + StringName new_end_node; + if (state_machine->get_end_node() == selected_node) { //toggle it + new_end_node = StringName(); + } else { + new_end_node = selected_node; + } + + updating = true; + undo_redo->create_action("Set Start Node (Autoplay)"); + undo_redo->add_do_method(state_machine.ptr(), "set_end_node", new_end_node); + undo_redo->add_undo_method(state_machine.ptr(), "set_end_node", state_machine->get_end_node()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + updating = false; + state_machine_draw->update(); + } +} +void AnimationNodeStateMachineEditor::_update_mode() { + + if (tool_select->is_pressed()) { + tool_erase_hb->show(); + tool_erase->set_disabled(selected_node == StringName() && selected_transition_from == StringName() && selected_transition_to == StringName()); + tool_autoplay->set_disabled(selected_node == StringName()); + tool_end->set_disabled(selected_node == StringName()); + } else { + tool_erase_hb->hide(); + } +} + +void AnimationNodeStateMachineEditor::_bind_methods() { + + ClassDB::bind_method("_state_machine_gui_input", &AnimationNodeStateMachineEditor::_state_machine_gui_input); + ClassDB::bind_method("_state_machine_draw", &AnimationNodeStateMachineEditor::_state_machine_draw); + ClassDB::bind_method("_state_machine_pos_draw", &AnimationNodeStateMachineEditor::_state_machine_pos_draw); + ClassDB::bind_method("_update_graph", &AnimationNodeStateMachineEditor::_update_graph); + + ClassDB::bind_method("_add_menu_type", &AnimationNodeStateMachineEditor::_add_menu_type); + ClassDB::bind_method("_add_animation_type", &AnimationNodeStateMachineEditor::_add_animation_type); + + ClassDB::bind_method("_name_edited", &AnimationNodeStateMachineEditor::_name_edited); + + ClassDB::bind_method("_goto_parent", &AnimationNodeStateMachineEditor::_goto_parent); + ClassDB::bind_method("_removed_from_graph", &AnimationNodeStateMachineEditor::_removed_from_graph); + + ClassDB::bind_method("_open_editor", &AnimationNodeStateMachineEditor::_open_editor); + ClassDB::bind_method("_scroll_changed", &AnimationNodeStateMachineEditor::_scroll_changed); + + ClassDB::bind_method("_erase_selected", &AnimationNodeStateMachineEditor::_erase_selected); + ClassDB::bind_method("_autoplay_selected", &AnimationNodeStateMachineEditor::_autoplay_selected); + ClassDB::bind_method("_end_selected", &AnimationNodeStateMachineEditor::_end_selected); + ClassDB::bind_method("_update_mode", &AnimationNodeStateMachineEditor::_update_mode); +} + +AnimationNodeStateMachineEditor *AnimationNodeStateMachineEditor::singleton = NULL; + +AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { + + singleton = this; + updating = false; + + HBoxContainer *top_hb = memnew(HBoxContainer); + add_child(top_hb); + + goto_parent_hbox = memnew(HBoxContainer); + goto_parent = memnew(ToolButton); + goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED); + goto_parent_hbox->add_child(goto_parent); + goto_parent_hbox->add_child(memnew(VSeparator)); + top_hb->add_child(goto_parent_hbox); + + Ref<ButtonGroup> bg; + bg.instance(); + + tool_select = memnew(ToolButton); + top_hb->add_child(tool_select); + tool_select->set_toggle_mode(true); + tool_select->set_button_group(bg); + tool_select->set_pressed(true); + tool_select->set_tooltip(TTR("Select and move nodes.\nRMB to add new nodes.\nShift+LMB to create connections.")); + tool_select->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED); + + tool_create = memnew(ToolButton); + top_hb->add_child(tool_create); + tool_create->set_toggle_mode(true); + tool_create->set_button_group(bg); + tool_create->set_tooltip(TTR("Create new nodes.")); + tool_create->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED); + + tool_connect = memnew(ToolButton); + top_hb->add_child(tool_connect); + tool_connect->set_toggle_mode(true); + tool_connect->set_button_group(bg); + tool_connect->set_tooltip(TTR("Connect nodes.")); + tool_connect->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED); + + tool_erase_hb = memnew(HBoxContainer); + top_hb->add_child(tool_erase_hb); + tool_erase_hb->add_child(memnew(VSeparator)); + tool_erase = memnew(ToolButton); + tool_erase->set_tooltip(TTR("Remove selected node or transition")); + tool_erase_hb->add_child(tool_erase); + tool_erase->connect("pressed", this, "_erase_selected"); + tool_erase->set_disabled(true); + + tool_erase_hb->add_child(memnew(VSeparator)); + + tool_autoplay = memnew(ToolButton); + tool_autoplay->set_tooltip(TTR("Toggle autoplay this animation on start, restart or seek to zero.")); + tool_erase_hb->add_child(tool_autoplay); + tool_autoplay->connect("pressed", this, "_autoplay_selected"); + tool_autoplay->set_disabled(true); + + tool_end = memnew(ToolButton); + tool_end->set_tooltip(TTR("Set the end animation. This is useful for sub-transitions.")); + tool_erase_hb->add_child(tool_end); + tool_end->connect("pressed", this, "_end_selected"); + tool_end->set_disabled(true); + + top_hb->add_child(memnew(VSeparator)); + top_hb->add_child(memnew(Label(TTR("Transition: ")))); + transition_mode = memnew(OptionButton); + top_hb->add_child(transition_mode); + + top_hb->add_spacer(); + + top_hb->add_child(memnew(Label("Play Mode:"))); + play_mode = memnew(OptionButton); + top_hb->add_child(play_mode); + + GridContainer *main_grid = memnew(GridContainer); + main_grid->set_columns(2); + add_child(main_grid); + main_grid->set_v_size_flags(SIZE_EXPAND_FILL); + + panel = memnew(PanelContainer); + panel->set_clip_contents(true); + main_grid->add_child(panel); + panel->set_h_size_flags(SIZE_EXPAND_FILL); + + state_machine_draw = memnew(Control); + state_machine_draw->connect("gui_input", this, "_state_machine_gui_input"); + state_machine_draw->connect("draw", this, "_state_machine_draw"); + state_machine_draw->set_focus_mode(FOCUS_ALL); + + state_machine_play_pos = memnew(Control); + state_machine_draw->add_child(state_machine_play_pos); + state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent + state_machine_play_pos->set_anchors_and_margins_preset(PRESET_WIDE); + state_machine_play_pos->connect("draw", this, "_state_machine_pos_draw"); + + panel->add_child(state_machine_draw); + panel->set_v_size_flags(SIZE_EXPAND_FILL); + + v_scroll = memnew(VScrollBar); + main_grid->add_child(v_scroll); + v_scroll->connect("value_changed", this, "_scroll_changed"); + + h_scroll = memnew(HScrollBar); + main_grid->add_child(h_scroll); + h_scroll->connect("value_changed", this, "_scroll_changed"); + + main_grid->add_child(memnew(Control)); //empty bottom right + + error_panel = memnew(PanelContainer); + add_child(error_panel); + error_label = memnew(Label); + error_panel->add_child(error_label); + error_label->set_text("eh"); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + set_custom_minimum_size(Size2(0, 300 * EDSCALE)); + + menu = memnew(PopupMenu); + add_child(menu); + menu->connect("index_pressed", this, "_add_menu_type"); + + animations_menu = memnew(PopupMenu); + menu->add_child(animations_menu); + animations_menu->set_name("animations"); + animations_menu->connect("index_pressed", this, "_add_animation_type"); + + name_edit = memnew(LineEdit); + state_machine_draw->add_child(name_edit); + name_edit->hide(); + name_edit->connect("text_entered", this, "_name_edited"); + name_edit->set_as_toplevel(true); + + over_text = false; + + over_node_what = -1; + dragging_selected_attempt = false; + connecting = false; + + last_active = false; + + error_time = 0; +} + +void AnimationNodeStateMachineEditorPlugin::edit(Object *p_object) { + + anim_tree_editor->edit(Object::cast_to<AnimationNodeStateMachine>(p_object)); +} + +bool AnimationNodeStateMachineEditorPlugin::handles(Object *p_object) const { + + return p_object->is_class("AnimationNodeStateMachine"); +} + +void AnimationNodeStateMachineEditorPlugin::make_visible(bool p_visible) { + + if (p_visible) { + //editor->hide_animation_player_editors(); + //editor->animation_panel_make_visible(true); + button->show(); + editor->make_bottom_panel_item_visible(anim_tree_editor); + anim_tree_editor->set_process(true); + } else { + + if (anim_tree_editor->is_visible_in_tree()) + editor->hide_bottom_panel(); + button->hide(); + anim_tree_editor->set_process(false); + } +} + +AnimationNodeStateMachineEditorPlugin::AnimationNodeStateMachineEditorPlugin(EditorNode *p_node) { + + editor = p_node; + anim_tree_editor = memnew(AnimationNodeStateMachineEditor); + anim_tree_editor->set_custom_minimum_size(Size2(0, 300)); + + button = editor->add_bottom_panel_item(TTR("StateMachine"), anim_tree_editor); + button->hide(); +} + +AnimationNodeStateMachineEditorPlugin::~AnimationNodeStateMachineEditorPlugin() { +} diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h new file mode 100644 index 0000000000..efd3de7415 --- /dev/null +++ b/editor/plugins/animation_state_machine_editor.h @@ -0,0 +1,167 @@ +#ifndef ANIMATION_STATE_MACHINE_EDITOR_H +#define ANIMATION_STATE_MACHINE_EDITOR_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/animation/animation_node_state_machine.h" +#include "scene/gui/button.h" +#include "scene/gui/graph_edit.h" +#include "scene/gui/popup.h" +#include "scene/gui/tree.h" + +class AnimationNodeStateMachineEditor : public VBoxContainer { + + GDCLASS(AnimationNodeStateMachineEditor, VBoxContainer); + + Ref<AnimationNodeStateMachine> state_machine; + + ToolButton *tool_select; + ToolButton *tool_create; + ToolButton *tool_connect; + LineEdit *name_edit; + + HBoxContainer *tool_erase_hb; + ToolButton *tool_erase; + ToolButton *tool_autoplay; + ToolButton *tool_end; + + OptionButton *transition_mode; + OptionButton *play_mode; + + HBoxContainer *goto_parent_hbox; + ToolButton *goto_parent; + + PanelContainer *panel; + + StringName selected_node; + + HScrollBar *h_scroll; + VScrollBar *v_scroll; + + Control *state_machine_draw; + Control *state_machine_play_pos; + + PanelContainer *error_panel; + Label *error_label; + + bool updating; + + UndoRedo *undo_redo; + + static AnimationNodeStateMachineEditor *singleton; + + void _state_machine_gui_input(const Ref<InputEvent> &p_event); + void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance); + void _state_machine_draw(); + void _state_machine_pos_draw(); + + void _update_graph(); + + PopupMenu *menu; + PopupMenu *animations_menu; + Vector<String> animations_to_add; + + Vector2 add_node_pos; + + bool dragging_selected_attempt; + bool dragging_selected; + Vector2 drag_from; + Vector2 drag_ofs; + StringName snap_x; + StringName snap_y; + + bool connecting; + StringName connecting_from; + Vector2 connecting_to; + StringName connecting_to_node; + + void _add_menu_type(int p_index); + void _add_animation_type(int p_index); + + void _goto_parent(); + + void _removed_from_graph(); + + struct NodeRect { + StringName node_name; + Rect2 node; + Rect2 play; + Rect2 name; + Rect2 edit; + }; + + Vector<NodeRect> node_rects; + + struct TransitionLine { + StringName from_node; + StringName to_node; + Vector2 from; + Vector2 to; + AnimationNodeStateMachineTransition::SwitchMode mode; + bool disabled; + bool auto_advance; + float width; + }; + + Vector<TransitionLine> transition_lines; + + StringName selected_transition_from; + StringName selected_transition_to; + + bool over_text; + StringName over_node; + int over_node_what; + + String prev_name; + void _name_edited(const String &p_text); + void _open_editor(const String &p_name); + void _scroll_changed(double); + + void _clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect); + void _clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect); + + void _erase_selected(); + void _update_mode(); + void _autoplay_selected(); + void _end_selected(); + + bool last_active; + StringName last_blend_from_node; + StringName last_current_node; + Vector<StringName> last_travel_path; + float last_play_pos; + + float error_time; + String error_text; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + static AnimationNodeStateMachineEditor *get_singleton() { return singleton; } + void edit(AnimationNodeStateMachine *p_state_machine); + AnimationNodeStateMachineEditor(); +}; + +class AnimationNodeStateMachineEditorPlugin : public EditorPlugin { + + GDCLASS(AnimationNodeStateMachineEditorPlugin, EditorPlugin); + + AnimationNodeStateMachineEditor *anim_tree_editor; + EditorNode *editor; + Button *button; + +public: + virtual String get_name() const { return "StateMachine"; } + bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + AnimationNodeStateMachineEditorPlugin(EditorNode *p_node); + ~AnimationNodeStateMachineEditorPlugin(); +}; + +#endif // ANIMATION_STATE_MACHINE_EDITOR_H diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index d595d4dd98..505dd4ab76 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -234,6 +234,7 @@ void EditorAssetLibraryItemDescription::_preview_click(int p_id) { if (!preview_images[i].is_video) { if (preview_images[i].image.is_valid()) { preview->set_texture(preview_images[i].image); + minimum_size_changed(); } } else { _link_click(preview_images[i].video_link); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index ca5aa7039d..7c4cd6cb3d 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -30,7 +30,6 @@ #include "canvas_item_editor_plugin.h" -#include "editor/animation_editor.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/plugins/animation_player_editor_plugin.h" @@ -365,7 +364,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) { void CanvasItemEditor::_keying_changed() { - if (AnimationPlayerEditor::singleton->get_key_editor()->is_visible_in_tree()) + if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) animation_hb->show(); else animation_hb->hide(); @@ -1984,32 +1983,53 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> m = p_event; if (m.is_valid()) { - if (drag_type == DRAG_NONE && tool == TOOL_SELECT) { - Point2 click = transform.affine_inverse().xform(m->get_position()); - - //Checks if the hovered items changed, update the viewport if so - Vector<_SelectResult> hovering_results_tmp; - _get_canvas_items_at_pos(click, hovering_results_tmp); - hovering_results_tmp.sort(); - bool changed = false; - if (hovering_results.size() == hovering_results_tmp.size()) { - for (int i = 0; i < hovering_results.size(); i++) { - if (hovering_results[i].item != hovering_results_tmp[i].item) { - changed = true; - break; - } - } - } else { - changed = true; - } + Point2 click = transform.affine_inverse().xform(m->get_position()); - if (changed) { - hovering_results = hovering_results_tmp; - viewport->update(); + // Checks if the hovered items changed, update the viewport if so + Vector<_SelectResult> hovering_results_items; + _get_canvas_items_at_pos(click, hovering_results_items); + hovering_results_items.sort(); + + // Compute the nodes names and icon position + Vector<_HoverResult> hovering_results_tmp; + for (int i = 0; i < hovering_results_items.size(); i++) { + CanvasItem *canvas_item = hovering_results_items[i].item; + + if (canvas_item->_edit_use_rect()) + continue; + + _HoverResult hover_result; + hover_result.position = canvas_item->get_global_transform_with_canvas().get_origin(); + if (has_icon(canvas_item->get_class(), "EditorIcons")) + hover_result.icon = get_icon(canvas_item->get_class(), "EditorIcons"); + else + hover_result.icon = get_icon("Object", "EditorIcons"); + hover_result.name = canvas_item->get_name(); + + hovering_results_tmp.push_back(hover_result); + } + + // Check if changed, if so, update. + bool changed = false; + if (hovering_results_tmp.size() == hovering_results.size()) { + for (int i = 0; i < hovering_results_tmp.size(); i++) { + _HoverResult a = hovering_results_tmp[i]; + _HoverResult b = hovering_results[i]; + if (a.icon != b.icon || a.name != b.name || a.position != b.position) { + changed = true; + break; + } } + } else { + changed = true; + } - return true; + if (changed) { + hovering_results = hovering_results_tmp; + viewport->update(); } + + return true; } return false; @@ -2770,26 +2790,15 @@ void CanvasItemEditor::_draw_hover() { List<Rect2> previous_rects; for (int i = 0; i < hovering_results.size(); i++) { - // Draw the node's name and icon - CanvasItem *canvas_item = hovering_results[i].item; - - if (canvas_item->_edit_use_rect()) - continue; - Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); + Ref<Texture> node_icon = hovering_results[i].icon; + String node_name = hovering_results[i].name; - // Get the resources - Ref<Texture> node_icon; - if (has_icon(canvas_item->get_class(), "EditorIcons")) - node_icon = get_icon(canvas_item->get_class(), "EditorIcons"); - else - node_icon = get_icon("Object", "EditorIcons"); Ref<Font> font = get_font("font", "Label"); - String node_name = canvas_item->get_name(); Size2 node_name_size = font->get_string_size(node_name); Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3)); - Point2 pos = xform.get_origin() - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4); + Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4); // Rectify the position to avoid overlaping items for (List<Rect2>::Element *E = previous_rects.front(); E; E = E->next()) { if (E->get().intersects(Rect2(pos, item_size))) { @@ -2799,8 +2808,10 @@ void CanvasItemEditor::_draw_hover() { previous_rects.push_back(Rect2(pos, item_size)); - // Draw the node icon and name + // Draw icon viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5)); + + // Draw name viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, Color(1.0, 1.0, 1.0, 0.5)); } } @@ -3080,7 +3091,7 @@ void CanvasItemEditor::_notification(int p_what) { select_sb->set_default_margin(Margin(i), 4); } - AnimationPlayerEditor::singleton->get_key_editor()->connect("visibility_changed", this, "_keying_changed"); + AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", this, "_keying_changed"); _keying_changed(); get_tree()->connect("node_added", this, "_tree_changed", varray()); get_tree()->connect("node_removed", this, "_tree_changed", varray()); @@ -3692,11 +3703,11 @@ void CanvasItemEditor::_popup_callback(int p_op) { Node2D *n2d = Object::cast_to<Node2D>(canvas_item); if (key_pos) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), existing); if (key_rot) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation_degrees", Math::rad2deg(n2d->get_rotation()), existing); if (key_scale) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), existing); if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { //look for an IK chain @@ -3723,11 +3734,11 @@ void CanvasItemEditor::_popup_callback(int p_op) { for (List<Node2D *>::Element *F = ik_chain.front(); F; F = F->next()) { if (key_pos) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), existing); if (key_rot) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), existing); if (key_scale) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), existing); } } } @@ -3737,11 +3748,11 @@ void CanvasItemEditor::_popup_callback(int p_op) { Control *ctrl = Object::cast_to<Control>(canvas_item); if (key_pos) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), existing); if (key_rot) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation_degrees(), existing); if (key_scale) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), existing); } } @@ -3837,7 +3848,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { ctrl->set_position(Point2()); /* if (key_scale) - AnimationPlayerEditor::singleton->get_key_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); */ } } @@ -4339,13 +4350,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->add_child(snap_button); snap_button->set_toggle_mode(true); snap_button->connect("toggled", this, "_button_toggle_snap"); - snap_button->set_tooltip(TTR("Toggles snapping")); - snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_snap", TTR("Use Snap"), KEY_S)); + snap_button->set_tooltip(TTR("Toggle snapping.")); + snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_snap", TTR("Use Snap"), KEY_MASK_SHIFT | KEY_S)); snap_config_menu = memnew(MenuButton); hb->add_child(snap_config_menu); snap_config_menu->set_h_size_flags(SIZE_SHRINK_END); - snap_config_menu->set_tooltip(TTR("Snapping options")); + snap_config_menu->set_tooltip(TTR("Snapping Options")); PopupMenu *p = snap_config_menu->get_popup(); p->connect("id_pressed", this, "_popup_callback"); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 4d2af11303..adc4010f39 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -257,9 +257,15 @@ class CanvasItemEditor : public VBoxContainer { return has_z && p_rr.has_z ? p_rr.z_index < z_index : p_rr.has_z; } }; - Vector<_SelectResult> selection_results; - Vector<_SelectResult> hovering_results; + + struct _HoverResult { + + Point2 position; + Ref<Texture> icon; + String name; + }; + Vector<_HoverResult> hovering_results; struct BoneList { diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index ac4166bb98..0d25b3685a 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -97,6 +97,7 @@ Ref<Texture> EditorTexturePreviewPlugin::generate(const RES &p_from) { if (img.is_null() || img->empty()) return Ref<Texture>(); + img = img->duplicate(); img->clear_mipmaps(); int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); @@ -553,274 +554,92 @@ EditorScriptPreviewPlugin::EditorScriptPreviewPlugin() { } /////////////////////////////////////////////////////////////////// -// FIXME: Needs to be rewritten for AudioStream in Godot 3.0+ -#if 0 -bool EditorSamplePreviewPlugin::handles(const String& p_type) const { +bool EditorAudioStreamPreviewPlugin::handles(const String &p_type) const { - return ClassDB::is_parent_class(p_type,"Sample"); + return ClassDB::is_parent_class(p_type, "AudioStream"); } -Ref<Texture> EditorSamplePreviewPlugin::generate(const RES& p_from) { - - Ref<Sample> smp =p_from; - ERR_FAIL_COND_V(smp.is_null(),Ref<Texture>()); +Ref<Texture> EditorAudioStreamPreviewPlugin::generate(const RES &p_from) { + Ref<AudioStream> stream = p_from; + ERR_FAIL_COND_V(stream.is_null(), Ref<Texture>()); int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); - thumbnail_size*=EDSCALE; + thumbnail_size *= EDSCALE; PoolVector<uint8_t> img; int w = thumbnail_size; int h = thumbnail_size; - img.resize(w*h*3); + img.resize(w * h * 3); PoolVector<uint8_t>::Write imgdata = img.write(); - uint8_t * imgw = imgdata.ptr(); - PoolVector<uint8_t> data = smp->get_data(); - PoolVector<uint8_t>::Read sampledata = data.read(); - const uint8_t *sdata=sampledata.ptr(); + uint8_t *imgw = imgdata.ptr(); - bool stereo = smp->is_stereo(); - bool _16=smp->get_format()==Sample::FORMAT_PCM16; - int len = smp->get_length(); + Ref<AudioStreamPlayback> playback = stream->instance_playback(); - if (len<1) - return Ref<Texture>(); + float len_s = stream->get_length(); + if (len_s == 0) { + len_s = 60; //one minute audio if no length specified + } + int frame_length = AudioServer::get_singleton()->get_mix_rate() * len_s; - if (smp->get_format()==Sample::FORMAT_IMA_ADPCM) { - - struct IMA_ADPCM_State { - - int16_t step_index; - int32_t predictor; - /* values at loop point */ - int16_t loop_step_index; - int32_t loop_predictor; - int32_t last_nibble; - int32_t loop_pos; - int32_t window_ofs; - const uint8_t *ptr; - } ima_adpcm; - - ima_adpcm.step_index=0; - ima_adpcm.predictor=0; - ima_adpcm.loop_step_index=0; - ima_adpcm.loop_predictor=0; - ima_adpcm.last_nibble=-1; - ima_adpcm.loop_pos=0x7FFFFFFF; - ima_adpcm.window_ofs=0; - ima_adpcm.ptr=NULL; - - - for(int i=0;i<w;i++) { - - float max[2]={-1e10,-1e10}; - float min[2]={1e10,1e10}; - int from = i*len/w; - int to = (i+1)*len/w; - if (to>=len) - to=len-1; - - for(int j=from;j<to;j++) { - - while(j>ima_adpcm.last_nibble) { - - static const int16_t _ima_adpcm_step_table[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, - 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, - 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, - 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, - 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, - 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 - }; - - static const int8_t _ima_adpcm_index_table[16] = { - -1, -1, -1, -1, 2, 4, 6, 8, - -1, -1, -1, -1, 2, 4, 6, 8 - }; - - int16_t nibble,diff,step; - - ima_adpcm.last_nibble++; - const uint8_t *src_ptr=sdata; - - int ofs = ima_adpcm.last_nibble>>1; - - if (stereo) - ofs*=2; - - - nibble = (ima_adpcm.last_nibble&1)? - (src_ptr[ofs]>>4):(src_ptr[ofs]&0xF); - step=_ima_adpcm_step_table[ima_adpcm.step_index]; - - ima_adpcm.step_index += _ima_adpcm_index_table[nibble]; - if (ima_adpcm.step_index<0) - ima_adpcm.step_index=0; - if (ima_adpcm.step_index>88) - ima_adpcm.step_index=88; - - diff = step >> 3 ; - if (nibble & 1) - diff += step >> 2 ; - if (nibble & 2) - diff += step >> 1 ; - if (nibble & 4) - diff += step ; - if (nibble & 8) - diff = -diff ; - - ima_adpcm.predictor+=diff; - if (ima_adpcm.predictor<-0x8000) - ima_adpcm.predictor=-0x8000; - else if (ima_adpcm.predictor>0x7FFF) - ima_adpcm.predictor=0x7FFF; - - - /* store loop if there */ - if (ima_adpcm.last_nibble==ima_adpcm.loop_pos) { - - ima_adpcm.loop_step_index = ima_adpcm.step_index; - ima_adpcm.loop_predictor = ima_adpcm.predictor; - } + Vector<AudioFrame> frames; + frames.resize(frame_length); - } + playback->start(); + playback->mix(frames.ptrw(), 1, frames.size()); + playback->stop(); - float v=ima_adpcm.predictor/32767.0; - if (v>max[0]) - max[0]=v; - if (v<min[0]) - min[0]=v; - } - max[0]*=0.8; - min[0]*=0.8; - - for(int j=0;j<h;j++) { - float v = (j/(float)h) * 2.0 - 1.0; - uint8_t* imgofs = &imgw[(uint64_t(j)*w+i)*3]; - if (v>min[0] && v<max[0]) { - imgofs[0]=255; - imgofs[1]=150; - imgofs[2]=80; - } else { - imgofs[0]=0; - imgofs[1]=0; - imgofs[2]=0; - } - } - } - } else { - for(int i=0;i<w;i++) { - // i trust gcc will optimize this loop - float max[2]={-1e10,-1e10}; - float min[2]={1e10,1e10}; - int c=stereo?2:1; - int from = uint64_t(i)*len/w; - int to = (uint64_t(i)+1)*len/w; - if (to>=len) - to=len-1; - - if (_16) { - const int16_t*src =(const int16_t*)sdata; - - for(int j=0;j<c;j++) { - - for(int k=from;k<=to;k++) { - - float v = src[uint64_t(k)*c+j]/32768.0; - if (v>max[j]) - max[j]=v; - if (v<min[j]) - min[j]=v; - } + for (int i = 0; i < w; i++) { - } - } else { - - const int8_t*src =(const int8_t*)sdata; + float max = -1000; + float min = 1000; + int from = uint64_t(i) * frame_length / w; + int to = uint64_t(i + 1) * frame_length / w; + to = MIN(to, frame_length); + from = MIN(from, frame_length - 1); + if (to == from) { + to = from + 1; + } - for(int j=0;j<c;j++) { + for (int j = from; j < to; j++) { - for(int k=from;k<=to;k++) { + max = MAX(max, frames[j].l); + max = MAX(max, frames[j].r); - float v = src[uint64_t(k)*c+j]/128.0; - if (v>max[j]) - max[j]=v; - if (v<min[j]) - min[j]=v; - } + min = MIN(min, frames[j].l); + min = MIN(min, frames[j].r); + } - } - } + int pfrom = CLAMP((min * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4; + int pto = CLAMP((max * 0.5 + 0.5) * h / 2, 0, h / 2) + h / 4; - max[0]*=0.8; - max[1]*=0.8; - min[0]*=0.8; - min[1]*=0.8; - - if (!stereo) { - for(int j=0;j<h;j++) { - float v = (j/(float)h) * 2.0 - 1.0; - uint8_t* imgofs = &imgw[(j*w+i)*3]; - if (v>min[0] && v<max[0]) { - imgofs[0]=255; - imgofs[1]=150; - imgofs[2]=80; - } else { - imgofs[0]=0; - imgofs[1]=0; - imgofs[2]=0; - } - } + for (int j = 0; j < h; j++) { + uint8_t *p = &imgw[(j * w + i) * 3]; + if (j < pfrom || j > pto) { + p[0] = 100; + p[1] = 100; + p[2] = 100; } else { - - for(int j=0;j<h;j++) { - - int half; - float v; - if (j<(h/2)) { - half=0; - v = (j/(float)(h/2)) * 2.0 - 1.0; - } else { - half=1; - if( (float)(h/2) != 0 ) { - v = ((j-(h/2))/(float)(h/2)) * 2.0 - 1.0; - } else { - v = ((j-(h/2))/(float)(1/2)) * 2.0 - 1.0; - } - } - - uint8_t* imgofs = &imgw[(j*w+i)*3]; - if (v>min[half] && v<max[half]) { - imgofs[0]=255; - imgofs[1]=150; - imgofs[2]=80; - } else { - imgofs[0]=0; - imgofs[1]=0; - imgofs[2]=0; - } - } - + p[0] = 180; + p[1] = 180; + p[2] = 180; } - } } imgdata = PoolVector<uint8_t>::Write(); - post_process_preview(img); + //post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>( memnew( ImageTexture)); - ptex->create_from_image(Image(w,h,0,Image::FORMAT_RGB8,img),0); + Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); + Ref<Image> image; + image.instance(); + image->create(w, h, false, Image::FORMAT_RGB8, img); + ptex->create_from_image(image, 0); return ptex; - } -EditorSamplePreviewPlugin::EditorSamplePreviewPlugin() { +EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() { } -#endif /////////////////////////////////////////////////////////////////////////// diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index 332f991b49..140d9f849f 100644 --- a/editor/plugins/editor_preview_plugins.h +++ b/editor/plugins/editor_preview_plugins.h @@ -100,17 +100,13 @@ public: EditorScriptPreviewPlugin(); }; -// FIXME: Needs to be rewritten for AudioStream in Godot 3.0+ -#if 0 -class EditorSamplePreviewPlugin : public EditorResourcePreviewGenerator { +class EditorAudioStreamPreviewPlugin : public EditorResourcePreviewGenerator { public: + virtual bool handles(const String &p_type) const; + virtual Ref<Texture> generate(const RES &p_from); - virtual bool handles(const String& p_type) const; - virtual Ref<Texture> generate(const RES& p_from); - - EditorSamplePreviewPlugin(); + EditorAudioStreamPreviewPlugin(); }; -#endif class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator { diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp new file mode 100644 index 0000000000..89c1b3a978 --- /dev/null +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -0,0 +1,293 @@ +#include "root_motion_editor_plugin.h" +#include "editor/editor_node.h" +#include "scene/main/viewport.h" + +void EditorPropertyRootMotion::_confirmed() { + + TreeItem *ti = filters->get_selected(); + if (!ti) + return; + + NodePath path = ti->get_metadata(0); + emit_signal("property_changed", get_edited_property(), path); + update_property(); + filter_dialog->hide(); //may come from activated +} + +void EditorPropertyRootMotion::_node_assign() { + + NodePath current = get_edited_object()->get(get_edited_property()); + + AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object()); + if (!atree->has_node(atree->get_animation_player())) { + EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer")); + return; + } + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(atree->get_node(atree->get_animation_player())); + if (!player) { + EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid")); + return; + } + + Node *base = player->get_node(player->get_root()); + + if (!base) { + EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names.")); + return; + } + + Set<String> paths; + { + List<StringName> animations; + player->get_animation_list(&animations); + + for (List<StringName>::Element *E = animations.front(); E; E = E->next()) { + + Ref<Animation> anim = player->get_animation(E->get()); + for (int i = 0; i < anim->get_track_count(); i++) { + paths.insert(anim->track_get_path(i)); + } + } + } + + filters->clear(); + TreeItem *root = filters->create_item(); + + Map<String, TreeItem *> parenthood; + + for (Set<String>::Element *E = paths.front(); E; E = E->next()) { + + NodePath path = E->get(); + TreeItem *ti = NULL; + String accum; + for (int i = 0; i < path.get_name_count(); i++) { + String name = path.get_name(i); + if (accum != String()) { + accum += "/"; + } + accum += name; + if (!parenthood.has(accum)) { + if (ti) { + ti = filters->create_item(ti); + } else { + ti = filters->create_item(root); + } + parenthood[accum] = ti; + ti->set_text(0, name); + ti->set_selectable(0, false); + ti->set_editable(0, false); + + if (base->has_node(accum)) { + Node *node = base->get_node(accum); + if (has_icon(node->get_class(), "EditorIcons")) { + ti->set_icon(0, get_icon(node->get_class(), "EditorIcons")); + } else { + ti->set_icon(0, get_icon("Node", "EditorIcons")); + } + } + + } else { + ti = parenthood[accum]; + } + } + + Node *node = NULL; + if (base->has_node(accum)) { + node = base->get_node(accum); + } + if (!node) + continue; //no node, cant edit + + if (path.get_subname_count()) { + + String concat = path.get_concatenated_subnames(); + + Skeleton *skeleton = Object::cast_to<Skeleton>(node); + if (skeleton && skeleton->find_bone(concat) != -1) { + //path in skeleton + String bone = concat; + int idx = skeleton->find_bone(bone); + List<String> bone_path; + while (idx != -1) { + bone_path.push_front(skeleton->get_bone_name(idx)); + idx = skeleton->get_bone_parent(idx); + } + + accum += ":"; + for (List<String>::Element *F = bone_path.front(); F; F = F->next()) { + if (F != bone_path.front()) { + accum += "/"; + } + + accum += F->get(); + if (!parenthood.has(accum)) { + ti = filters->create_item(ti); + parenthood[accum] = ti; + ti->set_text(0, F->get()); + ti->set_selectable(0, true); + ti->set_editable(0, false); + ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons")); + ti->set_metadata(0, accum); + } else { + ti = parenthood[accum]; + } + } + + ti->set_selectable(0, true); + ti->set_text(0, concat); + ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons")); + ti->set_metadata(0, path); + if (path == current) { + ti->select(0); + } + + } else { + //just a property + ti = filters->create_item(ti); + ti->set_text(0, concat); + ti->set_selectable(0, true); + ti->set_metadata(0, path); + if (path == current) { + ti->select(0); + } + } + } else { + if (ti) { + //just a node, likely call or animation track + ti->set_selectable(0, true); + ti->set_metadata(0, path); + if (path == current) { + ti->select(0); + } + } + } + } + + filters->ensure_cursor_is_visible(); + filter_dialog->popup_centered_ratio(); +} + +void EditorPropertyRootMotion::_node_clear() { + + emit_signal("property_changed", get_edited_property(), NodePath()); + update_property(); +} + +void EditorPropertyRootMotion::update_property() { + + NodePath p = get_edited_object()->get(get_edited_property()); + + assign->set_tooltip(p); + if (p == NodePath()) { + assign->set_icon(Ref<Texture>()); + assign->set_text(TTR("Assign..")); + assign->set_flat(false); + return; + } + assign->set_flat(true); + + Node *base_node = NULL; + if (base_hint != NodePath()) { + if (get_tree()->get_root()->has_node(base_hint)) { + base_node = get_tree()->get_root()->get_node(base_hint); + } + } else { + base_node = Object::cast_to<Node>(get_edited_object()); + } + + if (!base_node || !base_node->has_node(p)) { + assign->set_icon(Ref<Texture>()); + assign->set_text(p); + return; + } + + Node *target_node = base_node->get_node(p); + ERR_FAIL_COND(!target_node); + + assign->set_text(target_node->get_name()); + + Ref<Texture> icon; + if (has_icon(target_node->get_class(), "EditorIcons")) + icon = get_icon(target_node->get_class(), "EditorIcons"); + else + icon = get_icon("Node", "EditorIcons"); + + assign->set_icon(icon); +} + +void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) { + + base_hint = p_base_hint; +} + +void EditorPropertyRootMotion::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + Ref<Texture> t = get_icon("Clear", "EditorIcons"); + clear->set_icon(t); + } +} + +void EditorPropertyRootMotion::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_confirmed"), &EditorPropertyRootMotion::_confirmed); + ClassDB::bind_method(D_METHOD("_node_assign"), &EditorPropertyRootMotion::_node_assign); + ClassDB::bind_method(D_METHOD("_node_clear"), &EditorPropertyRootMotion::_node_clear); +} + +EditorPropertyRootMotion::EditorPropertyRootMotion() { + + HBoxContainer *hbc = memnew(HBoxContainer); + add_child(hbc); + assign = memnew(Button); + assign->set_flat(true); + assign->set_h_size_flags(SIZE_EXPAND_FILL); + assign->set_clip_text(true); + assign->connect("pressed", this, "_node_assign"); + hbc->add_child(assign); + + clear = memnew(Button); + clear->set_flat(true); + clear->connect("pressed", this, "_node_clear"); + hbc->add_child(clear); + + filter_dialog = memnew(ConfirmationDialog); + add_child(filter_dialog); + filter_dialog->set_title(TTR("Edit Filtered Tracks:")); + filter_dialog->connect("confirmed", this, "_confirmed"); + + filters = memnew(Tree); + filter_dialog->add_child(filters); + filters->set_v_size_flags(SIZE_EXPAND_FILL); + filters->set_hide_root(true); + filters->connect("item_activated", this, "_confirmed"); + //filters->connect("item_edited", this, "_filter_edited"); +} +////////////////////////// + +bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) { + return true; //can handle everything +} + +void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) { + //do none +} + +bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) { + + if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) { + print_line("use custom!"); + EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion); + if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) { + editor->setup(p_hint_text); + } + add_property_editor(p_path, editor); + return true; + } + + return false; //can be overriden, although it will most likely be last anyway +} + +void EditorInspectorRootMotionPlugin::parse_end() { + //do none +} diff --git a/editor/plugins/root_motion_editor_plugin.h b/editor/plugins/root_motion_editor_plugin.h new file mode 100644 index 0000000000..84af47872f --- /dev/null +++ b/editor/plugins/root_motion_editor_plugin.h @@ -0,0 +1,42 @@ +#ifndef ROOT_MOTION_EDITOR_PLUGIN_H +#define ROOT_MOTION_EDITOR_PLUGIN_H + +#include "editor/editor_inspector.h" +#include "editor/editor_spin_slider.h" +#include "editor/property_selector.h" +#include "scene/animation/animation_tree.h" + +class EditorPropertyRootMotion : public EditorProperty { + GDCLASS(EditorPropertyRootMotion, EditorProperty) + Button *assign; + Button *clear; + NodePath base_hint; + + ConfirmationDialog *filter_dialog; + Tree *filters; + + void _confirmed(); + void _node_assign(); + void _node_clear(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + virtual void update_property(); + void setup(const NodePath &p_base_hint); + EditorPropertyRootMotion(); +}; + +class EditorInspectorRootMotionPlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorRootMotionPlugin, EditorInspectorPlugin) + +public: + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage); + virtual void parse_end(); +}; + +#endif // ROOT_MOTION_EDITOR_PLUGIN_H diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 94dcbd8e18..aa4673f41e 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -804,12 +804,12 @@ bool ScriptEditor::_test_script_times_on_disk(Ref<Script> p_for_script) { void ScriptEditor::_file_dialog_action(String p_file) { switch (file_dialog_option) { - case FILE_SAVE_THEME_AS: { + case THEME_SAVE_AS: { if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) { editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); } } break; - case FILE_IMPORT_THEME: { + case THEME_IMPORT: { if (!EditorSettings::get_singleton()->import_text_editor_theme(p_file)) { editor->show_warning(TTR("Error importing theme"), TTR("Error importing")); } @@ -859,33 +859,6 @@ void ScriptEditor::_menu_option(int p_option) { save_all_scripts(); } break; - case FILE_IMPORT_THEME: { - file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); - file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - file_dialog_option = FILE_IMPORT_THEME; - file_dialog->clear_filters(); - file_dialog->add_filter("*.tet"); - file_dialog->popup_centered_ratio(); - file_dialog->set_title(TTR("Import Theme")); - } break; - case FILE_RELOAD_THEME: { - EditorSettings::get_singleton()->load_text_editor_theme(); - } break; - case FILE_SAVE_THEME: { - if (!EditorSettings::get_singleton()->save_text_editor_theme()) { - editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); - } - } break; - case FILE_SAVE_THEME_AS: { - file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); - file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - file_dialog_option = FILE_SAVE_THEME_AS; - file_dialog->clear_filters(); - file_dialog->add_filter("*.tet"); - file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); - file_dialog->popup_centered_ratio(); - file_dialog->set_title(TTR("Save Theme As...")); - } break; case SEARCH_HELP: { help_search_dialog->popup(); @@ -1143,6 +1116,38 @@ void ScriptEditor::_menu_option(int p_option) { } } +void ScriptEditor::_theme_option(int p_option) { + switch (p_option) { + case THEME_IMPORT: { + file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); + file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_dialog_option = THEME_IMPORT; + file_dialog->clear_filters(); + file_dialog->add_filter("*.tet"); + file_dialog->popup_centered_ratio(); + file_dialog->set_title(TTR("Import Theme")); + } break; + case THEME_RELOAD: { + EditorSettings::get_singleton()->load_text_editor_theme(); + } break; + case THEME_SAVE: { + if (!EditorSettings::get_singleton()->save_text_editor_theme()) { + editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); + } + } break; + case THEME_SAVE_AS: { + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file_dialog_option = THEME_SAVE_AS; + file_dialog->clear_filters(); + file_dialog->add_filter("*.tet"); + file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); + file_dialog->popup_centered_ratio(); + file_dialog->set_title(TTR("Save Theme As...")); + } break; + } +} + void ScriptEditor::_tab_changed(int p_which) { ensure_select_current(); @@ -1215,6 +1220,9 @@ void ScriptEditor::_notification(int p_what) { script_forward->set_icon(get_icon("Forward", "EditorIcons")); script_back->set_icon(get_icon("Back", "EditorIcons")); + members_overview_alphabeta_sort_button->set_icon(get_icon("Sort", "EditorIcons")); + filename->add_style_override("normal", editor->get_gui_base()->get_stylebox("normal", "LineEdit")); + recent_scripts->set_as_minsize(); } break; @@ -1404,17 +1412,20 @@ void ScriptEditor::_update_members_overview_visibility() { ScriptEditorBase *se = _get_current_editor(); if (!se) { - members_overview_buttons_hbox->set_visible(false); + members_overview_alphabeta_sort_button->set_visible(false); members_overview->set_visible(false); + overview_vbox->set_visible(false); return; } if (members_overview_enabled && se->show_members_overview()) { - members_overview_buttons_hbox->set_visible(true); + members_overview_alphabeta_sort_button->set_visible(true); members_overview->set_visible(true); + overview_vbox->set_visible(true); } else { - members_overview_buttons_hbox->set_visible(false); + members_overview_alphabeta_sort_button->set_visible(false); members_overview->set_visible(false); + overview_vbox->set_visible(false); } } @@ -1440,6 +1451,11 @@ void ScriptEditor::_update_members_overview() { members_overview->add_item(functions[i].get_slice(":", 0)); members_overview->set_item_metadata(i, functions[i].get_slice(":", 1).to_int() - 1); } + + String path = se->get_edited_script()->get_path(); + bool built_in = !path.is_resource_file(); + String name = built_in ? path.get_file() : se->get_name(); + filename->set_text(name); } void ScriptEditor::_update_help_overview_visibility() { @@ -1458,10 +1474,13 @@ void ScriptEditor::_update_help_overview_visibility() { } if (help_overview_enabled) { - members_overview_buttons_hbox->set_visible(false); + members_overview_alphabeta_sort_button->set_visible(false); help_overview->set_visible(true); + overview_vbox->set_visible(true); + filename->set_text(se->get_name()); } else { help_overview->set_visible(false); + overview_vbox->set_visible(false); } } @@ -2577,6 +2596,7 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_close_all_tabs", &ScriptEditor::_close_all_tabs); ClassDB::bind_method("_close_other_tabs", &ScriptEditor::_close_other_tabs); ClassDB::bind_method("_open_recent_script", &ScriptEditor::_open_recent_script); + ClassDB::bind_method("_theme_option", &ScriptEditor::_theme_option); ClassDB::bind_method("_editor_play", &ScriptEditor::_editor_play); ClassDB::bind_method("_editor_pause", &ScriptEditor::_editor_pause); ClassDB::bind_method("_editor_stop", &ScriptEditor::_editor_stop); @@ -2673,13 +2693,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { add_child(context_menu); context_menu->connect("id_pressed", this, "_menu_option"); - members_overview_vbox = memnew(VBoxContainer); - members_overview_vbox->set_custom_minimum_size(Size2(0, 90)); - members_overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL); + overview_vbox = memnew(VBoxContainer); + overview_vbox->set_custom_minimum_size(Size2(0, 90)); + overview_vbox->set_v_size_flags(SIZE_EXPAND_FILL); + + list_split->add_child(overview_vbox); + buttons_hbox = memnew(HBoxContainer); + overview_vbox->add_child(buttons_hbox); - list_split->add_child(members_overview_vbox); - members_overview_buttons_hbox = memnew(HBoxContainer); - members_overview_vbox->add_child(members_overview_buttons_hbox); + filename = memnew(Label); + filename->set_clip_text(true); + filename->set_h_size_flags(SIZE_EXPAND_FILL); + filename->add_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_stylebox("normal", "LineEdit")); + buttons_hbox->add_child(filename); members_overview_alphabeta_sort_button = memnew(ToolButton); members_overview_alphabeta_sort_button->set_tooltip(TTR("Toggle alphabetical sorting of the method list.")); @@ -2687,10 +2713,10 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { members_overview_alphabeta_sort_button->set_pressed(EditorSettings::get_singleton()->get("text_editor/tools/sort_members_outline_alphabetically")); members_overview_alphabeta_sort_button->connect("toggled", this, "_toggle_members_overview_alpha_sort"); - members_overview_buttons_hbox->add_child(members_overview_alphabeta_sort_button); + buttons_hbox->add_child(members_overview_alphabeta_sort_button); members_overview = memnew(ItemList); - members_overview_vbox->add_child(members_overview); + overview_vbox->add_child(members_overview); members_overview->set_allow_reselect(true); members_overview->set_custom_minimum_size(Size2(0, 90)); //need to give a bit of limit to avoid it from disappearing @@ -2699,7 +2725,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { members_overview->set_drag_forwarding(this); help_overview = memnew(ItemList); - members_overview_vbox->add_child(help_overview); + overview_vbox->add_child(help_overview); help_overview->set_allow_reselect(true); help_overview->set_custom_minimum_size(Size2(0, 90)); //need to give a bit of limit to avoid it from disappearing help_overview->set_v_size_flags(SIZE_EXPAND_FILL); @@ -2743,10 +2769,18 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_previous", TTR("History Prev"), KEY_MASK_ALT | KEY_LEFT), WINDOW_PREV); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_next", TTR("History Next"), KEY_MASK_ALT | KEY_RIGHT), WINDOW_NEXT); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTR("Import Theme")), FILE_IMPORT_THEME); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTR("Reload Theme")), FILE_RELOAD_THEME); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_theme", TTR("Save Theme")), FILE_SAVE_THEME); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As")), FILE_SAVE_THEME_AS); + + file_menu->get_popup()->add_submenu_item(TTR("Theme"), "Theme", FILE_THEME); + + theme_submenu = memnew(PopupMenu); + theme_submenu->set_name("Theme"); + file_menu->get_popup()->add_child(theme_submenu); + theme_submenu->connect("id_pressed", this, "_theme_option"); + theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTR("Import Theme")), THEME_IMPORT); + theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTR("Reload Theme")), THEME_RELOAD); + theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme", TTR("Save Theme")), THEME_SAVE); + theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As")), THEME_SAVE_AS); + file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTR("Close Docs")), CLOSE_DOCS); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KEY_MASK_CMD | KEY_W), FILE_CLOSE); @@ -2850,7 +2884,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { error_dialog = memnew(AcceptDialog); add_child(error_dialog); - error_dialog->get_ok()->set_text(TTR("I see..")); + error_dialog->get_ok()->set_text(TTR("I see...")); debugger = memnew(ScriptEditorDebugger(editor)); debugger->connect("goto_script_line", this, "_goto_script_line"); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index a2ff47cd99..67f506fdda 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -134,10 +134,7 @@ class ScriptEditor : public PanelContainer { FILE_SAVE, FILE_SAVE_AS, FILE_SAVE_ALL, - FILE_IMPORT_THEME, - FILE_RELOAD_THEME, - FILE_SAVE_THEME, - FILE_SAVE_THEME_AS, + FILE_THEME, FILE_RUN, FILE_CLOSE, CLOSE_DOCS, @@ -168,6 +165,13 @@ class ScriptEditor : public PanelContainer { WINDOW_SELECT_BASE = 100 }; + enum { + THEME_IMPORT, + THEME_RELOAD, + THEME_SAVE, + THEME_SAVE_AS + }; + enum ScriptSortBy { SORT_BY_NAME, SORT_BY_PATH, @@ -190,6 +194,7 @@ class ScriptEditor : public PanelContainer { uint64_t idle; PopupMenu *recent_scripts; + PopupMenu *theme_submenu; Button *help_search; Button *site_search; @@ -199,8 +204,9 @@ class ScriptEditor : public PanelContainer { ItemList *script_list; HSplitContainer *script_split; ItemList *members_overview; - VBoxContainer *members_overview_vbox; - HBoxContainer *members_overview_buttons_hbox; + VBoxContainer *overview_vbox; + HBoxContainer *buttons_hbox; + Label *filename; ToolButton *members_overview_alphabeta_sort_button; bool members_overview_enabled; ItemList *help_overview; @@ -250,6 +256,7 @@ class ScriptEditor : public PanelContainer { void _tab_changed(int p_which); void _menu_option(int p_option); + void _theme_option(int p_option); Tree *disk_changed_list; ConfirmationDialog *disk_changed; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 45f5e667fa..aef2a53dd1 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1727,7 +1727,7 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/select_all", TTR("Select All"), KEY_MASK_CMD | KEY_A); ED_SHORTCUT("script_text_editor/move_up", TTR("Move Up"), KEY_MASK_ALT | KEY_UP); ED_SHORTCUT("script_text_editor/move_down", TTR("Move Down"), KEY_MASK_ALT | KEY_DOWN); - ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_K); + ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_K); //leave these at zero, same can be accomplished with tab/shift-tab, including selection //the next/previous in history shortcut in this case makes a lot more sene. @@ -1740,28 +1740,36 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), 0); #ifdef OSX_ENABLED ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C); - ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CTRL | KEY_SPACE); #else ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_B); - ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CMD | KEY_SPACE); #endif - ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CTRL | KEY_MASK_ALT | KEY_T); - ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent To Spaces"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_Y); - ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent To Tabs"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_X); + ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CMD | KEY_SPACE); + ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_T); + ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent To Spaces"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Y); + ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent To Tabs"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_X); ED_SHORTCUT("script_text_editor/auto_indent", TTR("Auto Indent"), KEY_MASK_CMD | KEY_I); +#ifdef OSX_ENABLED + ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B); +#else ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9); - ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KEY_MASK_CTRL | KEY_MASK_SHIFT | KEY_F9); - ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Goto Next Breakpoint"), KEY_MASK_CTRL | KEY_PERIOD); - ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Goto Previous Breakpoint"), KEY_MASK_CTRL | KEY_COMMA); +#endif + ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F9); + ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Goto Next Breakpoint"), KEY_MASK_CMD | KEY_PERIOD); + ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Goto Previous Breakpoint"), KEY_MASK_CMD | KEY_COMMA); ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Convert To Uppercase"), KEY_MASK_SHIFT | KEY_F4); ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Convert To Lowercase"), KEY_MASK_SHIFT | KEY_F3); ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KEY_MASK_SHIFT | KEY_F2); ED_SHORTCUT("script_text_editor/find", TTR("Find..."), KEY_MASK_CMD | KEY_F); +#ifdef OSX_ENABLED + ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), KEY_MASK_CMD | KEY_G); + ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G); +#else ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), KEY_F3); ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3); +#endif ED_SHORTCUT("script_text_editor/replace", TTR("Replace..."), KEY_MASK_CMD | KEY_R); ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F); @@ -1769,7 +1777,11 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function..."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F); ED_SHORTCUT("script_text_editor/goto_line", TTR("Goto Line..."), KEY_MASK_CMD | KEY_L); +#ifdef OSX_ENABLED + ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_ALT | KEY_MASK_SHIFT | KEY_SPACE); +#else ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_SHIFT | KEY_F1); +#endif ScriptEditor::register_create_script_editor_function(create_editor); } diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 5b713ef3c4..30fff474d7 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -32,7 +32,7 @@ #include "camera_matrix.h" #include "core/os/input.h" -#include "editor/animation_editor.h" + #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/plugins/animation_player_editor_plugin.h" @@ -217,7 +217,7 @@ bool SpatialEditorGizmo::intersect_frustum(const Camera *p_camera, const Vector< return false; } -bool SpatialEditorGizmo::intersect_ray(const Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) { +bool SpatialEditorGizmo::intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) { return false; } @@ -320,24 +320,20 @@ void SpatialEditorViewport::_select_clicked(bool p_append, bool p_single) { void SpatialEditorViewport::_select(Spatial *p_node, bool p_append, bool p_single) { if (!p_append) { + editor_selection->clear(); + } - // should not modify the selection.. + if (editor_selection->is_selected(p_node)) { + //erase + editor_selection->remove_node(p_node); + } else { - editor_selection->clear(); editor_selection->add_node(p_node); + } + if (p_single) { if (Engine::get_singleton()->is_editor_hint()) editor->call("edit_node", p_node); - - } else { - - if (editor_selection->is_selected(p_node) && p_single) { - //erase - editor_selection->remove_node(p_node); - } else { - - editor_selection->add_node(p_node); - } } } @@ -376,7 +372,7 @@ ObjectID SpatialEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, Vector3 normal; int handle = -1; - bool inters = seg->intersect_ray(camera, p_pos, point, normal, NULL, p_alt_select); + bool inters = seg->intersect_ray(camera, p_pos, point, normal, &handle, p_alt_select); if (!inters) continue; @@ -475,7 +471,7 @@ void SpatialEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_incl Vector3 SpatialEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) { CameraMatrix cm; - cm.set_perspective(get_fov(), get_size().aspect(), get_znear(), get_zfar()); + cm.set_perspective(get_fov(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar()); float screen_w, screen_h; cm.get_viewport_size(screen_w, screen_h); @@ -485,7 +481,7 @@ Vector3 SpatialEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) { camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); camera_transform.translate(0, 0, cursor.distance); - return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_w, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_h, -get_znear())); + return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_w, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_h, -(get_znear() + p_vector3.z))); } void SpatialEditorViewport::_select_region() { @@ -493,23 +489,25 @@ void SpatialEditorViewport::_select_region() { if (cursor.region_begin == cursor.region_end) return; //nothing really + float z_offset = MAX(0.0, 5.0 - get_znear()); + Vector3 box[4] = { Vector3( MIN(cursor.region_begin.x, cursor.region_end.x), MIN(cursor.region_begin.y, cursor.region_end.y), - 0), + z_offset), Vector3( MAX(cursor.region_begin.x, cursor.region_end.x), MIN(cursor.region_begin.y, cursor.region_end.y), - 0), + z_offset), Vector3( MAX(cursor.region_begin.x, cursor.region_end.x), MAX(cursor.region_begin.y, cursor.region_end.y), - 0), + z_offset), Vector3( MIN(cursor.region_begin.x, cursor.region_end.x), MAX(cursor.region_begin.y, cursor.region_end.y), - 0) + z_offset) }; Vector<Plane> frustum; @@ -529,7 +527,7 @@ void SpatialEditorViewport::_select_region() { frustum.push_back(near); Plane far = -near; - far.d += 500.0; + far.d += get_zfar(); frustum.push_back(far); @@ -544,19 +542,26 @@ void SpatialEditorViewport::_select_region() { if (!sp) continue; + Spatial *root_sp = sp; + while (root_sp && root_sp != edited_scene && root_sp->get_owner() != edited_scene && !edited_scene->is_editable_instance(root_sp->get_owner())) { + root_sp = Object::cast_to<Spatial>(root_sp->get_owner()); + } + + if (selected.find(root_sp) != -1) continue; + Ref<SpatialEditorGizmo> seg = sp->get_gizmo(); if (!seg.is_valid()) continue; - Spatial *root_sp = sp; - while (root_sp && root_sp != edited_scene && root_sp->get_owner() != edited_scene && !edited_scene->is_editable_instance(root_sp->get_owner())) { - root_sp = Object::cast_to<Spatial>(root_sp->get_owner()); + if (seg->intersect_frustum(camera, frustum)) { + selected.push_back(root_sp); } + } - if (selected.find(root_sp) == -1) - if (seg->intersect_frustum(camera, frustum)) - _select(root_sp, true, false); + bool single = selected.size() == 1; + for (int i = 0; i < selected.size(); i++) { + _select(selected[i], true, single); } } @@ -1170,6 +1175,9 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (cursor.region_select) { + + if (!clicked_wants_append) _clear_selected(); + _select_region(); cursor.region_select = false; surface->update(); @@ -1279,7 +1287,6 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (cursor.region_select && nav_mode == NAVIGATION_NONE) { - cursor.region_end = m->get_position(); surface->update(); return; @@ -1829,7 +1836,7 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (!get_selected_count() || _edit.mode != TRANSFORM_NONE) return; - if (!AnimationPlayerEditor::singleton->get_key_editor()->has_keying()) { + if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { set_message(TTR("Keying is disabled (no key inserted).")); return; } @@ -2153,10 +2160,7 @@ void SpatialEditorViewport::_notification(int p_what) { VisualInstance *vi = Object::cast_to<VisualInstance>(sp); - if (se->aabb.has_no_surface()) { - - se->aabb = vi ? vi->get_aabb() : AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); - } + se->aabb = vi ? vi->get_aabb() : AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); Transform t = sp->get_global_gizmo_transform(); t.translate(se->aabb.position); diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index 7736db67b1..637926a913 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -62,7 +62,7 @@ public: virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false); virtual bool intersect_frustum(const Camera *p_camera, const Vector<Plane> &p_frustum); - virtual bool intersect_ray(const Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false); + virtual bool intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false); SpatialEditorGizmo(); }; diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index 72b3af5a09..7264af3488 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -35,6 +35,7 @@ #include "editor/editor_settings.h" #include "os/input.h" #include "os/keyboard.h" +#include "scene/gui/split_container.h" void TileMapEditor::_notification(int p_what) { @@ -132,16 +133,14 @@ void TileMapEditor::_menu_option(int p_option) { if (!selection_active) return; - undo_redo->create_action(TTR("Erase Selection")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Erase Selection")); for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { _set_cell(Point2i(j, i), TileMap::INVALID_CELL, false, false, false); } } - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); selection_active = false; copydata.clear(); @@ -168,6 +167,13 @@ void TileMapEditor::_menu_option(int p_option) { } } +void TileMapEditor::_palette_selected(int index) { + + if (manual_autotile) { + _update_palette(); + } +} + void TileMapEditor::_canvas_mouse_enter() { mouse_over = true; @@ -200,6 +206,46 @@ void TileMapEditor::set_selected_tile(int p_tile) { } } +void TileMapEditor::_create_set_cell_undo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) { + + Dictionary cell_old; + Dictionary cell_new; + + cell_old["id"] = p_cell_old.idx; + cell_old["flip_h"] = p_cell_old.xf; + cell_old["flip_y"] = p_cell_old.yf; + cell_old["transpose"] = p_cell_old.tr; + cell_old["auto_coord"] = p_cell_old.ac; + + cell_new["id"] = p_cell_new.idx; + cell_new["flip_h"] = p_cell_new.xf; + cell_new["flip_y"] = p_cell_new.yf; + cell_new["transpose"] = p_cell_new.tr; + cell_new["auto_coord"] = p_cell_new.ac; + + undo_redo->add_undo_method(node, "set_celld", p_vec, cell_old); + undo_redo->add_do_method(node, "set_celld", p_vec, cell_new); +} + +void TileMapEditor::_start_undo(const String &p_action) { + + undo_data.clear(); + undo_redo->create_action(p_action); +} + +void TileMapEditor::_finish_undo() { + + if (undo_data.size()) { + for (Map<Point2i, CellOp>::Element *E = undo_data.front(); E; E = E->next()) { + _create_set_cell_undo(E->key(), E->get(), _get_op_from_cell(E->key())); + } + + undo_data.clear(); + } + + undo_redo->commit_action(); +} + void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h, bool p_flip_v, bool p_transpose) { ERR_FAIL_COND(!node); @@ -209,12 +255,46 @@ void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h, bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y); bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y); bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y); + Vector2 prev_position = node->get_cell_autotile_coord(p_pos.x, p_pos.y); - if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose) + Vector2 position; + int current = manual_palette->get_current(); + if (current != -1) { + position = manual_palette->get_item_metadata(current); + } else { + // if there is no manual tile selected, that either means that + // autotiling is enabled, or the given tile is not autotiling. Either + // way, the coordinate of the tile does not matter, so assigning it to + // the coordinate of the existing tile works fine. + position = prev_position; + } + + if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose && prev_position == position) return; //check that it's actually different + for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { + for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { + Point2i p = Point2i(x, y); + if (!undo_data.has(p)) { + undo_data[p] = _get_op_from_cell(p); + } + } + } + node->set_cell(p_pos.x, p_pos.y, p_value, p_flip_h, p_flip_v, p_transpose); - node->update_bitmask_area(Point2(p_pos)); + if (manual_autotile) { + if (current != -1) { + node->set_cell_autotile_coord(p_pos.x, p_pos.y, position); + } + } else { + // manually placing tiles should not update bitmasks + node->update_bitmask_area(Point2(p_pos)); + } +} + +void TileMapEditor::_manual_toggled(bool p_enabled) { + manual_autotile = p_enabled; + _update_palette(); } void TileMapEditor::_text_entered(const String &p_text) { @@ -261,6 +341,8 @@ void TileMapEditor::_update_palette() { int selected = get_selected_tile(); palette->clear(); + manual_palette->clear(); + manual_palette->hide(); Ref<TileSet> tileset = node->get_tileset(); if (tileset.is_null()) @@ -268,7 +350,6 @@ void TileMapEditor::_update_palette() { List<int> tiles; tileset->get_tile_list(&tiles); - if (tiles.empty()) return; @@ -284,6 +365,9 @@ void TileMapEditor::_update_palette() { palette->set_fixed_icon_size(Size2(min_size, min_size)); palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1)); + palette->set_same_column_width(true); + manual_palette->set_fixed_icon_size(Size2(min_size, min_size)); + manual_palette->set_same_column_width(true); String filter = search_box->get_text().strip_edges(); @@ -344,12 +428,51 @@ void TileMapEditor::_update_palette() { palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id); } - palette->set_same_column_width(true); - if (selected != -1) set_selected_tile(selected); else palette->select(0); + + if (manual_autotile && tileset->tile_get_tile_mode(get_selected_tile()) == TileSet::AUTO_TILE) { + + const Map<Vector2, uint16_t> &tiles = tileset->autotile_get_bitmask_map(get_selected_tile()); + + Vector<Vector2> entries; + for (const Map<Vector2, uint16_t>::Element *E = tiles.front(); E; E = E->next()) { + entries.push_back(E->key()); + } + entries.sort(); + + Ref<Texture> tex = tileset->tile_get_texture(get_selected_tile()); + + for (int i = 0; i < entries.size(); i++) { + + manual_palette->add_item(String()); + + if (tex.is_valid()) { + + Rect2 region = tileset->tile_get_region(get_selected_tile()); + int spacing = tileset->autotile_get_spacing(get_selected_tile()); + region.size = tileset->autotile_get_size(get_selected_tile()); + region.position += (region.size + Vector2(spacing, spacing)) * entries[i]; + + if (!region.has_no_area()) + manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region); + + manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex); + } + + manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries[i]); + } + } + + if (manual_palette->get_item_count() > 0) { + // Only show the manual palette if at least tile exists in it + int selected = manual_palette->get_current(); + if (selected == -1) selected = 0; + manual_palette->set_current(selected); + manual_palette->show(); + } } void TileMapEditor::_pick_tile(const Point2 &p_pos) { @@ -533,9 +656,17 @@ void TileMapEditor::_draw_cell(int p_cell, const Point2i &p_point, bool p_flip_h Rect2 r = node->get_tileset()->tile_get_region(p_cell); if (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::AUTO_TILE) { + Vector2 offset; + int selected = manual_palette->get_current(); + if (manual_autotile && selected != -1) { + offset = manual_palette->get_item_metadata(selected); + } else { + offset = node->get_tileset()->autotile_get_icon_coordinate(p_cell); + } + int spacing = node->get_tileset()->autotile_get_spacing(p_cell); r.size = node->get_tileset()->autotile_get_size(p_cell); - r.position += (r.size + Vector2(spacing, spacing)) * node->get_tileset()->autotile_get_icon_coordinate(p_cell); + r.position += (r.size + Vector2(spacing, spacing)) * offset; } Size2 sc = p_xform.get_scale(); @@ -760,8 +891,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { tool = TOOL_PAINTING; - undo_redo->create_action(TTR("Paint TileMap")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Paint TileMap")); } } else if (tool == TOOL_PICKING) { @@ -785,8 +915,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (id != TileMap::INVALID_CELL) { _set_cell(over_tile, id, flip_h, flip_v, transpose); - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); paint_undo.clear(); } @@ -796,14 +925,12 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (id != TileMap::INVALID_CELL) { - undo_redo->create_action(TTR("Line Draw")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Line Draw")); for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { _set_cell(E->key(), id, flip_h, flip_v, transpose); } - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); paint_undo.clear(); @@ -815,16 +942,14 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { if (id != TileMap::INVALID_CELL) { - undo_redo->create_action(TTR("Rectangle Paint")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Rectangle Paint")); for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { _set_cell(Point2i(j, i), id, flip_h, flip_v, transpose); } } - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); canvas_item_editor->update(); } @@ -832,14 +957,12 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { Point2 ofs = over_tile - rectangle.position; - undo_redo->create_action(TTR("Duplicate")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Duplicate")); for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) { _set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose); } - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); copydata.clear(); @@ -848,8 +971,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { Point2 ofs = over_tile - rectangle.position; - undo_redo->create_action(TTR("Move")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Move")); for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { @@ -860,8 +982,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { _set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose); } - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); copydata.clear(); selection_active = false; @@ -880,7 +1001,6 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { return false; undo_redo->create_action(TTR("Bucket Fill")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); Dictionary op; op["id"] = get_selected_tile(); @@ -890,7 +1010,6 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { _fill_points(points, op); - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); undo_redo->commit_action(); // We want to keep the bucket-tool active @@ -942,8 +1061,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { Point2 local = node->world_to_map(xform_inv.xform(mb->get_position())); - undo_redo->create_action(TTR("Erase TileMap")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); + _start_undo(TTR("Erase TileMap")); if (mb->get_shift()) { #ifdef APPLE_STYLE_KEYS @@ -970,8 +1088,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } else { if (tool == TOOL_ERASING || tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) { - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); + _finish_undo(); if (tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) { canvas_item_editor->update(); @@ -1503,12 +1620,14 @@ void TileMapEditor::_tileset_settings_changed() { void TileMapEditor::_icon_size_changed(float p_value) { if (node) { palette->set_icon_scale(p_value); + manual_palette->set_icon_scale(p_value); _update_palette(); } } void TileMapEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_manual_toggled"), &TileMapEditor::_manual_toggled); ClassDB::bind_method(D_METHOD("_text_entered"), &TileMapEditor::_text_entered); ClassDB::bind_method(D_METHOD("_text_changed"), &TileMapEditor::_text_changed); ClassDB::bind_method(D_METHOD("_sbox_input"), &TileMapEditor::_sbox_input); @@ -1517,6 +1636,7 @@ void TileMapEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_canvas_mouse_exit"), &TileMapEditor::_canvas_mouse_exit); ClassDB::bind_method(D_METHOD("_tileset_settings_changed"), &TileMapEditor::_tileset_settings_changed); ClassDB::bind_method(D_METHOD("_update_transform_buttons"), &TileMapEditor::_update_transform_buttons); + ClassDB::bind_method(D_METHOD("_palette_selected"), &TileMapEditor::_palette_selected); ClassDB::bind_method(D_METHOD("_fill_points"), &TileMapEditor::_fill_points); ClassDB::bind_method(D_METHOD("_erase_points"), &TileMapEditor::_erase_points); @@ -1534,6 +1654,7 @@ TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) { op.yf = true; if (node->is_cell_transposed(p_pos.x, p_pos.y)) op.tr = true; + op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y); } return op; } @@ -1574,6 +1695,8 @@ void TileMapEditor::_update_transform_buttons(Object *p_button) { TileMapEditor::TileMapEditor(EditorNode *p_editor) { node = NULL; + manual_autotile = false; + manual_position = Vector2(0, 0); canvas_item_editor = NULL; editor = p_editor; undo_redo = editor->get_undo_redo(); @@ -1601,6 +1724,11 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { HBoxContainer *tool_hb2 = memnew(HBoxContainer); add_child(tool_hb2); + manual_button = memnew(CheckBox); + manual_button->set_text("Disable Autotile"); + manual_button->connect("toggled", this, "_manual_toggled"); + add_child(manual_button); + search_box = memnew(LineEdit); search_box->set_h_size_flags(SIZE_EXPAND_FILL); search_box->connect("text_entered", this, "_text_entered"); @@ -1619,14 +1747,30 @@ TileMapEditor::TileMapEditor(EditorNode *p_editor) { int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80); + VSplitContainer *palette_container = memnew(VSplitContainer); + palette_container->set_v_size_flags(SIZE_EXPAND_FILL); + palette_container->set_custom_minimum_size(Size2(mw, 0)); + add_child(palette_container); + // Add tile palette palette = memnew(ItemList); + palette->set_h_size_flags(SIZE_EXPAND_FILL); palette->set_v_size_flags(SIZE_EXPAND_FILL); - palette->set_custom_minimum_size(Size2(mw, 0)); palette->set_max_columns(0); palette->set_icon_mode(ItemList::ICON_MODE_TOP); palette->set_max_text_lines(2); - add_child(palette); + palette->connect("item_selected", this, "_palette_selected"); + palette_container->add_child(palette); + + // Add autotile override palette + manual_palette = memnew(ItemList); + manual_palette->set_h_size_flags(SIZE_EXPAND_FILL); + manual_palette->set_v_size_flags(SIZE_EXPAND_FILL); + manual_palette->set_max_columns(0); + manual_palette->set_icon_mode(ItemList::ICON_MODE_TOP); + manual_palette->set_max_text_lines(2); + manual_palette->hide(); + palette_container->add_child(manual_palette); // Add menu items toolbar = memnew(HBoxContainer); diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h index 642870aec0..77e9a33892 100644 --- a/editor/plugins/tile_map_editor_plugin.h +++ b/editor/plugins/tile_map_editor_plugin.h @@ -35,6 +35,7 @@ #include "editor/editor_plugin.h" #include "scene/2d/tile_map.h" +#include "scene/gui/check_box.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/menu_button.h" @@ -77,6 +78,8 @@ class TileMapEditor : public VBoxContainer { }; TileMap *node; + bool manual_autotile; + Vector2 manual_position; EditorNode *editor; UndoRedo *undo_redo; @@ -85,6 +88,7 @@ class TileMapEditor : public VBoxContainer { LineEdit *search_box; HSlider *size_slider; ItemList *palette; + ItemList *manual_palette; HBoxContainer *toolbar; @@ -97,6 +101,7 @@ class TileMapEditor : public VBoxContainer { ToolButton *rotate_90; ToolButton *rotate_180; ToolButton *rotate_270; + CheckBox *manual_button; Tool tool; @@ -124,6 +129,7 @@ class TileMapEditor : public VBoxContainer { bool xf; bool yf; bool tr; + Vector2 ac; CellOp() : idx(TileMap::INVALID_CELL), @@ -150,6 +156,8 @@ class TileMapEditor : public VBoxContainer { List<TileData> copydata; + Map<Point2i, CellOp> undo_data; + void _pick_tile(const Point2 &p_pos); PoolVector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false); @@ -168,12 +176,17 @@ class TileMapEditor : public VBoxContainer { int get_selected_tile() const; void set_selected_tile(int p_tile); + void _manual_toggled(bool p_enabled); void _text_entered(const String &p_text); void _text_changed(const String &p_text); void _sbox_input(const Ref<InputEvent> &p_ie); void _update_palette(); void _menu_option(int p_option); + void _palette_selected(int index); + void _start_undo(const String &p_action); + void _finish_undo(); + void _create_set_cell_undo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new); void _set_cell(const Point2i &p_pos, int p_value, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false); void _canvas_mouse_enter(); diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp index c79cf02062..087c4293f1 100644 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ b/editor/plugins/tile_set_editor_plugin.cpp @@ -123,10 +123,10 @@ void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) { for (List<uint32_t>::Element *E = shapes.front(); E; E = E->next()) { if (sb->is_shape_owner_disabled(E->get())) continue; - Transform2D shape_transform = sb->shape_owner_get_transform(E->get()); + Transform2D shape_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get()); bool one_way = sb->is_shape_owner_one_way_collision_enabled(E->get()); - shape_transform[2] -= phys_offset - sb->get_transform().xform(shape_transform[2]); + shape_transform[2] -= phys_offset; for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) { diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 82fd727620..cf136a5e58 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -149,53 +149,71 @@ void ProjectSettingsEditor::_action_edited() { if (!ti) return; - String new_name = ti->get_text(0); - String old_name = add_at.substr(add_at.find("/") + 1, add_at.length()); + if (input_editor->get_selected_column() == 0) { - if (new_name == old_name) - return; + String new_name = ti->get_text(0); + String old_name = add_at.substr(add_at.find("/") + 1, add_at.length()); - if (new_name == "" || !_validate_action_name(new_name)) { + if (new_name == old_name) + return; - ti->set_text(0, old_name); - add_at = "input/" + old_name; + if (new_name == "" || !_validate_action_name(new_name)) { - message->set_text(TTR("Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\', or '\"'.")); - message->popup_centered(Size2(300, 100) * EDSCALE); - return; - } + ti->set_text(0, old_name); + add_at = "input/" + old_name; - String action_prop = "input/" + new_name; + message->set_text(TTR("Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or '\"'")); + message->popup_centered(Size2(300, 100) * EDSCALE); + return; + } - if (ProjectSettings::get_singleton()->has_setting(action_prop)) { + String action_prop = "input/" + new_name; - ti->set_text(0, old_name); - add_at = "input/" + old_name; + if (ProjectSettings::get_singleton()->has_setting(action_prop)) { - message->set_text(vformat(TTR("Action '%s' already exists!"), new_name)); - message->popup_centered(Size2(300, 100) * EDSCALE); - return; - } + ti->set_text(0, old_name); + add_at = "input/" + old_name; - int order = ProjectSettings::get_singleton()->get_order(add_at); - Dictionary action = ProjectSettings::get_singleton()->get(add_at); - - setting = true; - undo_redo->create_action(TTR("Rename Input Action Event")); - undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", add_at); - undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", action_prop, action); - undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", action_prop, order); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", action_prop); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", add_at, action); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", add_at, order); - undo_redo->add_do_method(this, "_update_actions"); - undo_redo->add_undo_method(this, "_update_actions"); - undo_redo->add_do_method(this, "_settings_changed"); - undo_redo->add_undo_method(this, "_settings_changed"); - undo_redo->commit_action(); - setting = false; + message->set_text(vformat(TTR("Action '%s' already exists!"), new_name)); + message->popup_centered(Size2(300, 100) * EDSCALE); + return; + } - add_at = action_prop; + int order = ProjectSettings::get_singleton()->get_order(add_at); + Dictionary action = ProjectSettings::get_singleton()->get(add_at); + + setting = true; + undo_redo->create_action(TTR("Rename Input Action Event")); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", add_at); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", action_prop, action); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", action_prop, order); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", action_prop); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", add_at, action); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", add_at, order); + undo_redo->add_do_method(this, "_update_actions"); + undo_redo->add_undo_method(this, "_update_actions"); + undo_redo->add_do_method(this, "_settings_changed"); + undo_redo->add_undo_method(this, "_settings_changed"); + undo_redo->commit_action(); + setting = false; + + add_at = action_prop; + } else if (input_editor->get_selected_column() == 1) { + + String name = "input/" + ti->get_text(0); + Dictionary old_action = ProjectSettings::get_singleton()->get(name); + Dictionary new_action = old_action.duplicate(); + new_action["deadzone"] = ti->get_range(1); + + undo_redo->create_action(TTR("Change Action deadzone")); + undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", name, new_action); + undo_redo->add_do_method(this, "_update_actions"); + undo_redo->add_do_method(this, "_settings_changed"); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", name, old_action); + undo_redo->add_undo_method(this, "_update_actions"); + undo_redo->add_undo_method(this, "_settings_changed"); + undo_redo->commit_action(); + } } void ProjectSettingsEditor::_device_input_add() { @@ -237,24 +255,18 @@ void ProjectSettingsEditor::_device_input_add() { jm->set_axis_value(device_index->get_selected() & 1 ? 1 : -1); jm->set_device(_get_current_device()); - bool should_update_event = true; - Variant deadzone = device_special_value->get_value(); for (int i = 0; i < events.size(); i++) { Ref<InputEventJoypadMotion> aie = events[i]; if (aie.is_null()) continue; + if (aie->get_device() == jm->get_device() && aie->get_axis() == jm->get_axis() && aie->get_axis_value() == jm->get_axis_value()) { - should_update_event = false; - break; + return; } } - if (!should_update_event && deadzone == action["deadzone"]) - return; - ie = jm; - action["deadzone"] = deadzone; } break; case INPUT_JOY_BUTTON: { @@ -430,8 +442,6 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even press_a_key->popup_centered(Size2(250, 80) * EDSCALE); press_a_key->grab_focus(); - device_special_value_label->hide(); - device_special_value->hide(); } break; case INPUT_MOUSE_BUTTON: { @@ -458,8 +468,6 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even device_input->get_ok()->set_text(TTR("Add")); } - device_special_value_label->hide(); - device_special_value->hide(); } break; case INPUT_JOY_MOTION: { @@ -482,14 +490,6 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even device_input->get_ok()->set_text(TTR("Add")); } - device_special_value_label->set_text(TTR("Deadzone (global to the action):")); - device_special_value_label->show(); - device_special_value->set_min(0.0f); - device_special_value->set_max(1.0f); - device_special_value->set_step(0.01f); - Dictionary action = ProjectSettings::get_singleton()->get(add_at); - device_special_value->set_value(action.has("deadzone") ? action["deadzone"] : Variant(0.5f)); - device_special_value->show(); } break; case INPUT_JOY_BUTTON: { @@ -512,8 +512,6 @@ void ProjectSettingsEditor::_add_item(int p_item, Ref<InputEvent> p_exiting_even device_input->get_ok()->set_text(TTR("Add")); } - device_special_value_label->hide(); - device_special_value->hide(); } break; default: {} } @@ -673,17 +671,24 @@ void ProjectSettingsEditor::_update_actions() { if (name == "") continue; + Dictionary action = ProjectSettings::get_singleton()->get(pi.name); + Array events = action["events"]; + TreeItem *item = input_editor->create_item(root); item->set_text(0, name); - item->add_button(0, get_icon("Add", "EditorIcons"), 1, false, TTR("Add Event")); - if (!ProjectSettings::get_singleton()->get_input_presets().find(pi.name)) { - item->add_button(0, get_icon("Remove", "EditorIcons"), 2, false, TTR("Remove")); - item->set_editable(0, true); - } item->set_custom_bg_color(0, get_color("prop_subsection", "Editor")); - Dictionary action = ProjectSettings::get_singleton()->get(pi.name); - Array events = action["events"]; + item->set_editable(1, true); + item->set_cell_mode(1, TreeItem::CELL_MODE_RANGE); + item->set_range_config(1, 0.0, 1.0, 0.01); + item->set_range(1, action["deadzone"]); + item->set_custom_bg_color(1, get_color("prop_subsection", "Editor")); + + item->add_button(2, get_icon("Add", "EditorIcons"), 1, false, TTR("Add Event")); + if (!ProjectSettings::get_singleton()->get_input_presets().find(pi.name)) { + item->add_button(2, get_icon("Remove", "EditorIcons"), 2, false, TTR("Remove")); + item->set_editable(2, true); + } for (int i = 0; i < events.size(); i++) { @@ -752,10 +757,11 @@ void ProjectSettingsEditor::_update_actions() { action->set_text(0, str); action->set_icon(0, get_icon("JoyAxis", "EditorIcons")); } - action->add_button(0, get_icon("Edit", "EditorIcons"), 3, false, TTR("Edit")); - action->add_button(0, get_icon("Remove", "EditorIcons"), 2, false, TTR("Remove")); action->set_metadata(0, i); action->set_meta("__input", event); + + action->add_button(2, get_icon("Edit", "EditorIcons"), 3, false, TTR("Edit")); + action->add_button(2, get_icon("Remove", "EditorIcons"), 2, false, TTR("Remove")); } } @@ -894,7 +900,7 @@ void ProjectSettingsEditor::_action_check(String p_action) { if (!_validate_action_name(p_action)) { - action_add_error->set_text(TTR("Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or '\"'")); + action_add_error->set_text(TTR("Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or '\"'.")); action_add_error->show(); action_add->set_disabled(true); return; @@ -1790,6 +1796,14 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { input_editor = memnew(Tree); vbc->add_child(input_editor); input_editor->set_v_size_flags(SIZE_EXPAND_FILL); + input_editor->set_columns(3); + input_editor->set_column_titles_visible(true); + input_editor->set_column_title(0, TTR("Action")); + input_editor->set_column_title(1, TTR("Deadzone")); + input_editor->set_column_expand(1, false); + input_editor->set_column_min_width(1, 80); + input_editor->set_column_expand(2, false); + input_editor->set_column_min_width(2, 50); input_editor->connect("item_edited", this, "_action_edited"); input_editor->connect("item_activated", this, "_action_activated"); input_editor->connect("cell_selected", this, "_action_selected"); @@ -1846,14 +1860,6 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { device_index = memnew(OptionButton); vbc_right->add_child(device_index); - l = memnew(Label); - l->set_text(TTR("Special value:")); - vbc_right->add_child(l); - device_special_value_label = l; - - device_special_value = memnew(SpinBox); - vbc_right->add_child(device_special_value); - setting = false; //translations @@ -1979,7 +1985,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { tab_container->add_child(plugin_settings); timer = memnew(Timer); - timer->set_wait_time(1.5); + timer->set_wait_time(0.1); timer->connect("timeout", ProjectSettings::get_singleton(), "save"); timer->set_one_shot(true); add_child(timer); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index b8bfdcd876..0ced88d7f6 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -83,8 +83,6 @@ class ProjectSettingsEditor : public AcceptDialog { OptionButton *device_id; OptionButton *device_index; Label *device_index_label; - SpinBox *device_special_value; - Label *device_special_value_label; MenuButton *popup_copy_to_feature; LineEdit *action_name; diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index e912ebe03a..7f46844f6c 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -847,6 +847,7 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant:: if (!color_picker) { //late init for performance color_picker = memnew(ColorPicker); + color_picker->set_deferred_mode(true); add_child(color_picker); color_picker->hide(); color_picker->connect("color_changed", this, "_color_changed"); diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index 3e95064ead..d927e07976 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -120,33 +120,33 @@ void PropertySelector::_update_search() { bool found = false; Ref<Texture> type_icons[Variant::VARIANT_MAX] = { - Control::get_icon("MiniVariant", "EditorIcons"), - Control::get_icon("MiniBoolean", "EditorIcons"), - Control::get_icon("MiniInteger", "EditorIcons"), - Control::get_icon("MiniFloat", "EditorIcons"), - Control::get_icon("MiniString", "EditorIcons"), - Control::get_icon("MiniVector2", "EditorIcons"), - Control::get_icon("MiniRect2", "EditorIcons"), - Control::get_icon("MiniVector3", "EditorIcons"), - Control::get_icon("MiniMatrix2", "EditorIcons"), - Control::get_icon("MiniPlane", "EditorIcons"), - Control::get_icon("MiniQuat", "EditorIcons"), - Control::get_icon("MiniAabb", "EditorIcons"), - Control::get_icon("MiniMatrix3", "EditorIcons"), - Control::get_icon("MiniTransform", "EditorIcons"), - Control::get_icon("MiniColor", "EditorIcons"), - Control::get_icon("MiniPath", "EditorIcons"), - Control::get_icon("MiniRid", "EditorIcons"), - Control::get_icon("MiniObject", "EditorIcons"), - Control::get_icon("MiniDictionary", "EditorIcons"), - Control::get_icon("MiniArray", "EditorIcons"), - Control::get_icon("MiniRawArray", "EditorIcons"), - Control::get_icon("MiniIntArray", "EditorIcons"), - Control::get_icon("MiniFloatArray", "EditorIcons"), - Control::get_icon("MiniStringArray", "EditorIcons"), - Control::get_icon("MiniVector2Array", "EditorIcons"), - Control::get_icon("MiniVector3Array", "EditorIcons"), - Control::get_icon("MiniColorArray", "EditorIcons") + Control::get_icon("Variant", "EditorIcons"), + Control::get_icon("bool", "EditorIcons"), + Control::get_icon("int", "EditorIcons"), + Control::get_icon("float", "EditorIcons"), + Control::get_icon("String", "EditorIcons"), + Control::get_icon("Vector2", "EditorIcons"), + Control::get_icon("Rect2", "EditorIcons"), + Control::get_icon("Vector3", "EditorIcons"), + Control::get_icon("Transform2D", "EditorIcons"), + Control::get_icon("Plane", "EditorIcons"), + Control::get_icon("Quat", "EditorIcons"), + Control::get_icon("AABB", "EditorIcons"), + Control::get_icon("Basis", "EditorIcons"), + Control::get_icon("Transform", "EditorIcons"), + Control::get_icon("Color", "EditorIcons"), + Control::get_icon("Path", "EditorIcons"), + Control::get_icon("RID", "EditorIcons"), + Control::get_icon("Object", "EditorIcons"), + Control::get_icon("Dictionary", "EditorIcons"), + Control::get_icon("Array", "EditorIcons"), + Control::get_icon("PoolByteArray", "EditorIcons"), + Control::get_icon("PoolIntArray", "EditorIcons"), + Control::get_icon("PoolRealArray", "EditorIcons"), + Control::get_icon("PoolStringArray", "EditorIcons"), + Control::get_icon("PoolVector2Array", "EditorIcons"), + Control::get_icon("PoolVector3Array", "EditorIcons"), + Control::get_icon("PoolColorArray", "EditorIcons") }; for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { @@ -175,6 +175,10 @@ void PropertySelector::_update_search() { if (search_box->get_text() != String() && E->get().name.find(search_box->get_text()) == -1) continue; + + if (type_filter.size() && type_filter.find(E->get().type) == -1) + continue; + TreeItem *item = search_options->create_item(category ? category : root); item->set_text(0, E->get().name); item->set_metadata(0, E->get().name); @@ -534,6 +538,10 @@ void PropertySelector::select_property_from_instance(Object *p_instance, const S _update_search(); } +void PropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) { + type_filter = p_type_filter; +} + void PropertySelector::_bind_methods() { ClassDB::bind_method(D_METHOD("_text_changed"), &PropertySelector::_text_changed); diff --git a/editor/property_selector.h b/editor/property_selector.h index d9b1aee422..f5b34d210e 100644 --- a/editor/property_selector.h +++ b/editor/property_selector.h @@ -60,6 +60,8 @@ class PropertySelector : public ConfirmationDialog { void _item_selected(); + Vector<Variant::Type> type_filter; + protected: void _notification(int p_what); static void _bind_methods(); @@ -75,6 +77,8 @@ public: void select_property_from_basic_type(Variant::Type p_type, const String &p_current = ""); void select_property_from_instance(Object *p_instance, const String &p_current = ""); + void set_type_filter(const Vector<Variant::Type> &p_type_filter); + PropertySelector(); }; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 32b4e7f962..77ee65879b 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -33,7 +33,7 @@ #include "core/io/resource_saver.h" #include "core/os/keyboard.h" #include "core/project_settings.h" -#include "editor/animation_editor.h" + #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/multi_node_edit.h" @@ -1248,7 +1248,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V path_renames[ni].second = fixed_node_path; } - editor_data->get_undo_redo().add_do_method(sed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, -1); + editor_data->get_undo_redo().add_do_method(sed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, p_position_in_parent + inc); editor_data->get_undo_redo().add_undo_method(sed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)) + "/" + new_name), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index()); if (p_keep_global_xform) { @@ -1262,8 +1262,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_do_method(this, "_set_owners", edited_scene, owners); - if (AnimationPlayerEditor::singleton->get_key_editor()->get_root() == node) - editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_key_editor(), "set_root", node); + if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) + editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node); editor_data->get_undo_redo().add_undo_method(new_parent, "remove_child", node); editor_data->get_undo_redo().add_undo_method(node, "set_name", former_names[ni]); @@ -1290,8 +1290,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node); editor_data->get_undo_redo().add_undo_method(node->get_parent(), "move_child", node, child_pos); editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); - if (AnimationPlayerEditor::singleton->get_key_editor()->get_root() == node) - editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_key_editor(), "set_root", node); + if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) + editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node); if (p_keep_global_xform) { if (Object::cast_to<Node2D>(node)) @@ -1392,8 +1392,8 @@ void SceneTreeDock::_delete_confirm() { editor_data->get_undo_redo().add_do_method(n->get_parent(), "remove_child", n); editor_data->get_undo_redo().add_undo_method(n->get_parent(), "add_child", n); editor_data->get_undo_redo().add_undo_method(n->get_parent(), "move_child", n, n->get_index()); - if (AnimationPlayerEditor::singleton->get_key_editor()->get_root() == n) - editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_key_editor(), "set_root", n); + if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == n) + editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", n); editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); editor_data->get_undo_redo().add_undo_reference(n); @@ -1895,8 +1895,6 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_icon_shortcut(get_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/clear_script"), TOOL_CLEAR_SCRIPT); menu->add_separator(); menu->add_icon_shortcut(get_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/rename"), TOOL_RENAME); - } else { // multi select - menu->add_icon_shortcut(get_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/batch_rename"), TOOL_BATCH_RENAME); } menu->add_icon_shortcut(get_icon("Reload", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/change_node_type"), TOOL_REPLACE); menu->add_separator(); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index dd79ae63d6..88d614ab89 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -30,6 +30,7 @@ #include "scene_tree_editor.h" +#include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor_node.h" #include "message_queue.h" @@ -90,6 +91,12 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i _update_tree(); emit_signal("node_changed"); } + } else if (p_id == BUTTON_PIN) { + + if (n->is_class("AnimationPlayer")) { + AnimationPlayerEditor::singleton->unpin(); + _update_tree(); + } } else if (p_id == BUTTON_GROUP) { if (n->is_class("CanvasItem")) { @@ -159,6 +166,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { } TreeItem *item = tree->create_item(p_parent); + item->set_text(0, p_node->get_name()); if (can_rename && !part_of_subscene /*(p_node->get_owner() == get_scene_node() || p_node==get_scene_node())*/) item->set_editable(0, true); @@ -189,7 +197,9 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (part_of_subscene) { //item->set_selectable(0,marked_selectable); - item->set_custom_color(0, get_color("disabled_font_color", "Editor")); + if (valid_types.size() == 0) { + item->set_custom_color(0, get_color("disabled_font_color", "Editor")); + } } else if (marked.has(p_node)) { @@ -219,7 +229,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { bool has_groups = p_node->has_persistent_groups(); if (has_connections && has_groups) { - item->add_button(0, get_icon("SignalsAndGroups", "EditorIcons"), BUTTON_SIGNALS, false, TTR("Node has connection(s) and group(s)\nClick to show signals dock.")); + item->add_button(0, get_icon("SignalsAndGroups", "EditorIcons"), BUTTON_SIGNALS, false, TTR("Node has connection(s) and group(s).\nClick to show signals dock.")); } else if (has_connections) { item->add_button(0, get_icon("Signals", "EditorIcons"), BUTTON_SIGNALS, false, TTR("Node has connections.\nClick to show signals dock.")); } else if (has_groups) { @@ -245,18 +255,18 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { if (!p_node->get_script().is_null()) { - item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT, false, TTR("Open script")); + item->add_button(0, get_icon("Script", "EditorIcons"), BUTTON_SCRIPT, false, TTR("Open Script")); } if (p_node->is_class("CanvasItem")) { bool is_locked = p_node->has_meta("_edit_lock_"); //_edit_group_ if (is_locked) - item->add_button(0, get_icon("Lock", "EditorIcons"), BUTTON_LOCK, false, TTR("Node is locked.\nClick to unlock")); + item->add_button(0, get_icon("Lock", "EditorIcons"), BUTTON_LOCK, false, TTR("Node is locked.\nClick to unlock it.")); bool is_grouped = p_node->has_meta("_edit_group_"); if (is_grouped) - item->add_button(0, get_icon("Group", "EditorIcons"), BUTTON_GROUP, false, TTR("Children are not selectable.\nClick to make selectable")); + item->add_button(0, get_icon("Group", "EditorIcons"), BUTTON_GROUP, false, TTR("Children are not selectable.\nClick to make selectable.")); bool v = p_node->call("is_visible"); if (v) @@ -272,7 +282,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { bool is_locked = p_node->has_meta("_edit_lock_"); if (is_locked) - item->add_button(0, get_icon("Lock", "EditorIcons"), BUTTON_LOCK, false, TTR("Node is locked.\nClick to unlock")); + item->add_button(0, get_icon("Lock", "EditorIcons"), BUTTON_LOCK, false, TTR("Node is locked.\nClick to unlock it.")); bool v = p_node->call("is_visible"); if (v) @@ -284,6 +294,13 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { p_node->connect("visibility_changed", this, "_node_visibility_changed", varray(p_node)); _update_visibility_color(p_node, item); + } else if (p_node->is_class("AnimationPlayer")) { + + bool is_pinned = AnimationPlayerEditor::singleton->get_player() == p_node && AnimationPlayerEditor::singleton->is_pinned(); + + if (is_pinned) { + item->add_button(0, get_icon("Pin", "EditorIcons"), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin.")); + } } } @@ -309,6 +326,22 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { keep = keep || child_keep; } + if (valid_types.size()) { + bool valid = false; + for (int i = 0; i < valid_types.size(); i++) { + if (p_node->is_class(valid_types[i])) { + valid = true; + break; + } + } + + if (!valid) { + //item->set_selectable(0,marked_selectable); + item->set_custom_color(0, get_color("disabled_font_color", "Editor")); + item->set_selectable(0, false); + } + } + if (!keep) { memdelete(item); return false; @@ -702,6 +735,10 @@ bool SceneTreeEditor::get_display_foreign_nodes() const { return display_foreign; } +void SceneTreeEditor::set_valid_types(const Vector<StringName> &p_valid) { + valid_types = p_valid; +} + void SceneTreeEditor::set_editor_selection(EditorSelection *p_selection) { editor_selection = p_selection; diff --git a/editor/scene_tree_editor.h b/editor/scene_tree_editor.h index 896fd6c431..c4f63f5736 100644 --- a/editor/scene_tree_editor.h +++ b/editor/scene_tree_editor.h @@ -55,6 +55,7 @@ class SceneTreeEditor : public Control { BUTTON_WARNING = 5, BUTTON_SIGNALS = 6, BUTTON_GROUPS = 7, + BUTTON_PIN = 8, }; Tree *tree; @@ -130,6 +131,8 @@ class SceneTreeEditor : public Control { List<StringName> *script_types; bool _is_script_type(const StringName &p_type) const; + Vector<StringName> valid_types; + public: void set_filter(const String &p_filter); String get_filter() const; @@ -146,6 +149,7 @@ public: void set_editor_selection(EditorSelection *p_selection); void set_show_enabled_subscene(bool p_show) { show_enabled_subscene = p_show; } + void set_valid_types(const Vector<StringName> &p_valid); void update_tree() { _update_tree(); } diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index c3e9e4ab62..45041bcf59 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -61,7 +61,7 @@ void EditorSettingsDialog::_settings_property_edited(const String &p_name) { if (full_name == "text_editor/theme/color_theme") { property_editor->get_property_editor()->update_tree(); } else if (full_name == "interface/theme/accent_color" || full_name == "interface/theme/base_color" || full_name == "interface/theme/contrast") { - EditorSettings::get_singleton()->set_manually("interface/theme/preset", 6); // set preset to Custom + EditorSettings::get_singleton()->set_manually("interface/theme/preset", "Custom"); // set preset to Custom } else if (full_name.begins_with("text_editor/highlighting")) { EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", "Custom"); } @@ -493,7 +493,7 @@ EditorSettingsDialog::EditorSettingsDialog() { //get_cancel()->set_text("Close"); timer = memnew(Timer); - timer->set_wait_time(1.5); + timer->set_wait_time(0.1); timer->connect("timeout", this, "_settings_save"); timer->set_one_shot(true); add_child(timer); diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index 2652b09763..873420b383 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -201,6 +201,9 @@ void EditorSpatialGizmo::add_unscaled_billboard(const Ref<Material> &p_material, } } + selectable_icon_size = p_scale; + mesh->set_custom_aabb(AABB(Vector3(-selectable_icon_size, -selectable_icon_size, -selectable_icon_size) * 40.0f, Vector3(selectable_icon_size, selectable_icon_size, selectable_icon_size) * 80.0f)); + ins.mesh = mesh; ins.unscaled = true; ins.billboard = true; @@ -209,13 +212,13 @@ void EditorSpatialGizmo::add_unscaled_billboard(const Ref<Material> &p_material, VS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); } + selectable_icon_size = p_scale * 2.0; + instances.push_back(ins); } -void EditorSpatialGizmo::add_collision_triangles(const Ref<TriangleMesh> &p_tmesh, const AABB &p_bounds) { - +void EditorSpatialGizmo::add_collision_triangles(const Ref<TriangleMesh> &p_tmesh) { collision_mesh = p_tmesh; - collision_mesh_bounds = p_bounds; } void EditorSpatialGizmo::add_collision_segments(const Vector<Vector3> &p_lines) { @@ -332,64 +335,74 @@ bool EditorSpatialGizmo::intersect_frustum(const Camera *p_camera, const Vector< ERR_FAIL_COND_V(!spatial_node, false); ERR_FAIL_COND_V(!valid, false); - if (collision_segments.size()) { + if (selectable_icon_size > 0.0f) { + Vector3 origin = spatial_node->get_global_transform().get_origin(); const Plane *p = p_frustum.ptr(); int fc = p_frustum.size(); - int vc = collision_segments.size(); - const Vector3 *vptr = collision_segments.ptr(); - Transform t = spatial_node->get_global_transform(); + bool any_out = false; - for (int i = 0; i < vc / 2; i++) { + for (int j = 0; j < fc; j++) { - Vector3 a = t.xform(vptr[i * 2 + 0]); - Vector3 b = t.xform(vptr[i * 2 + 1]); + if (p[j].is_point_over(origin)) { + any_out = true; + break; + } + } + + if (!any_out) + return true; + return false; + } - bool any_out = false; - for (int j = 0; j < fc; j++) { + if (collision_segments.size()) { - if (p[j].distance_to(a) > 0 && p[j].distance_to(b) > 0) { + const Plane *p = p_frustum.ptr(); + int fc = p_frustum.size(); + int vc = collision_segments.size(); + const Vector3 *vptr = collision_segments.ptr(); + Transform t = spatial_node->get_global_transform(); + + bool any_out = false; + for (int j = 0; j < fc; j++) { + for (int i = 0; i < vc; i++) { + Vector3 v = t.xform(vptr[i]); + if (p[j].is_point_over(v)) { any_out = true; break; } } - - if (!any_out) - return true; + if (any_out) break; } - return false; + if (!any_out) return true; } - if (collision_mesh_bounds.size != Vector3(0.0, 0.0, 0.0)) { + if (collision_mesh.is_valid()) { Transform t = spatial_node->get_global_transform(); - const Plane *p = p_frustum.ptr(); - int fc = p_frustum.size(); - Vector3 mins = t.xform(collision_mesh_bounds.get_position()); - Vector3 max = t.xform(collision_mesh_bounds.get_position() + collision_mesh_bounds.get_size()); - - bool any_out = false; + Vector3 mesh_scale = t.get_basis().get_scale(); + t.orthonormalize(); - for (int j = 0; j < fc; j++) { + Transform it = t.affine_inverse(); - if (p[j].distance_to(mins) > 0 || p[j].distance_to(max) > 0) { + Vector<Plane> transformed_frustum; - any_out = true; - break; - } + for (int i = 0; i < 4; i++) { + transformed_frustum.push_back(it.xform(p_frustum[i])); } - if (!any_out) + if (collision_mesh->inside_convex_shape(transformed_frustum.ptr(), transformed_frustum.size(), mesh_scale)) { return true; + } } return false; } -bool EditorSpatialGizmo::intersect_ray(const Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) { +bool EditorSpatialGizmo::intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle, bool p_sec_first) { ERR_FAIL_COND_V(!spatial_node, false); ERR_FAIL_COND_V(!valid, false); @@ -453,6 +466,43 @@ bool EditorSpatialGizmo::intersect_ray(const Camera *p_camera, const Point2 &p_p } } + if (selectable_icon_size > 0.0f) { + + Transform t = spatial_node->get_global_transform(); + t.orthonormalize(); + t.set_look_at(t.origin, p_camera->get_camera_transform().origin, Vector3(0, 1, 0)); + + float scale = t.origin.distance_to(p_camera->get_camera_transform().origin); + + if (p_camera->get_projection() == Camera::PROJECTION_ORTHOGONAL) { + float h = Math::abs(p_camera->get_size()); + scale = (h * 2.0); + } + + Point2 center = p_camera->unproject_position(t.origin); + + Transform oct = p_camera->get_camera_transform(); + + p_camera->look_at(t.origin, Vector3(0, 1, 0)); + Vector3 c0 = t.xform(Vector3(selectable_icon_size, selectable_icon_size, 0) * scale); + Vector3 c1 = t.xform(Vector3(-selectable_icon_size, -selectable_icon_size, 0) * scale); + + Point2 p0 = p_camera->unproject_position(c0); + Point2 p1 = p_camera->unproject_position(c1); + + p_camera->set_global_transform(oct); + + Rect2 rect(p0, p1 - p0); + + rect.set_position(center - rect.get_size() / 2.0); + + if (rect.has_point(p_point)) { + return true; + } + + return false; + } + if (collision_segments.size()) { Plane camp(p_camera->get_transform().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized()); @@ -664,7 +714,7 @@ void EditorSpatialGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("add_lines", "lines", "material", "billboard"), &EditorSpatialGizmo::add_lines, DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_mesh", "mesh", "billboard", "skeleton"), &EditorSpatialGizmo::add_mesh, DEFVAL(false), DEFVAL(RID())); ClassDB::bind_method(D_METHOD("add_collision_segments", "segments"), &EditorSpatialGizmo::add_collision_segments); - ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles", "bounds"), &EditorSpatialGizmo::add_collision_triangles); + ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles"), &EditorSpatialGizmo::add_collision_triangles); ClassDB::bind_method(D_METHOD("add_unscaled_billboard", "material", "default_scale"), &EditorSpatialGizmo::add_unscaled_billboard, DEFVAL(1)); ClassDB::bind_method(D_METHOD("add_handles", "handles", "billboard", "secondary"), &EditorSpatialGizmo::add_handles, DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_spatial_node", "node"), &EditorSpatialGizmo::_set_spatial_node); @@ -1272,14 +1322,15 @@ bool MeshInstanceSpatialGizmo::can_draw() const { } void MeshInstanceSpatialGizmo::redraw() { + clear(); + Ref<Mesh> m = mesh->get_mesh(); if (!m.is_valid()) return; //none Ref<TriangleMesh> tm = m->generate_triangle_mesh(); if (tm.is_valid()) { - AABB aabb; - add_collision_triangles(tm, aabb); + add_collision_triangles(tm); } } @@ -1291,6 +1342,27 @@ MeshInstanceSpatialGizmo::MeshInstanceSpatialGizmo(MeshInstance *p_mesh) { ///// +bool Sprite3DSpatialGizmo::can_draw() const { + return true; +} +void Sprite3DSpatialGizmo::redraw() { + + clear(); + + Ref<TriangleMesh> tm = sprite->generate_triangle_mesh(); + if (tm.is_valid()) { + add_collision_triangles(tm); + } +} + +Sprite3DSpatialGizmo::Sprite3DSpatialGizmo(SpriteBase3D *p_sprite) { + + sprite = p_sprite; + set_spatial_node(p_sprite); +} + +/// + void Position3DSpatialGizmo::redraw() { clear(); @@ -2540,8 +2612,9 @@ void ParticlesGizmo::redraw() { } //add_unscaled_billboard(SpatialEditorGizmos::singleton->visi,0.05); - add_unscaled_billboard(icon, 0.05); + add_handles(handles); + add_unscaled_billboard(icon, 0.05); } ParticlesGizmo::ParticlesGizmo(Particles *p_particles) { diff --git a/editor/spatial_editor_gizmos.h b/editor/spatial_editor_gizmos.h index c5dc36cb22..924f82dc16 100644 --- a/editor/spatial_editor_gizmos.h +++ b/editor/spatial_editor_gizmos.h @@ -49,6 +49,7 @@ #include "scene/3d/ray_cast.h" #include "scene/3d/reflection_probe.h" #include "scene/3d/room_instance.h" +#include "scene/3d/sprite_3d.h" #include "scene/3d/vehicle_body.h" #include "scene/3d/visibility_notifier.h" @@ -80,7 +81,6 @@ class EditorSpatialGizmo : public SpatialEditorGizmo { Vector<Vector3> collision_segments; Ref<TriangleMesh> collision_mesh; - AABB collision_mesh_bounds; struct Handle { Vector3 pos; @@ -89,6 +89,7 @@ class EditorSpatialGizmo : public SpatialEditorGizmo { Vector<Vector3> handles; Vector<Vector3> secondary_handles; + float selectable_icon_size = -1.0f; bool billboard_handle; bool valid; @@ -102,7 +103,7 @@ protected: void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false); void add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard = false, const RID &p_skeleton = RID()); void add_collision_segments(const Vector<Vector3> &p_lines); - void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh, const AABB &p_bounds = AABB()); + void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh); void add_unscaled_billboard(const Ref<Material> &p_material, float p_scale = 1); void add_handles(const Vector<Vector3> &p_handles, bool p_billboard = false, bool p_secondary = false); void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3()); @@ -118,7 +119,7 @@ protected: public: virtual Vector3 get_handle_pos(int p_idx) const; virtual bool intersect_frustum(const Camera *p_camera, const Vector<Plane> &p_frustum); - virtual bool intersect_ray(const Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false); + virtual bool intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false); void clear(); void create(); @@ -192,6 +193,18 @@ public: MeshInstanceSpatialGizmo(MeshInstance *p_mesh = NULL); }; +class Sprite3DSpatialGizmo : public EditorSpatialGizmo { + + GDCLASS(Sprite3DSpatialGizmo, EditorSpatialGizmo); + + SpriteBase3D *sprite; + +public: + virtual bool can_draw() const; + void redraw(); + Sprite3DSpatialGizmo(SpriteBase3D *p_sprite = NULL); +}; + class Position3DSpatialGizmo : public EditorSpatialGizmo { GDCLASS(Position3DSpatialGizmo, EditorSpatialGizmo); diff --git a/editor/translations/af.po b/editor/translations/af.po index 8644e20317..c5853bbb2f 100644 --- a/editor/translations/af.po +++ b/editor/translations/af.po @@ -502,7 +502,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Koppel '%s' aan '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Koppel..." #: editor/connections_dialog.cpp @@ -928,11 +928,11 @@ msgid "Move Audio Bus" msgstr "Skuif Oudio-Bus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Stoor Oudio-Bus Uitleg As..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "Ligging van Nuwe Uitleg..." #: editor/editor_audio_buses.cpp @@ -1071,11 +1071,11 @@ msgid "Updating Scene" msgstr "Toneel word Opgedateer" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Plaaslike veranderinge word gebêre..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "Toneel word opgedateer..." #: editor/editor_data.cpp @@ -1146,7 +1146,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1416,12 +1416,12 @@ msgid "Error saving resource!" msgstr "Fout tydens storing van hulpbron!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Stoor Hulpbron As..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Ek sien..." #: editor/editor_node.cpp @@ -1626,11 +1626,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1642,7 +1642,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1694,7 +1694,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1839,7 +1839,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1851,11 +1851,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1875,15 +1875,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2128,7 +2128,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2237,7 +2237,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2388,7 +2388,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2464,7 +2464,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2481,7 +2481,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2495,7 +2495,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2632,11 +2632,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2649,16 +2649,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Dupliseer" #: editor/filesystem_dock.cpp @@ -2684,7 +2684,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2750,7 +2750,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2762,7 +2762,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2778,7 +2778,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2798,7 +2798,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3213,7 +3213,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3221,7 +3221,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3290,7 +3290,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3357,7 +3357,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3544,6 +3544,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3965,7 +3966,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4170,7 +4171,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4532,7 +4533,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4629,7 +4630,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4835,15 +4836,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5294,11 +5295,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5551,7 +5548,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5619,7 +5616,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5808,7 +5805,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5899,6 +5896,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Ongeldige naam." + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Kon nie vouer skep nie." @@ -6087,8 +6089,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6116,7 +6118,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6300,7 +6302,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6396,11 +6398,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6571,7 +6573,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/ar.po b/editor/translations/ar.po index a57dc0f0cc..ccf2b97d9a 100644 --- a/editor/translations/ar.po +++ b/editor/translations/ar.po @@ -3,6 +3,7 @@ # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. # +# Adel <dragonhunter250@gmail.com>, 2018. # athomield <athomield@hotmail.com>, 2017. # Basil Al-Khateeb <basil.y.alkhateeb@gmail.com>, 2017. # Jamal Alyafei <jamal.qassim@gmail.com>, 2017. @@ -13,14 +14,15 @@ # noureldin sharaf <sharaf.noureldin@yahoo.com>, 2017. # omar anwar aglan <omar.aglan91@yahoo.com>, 2017-2018. # OWs Tetra <owstetra@gmail.com>, 2017. +# Rached Noureddine <rached.noureddine@gmail.com>, 2018. # Rex_sa <asd1234567890m@gmail.com>, 2017. # Wajdi Feki <wajdi.feki@gmail.com>, 2017. # msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-01-30 16:34+0000\n" -"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n" +"PO-Revision-Date: 2018-05-28 18:34+0000\n" +"Last-Translator: Rached Noureddine <rached.noureddine@gmail.com>\n" "Language-Team: Arabic <https://hosted.weblate.org/projects/godot-engine/" "godot/ar/>\n" "Language: ar\n" @@ -28,7 +30,7 @@ msgstr "" "Content-Transfer-Encoding: 8-bit\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 2.19-dev\n" +"X-Generator: Weblate 3.0-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -508,7 +510,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "قطع إتصال'%s' من '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "يتصل..." #: editor/connections_dialog.cpp @@ -928,11 +930,11 @@ msgid "Move Audio Bus" msgstr "ØªØØ±ÙŠÙƒ بيوس الصوت" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Ø¥ØÙظ نسق بيوس الصوت كـ..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "المكان للنسق الجديد..." #: editor/editor_audio_buses.cpp @@ -1068,11 +1070,11 @@ msgid "Updating Scene" msgstr "ÙŠÙØØ¯Ø« المشهد" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "جاري تخزين التعديلات المØÙ„ية.." +msgid "Storing local changes..." +msgstr "جاري تخزين التعديلات المØÙ„ية..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "ÙŠÙØØ¯Ø« المشهد..." #: editor/editor_data.cpp @@ -1141,8 +1143,8 @@ msgid "Show In File Manager" msgstr "أظهر ÙÙŠ مدير Ø§Ù„Ù…Ù„ÙØ§Øª" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "مجلد جديد.." +msgid "New Folder..." +msgstr "مجلد جديد..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1403,20 +1405,20 @@ msgstr "أخلاء الخرج" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "تصدير المشروع ÙØ´Ù„, رمز الخطأ % d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "خطأ ÙÙŠ ØÙظ المورد!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "ØÙظ المورد باسم..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "أنا أري.." +msgid "I see..." +msgstr "أنا أري..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1640,11 +1642,11 @@ msgid "Open Base Scene" msgstr "ÙØªØ مشهد أساسي" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "ÙØªØ سريع للمشهد..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "ÙØªØ سريع للكود..." #: editor/editor_node.cpp @@ -1656,8 +1658,8 @@ msgid "Save changes to '%s' before closing?" msgstr "هل تريد ØÙظ التغييرات إلي'%s' قبل الإغلاق؟" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "ØÙظ المشهد كـ.." +msgid "Save Scene As..." +msgstr "ØÙظ المشهد كـ..." #: editor/editor_node.cpp msgid "No" @@ -1708,7 +1710,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "هذا Ø§Ù„ÙØ¹Ù„ لا يمكن إرجاعة. إرجاع علي أية ØØ§Ù„ØŸ" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "تشغيل مشهد بسرعة..." #: editor/editor_node.cpp @@ -1865,8 +1867,8 @@ msgid "Previous tab" msgstr "التبويب السابق" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Ùلتر Ø§Ù„Ù…Ù„ÙØ§Øª.." +msgid "Filter Files..." +msgstr "Ùلتر Ø§Ù„Ù…Ù„ÙØ§Øª..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1877,12 +1879,12 @@ msgid "New Scene" msgstr "مشهد جديد" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "مشهد مورث جديد.." +msgid "New Inherited Scene..." +msgstr "مشهد مورث جديد..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Ø§ÙØªØ مشهد.." +msgid "Open Scene..." +msgstr "Ø§ÙØªØ مشهد..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1901,16 +1903,16 @@ msgid "Open Recent" msgstr "ÙÙØªØ مؤخراً" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "تØÙˆÙŠÙ„ الي.." +msgid "Convert To..." +msgstr "تØÙˆÙŠÙ„ الي..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "مكتبة الميش.." +msgid "MeshLibrary..." +msgstr "مكتبة الميش..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "مجموعة البلاط.." +msgid "TileSet..." +msgstr "مجموعة البلاط..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2171,7 +2173,7 @@ msgid "Save the currently edited resource." msgstr "ØÙظ المورد الذي يتم تعديله ØØ§Ù„يا." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "ØÙظ باسم..." #: editor/editor_node.cpp @@ -2280,8 +2282,8 @@ msgid "Creating Mesh Previews" msgstr "ÙŠÙنشئ مستعرضات الميش" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "الصورة المصغرة.." +msgid "Thumbnail..." +msgstr "الصورة المصغرة..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2433,8 +2435,8 @@ msgid "(Current)" msgstr "(Ø§Ù„ØØ§Ù„ÙŠ)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "يستقبل المرايا، من ÙØ¶Ù„Ùƒ إنتظر.." +msgid "Retrieving mirrors, please wait..." +msgstr "يستقبل المرايا، من ÙØ¶Ù„Ùƒ إنتظر..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2511,8 +2513,8 @@ msgid "Error requesting url: " msgstr "خطأ ÙÙŠ طلب الرابط: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "يتصل Ø¨Ø§Ù„Ø³Ø±ÙØ±.." +msgid "Connecting to Mirror..." +msgstr "يتصل Ø¨Ø§Ù„Ø³Ø±ÙØ±..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2528,7 +2530,7 @@ msgstr "لا يمكن الØÙ„" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "جاري الإتصال..." #: editor/export_template_manager.cpp @@ -2541,7 +2543,7 @@ msgstr "متصل" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "جار الطلب..." #: editor/export_template_manager.cpp @@ -2674,12 +2676,12 @@ msgid "Collapse all" msgstr "طوي الكل" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "إعادة تسمية.." +msgid "Rename..." +msgstr "إعادة تسمية..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "ØªØØ±ÙŠÙƒ إلي.." +msgid "Move To..." +msgstr "ØªØØ±ÙŠÙƒ إلي..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2690,15 +2692,15 @@ msgid "Instance" msgstr "نموذج" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "تعديل التبعيات.." +msgid "Edit Dependencies..." +msgstr "تعديل التبعيات..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "أظهر المÙلاك.." +msgid "View Owners..." +msgstr "أظهر المÙلاك..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "تكرير..." #: editor/filesystem_dock.cpp @@ -2724,10 +2726,10 @@ msgstr "نمذج المشهد(المشاهد) Ø§Ù„Ù…ØØ¯Ø¯Ø© كطÙÙ„ للعقد #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "ÙŠÙØØµ Ø§Ù„Ù…Ù„ÙØ§ØªØŒ\n" -"من ÙØ¶Ù„Ùƒ إنتظر.." +"من ÙØ¶Ù„Ùƒ إنتظر..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2792,8 +2794,8 @@ msgid "Import Scene" msgstr "إستيراد مشهد" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "ØØ§Ø±ÙŠ Ø¥Ø³ØªÙŠØ±Ø§Ø¯ المشهد.." +msgid "Importing Scene..." +msgstr "ØØ§Ø±ÙŠ Ø¥Ø³ØªÙŠØ±Ø§Ø¯ المشهد..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2804,8 +2806,8 @@ msgid "Generating for Mesh: " msgstr "انشاء من اجل الميش: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "تشغيل الكود Ø§Ù„Ù…ÙØ®ØµØµ.." +msgid "Running Custom Script..." +msgstr "تشغيل الكود Ø§Ù„Ù…ÙØ®ØµØµ..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2820,8 +2822,8 @@ msgid "Error running post-import script:" msgstr "خطأ ÙÙŠ تشغيل الكود الملصق- المستورد:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "جاري الØÙظ.." +msgid "Saving..." +msgstr "جاري الØÙظ..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2840,8 +2842,8 @@ msgid "Import As:" msgstr "إستيراد كـ:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "إعداد Ù…ÙØ³Ø¨Ù‚.." +msgid "Preset..." +msgstr "إعداد Ù…ÙØ³Ø¨Ù‚..." #: editor/import_dock.cpp msgid "Reimport" @@ -3258,16 +3260,16 @@ msgid "Transition Node" msgstr "عقدة التنقل" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "إستيراد Ø§Ù„ØØ±ÙƒØ©.." +msgid "Import Animations..." +msgstr "إستيراد Ø§Ù„ØØ±ÙƒØ©..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "تعديل مصاÙÙŠ العقد" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "الÙلترة.." +msgid "Filters..." +msgstr "الÙلترة..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3334,7 +3336,7 @@ msgid "Fetching:" msgstr "يجلب:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "جاري الØÙ„..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3401,7 +3403,7 @@ msgid "Site:" msgstr "الموقع:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "الدعم..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3595,6 +3597,7 @@ msgid "Use Rotation Snap" msgstr "إستعمال كبس التدوير" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "تعديل الكبس..." @@ -3691,14 +3694,12 @@ msgid "Show Guides" msgstr "أظهر الموجهات" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "إظهار الشبكة" +msgstr "إظهار المركز" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "أظهر المساعدات" +msgstr "أظهر الشاشة" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3758,11 +3759,11 @@ msgstr "Ø¥Ø¶Ø§ÙØ© %s..." #: editor/plugins/canvas_item_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ok" -msgstr "" +msgstr "ØØ³Ù†Ø§" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Cannot instantiate multiple nodes without root." -msgstr "" +msgstr "لا يمكن إنشاء عقد متعددة بدون العقدة الجذر." #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp @@ -3991,7 +3992,7 @@ msgstr "الميش ليس لديه Ø³Ø·Ø Ù„ÙƒÙŠ ينشئ ØØ¯ÙˆØ¯ منه!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "شبكة بسيطة ليست PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4022,8 +4023,8 @@ msgid "Create Convex Collision Sibling" msgstr "إنشاء متصادم Ù…ØØ¯Ø¨ قريب" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "إنشاء شبكة الخطوط العريضة .." +msgid "Create Outline Mesh..." +msgstr "إنشاء شبكة الخطوط العريضة ..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4171,7 +4172,7 @@ msgstr "إنشاء مجال Ø§Ù„Ø¥Ø±ØªÙØ§Ø¹..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Marking walkable triangles..." -msgstr "تعليم مثلثات Ø§Ù„ØªØØ±Ùƒ.." +msgstr "تعليم مثلثات Ø§Ù„ØªØØ±Ùƒ..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." @@ -4216,101 +4217,101 @@ msgstr "إنشاء Ù…ÙØ¶Ù„ع التنقل" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp msgid "Generating AABB" -msgstr "ÙŠÙنشئ AABB" +msgstr "توليد AABB" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Can only set point into a ParticlesMaterial process material" -msgstr "" +msgstr "لا يمكن إنشاء سوى نقطة ÙˆØÙŠØ¯Ø© داخل ParticlesMaterial معالج المواد" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Error loading image:" -msgstr "" +msgstr "خطأ تØÙ…يل الصورة:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "" +msgid "No pixels with transparency > 128 in image..." +msgstr "لا بيكسل Ø¨Ø´ÙØ§Ùية > 128 ÙÙŠ الصورة..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" -msgstr "" +msgstr "توليد Rect الرؤية" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Load Emission Mask" -msgstr "" +msgstr "ØÙ…Ù„ قناع الانبعاث" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Clear Emission Mask" -msgstr "" +msgstr "Ø¥Ù…Ø³Ø Ù‚Ù†Ø§Ø¹ الانبعاث" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp msgid "Particles" -msgstr "" +msgstr "جسيمات" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generated Point Count:" -msgstr "" +msgstr "عدد النقاط المولدة:" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp msgid "Generation Time (sec):" -msgstr "" +msgstr "وقت التوليد (تانية):" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Emission Mask" -msgstr "" +msgstr "قناع الانبعاث" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Capture from Pixel" -msgstr "" +msgstr "التقط من البيكسل" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Emission Colors" -msgstr "" +msgstr "الوان الانبعاث" #: editor/plugins/particles_editor_plugin.cpp msgid "Node does not contain geometry." -msgstr "" +msgstr "العقدة لا ØªØØªÙˆÙŠ Ø¹Ù„Ù‰ هندسة." #: editor/plugins/particles_editor_plugin.cpp msgid "Node does not contain geometry (faces)." -msgstr "" +msgstr "العقدة لا ØªØØªÙˆÙŠ Ø¹Ù„Ù‰ هندسة (الوجوه)." #: editor/plugins/particles_editor_plugin.cpp msgid "A processor material of type 'ParticlesMaterial' is required." -msgstr "" +msgstr "معالج المواد من نوع 'ParticlesMaterial' مطلوب." #: editor/plugins/particles_editor_plugin.cpp msgid "Faces contain no area!" -msgstr "" +msgstr "الوجوه لا ØªØØªÙˆÙŠ Ø¹Ù„Ù‰ منطقة!" #: editor/plugins/particles_editor_plugin.cpp msgid "No faces!" -msgstr "" +msgstr "لا وجوه!" #: editor/plugins/particles_editor_plugin.cpp msgid "Generate AABB" -msgstr "" +msgstr "ولد AABB" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Mesh" -msgstr "" +msgstr "أنشئ نقاط إنبعاث من الشبكة" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Node" -msgstr "" +msgstr "أنشئ نقاط إنبعاث من العقدة" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emitter" -msgstr "" +msgstr "أنشئ باعث" #: editor/plugins/particles_editor_plugin.cpp msgid "Emission Points:" -msgstr "" +msgstr "نقاط الانبعاث:" #: editor/plugins/particles_editor_plugin.cpp msgid "Surface Points" -msgstr "" +msgstr "نقاط Ø§Ù„Ù…Ø³Ø§ØØ©" #: editor/plugins/particles_editor_plugin.cpp msgid "Surface Points+Normal (Directed)" @@ -4318,15 +4319,15 @@ msgstr "" #: editor/plugins/particles_editor_plugin.cpp msgid "Volume" -msgstr "" +msgstr "ØØ¬Ù…" #: editor/plugins/particles_editor_plugin.cpp msgid "Emission Source: " -msgstr "" +msgstr "مصدر الانبعاث: " #: editor/plugins/particles_editor_plugin.cpp msgid "Generate Visibility AABB" -msgstr "" +msgstr "ولد رؤية AABB" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Remove Point from Curve" @@ -4338,39 +4339,39 @@ msgstr "" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Remove In-Control from Curve" -msgstr "" +msgstr "أزل In-Control من المنØÙ†Ù‰" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Add Point to Curve" -msgstr "" +msgstr "أض٠نقطة للمنØÙ†Ù‰" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move Point in Curve" -msgstr "" +msgstr "ØØ±Ùƒ النقطة داخل المنØÙ†Ù‰" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move In-Control in Curve" -msgstr "" +msgstr "ØØ±Ùƒ In-Control داخل المنØÙ†Ù‰" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move Out-Control in Curve" -msgstr "" +msgstr "ØØ±Ùƒ Out-Control داخل المنØÙ†Ù‰" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Select Points" -msgstr "" +msgstr "إختر النقاط" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Shift+Drag: Select Control Points" -msgstr "" +msgstr "Shift+Ø³ØØ¨: إختر نقاط التØÙƒÙ…" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Click: Add Point" -msgstr "" +msgstr "إظغط: أض٠نقطة" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp @@ -4588,7 +4589,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4685,7 +4686,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4891,15 +4892,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5350,11 +5351,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5607,7 +5604,7 @@ msgid "Remove All" msgstr "Ù…Ø³Ø Ø§Ù„ÙƒÙ„" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5635,33 +5632,39 @@ msgid "Create From Current Editor Theme" msgstr "" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "CheckBox Radio1" -msgstr "" +msgstr "صندوق تأشير Ù¡" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "CheckBox Radio2" -msgstr "" +msgstr "صندوق تأشير٢" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "Item" -msgstr "" +msgstr "عنصر" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "Check Item" -msgstr "" +msgstr "اختار العنصر" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "Checked Item" -msgstr "" +msgstr "عنصر مَضْبÙوط" #: editor/plugins/theme_editor_plugin.cpp #, fuzzy msgid "Radio Item" -msgstr "Ø¥Ø¶Ø§ÙØ© عنصر" +msgstr "عنصر انتقاء" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "Checked Radio Item" -msgstr "" +msgstr "عنصر انتقاء مَضْبÙوط" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5672,24 +5675,26 @@ msgid "Many" msgstr "" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp +#, fuzzy msgid "Options" -msgstr "" +msgstr "الخيارات" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "بكثير، خيارات عديدة،!" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" -msgstr "" +msgstr "علامة التبويب 1" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 2" -msgstr "" +msgstr "علامة التبويب 2" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 3" -msgstr "" +msgstr "علامة التبويب 3" #: editor/plugins/theme_editor_plugin.cpp msgid "Data Type:" @@ -5864,7 +5869,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5954,6 +5959,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "اسم غير صالØ." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "لا يمكن إنشاء المجلد." @@ -6140,8 +6150,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6169,7 +6179,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6353,7 +6363,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6449,11 +6459,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6624,7 +6634,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8068,6 +8078,9 @@ msgstr "" msgid "Invalid font size." msgstr "" +#~ msgid "Next" +#~ msgstr "التالي" + #~ msgid "Can't contain '/' or ':'" #~ msgstr "لا يمكن أن ÙŠØØªÙˆÙŠ Ø¹Ù„ÙŠ '/' أو ':'" @@ -8080,9 +8093,6 @@ msgstr "" #~ msgid "Can't write file." #~ msgstr "لا يمكن كتابة الملÙ." -#~ msgid "Next" -#~ msgstr "التالي" - #~ msgid "Not found!" #~ msgstr "لم يوجد!" @@ -8131,8 +8141,8 @@ msgstr "" #~ msgid "Exporting for %s" #~ msgstr "التصدير كـ %s" -#~ msgid "Setting Up.." -#~ msgstr "جاري الإعداد.." +#~ msgid "Setting Up..." +#~ msgstr "جاري الإعداد..." #~ msgid "The quick brown fox jumps over the lazy dog." #~ msgstr "أبجد هوز ØØ·ÙŠ ÙƒÙ„Ù…Ù† ØµØ¹ÙØµ قرشت ثخذ ضظغ." diff --git a/editor/translations/bg.po b/editor/translations/bg.po index 741f6ab209..9f366b3d2f 100644 --- a/editor/translations/bg.po +++ b/editor/translations/bg.po @@ -497,7 +497,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -907,11 +907,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1048,12 +1048,12 @@ msgid "Updating Scene" msgstr "ОбновÑване на Ñцената" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "ОбновÑване на Ñцената.." +msgid "Updating scene..." +msgstr "ОбновÑване на Ñцената..." #: editor/editor_data.cpp msgid "[empty]" @@ -1121,8 +1121,8 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Ðова папка.." +msgid "New Folder..." +msgstr "Ðова папка..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1384,12 +1384,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1594,11 +1594,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Бързо отварÑне на Ñцена.." +msgid "Quick Open Scene..." +msgstr "Бързо отварÑне на Ñцена..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1610,8 +1610,8 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Запазване на Ñцената като.." +msgid "Save Scene As..." +msgstr "Запазване на Ñцената като..." #: editor/editor_node.cpp msgid "No" @@ -1662,8 +1662,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Бързо пуÑкане на Ñцена.." +msgid "Quick Run Scene..." +msgstr "Бързо пуÑкане на Ñцена..." #: editor/editor_node.cpp msgid "Quit" @@ -1812,7 +1812,7 @@ msgid "Previous tab" msgstr "Предишен подпрозорец" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1824,12 +1824,12 @@ msgid "New Scene" msgstr "Ðова Ñцена" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "ОтварÑне на Ñцена.." +msgid "Open Scene..." +msgstr "ОтварÑне на Ñцена..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1848,15 +1848,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2101,7 +2101,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2211,7 +2211,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2363,7 +2363,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2420,7 +2420,7 @@ msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy msgid "Request Failed." -msgstr "Запитване.." +msgstr "Запитване..." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp @@ -2443,8 +2443,8 @@ msgstr "Имаше грешка при внаÑÑнето:" #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." -msgstr "Свързване.." +msgid "Connecting to Mirror..." +msgstr "Свързване..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2460,8 +2460,8 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Свързване.." +msgid "Connecting..." +msgstr "Свързване..." #: editor/export_template_manager.cpp #, fuzzy @@ -2475,8 +2475,8 @@ msgstr "ИзрÑзване на възелите" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Запитване.." +msgid "Requesting..." +msgstr "Запитване..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2485,7 +2485,7 @@ msgstr "" #: editor/export_template_manager.cpp #, fuzzy msgid "Connection Error" -msgstr "Свързване.." +msgstr "Свързване..." #: editor/export_template_manager.cpp msgid "SSL Handshake Error" @@ -2616,11 +2616,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2633,15 +2633,15 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "" #: editor/filesystem_dock.cpp @@ -2667,7 +2667,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2690,12 +2690,12 @@ msgstr "" #: editor/import/resource_importer_scene.cpp #, fuzzy msgid "Import as Single Scene" -msgstr "ВнаÑÑне на Ñцената.." +msgstr "ВнаÑÑне на Ñцената..." #: editor/import/resource_importer_scene.cpp #, fuzzy msgid "Import with Separate Animations" -msgstr "ВнаÑÑне на анимации.." +msgstr "ВнаÑÑне на анимации..." #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials" @@ -2736,8 +2736,8 @@ msgid "Import Scene" msgstr "ВнаÑÑне на Ñцена" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "ВнаÑÑне на Ñцената.." +msgid "Importing Scene..." +msgstr "ВнаÑÑне на Ñцената..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2748,7 +2748,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2764,7 +2764,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2785,7 +2785,7 @@ msgid "Import As:" msgstr "ВнаÑÑне като:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3203,15 +3203,15 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "ВнаÑÑне на анимации.." +msgid "Import Animations..." +msgstr "ВнаÑÑне на анимации..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3280,7 +3280,7 @@ msgid "Fetching:" msgstr "ИзтеглÑне:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3348,7 +3348,7 @@ msgid "Site:" msgstr "МÑÑто:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Поддръжка" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3538,6 +3538,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3960,7 +3961,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4167,7 +4168,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4529,7 +4530,7 @@ msgid "Import Theme" msgstr "ВнаÑÑне на тема" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4627,7 +4628,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4835,15 +4836,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5299,11 +5300,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5558,7 +5555,7 @@ msgid "Remove All" msgstr "ЗатварÑне на вÑичко" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5626,7 +5623,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5817,7 +5814,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5911,6 +5908,11 @@ msgstr "ВнеÑен проект" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Име:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "ÐеуÑпешно Ñъздаване на папка." @@ -6104,8 +6106,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6133,7 +6135,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6317,7 +6319,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6414,11 +6416,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6592,7 +6594,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8139,8 +8141,8 @@ msgstr "" #~ "Status: Needs Re-Import" #~ msgstr "Запазване и повторно внаÑÑне" -#~ msgid "Re-Import.." -#~ msgstr "Повторно внаÑÑне.." +#~ msgid "Re-Import..." +#~ msgstr "Повторно внаÑÑне..." #~ msgid "Font Import" #~ msgstr "ВнаÑÑне на шрифт" @@ -8229,5 +8231,5 @@ msgstr "" #~ msgid "Export all files in the project directory." #~ msgstr "ИзнаÑÑне на вÑички файлове в папката на проекта." -#~ msgid "Export.." -#~ msgstr "ИзнаÑÑне.." +#~ msgid "Export..." +#~ msgstr "ИзнаÑÑне..." diff --git a/editor/translations/bn.po b/editor/translations/bn.po index b8cd30b562..3d00e3450c 100644 --- a/editor/translations/bn.po +++ b/editor/translations/bn.po @@ -502,8 +502,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "'%s' à¦à¦° সাথে '%s' সংযà§à¦•à§à¦¤ করà§à¦¨" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "সংযোগ.." +msgid "Connect..." +msgstr "সংযোগ..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -927,12 +927,12 @@ msgid "Move Audio Bus" msgstr "অডিও বাস মà§à¦ করà§à¦¨" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "অডিও বাস লেআউট সেঠকরà§à¦¨.." +msgid "Save Audio Bus Layout As..." +msgstr "অডিও বাস লেআউট সেঠকরà§à¦¨..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "নতà§à¦¨ লেআউট লোকেশন.." +msgid "Location for New Layout..." +msgstr "নতà§à¦¨ লেআউট লোকেশন..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1073,12 +1073,12 @@ msgid "Updating Scene" msgstr "দৃশà§à¦¯ হাল নাগাদ হচà§à¦›à§‡" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "সà§à¦¥à¦¾à¦¨à§€à§Ÿ পরিবরà§à¦¤à¦¨-সমূহ সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡.." +msgid "Storing local changes..." +msgstr "সà§à¦¥à¦¾à¦¨à§€à§Ÿ পরিবরà§à¦¤à¦¨-সমূহ সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "দৃশà§à¦¯ হাল নাগাদ হচà§à¦›à§‡.." +msgid "Updating scene..." +msgstr "দৃশà§à¦¯ হাল নাগাদ হচà§à¦›à§‡..." #: editor/editor_data.cpp #, fuzzy @@ -1151,7 +1151,7 @@ msgstr "ফাইল-মà§à¦¯à¦¾à¦¨à§‡à¦œà¦¾à¦°à§‡ দেখà§à¦¨" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp #, fuzzy -msgid "New Folder.." +msgid "New Folder..." msgstr "ফোলà§à¦¡à¦¾à¦° তৈরি করà§à¦¨" #: editor/editor_file_dialog.cpp @@ -1437,13 +1437,13 @@ msgid "Error saving resource!" msgstr "রিসোরà§à¦¸ সংরকà§à¦·à¦£à§‡ সমসà§à¦¯à¦¾ হয়েছে!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "রিসোরà§à¦¸ à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨.." +msgid "Save Resource As..." +msgstr "রিসোরà§à¦¸ à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "বà§à¦à¦²à¦¾à¦®.." +msgid "I see..." +msgstr "বà§à¦à¦²à¦¾à¦®..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1680,12 +1680,12 @@ msgid "Open Base Scene" msgstr "গোড়ার দৃশà§à¦¯ খà§à¦²à§à¦¨" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "দà§à¦°à§à¦¤ দৃশà§à¦¯ খà§à¦²à§à¦¨.." +msgid "Quick Open Scene..." +msgstr "দà§à¦°à§à¦¤ দৃশà§à¦¯ খà§à¦²à§à¦¨..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "দà§à¦°à§à¦¤ সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ খà§à¦²à§à¦¨.." +msgid "Quick Open Script..." +msgstr "দà§à¦°à§à¦¤ সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ খà§à¦²à§à¦¨..." #: editor/editor_node.cpp #, fuzzy @@ -1697,8 +1697,8 @@ msgid "Save changes to '%s' before closing?" msgstr "'%s' বনà§à¦§ করার পূরà§à¦¬à§‡ পরিবরà§à¦¤à¦¨à¦¸à¦®à§‚হ সংরকà§à¦·à¦£ করবেন?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "দৃশà§à¦¯ à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨.." +msgid "Save Scene As..." +msgstr "দৃশà§à¦¯ à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨..." #: editor/editor_node.cpp #, fuzzy @@ -1752,8 +1752,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "à¦à¦‡ কাজটি অসমà§à¦ªà¦¾à¦¦à¦¿à¦¤ করা সমà§à¦à¦¬ হবে না। তবà§à¦“ পà§à¦°à¦¤à§à¦¯à¦¾à¦¬à¦°à§à¦¤à¦¨ করবেন?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "দà§à¦°à§à¦¤ দৃশà§à¦¯ চালান.." +msgid "Quick Run Scene..." +msgstr "দà§à¦°à§à¦¤ দৃশà§à¦¯ চালান..." #: editor/editor_node.cpp msgid "Quit" @@ -1915,8 +1915,8 @@ msgstr "পূরà§à¦¬à§‡à¦° টà§à¦¯à¦¾à¦¬" #: editor/editor_node.cpp #, fuzzy -msgid "Filter Files.." -msgstr "দà§à¦°à§à¦¤ ফাইলসমূহ ফিলà§à¦Ÿà¦¾à¦° করà§à¦¨.." +msgid "Filter Files..." +msgstr "দà§à¦°à§à¦¤ ফাইলসমূহ ফিলà§à¦Ÿà¦¾à¦° করà§à¦¨..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1927,12 +1927,12 @@ msgid "New Scene" msgstr "নতà§à¦¨ দৃশà§à¦¯" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "নতà§à¦¨ উতà§à¦¤à¦°à¦¾à¦§à¦¿à¦•ারী দৃশà§à¦¯.." +msgid "New Inherited Scene..." +msgstr "নতà§à¦¨ উতà§à¦¤à¦°à¦¾à¦§à¦¿à¦•ারী দৃশà§à¦¯..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "দৃশà§à¦¯ খà§à¦²à§à¦¨.." +msgid "Open Scene..." +msgstr "দৃশà§à¦¯ খà§à¦²à§à¦¨..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1951,16 +1951,16 @@ msgid "Open Recent" msgstr "সামà§à¦ªà§à¦°à¦¤à¦¿à¦•সমূহ খà§à¦²à§à¦¨" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨.." +msgid "Convert To..." +msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary (মেস-লাইবà§à¦°à§‡à¦°à¦¿).." +msgid "MeshLibrary..." +msgstr "MeshLibrary (মেস-লাইবà§à¦°à§‡à¦°à¦¿)..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet (টাইল-সেট).." +msgid "TileSet..." +msgstr "TileSet (টাইল-সেট)..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2228,8 +2228,8 @@ msgid "Save the currently edited resource." msgstr "à¦à¦‡-মà§à¦¹à§‚রà§à¦¤à§‡ সমà§à¦ªà¦¾à¦¦à¦¿à¦¤ রিসোরà§à¦¸à¦Ÿà¦¿ সংরকà§à¦·à¦£ করà§à¦¨à¥¤" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨.." +msgid "Save As..." +msgstr "à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2300,7 +2300,7 @@ msgstr "à¦à¦•টি সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ খà§à¦²à§à¦¨ à¦à¦¬à¦‚ চঠ#: editor/editor_node.cpp #, fuzzy msgid "New Inherited" -msgstr "নতà§à¦¨ উতà§à¦¤à¦°à¦¾à¦§à¦¿à¦•ারী দৃশà§à¦¯.." +msgstr "নতà§à¦¨ উতà§à¦¤à¦°à¦¾à¦§à¦¿à¦•ারী দৃশà§à¦¯..." #: editor/editor_node.cpp msgid "Load Errors" @@ -2346,8 +2346,8 @@ msgid "Creating Mesh Previews" msgstr "মেস লাইবà§à¦°à§‡à¦°à¦¿ তৈরি হচà§à¦›à§‡" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "থামà§à¦¬à¦¨à§‡à¦‡à¦².." +msgid "Thumbnail..." +msgstr "থামà§à¦¬à¦¨à§‡à¦‡à¦²..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2507,8 +2507,8 @@ msgid "(Current)" msgstr "বরà§à¦¤à¦®à¦¾à¦¨:" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "মিরর রিটà§à¦°à¦¾à¦‡à¦ করা হচà§à¦›à§‡, দযা করে অপেকà§à¦·à¦¾ করà§à¦¨.." +msgid "Retrieving mirrors, please wait..." +msgstr "মিরর রিটà§à¦°à¦¾à¦‡à¦ করা হচà§à¦›à§‡, দযা করে অপেকà§à¦·à¦¾ করà§à¦¨..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2557,7 +2557,7 @@ msgstr "সমসà§à¦¯à¦¾ সমাধানে বà§à¦¯à¦°à§à¦¥à¥¤" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy msgid "Can't connect." -msgstr "সংযোগ.." +msgstr "সংযোগ..." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp @@ -2592,8 +2592,8 @@ msgstr "à¦à¦Ÿà¦²à¦¾à¦¸/মানচিতà§à¦°à¦¾à¦¬à¦²à§€ সংরকà§à¦·à #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." -msgstr "সংযোগ.." +msgid "Connecting to Mirror..." +msgstr "সংযোগ..." #: editor/export_template_manager.cpp #, fuzzy @@ -2603,7 +2603,7 @@ msgstr "সংযোগ বিচà§à¦›à¦¿à¦¨à§à¦¨ করà§à¦¨" #: editor/export_template_manager.cpp #, fuzzy msgid "Resolving" -msgstr "সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡.." +msgstr "সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡..." #: editor/export_template_manager.cpp msgid "Can't Resolve" @@ -2612,13 +2612,13 @@ msgstr "কাংখিত সমাধানে বà§à¦¯à¦°à§à¦¥" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Connecting.." -msgstr "সংযোগ.." +msgid "Connecting..." +msgstr "সংযোগ..." #: editor/export_template_manager.cpp #, fuzzy msgid "Can't Connect" -msgstr "সংযোগ.." +msgstr "সংযোগ..." #: editor/export_template_manager.cpp #, fuzzy @@ -2628,7 +2628,7 @@ msgstr "সংযোগ" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Requesting.." +msgid "Requesting..." msgstr "পরীকà§à¦·à¦¾à¦®à§‚লক উৎস" #: editor/export_template_manager.cpp @@ -2639,7 +2639,7 @@ msgstr "নীচে" #: editor/export_template_manager.cpp #, fuzzy msgid "Connection Error" -msgstr "সংযোগ.." +msgstr "সংযোগ..." #: editor/export_template_manager.cpp #, fuzzy @@ -2747,7 +2747,7 @@ msgstr "বà§à¦¯à¦¬à¦¹à§ƒà¦¤ নামে অগà§à¦°à¦¹à¦£à¦¯à§‹à¦—à§à¦¯ অ #: editor/filesystem_dock.cpp #, fuzzy msgid "No name provided." -msgstr "পà§à¦¨à¦ƒà¦¨à¦¾à¦®à¦•রণ করà§à¦¨ অথবা সরান.." +msgstr "পà§à¦¨à¦ƒà¦¨à¦¾à¦®à¦•রণ করà§à¦¨ অথবা সরান..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2790,12 +2790,12 @@ msgstr "কলাপà§à¦¸ করà§à¦¨" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Rename.." +msgid "Rename..." msgstr "পà§à¦¨à¦ƒà¦¨à¦¾à¦®à¦•রণ করà§à¦¨" #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "à¦à¦–ানে সরান.." +msgid "Move To..." +msgstr "à¦à¦–ানে সরান..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2807,16 +2807,16 @@ msgid "Instance" msgstr "ইনসà§à¦Ÿà§à¦¯à¦¾à¦¨à§à¦¸" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "নিরà§à¦à¦°à¦¤à¦¾à¦¸à¦®à§‚হ সমà§à¦ªà¦¾à¦¦à¦¨ করà§à¦¨.." +msgid "Edit Dependencies..." +msgstr "নিরà§à¦à¦°à¦¤à¦¾à¦¸à¦®à§‚হ সমà§à¦ªà¦¾à¦¦à¦¨ করà§à¦¨..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "সà§à¦¬à¦¤à§à¦¬à¦¾à¦§à¦¿à¦•ারীদের দেখà§à¦¨.." +msgid "View Owners..." +msgstr "সà§à¦¬à¦¤à§à¦¬à¦¾à¦§à¦¿à¦•ারীদের দেখà§à¦¨..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "ডà§à¦ªà§à¦²à¦¿à¦•েট" #: editor/filesystem_dock.cpp @@ -2842,10 +2842,10 @@ msgstr "নিরà§à¦¬à¦¾à¦šà¦¿à¦¤ দৃশà§à¦¯(সমূহ)-কে নিà #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "ফাইল সà§à¦•à§à¦¯à¦¾à¦¨ করা হচà§à¦›à§‡,\n" -"অনà§à¦—à§à¦°à¦¹à¦ªà§‚রà§à¦¬à¦• অপেকà§à¦·à¦¾ করà§à¦¨.." +"অনà§à¦—à§à¦°à¦¹à¦ªà§‚রà§à¦¬à¦• অপেকà§à¦·à¦¾ করà§à¦¨..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2867,12 +2867,12 @@ msgstr "গà§à¦°à§à¦ª/দল হতে অপসারণ করà§à¦¨" #: editor/import/resource_importer_scene.cpp #, fuzzy msgid "Import as Single Scene" -msgstr "দৃশà§à¦¯ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করা হচà§à¦›à§‡.." +msgstr "দৃশà§à¦¯ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করা হচà§à¦›à§‡..." #: editor/import/resource_importer_scene.cpp #, fuzzy msgid "Import with Separate Animations" -msgstr "অà§à¦¯à¦¾à¦¨à¦¿à¦®à§‡à¦¶à¦¨à¦¸à¦®à§‚হ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨.." +msgstr "অà§à¦¯à¦¾à¦¨à¦¿à¦®à§‡à¦¶à¦¨à¦¸à¦®à§‚হ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨..." #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials" @@ -2913,8 +2913,8 @@ msgid "Import Scene" msgstr "দৃশà§à¦¯ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "দৃশà§à¦¯ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করা হচà§à¦›à§‡.." +msgid "Importing Scene..." +msgstr "দৃশà§à¦¯ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করা হচà§à¦›à§‡..." #: editor/import/resource_importer_scene.cpp #, fuzzy @@ -2927,8 +2927,8 @@ msgid "Generating for Mesh: " msgstr "AABB উৎপনà§à¦¨ করà§à¦¨" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "সà§à¦¬à¦¨à¦¿à¦°à§à¦®à¦¿à¦¤ সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ চালানো হচà§à¦›à§‡.." +msgid "Running Custom Script..." +msgstr "সà§à¦¬à¦¨à¦¿à¦°à§à¦®à¦¿à¦¤ সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ চালানো হচà§à¦›à§‡..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2943,8 +2943,8 @@ msgid "Error running post-import script:" msgstr "ইমà§à¦ªà§‹à¦°à§à¦Ÿ-পরবরà§à¦¤à§€ সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ চালানোয় সমসà§à¦¯à¦¾ হয়েছে:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡.." +msgid "Saving..." +msgstr "সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2965,8 +2965,8 @@ msgid "Import As:" msgstr "ইমà§à¦ªà§‹à¦°à§à¦Ÿ" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "পà§à¦°à¦¿à¦¸à§‡à¦Ÿ.." +msgid "Preset..." +msgstr "পà§à¦°à¦¿à¦¸à§‡à¦Ÿ..." #: editor/import_dock.cpp #, fuzzy @@ -3392,16 +3392,16 @@ msgid "Transition Node" msgstr "টà§à¦°à§à¦¯à¦¾à¦¨à¦œà¦¿à¦¶à¦¨ নোড" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "অà§à¦¯à¦¾à¦¨à¦¿à¦®à§‡à¦¶à¦¨à¦¸à¦®à§‚হ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨.." +msgid "Import Animations..." +msgstr "অà§à¦¯à¦¾à¦¨à¦¿à¦®à§‡à¦¶à¦¨à¦¸à¦®à§‚হ ইমà§à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "নোড ফিলà§à¦Ÿà¦¾à¦°à¦¸à¦®à§‚হ সমà§à¦ªà¦¾à¦¦à¦¨ করà§à¦¨" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "ফিলà§à¦Ÿà¦¾à¦°à¦¸à¦®à§‚হ.." +msgid "Filters..." +msgstr "ফিলà§à¦Ÿà¦¾à¦°à¦¸à¦®à§‚হ..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3474,8 +3474,8 @@ msgstr "খà§à¦à¦œà§‡ আনার চেসà§à¦Ÿà¦¾ চলছে:" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Resolving.." -msgstr "সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡.." +msgid "Resolving..." +msgstr "সংরকà§à¦·à¦¿à¦¤ হচà§à¦›à§‡..." #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy @@ -3543,8 +3543,8 @@ msgid "Site:" msgstr "ওয়েবসাইট:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "সমরà§à¦¥à¦¨.." +msgid "Support..." +msgstr "সমরà§à¦¥à¦¨..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3743,9 +3743,10 @@ msgid "Use Rotation Snap" msgstr "ঘূরà§à¦£à¦¨ সà§à¦¨à§à¦¯à¦¾à¦ª বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp #, fuzzy msgid "Configure Snap..." -msgstr "সà§à¦¨à§à¦¯à¦¾à¦ª কনফিগার করà§à¦¨.." +msgstr "সà§à¦¨à§à¦¯à¦¾à¦ª কনফিগার করà§à¦¨..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -4189,8 +4190,8 @@ msgid "Create Convex Collision Sibling" msgstr "কনà¦à§‡à¦•à§à¦¸ কলিশ়ন সহোদর তৈরি করà§à¦¨" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "পà§à¦°à¦¾à¦¨à§à¦¤à¦°à§‡à¦–া মেস তৈরি করà§à¦¨.." +msgid "Create Outline Mesh..." +msgstr "পà§à¦°à¦¾à¦¨à§à¦¤à¦°à§‡à¦–া মেস তৈরি করà§à¦¨..." #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy @@ -4334,7 +4335,7 @@ msgstr "কনফিগারেশন তৈরি করা হচà§à¦›à§‡... #: editor/plugins/navigation_mesh_generator.cpp msgid "Calculating grid size..." -msgstr "গà§à¦°à¦¿à¦¡ সাইজ হিসাব করা হচà§à¦›à§‡.." +msgstr "গà§à¦°à¦¿à¦¡ সাইজ হিসাব করা হচà§à¦›à§‡..." #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy @@ -4344,7 +4345,7 @@ msgstr "লাইটের ওকটà§à¦°à§€ (octree) তৈরি করা ঠ#: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Marking walkable triangles..." -msgstr "অনà§à¦¬à¦¾à¦¦-সমà§à¦à¦¬ শবà§à¦¦à¦®à¦¾à¦²à¦¾/বাকà§à¦¯-সমূহ.." +msgstr "অনà§à¦¬à¦¾à¦¦-সমà§à¦à¦¬ শবà§à¦¦à¦®à¦¾à¦²à¦¾/বাকà§à¦¯-সমূহ..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." @@ -4367,7 +4368,7 @@ msgstr "ওকটà§à¦°à§€ (octree) গঠনবিনà§à¦¯à¦¾à¦¸ তৈরি #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Creating polymesh..." -msgstr "পà§à¦°à¦¾à¦¨à§à¦¤à¦°à§‡à¦–া মেস তৈরি করà§à¦¨.." +msgstr "পà§à¦°à¦¾à¦¨à§à¦¤à¦°à§‡à¦–া মেস তৈরি করà§à¦¨..." #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy @@ -4406,8 +4407,8 @@ msgid "Error loading image:" msgstr "ছবি লোডে সমসà§à¦¯à¦¾ হয়েছে:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "সà§à¦¬à¦šà§à¦›à¦¤à¦¾à¦¸à¦¹ কোনো পিকà§à¦¸à§‡à¦² নেই > ছবিতে ১২৮.." +msgid "No pixels with transparency > 128 in image..." +msgstr "সà§à¦¬à¦šà§à¦›à¦¤à¦¾à¦¸à¦¹ কোনো পিকà§à¦¸à§‡à¦² নেই > ছবিতে ১২৮..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4790,8 +4791,8 @@ msgid "Import Theme" msgstr "থিম ইমà§à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "থিম à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨.." +msgid "Save Theme As..." +msgstr "থিম à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4891,8 +4892,8 @@ msgstr "ফেবরিট/পà§à¦°à¦¿à¦¯à¦¼-সমূহ অদলবদল/ঠ#: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "খà§à¦à¦œà§à¦¨.." +msgid "Find..." +msgstr "খà§à¦à¦œà§à¦¨..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -5095,28 +5096,28 @@ msgstr "পূরà§à¦¬à§‡à¦° বিরতিবিনà§à¦¦à§à¦¤à§‡ যান" #: editor/plugins/script_text_editor.cpp #, fuzzy msgid "Convert To Uppercase" -msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨.." +msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨..." #: editor/plugins/script_text_editor.cpp #, fuzzy msgid "Convert To Lowercase" -msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨.." +msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨..." #: editor/plugins/script_text_editor.cpp msgid "Find Previous" msgstr "পূরà§à¦¬à§‡ খà§à¦à¦œà§à¦¨" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "পà§à¦°à¦¤à¦¿à¦¸à§à¦¥à¦¾à¦ªà¦¨.." +msgid "Replace..." +msgstr "পà§à¦°à¦¤à¦¿à¦¸à§à¦¥à¦¾à¦ªà¦¨..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "ফাংশনে যান.." +msgid "Goto Function..." +msgstr "ফাংশনে যান..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "লাইনে যান.." +msgid "Goto Line..." +msgstr "লাইনে যান..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5590,12 +5591,8 @@ msgid "Transform" msgstr "রà§à¦ªà¦¾à¦¨à§à¦¤à¦°" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "সà§à¦¨à§à¦¯à¦¾à¦ª কনফিগার করà§à¦¨.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "রà§à¦ªà¦¾à¦¨à§à¦¤à¦°à§‡à¦° à¦à¦° সংলাপ.." +msgid "Transform Dialog..." +msgstr "রà§à¦ªà¦¾à¦¨à§à¦¤à¦°à§‡à¦° à¦à¦° সংলাপ..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5854,8 +5851,8 @@ msgid "Remove All" msgstr "অপসারণ করà§à¦¨" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "থিম à¦à¦¡à¦¿à¦Ÿ করà§à¦¨.." +msgid "Edit theme..." +msgstr "থিম à¦à¦¡à¦¿à¦Ÿ করà§à¦¨..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5925,7 +5922,8 @@ msgid "Options" msgstr "সিদà§à¦§à¦¾à¦¨à§à¦¤à¦¸à¦®à§‚হ" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +#, fuzzy +msgid "Has,Many,Options" msgstr "আছে,অনেক,à¦à¦•াধিক,সিদà§à¦§à¦¾à¦¨à§à¦¤à¦¸à¦®à§‚হ!" #: editor/plugins/theme_editor_plugin.cpp @@ -6055,7 +6053,7 @@ msgstr "দৃশà§à¦¯ হতে à¦à¦•তà§à¦°à¦¿à¦¤ করবেন?" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet (টাইল-সেট).." +msgstr "TileSet (টাইল-সেট)..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -6123,11 +6121,11 @@ msgstr "" #: editor/project_export.cpp #, fuzzy msgid "Presets" -msgstr "পà§à¦°à¦¿à¦¸à§‡à¦Ÿ.." +msgstr "পà§à¦°à¦¿à¦¸à§‡à¦Ÿ..." #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "সংযোগ.." +msgid "Add..." +msgstr "সংযোগ..." #: editor/project_export.cpp msgid "Resources" @@ -6236,6 +6234,11 @@ msgstr "পà§à¦°à¦•লà§à¦ª ইমà§à¦ªà§‹à¦°à§à¦Ÿ করা হয়েছে #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "পà§à¦°à¦•লà§à¦ªà§‡à¦° নাম:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "ফোলà§à¦¡à¦¾à¦° তৈরী করা সমà§à¦à¦¬ হয়নি।" @@ -6332,7 +6335,7 @@ msgstr "নামহীন পà§à¦°à¦•লà§à¦ª" #: editor/project_manager.cpp #, fuzzy msgid "Can't open project" -msgstr "সংযোগ.." +msgstr "সংযোগ..." #: editor/project_manager.cpp msgid "Are you sure to open more than one project?" @@ -6418,7 +6421,7 @@ msgstr "পà§à¦¨à¦°à¦¾à¦°à¦®à§à¦ (সেঃ):" #: editor/project_manager.cpp #, fuzzy msgid "Can't run project" -msgstr "সংযোগ.." +msgstr "সংযোগ..." #: editor/project_manager.cpp msgid "" @@ -6444,8 +6447,8 @@ msgstr "মাউসের বোতাম" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6473,8 +6476,8 @@ msgid "Control+" msgstr "কনà§à¦Ÿà§à¦°à§‹à¦²+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "যেকোনো কী/চাবি চাপà§à¦¨.." +msgid "Press a Key..." +msgstr "যেকোনো কী/চাবি চাপà§à¦¨..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6667,8 +6670,8 @@ msgid "Property:" msgstr "পà§à¦°à¦ªà¦¾à¦°à§à¦Ÿà¦¿:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "ওà¦à¦¾à¦°à¦°à¦¾à¦‡à¦¡.." +msgid "Override For..." +msgstr "ওà¦à¦¾à¦°à¦°à¦¾à¦‡à¦¡..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6768,12 +6771,12 @@ msgid "Easing Out-In" msgstr "গমন-আগমন সহজ/আলগা করন" #: editor/property_editor.cpp -msgid "File.." -msgstr "ফাইল.." +msgid "File..." +msgstr "ফাইল..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "পথ.." +msgid "Dir..." +msgstr "পথ..." #: editor/property_editor.cpp msgid "Assign" @@ -6805,7 +6808,7 @@ msgstr "ফাইলসিসà§à¦Ÿà§‡à¦®" #: editor/property_editor.cpp #, fuzzy msgid "Convert To %s" -msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨.." +msgstr "à¦à¦¤à§‡ রূপানà§à¦¤à¦° করà§à¦¨..." #: editor/property_editor.cpp msgid "Error loading file: Not a resource!" @@ -6953,8 +6956,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "ইনà§à¦¸à¦Ÿà§à¦¯à¦¾à¦¨à§à¦¸ করা দৃশà§à¦¯à§‡ à¦à¦Ÿà¦¿ করা সমà§à¦à¦¬ হবে না।" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "নতà§à¦¨ দৃশà§à¦¯ à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨.." +msgid "Save New Scene As..." +msgstr "নতà§à¦¨ দৃশà§à¦¯ à¦à¦‡à¦°à§‚পে সংরকà§à¦·à¦£ করà§à¦¨..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7497,12 +7500,12 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Platform" -msgstr "পà§à¦²à¦¾à¦Ÿà¦«à¦°à§à¦®à§‡ পà§à¦°à¦¤à¦¿à¦²à¦¿à¦ªà¦¿ করà§à¦¨.." +msgstr "পà§à¦²à¦¾à¦Ÿà¦«à¦°à§à¦®à§‡ পà§à¦°à¦¤à¦¿à¦²à¦¿à¦ªà¦¿ করà§à¦¨..." #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Dynamic Library" -msgstr "MeshLibrary (মেস-লাইবà§à¦°à§‡à¦°à¦¿).." +msgstr "MeshLibrary (মেস-লাইবà§à¦°à§‡à¦°à¦¿)..." #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" @@ -7516,7 +7519,7 @@ msgstr "জিডিনà§à¦¯à¦¾à¦Ÿà¦¿à¦" #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy msgid "Library" -msgstr "MeshLibrary (মেস-লাইবà§à¦°à§‡à¦°à¦¿).." +msgstr "MeshLibrary (মেস-লাইবà§à¦°à§‡à¦°à¦¿)..." #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy @@ -8544,6 +8547,13 @@ msgstr "ফনà§à¦Ÿ তà§à¦²à¦¤à§‡/লোডে সমসà§à¦¯à¦¾ হয়েঠmsgid "Invalid font size." msgstr "ফনà§à¦Ÿà§‡à¦° আকার অগà§à¦°à¦¹à¦¨à¦¯à§‹à¦—à§à¦¯à¥¤" +#, fuzzy +#~ msgid "Previous" +#~ msgstr "পূরà§à¦¬à§‡à¦° টà§à¦¯à¦¾à¦¬" + +#~ msgid "Next" +#~ msgstr "পরবরà§à¦¤à§€" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "অকারà§à¦¯à¦•র অà§à¦¯à¦¾à¦•শন ('/' বা ':' ছাড়া কিছà§à¦‡ যাবে না)।" @@ -8573,9 +8583,6 @@ msgstr "ফনà§à¦Ÿà§‡à¦° আকার অগà§à¦°à¦¹à¦¨à¦¯à§‹à¦—à§à¦¯à¥¤" #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "পà§à¦°à¦•লà§à¦ªà§‡à¦° পথে engine.cfg তৈরি করা সমà§à¦à¦¬ হয়নি।" -#~ msgid "Next" -#~ msgstr "পরবরà§à¦¤à§€" - #~ msgid "Not found!" #~ msgstr "খà§à¦à¦œà§‡ পাওয়া যায়নি!" @@ -8717,8 +8724,8 @@ msgstr "ফনà§à¦Ÿà§‡à¦° আকার অগà§à¦°à¦¹à¦¨à¦¯à§‹à¦—à§à¦¯à¥¤" #~ msgid "Exporting for %s" #~ msgstr "%s à¦à¦° জনà§à¦¯ à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ (export) হচà§à¦›à§‡" -#~ msgid "Setting Up.." -#~ msgstr "সà§à¦¥à¦¾à¦ªà¦¿à¦¤/বিনà§à¦¯à¦¸à§à¦¤ হচà§à¦›à§‡.." +#~ msgid "Setting Up..." +#~ msgstr "সà§à¦¥à¦¾à¦ªà¦¿à¦¤/বিনà§à¦¯à¦¸à§à¦¤ হচà§à¦›à§‡..." #~ msgid "Error loading scene." #~ msgstr "দৃশà§à¦¯ লোডে সমসà§à¦¯à¦¾ হয়েছে।" @@ -8772,8 +8779,8 @@ msgstr "ফনà§à¦Ÿà§‡à¦° আকার অগà§à¦°à¦¹à¦¨à¦¯à§‹à¦—à§à¦¯à¥¤" #~ msgid "Info" #~ msgstr "তথà§à¦¯" -#~ msgid "Re-Import.." -#~ msgstr "পà§à¦¨-ইমà§à¦ªà§‹à¦°à§à¦Ÿ.." +#~ msgid "Re-Import..." +#~ msgstr "পà§à¦¨-ইমà§à¦ªà§‹à¦°à§à¦Ÿ..." #~ msgid "No bit masks to import!" #~ msgstr "ইমà§à¦ªà§‹à¦°à§à¦Ÿ করার জনà§à¦¯ কোনো বিট মাসà§à¦• নেই!" @@ -9170,14 +9177,14 @@ msgstr "ফনà§à¦Ÿà§‡à¦° আকার অগà§à¦°à¦¹à¦¨à¦¯à§‹à¦—à§à¦¯à¥¤" #~ msgid "Zoom (%):" #~ msgstr "জà§à¦®à§ (%):" -#~ msgid "Skeleton.." -#~ msgstr "সà§à¦•েলেটন/কাঠাম.." +#~ msgid "Skeleton..." +#~ msgstr "সà§à¦•েলেটন/কাঠাম..." #~ msgid "Zoom Reset" #~ msgstr "জà§à¦®à§ পà§à¦¨:সà§à¦¥à¦¾à¦ªà¦¨ করà§à¦¨" -#~ msgid "Zoom Set.." -#~ msgstr "জà§à¦®à§ নিরà§à¦§à¦¾à¦°à¦£ করà§à¦¨.." +#~ msgid "Zoom Set..." +#~ msgstr "জà§à¦®à§ নিরà§à¦§à¦¾à¦°à¦£ করà§à¦¨..." #~ msgid "Set a Value" #~ msgstr "à¦à¦•টি মান নিরà§à¦§à¦¾à¦°à¦£ করà§à¦¨" @@ -9642,8 +9649,8 @@ msgstr "ফনà§à¦Ÿà§‡à¦° আকার অগà§à¦°à¦¹à¦¨à¦¯à§‹à¦—à§à¦¯à¥¤" #~ msgid "Export Project PCK" #~ msgstr "পà§à¦°à¦•লà§à¦ªà§‡à¦° PCK à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ করà§à¦¨" -#~ msgid "Export.." -#~ msgstr "à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ.." +#~ msgid "Export..." +#~ msgstr "à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ..." #~ msgid "Project Export" #~ msgstr "à¦à¦•à§à¦¸à¦ªà§‹à¦°à§à¦Ÿ পà§à¦°à¦•লà§à¦ª" diff --git a/editor/translations/ca.po b/editor/translations/ca.po index a1f92b5316..d2bffb0f84 100644 --- a/editor/translations/ca.po +++ b/editor/translations/ca.po @@ -2,14 +2,13 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # BennyBeat <bennybeat@gmail.com>, 2017. +# Javier Ocampos <xavier.ocampos@gmail.com>, 2018. # Roger Blanco Ribera <roger.blancoribera@gmail.com>, 2016-2018. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-03-10 03:34+0000\n" +"PO-Revision-Date: 2018-06-08 03:41+0000\n" "Last-Translator: Roger Blanco Ribera <roger.blancoribera@gmail.com>\n" "Language-Team: Catalan <https://hosted.weblate.org/projects/godot-engine/" "godot/ca/>\n" @@ -17,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.20-dev\n" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -498,8 +497,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Desconnecta '%s' de '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Connecta.." +msgid "Connect..." +msgstr "Connecta..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -919,11 +918,11 @@ msgid "Move Audio Bus" msgstr "Mou el Bus d'Àudio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Anomena i Desa el Disseny del Bus d'Àudio..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "Ubicació del Nou Disseny..." #: editor/editor_audio_buses.cpp @@ -1065,12 +1064,12 @@ msgid "Updating Scene" msgstr "Actualitzant l'Escena" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Emmagatzemant canvis locals..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "S'està actualitzant l'escena.." +msgid "Updating scene..." +msgstr "S'està actualitzant l'escena..." #: editor/editor_data.cpp msgid "[empty]" @@ -1138,8 +1137,8 @@ msgid "Show In File Manager" msgstr "Mostra en el Gestor de Fitxers" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nou Directori.." +msgid "New Folder..." +msgstr "Nou Directori..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1400,19 +1399,19 @@ msgstr "Buida la Sortida" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "L'exportació del projecte ha fallat amb el codi d'error %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Error en desar recurs!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Anomena i Desa el Recurs..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Vaja..." #: editor/editor_node.cpp @@ -1642,11 +1641,11 @@ msgid "Open Base Scene" msgstr "Obre una Escena Base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "Obertura Rà pida d'Escenes..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "Obertura Rà pida d'Scripts..." #: editor/editor_node.cpp @@ -1658,7 +1657,7 @@ msgid "Save changes to '%s' before closing?" msgstr "Desar els canvis a '%s' abans de tancar?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "Anomena i Desa l'Escena..." #: editor/editor_node.cpp @@ -1711,7 +1710,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Aquesta acció no es pot desfer. N'esteu segur?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "Execució Rà pida de l'Escena..." #: editor/editor_node.cpp @@ -1872,7 +1871,7 @@ msgid "Previous tab" msgstr "Pestanya Anterior" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Filtrat de Fitxers..." #: editor/editor_node.cpp @@ -1884,11 +1883,11 @@ msgid "New Scene" msgstr "Nova Escena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Nova Escena heretada..." #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "Obre Escena..." #: editor/editor_node.cpp @@ -1908,15 +1907,15 @@ msgid "Open Recent" msgstr "Obre Recent" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "Converteix a..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "Biblioteca de Models (MeshLibrary)..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2181,7 +2180,7 @@ msgid "Save the currently edited resource." msgstr "Desa el recurs editat ara." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Anomena i Desa..." #: editor/editor_node.cpp @@ -2290,8 +2289,8 @@ msgid "Creating Mesh Previews" msgstr "Creant Previsualitzacions de Malles" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatura.." +msgid "Thumbnail..." +msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2443,7 +2442,7 @@ msgid "(Current)" msgstr "(Actual)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "S'estan buscant rèpliques..." #: editor/export_template_manager.cpp @@ -2490,7 +2489,7 @@ msgstr "No es pot resoldre." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't connect." -msgstr "No es pot connectar.." +msgstr "No es pot connectar..." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp @@ -2521,7 +2520,7 @@ msgid "Error requesting url: " msgstr "Error en la sol·licitud de l'url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "Connexió amb la Rèplica..." #: editor/export_template_manager.cpp @@ -2538,7 +2537,7 @@ msgstr "No es pot resoldre" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "Connexió en marxa..." #: editor/export_template_manager.cpp @@ -2551,7 +2550,7 @@ msgstr "Connectat" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "Sol·licitud en marxa..." #: editor/export_template_manager.cpp @@ -2685,11 +2684,11 @@ msgid "Collapse all" msgstr "Col·lapsar tot" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Reanomena.." +msgid "Rename..." +msgstr "Reanomena..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Mou cap a..." #: editor/filesystem_dock.cpp @@ -2701,16 +2700,16 @@ msgid "Instance" msgstr "Instà ncia" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "Edita Dependències..." #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Mostra Propietaris..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplica.." +msgid "Duplicate..." +msgstr "Duplica..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2735,7 +2734,7 @@ msgstr "Instancia les escenes seleccionades com a filles del node seleccionat." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "Analitzant Fitxers..." #: editor/filesystem_dock.cpp @@ -2801,7 +2800,7 @@ msgid "Import Scene" msgstr "Importa Escena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "Important Escena..." #: editor/import/resource_importer_scene.cpp @@ -2813,7 +2812,7 @@ msgid "Generating for Mesh: " msgstr "S'està generant per a la Malla: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "Executant Script Personalitzat..." #: editor/import/resource_importer_scene.cpp @@ -2829,7 +2828,7 @@ msgid "Error running post-import script:" msgstr "Error en l'execució de l'Script de post-importació:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Desant..." #: editor/import_dock.cpp @@ -2849,8 +2848,8 @@ msgid "Import As:" msgstr "Importar com a:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Configuració.." +msgid "Preset..." +msgstr "Configuració..." #: editor/import_dock.cpp msgid "Reimport" @@ -3268,7 +3267,7 @@ msgid "Transition Node" msgstr "Node de Transició" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "Importa animacions..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3276,7 +3275,7 @@ msgid "Edit Node Filters" msgstr "Edita els filtres de Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Filtres..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3344,8 +3343,8 @@ msgid "Fetching:" msgstr "Recollida:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "s'està resolent.." +msgid "Resolving..." +msgstr "s'està resolent..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3411,7 +3410,7 @@ msgid "Site:" msgstr "Lloc:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Suport..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3610,6 +3609,7 @@ msgid "Use Rotation Snap" msgstr "Rotació alineada" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Configura l'Alineament..." @@ -3706,14 +3706,12 @@ msgid "Show Guides" msgstr "Mostra les guies" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "Mostra l'Origen" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Vista" +msgstr "Mostra el Viewport" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4006,7 +4004,7 @@ msgstr "La Malla manca d'una superfÃcie on delinear-hi els contorns!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "El tipus primitiu de Mesh no és PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4037,7 +4035,7 @@ msgid "Create Convex Collision Sibling" msgstr "Crea col·lisions convexes entre nodes germans" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "Crea una malla de contorn..." #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4242,7 +4240,7 @@ msgid "Error loading image:" msgstr "Error en carregar la imatge:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "Cap pÃxel amb transparència > 128 en la imatge..." #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4603,7 +4601,7 @@ msgid "Import Theme" msgstr "Importa un Tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Desa el Tema com a..." #: editor/plugins/script_editor_plugin.cpp @@ -4700,7 +4698,7 @@ msgstr "Panell d'Scripts" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "Cerca..." #: editor/plugins/script_editor_plugin.cpp @@ -4910,15 +4908,15 @@ msgid "Find Previous" msgstr "Cerca l'Anterior" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "Substitueix..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Vés a la Funció.." +msgid "Goto Function..." +msgstr "Vés a la Funció..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "Vés a la LÃnia..." #: editor/plugins/script_text_editor.cpp @@ -5372,11 +5370,7 @@ msgid "Transform" msgstr "Transforma" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configura l'Alineament..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "Dià leg de Transformació..." #: editor/plugins/spatial_editor_plugin.cpp @@ -5629,7 +5623,7 @@ msgid "Remove All" msgstr "Treu-los tots" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Edita el Tema..." #: editor/plugins/theme_editor_plugin.cpp @@ -5677,14 +5671,12 @@ msgid "Checked Item" msgstr "Element validat" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Afegeix un Element" +msgstr "Element de rà dio" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Element validat" +msgstr "Element de rà dio validat" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5699,8 +5691,8 @@ msgid "Options" msgstr "Opcions" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Tens,Moltes,Diverses,Opcions!" +msgid "Has,Many,Options" +msgstr "Té,Moltes,Opcions" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5892,7 +5884,7 @@ msgid "Presets" msgstr "Configuracions prestablertes" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Afegeix..." #: editor/project_export.cpp @@ -5986,6 +5978,10 @@ msgid "Imported Project" msgstr "Project importat" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "El nom del Projecte no és và lid." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "No s'ha pogut crear el directori." @@ -6185,9 +6181,11 @@ msgstr "Botó del ratolÃ" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Nom d'acció no và lid. No pot estar buit ni contenir '/', ':', '=', '\\' o " +"'\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6214,8 +6212,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Premeu una Tecla.." +msgid "Press a Key..." +msgstr "Premeu una Tecla..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6398,7 +6396,7 @@ msgid "Property:" msgstr "Propietat:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "Substitutiu per a..." #: editor/project_settings_editor.cpp @@ -6494,11 +6492,11 @@ msgid "Easing Out-In" msgstr "Esmorteeix Sortida-Entrada" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "Fitxer..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "Directori..." #: editor/property_editor.cpp @@ -6671,7 +6669,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Aquesta operació no es pot dur a terme en escenes instanciadas." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Anomena i Desa la Nova Escena..." #: editor/scene_tree_dock.cpp @@ -7390,7 +7388,7 @@ msgstr "Trieu la distà ncia:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "El nom de la classe no pot ser una paraula clau reservada" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8101,7 +8099,7 @@ msgstr "Cal que la propietat Camà assenyali cap a un node Spatial và lid." #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment necessita un recurs Ambiental." #: scene/3d/scenario_fx.cpp msgid "" @@ -8115,6 +8113,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Aquest WorldEnvironment s'ignora. Afegiu una cà mera (per a escenes 3D) o " +"configureu el Background Mode a Canvas (per a escenes 2D)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8213,6 +8213,13 @@ msgstr "Error carregant lletra." msgid "Invalid font size." msgstr "La mida de la lletra no és và lida." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Pestanya Anterior" + +#~ msgid "Next" +#~ msgstr "Següent" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "L'Acció no és và lida (no es pot utilitzar ' / ' o ':')." @@ -8240,9 +8247,6 @@ msgstr "La mida de la lletra no és và lida." #~ msgstr "" #~ "No es pot trobat el el fitxer 'project.godot' en el camà del projecte." -#~ msgid "Next" -#~ msgstr "Següent" - #~ msgid "Not found!" #~ msgstr "No s'ha trobat!" @@ -8375,8 +8379,8 @@ msgstr "La mida de la lletra no és và lida." #~ msgid "Exporting for %s" #~ msgstr "Exportació per a %s" -#~ msgid "Setting Up.." -#~ msgstr "Instal·lant.." +#~ msgid "Setting Up..." +#~ msgstr "Instal·lant..." #~ msgid "Error loading scene." #~ msgstr "No s'ha pogut carregar l'escena." @@ -8433,7 +8437,7 @@ msgstr "La mida de la lletra no és và lida." #~ msgid "Info" #~ msgstr "Informació" -#~ msgid "Re-Import.." +#~ msgid "Re-Import..." #~ msgstr "ReImporta..." #~ msgid "No bit masks to import!" diff --git a/editor/translations/cs.po b/editor/translations/cs.po index ce26418cbf..1066bbad94 100644 --- a/editor/translations/cs.po +++ b/editor/translations/cs.po @@ -6,6 +6,7 @@ # Fadex <vitekpaulik@gmail.com>, 2017. # Jan 'spl!te' KondelÃk <j.kondelik@centrum.cz>, 2016. # Jiri Hysek <contact@jirihysek.com>, 2017. +# Josef KuchaÅ™ <josef.kuchar267@gmail.com>, 2018. # LudÄ›k Novotný <gladosicek@gmail.com>, 2016, 2018. # Martin Novák <maidx@seznam.cz>, 2017. # zxey <r.hozak@seznam.cz>, 2018. @@ -13,15 +14,15 @@ msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-01-24 12:07+0000\n" -"Last-Translator: zxey <r.hozak@seznam.cz>\n" +"PO-Revision-Date: 2018-05-21 12:36+0000\n" +"Last-Translator: Josef KuchaÅ™ <josef.kuchar267@gmail.com>\n" "Language-Team: Czech <https://hosted.weblate.org/projects/godot-engine/godot/" "cs/>\n" "Language: cs\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Weblate 2.19-dev\n" +"X-Generator: Weblate 3.0-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -32,9 +33,8 @@ msgid "All Selection" msgstr "VÅ¡echny vybrané" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Time" -msgstr "Animace: zmÄ›na hodnoty" +msgstr "Animace: ZmÄ›nit Äas klÃÄového snÃmku" #: editor/animation_editor.cpp msgid "Anim Change Transition" @@ -45,9 +45,8 @@ msgid "Anim Change Transform" msgstr "Animace: zmÄ›na transformace" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Value" -msgstr "Animace: zmÄ›na hodnoty" +msgstr "Animace: ZmÄ›nit hodnotu klÃÄového snÃmku" #: editor/animation_editor.cpp msgid "Anim Change Call" @@ -92,7 +91,7 @@ msgstr "Animace: zmÄ›na typu hodnot" #: editor/animation_editor.cpp #, fuzzy msgid "Anim Track Change Wrap Mode" -msgstr "ZmÄ›na režimu opakovánà animaÄnà stopy" +msgstr "Animace: ZmÄ›na režimu opakovánà animaÄnà stopy" #: editor/animation_editor.cpp msgid "Edit Node Curve" @@ -230,7 +229,7 @@ msgstr "ZmÄ›nit opakovánà animace" #: editor/animation_editor.cpp msgid "Anim Create Typed Value Key" -msgstr "" +msgstr "Animace: VytvoÅ™it typovaný klÃÄ" #: editor/animation_editor.cpp msgid "Anim Insert" @@ -326,7 +325,7 @@ msgstr "PÅ™echod" #: editor/animation_editor.cpp msgid "Scale Ratio:" -msgstr "PomÄ›r měřÃtka:" +msgstr "PomÄ›r zvÄ›tÅ¡enÃ:" #: editor/animation_editor.cpp msgid "Call Functions in Which Node?" @@ -504,8 +503,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Odpojit '%s' od '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "PÅ™ipojit.." +msgid "Connect..." +msgstr "PÅ™ipojit..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -876,7 +875,7 @@ msgstr "Ztlumit" #: editor/editor_audio_buses.cpp msgid "Bypass" -msgstr "" +msgstr "ObejÃt" #: editor/editor_audio_buses.cpp msgid "Bus options" @@ -924,12 +923,12 @@ msgid "Move Audio Bus" msgstr "PÅ™esunout Audio Bus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Uložit rozloženà Audio Busu jako.." +msgid "Save Audio Bus Layout As..." +msgstr "Uložit rozloženà Audio Busu jako..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "UmÃstÄ›nà pro nové rozloženÃ.." +msgid "Location for New Layout..." +msgstr "UmÃstÄ›nà pro nové rozloženÃ..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1066,12 +1065,12 @@ msgid "Updating Scene" msgstr "Aktualizuji scénu" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Ukládám lokálnà zmÄ›ny.." +msgid "Storing local changes..." +msgstr "Ukládám lokálnà zmÄ›ny..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Aktualizuji scénu.." +msgid "Updating scene..." +msgstr "Aktualizuji scénu..." #: editor/editor_data.cpp msgid "[empty]" @@ -1139,8 +1138,8 @@ msgid "Show In File Manager" msgstr "Ukázat ve správci souborů" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nová složka.." +msgid "New Folder..." +msgstr "Nová složka..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1209,14 +1208,12 @@ msgid "Focus Path" msgstr "" #: editor/editor_file_dialog.cpp -#, fuzzy msgid "Move Favorite Up" -msgstr "PÅ™esunout oblÃbenou položku o úroveň výš" +msgstr "PÅ™esunout oblÃbenou položku nahoru" #: editor/editor_file_dialog.cpp -#, fuzzy msgid "Move Favorite Down" -msgstr "PÅ™esunout oblÃbenou položku o úroveň nÞ" +msgstr "PÅ™esunout oblÃbenou položku dolů" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Go to parent folder" @@ -1245,7 +1242,7 @@ msgstr "" #: editor/editor_file_system.cpp msgid "(Re)Importing Assets" -msgstr "" +msgstr "(Re)Importovánà assetů" #: editor/editor_help.cpp editor/editor_node.cpp #: editor/plugins/script_editor_plugin.cpp @@ -1410,13 +1407,13 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Chápu.." +msgid "I see..." +msgstr "Chápu..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1622,12 +1619,12 @@ msgid "Open Base Scene" msgstr "OtevÅ™Ãt základnà scénu" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Rychlé otevÅ™enà scény.." +msgid "Quick Open Scene..." +msgstr "Rychle otevÅ™Ãt scénu..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Rychlé otevÅ™enà skriptu.." +msgid "Quick Open Script..." +msgstr "Rychlé otevÅ™enà skriptu..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1638,8 +1635,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Uložit zmÄ›ny '%s' pÅ™ed zavÅ™enÃm?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Uložit scénu jako.." +msgid "Save Scene As..." +msgstr "Uložit scénu jako..." #: editor/editor_node.cpp msgid "No" @@ -1690,7 +1687,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Tuto akci nelze vrátit zpÄ›t. PokraÄovat?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "Rychlé spuÅ¡tÄ›nà scény..." #: editor/editor_node.cpp @@ -1845,7 +1842,7 @@ msgid "Previous tab" msgstr "PÅ™edchozà záložka" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Filtrovat soubory..." #: editor/editor_node.cpp @@ -1857,12 +1854,12 @@ msgid "New Scene" msgstr "Nová scéna" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Nová odvozená scéna.." +msgid "New Inherited Scene..." +msgstr "Nová odvozená scéna..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "OtevÅ™Ãt scénu.." +msgid "Open Scene..." +msgstr "OtevÅ™Ãt scénu..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1881,16 +1878,16 @@ msgid "Open Recent" msgstr "OtevÅ™Ãt nedávné" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Konvertovat na.." +msgid "Convert To..." +msgstr "Konvertovat na..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2065,9 +2062,8 @@ msgid "Q&A" msgstr "Q&A" #: editor/editor_node.cpp -#, fuzzy msgid "Issue Tracker" -msgstr "Správa chyb" +msgstr "Issue Tracker" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp msgid "Community" @@ -2115,7 +2111,7 @@ msgstr "PÅ™ehrát vlastnà scénu" #: editor/editor_node.cpp msgid "Play Custom Scene" -msgstr "PÅ™ehrát vlastnà scénu" +msgstr "Spustit vlastnà scénu" #: editor/editor_node.cpp msgid "Spins when the editor window repaints!" @@ -2150,8 +2146,8 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Uložit jako.." +msgid "Save As..." +msgstr "Uložit jako..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2259,8 +2255,8 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Náhled.." +msgid "Thumbnail..." +msgstr "Náhled..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2304,14 +2300,12 @@ msgid "Average Time (sec)" msgstr "PrůmÄ›rný Äas (sek.)" #: editor/editor_profiler.cpp -#, fuzzy msgid "Frame %" -msgstr "% snÃmku" +msgstr "SnÃmek %" #: editor/editor_profiler.cpp -#, fuzzy msgid "Physics Frame %" -msgstr "% fyzikálnÃho snÃmku" +msgstr "Fyzikálnà snÃmek %" #: editor/editor_profiler.cpp editor/script_editor_debugger.cpp msgid "Time:" @@ -2326,7 +2320,6 @@ msgid "Self" msgstr "" #: editor/editor_profiler.cpp -#, fuzzy msgid "Frame #:" msgstr "SnÃmek Ä.:" @@ -2416,7 +2409,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2492,9 +2485,8 @@ msgid "Error requesting url: " msgstr "Chyba požadavku o url: " #: editor/export_template_manager.cpp -#, fuzzy -msgid "Connecting to Mirror.." -msgstr "PÅ™ipojuji se k mirroru.." +msgid "Connecting to Mirror..." +msgstr "PÅ™ipojuji se k zrcadlu..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2510,22 +2502,21 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "PÅ™ipojuji.." +msgid "Connecting..." +msgstr "PÅ™ipojuji..." #: editor/export_template_manager.cpp msgid "Can't Connect" msgstr "Nelze se pÅ™ipojit" #: editor/export_template_manager.cpp -#, fuzzy msgid "Connected" msgstr "PÅ™ipojeno" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "PosÃlá se žádost.." +msgid "Requesting..." +msgstr "PosÃlá se žádost..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2653,44 +2644,43 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "Collapse all" -msgstr "" +msgstr "Sbalit vÅ¡e" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "" +msgid "Rename..." +msgstr "PÅ™ejmenovat..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "" +msgid "Move To..." +msgstr "PÅ™esunout do..." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Open Scene(s)" -msgstr "OtevÅ™Ãt scénu" +msgstr "OtevÅ™Ãt scénu(y)" #: editor/filesystem_dock.cpp msgid "Instance" -msgstr "" +msgstr "Instance" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "" +msgid "Edit Dependencies..." +msgstr "Upravit závislosti..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "" +msgid "View Owners..." +msgstr "Zobrazit vlastnÃky..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplikovat.." +msgid "Duplicate..." +msgstr "Duplikovat..." #: editor/filesystem_dock.cpp msgid "Previous Directory" -msgstr "" +msgstr "PÅ™edchozà adresář" #: editor/filesystem_dock.cpp msgid "Next Directory" -msgstr "" +msgstr "NásledujÃcà adresář" #: editor/filesystem_dock.cpp msgid "Re-Scan Filesystem" @@ -2707,7 +2697,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2773,7 +2763,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2785,7 +2775,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2801,7 +2791,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2821,8 +2811,8 @@ msgid "Import As:" msgstr "Importovat jako:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Preset.." +msgid "Preset..." +msgstr "PÅ™edvolba..." #: editor/import_dock.cpp msgid "Reimport" @@ -2878,7 +2868,6 @@ msgid "" msgstr "" #: editor/plugins/abstract_polygon_2d_editor.cpp -#, fuzzy msgid "Delete points" msgstr "Odstranit body" @@ -2983,7 +2972,7 @@ msgstr "PÅ™ehrát vybranou animaci od vybrané pozice. (D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation position (in seconds)." -msgstr "" +msgstr "Pozice animace (v sekundách)." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Scale animation playback globally for the node." @@ -3034,17 +3023,14 @@ msgid "Enable Onion Skinning" msgstr "" #: editor/plugins/animation_player_editor_plugin.cpp -#, fuzzy msgid "Directions" msgstr "SmÄ›ry" #: editor/plugins/animation_player_editor_plugin.cpp -#, fuzzy msgid "Past" -msgstr "Minulý" +msgstr "PÅ™edcházejÃcÃ" #: editor/plugins/animation_player_editor_plugin.cpp -#, fuzzy msgid "Future" msgstr "BudoucÃ" @@ -3159,7 +3145,6 @@ msgid "Amount:" msgstr "MnožstvÃ:" #: editor/plugins/animation_tree_editor_plugin.cpp -#, fuzzy msgid "Blend:" msgstr "ProlÃnánÃ:" @@ -3241,21 +3226,20 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importovat animace.." +msgid "Import Animations..." +msgstr "Importovat animace..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filtry.." +msgid "Filters..." +msgstr "Filtry..." #: editor/plugins/animation_tree_editor_plugin.cpp -#, fuzzy msgid "AnimationTree" -msgstr "PÅ™iblÞenà animace." +msgstr "Strom animacÃ" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy @@ -3319,8 +3303,8 @@ msgid "Fetching:" msgstr "Stahuji:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "ZjišťovánÃ.." +msgid "Resolving..." +msgstr "ZjišťovánÃ..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3386,8 +3370,8 @@ msgid "Site:" msgstr "Web:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Podpora.." +msgid "Support..." +msgstr "Podpora..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3435,28 +3419,28 @@ msgstr "Nastavenà pÅ™ichycovánÃ" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid Offset:" -msgstr "" +msgstr "Offset mřÞky:" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid Step:" -msgstr "" +msgstr "Krok mřÞky:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotation Offset:" -msgstr "" +msgstr "Offset rotace:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotation Step:" -msgstr "" +msgstr "Krok rotace:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Pivot" -msgstr "" +msgstr "PÅ™emÃstit stÅ™ed" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Action" -msgstr "" +msgstr "PÅ™esunout akci" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move vertical guide" @@ -3487,23 +3471,20 @@ msgid "Create new horizontal and vertical guides" msgstr "VytvoÅ™it nové vodorovné a svislé vodÃtka" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Edit IK Chain" msgstr "Upravit IK Å™etÄ›zec" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Edit CanvasItem" -msgstr "" +msgstr "Upravit CanvasItem" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Anchors only" msgstr "Pouze kotvy" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Change Anchors and Margins" -msgstr "Upravit kotvy a okraje" +msgstr "ZmÄ›nit kotvy a okraje" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Change Anchors" @@ -3519,19 +3500,20 @@ msgstr "Režim výbÄ›ru" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Drag: Rotate" -msgstr "" +msgstr "TáhnutÃ: OtoÄit" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Alt+Drag: Move" -msgstr "" +msgstr "Alt+TáhnutÃ: PÅ™emÃstit" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Press 'v' to Change Pivot, 'Shift+v' to Drag Pivot (while moving)." msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#, fuzzy msgid "Alt+RMB: Depth list selection" -msgstr "" +msgstr "Alt+Pravé tlaÄÃko myÅ¡i:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Mode" @@ -3569,7 +3551,6 @@ msgid "Snapping options" msgstr "Možnosti pÅ™ichytávánÃ" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Snap to grid" msgstr "PÅ™ichytit k mřÞce" @@ -3578,6 +3559,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Nastavenà pÅ™ichytávánÃ..." @@ -3591,11 +3573,11 @@ msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Smart snapping" -msgstr "" +msgstr "Chytré pÅ™ichytávánÃ" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to parent" -msgstr "" +msgstr "PÅ™ichytit k rodiÄovi" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to node anchor" @@ -3611,7 +3593,7 @@ msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to guides" -msgstr "" +msgstr "PÅ™ichytit k vodÃtkům" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp @@ -3633,15 +3615,15 @@ msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Make Bones" -msgstr "" +msgstr "VytvoÅ™it kosti" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Clear Bones" -msgstr "" +msgstr "Vymazat kosti" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Bones" -msgstr "" +msgstr "Zobrazit kosti" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Make IK Chain" @@ -3662,9 +3644,8 @@ msgid "Show Grid" msgstr "Zobrazit mřÞku" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Helpers" -msgstr "Zobrazit pomocné" +msgstr "Zobrazit pomocnÃky" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Rulers" @@ -3675,9 +3656,8 @@ msgid "Show Guides" msgstr "Zobrazit vodÃtka" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Zobrazit mřÞku" +msgstr "Zobrazit poÄátek" #: editor/plugins/canvas_item_editor_plugin.cpp #, fuzzy @@ -3685,25 +3665,22 @@ msgid "Show Viewport" msgstr "Zobrazit pomocné" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Center Selection" msgstr "Vycentrovat výbÄ›r" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Frame Selection" -msgstr "" +msgstr "VýbÄ›r snÃmku" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Layout" msgstr "RozloženÃ" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Insert Keys" msgstr "Vložit klÃÄe" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Insert Key" msgstr "Vložit klÃÄ" @@ -3724,9 +3701,8 @@ msgid "Drag pivot from mouse position" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Set pivot at mouse position" -msgstr "Odstranit signál" +msgstr "Nastavit stÅ™ed na pozici myÅ¡i" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Multiply grid step by 2" @@ -3755,7 +3731,7 @@ msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Create Node" -msgstr "" +msgstr "VytvoÅ™it uzel" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp @@ -3774,7 +3750,7 @@ msgstr "" #: editor/plugins/collision_polygon_editor_plugin.cpp msgid "Create Poly3D" -msgstr "" +msgstr "VytvoÅ™it Poly3D" #: editor/plugins/collision_shape_2d_editor_plugin.cpp msgid "Set Handle" @@ -3782,17 +3758,17 @@ msgstr "" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Remove item %d?" -msgstr "" +msgstr "Odstranit %d?" #: editor/plugins/cube_grid_theme_editor_plugin.cpp #: editor/plugins/theme_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Add Item" -msgstr "" +msgstr "PÅ™idat položku" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Remove Selected Item" -msgstr "" +msgstr "Odstranit vybranou položku" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import from Scene" @@ -3804,11 +3780,11 @@ msgstr "Aktualizovat ze scény" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat0" -msgstr "" +msgstr "Flat0" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat1" -msgstr "" +msgstr "Flat1" #: editor/plugins/curve_editor_plugin.cpp #, fuzzy @@ -3883,7 +3859,7 @@ msgstr "" #: editor/plugins/item_list_editor_plugin.cpp msgid "Item %d" -msgstr "" +msgstr "Položka %d" #: editor/plugins/item_list_editor_plugin.cpp msgid "Items" @@ -3891,17 +3867,19 @@ msgstr "Položky" #: editor/plugins/item_list_editor_plugin.cpp msgid "Item List Editor" -msgstr "" +msgstr "Editor seznamu položek" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "" "No OccluderPolygon2D resource on this node.\n" "Create and assign one?" msgstr "" +"Na tomto uzlu nenà žádný OccluderPolygon2D.\n" +"VytvoÅ™it a pÅ™iÅ™adit k tomuto uzlu?" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create Occluder Polygon" -msgstr "" +msgstr "VytvoÅ™it Occluder Polygon" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create a new polygon from scratch." @@ -3937,19 +3915,19 @@ msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "This doesn't work on scene root!" -msgstr "" +msgstr "Toto v koÅ™enu scény nefunguje!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Shape" -msgstr "" +msgstr "VytvoÅ™it Trimesh Shape" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Shape" -msgstr "" +msgstr "VytvoÅ™it Convex Shape" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Navigation Mesh" -msgstr "" +msgstr "VytvoÅ™it Navigation Mesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Contained Mesh is not of type ArrayMesh." @@ -4008,7 +3986,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4020,7 +3998,6 @@ msgid "View UV2" msgstr "Zobrazit UV2" #: editor/plugins/mesh_instance_editor_plugin.cpp -#, fuzzy msgid "Unwrap UV2 for Lightmap/AO" msgstr "Rozbalit UV2 pro Lightmapu/AO" @@ -4103,15 +4080,15 @@ msgstr "" #: editor/plugins/multimesh_editor_plugin.cpp msgid "X-Axis" -msgstr "" +msgstr "Osa X" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Y-Axis" -msgstr "" +msgstr "Osa Y" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Z-Axis" -msgstr "" +msgstr "Osa Z" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh Up Axis:" @@ -4127,11 +4104,11 @@ msgstr "" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Random Scale:" -msgstr "" +msgstr "Náhodné měřÃtko:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate" -msgstr "" +msgstr "Naplnit" #: editor/plugins/navigation_mesh_editor_plugin.cpp msgid "Bake!" @@ -4215,7 +4192,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4278,7 +4255,7 @@ msgstr "" #: editor/plugins/particles_editor_plugin.cpp msgid "Generate AABB" -msgstr "" +msgstr "Vygenerovat AABB" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Mesh" @@ -4335,7 +4312,7 @@ msgstr "" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move Point in Curve" -msgstr "" +msgstr "PÅ™esunout bod v kÅ™ivce" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move In-Control in Curve" @@ -4348,17 +4325,18 @@ msgstr "" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Select Points" -msgstr "" +msgstr "Vybrat body" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp +#, fuzzy msgid "Shift+Drag: Select Control Points" -msgstr "" +msgstr "Shift+TáhnutÃ:" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Click: Add Point" -msgstr "" +msgstr "KliknutÃ: PÅ™idat bod" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp @@ -4382,16 +4360,16 @@ msgstr "" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Delete Point" -msgstr "" +msgstr "Odstranit bod" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Close Curve" -msgstr "" +msgstr "UzavÅ™Ãt kÅ™ivku" #: editor/plugins/path_editor_plugin.cpp msgid "Curve Point #" -msgstr "" +msgstr "Bod kÅ™ivky #" #: editor/plugins/path_editor_plugin.cpp #, fuzzy @@ -4399,9 +4377,8 @@ msgid "Set Curve Point Position" msgstr "Odstranit signál" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve In Position" -msgstr "Odstranit signál" +msgstr "Nastavit kÅ™ivku na pozici" #: editor/plugins/path_editor_plugin.cpp #, fuzzy @@ -4410,11 +4387,11 @@ msgstr "Odstranit signál" #: editor/plugins/path_editor_plugin.cpp msgid "Split Path" -msgstr "" +msgstr "RozdÄ›lit cestu" #: editor/plugins/path_editor_plugin.cpp msgid "Remove Path Point" -msgstr "" +msgstr "Odstranit bod cesty" #: editor/plugins/path_editor_plugin.cpp #, fuzzy @@ -4431,7 +4408,7 @@ msgstr "VytvoÅ™it UV mapu" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Transform UV Map" -msgstr "" +msgstr "Transformovat UV mapu" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Polygon 2D UV Editor" @@ -4443,16 +4420,15 @@ msgstr "PÅ™esunout bod" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Ctrl: Rotate" -msgstr "" +msgstr "Ctrl: OtoÄit" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Shift: Move All" msgstr "Shift: PÅ™esunout vÅ¡e" #: editor/plugins/polygon_2d_editor_plugin.cpp -#, fuzzy msgid "Shift+Ctrl: Scale" -msgstr "Shift+Ctrl: ZvÄ›tÅ¡enÃ" +msgstr "Shift+Ctrl: ZmÄ›nit měřÃtko" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Move Polygon" @@ -4464,7 +4440,7 @@ msgstr "OtoÄit polygon" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Scale Polygon" -msgstr "" +msgstr "ZmÄ›nit měřÃtko mnohoúhelnÃku" #: editor/plugins/polygon_2d_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4523,18 +4499,18 @@ msgstr "Schránka zdroje je prázdná!" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/scene_tree_dock.cpp editor/scene_tree_editor.cpp msgid "Open in Editor" -msgstr "" +msgstr "OtevÅ™Ãt v editoru" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/scene_tree_editor.cpp msgid "Instance:" -msgstr "" +msgstr "Instance:" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/plugins/theme_editor_plugin.cpp editor/project_settings_editor.cpp #: editor/scene_tree_editor.cpp editor/script_editor_debugger.cpp msgid "Type:" -msgstr "" +msgstr "Typ:" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp @@ -4581,13 +4557,12 @@ msgid "Import Theme" msgstr "Importovat motiv" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Uložit motiv jako.." +msgid "Save Theme As..." +msgstr "Uložit motiv jako..." #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid " Class Reference" -msgstr " ReferenÄnà tÅ™Ãdy" +msgstr " Reference tÅ™Ãdy" #: editor/plugins/script_editor_plugin.cpp msgid "Sort" @@ -4627,12 +4602,11 @@ msgstr "Uložit vÅ¡e" #: editor/plugins/script_editor_plugin.cpp msgid "Soft Reload Script" -msgstr "" +msgstr "Lehký restart skriptu" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Copy Script Path" -msgstr "ZkopÃrovat uzly" +msgstr "ZkopÃrovat cestu ke skriptu" #: editor/plugins/script_editor_plugin.cpp msgid "Show In File System" @@ -4680,8 +4654,8 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "NajÃt.." +msgid "Find..." +msgstr "NajÃt..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4699,7 +4673,7 @@ msgstr "Vstoupit" #: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp msgid "Break" -msgstr "" +msgstr "PÅ™eruÅ¡it" #: editor/plugins/script_editor_plugin.cpp editor/project_manager.cpp #: editor/script_editor_debugger.cpp @@ -4708,7 +4682,7 @@ msgstr "PokraÄovat" #: editor/plugins/script_editor_plugin.cpp msgid "Keep Debugger Open" -msgstr "" +msgstr "Nechat ladÃcà program otevÅ™ený" #: editor/plugins/script_editor_plugin.cpp msgid "Debug with external editor" @@ -4821,12 +4795,11 @@ msgstr "Odsadit zprava" #: editor/plugins/script_text_editor.cpp msgid "Toggle Comment" -msgstr "" +msgstr "PÅ™epnout komentář" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Fold/Unfold Line" -msgstr "Běž na řádek" +msgstr "Složit/Rozložit řádek" #: editor/plugins/script_text_editor.cpp msgid "Fold All Lines" @@ -4890,16 +4863,16 @@ msgid "Find Previous" msgstr "NajÃt pÅ™edchozÃ" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Nahradit.." +msgid "Replace..." +msgstr "Nahradit..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "PÅ™ejÃt na funkci.." +msgid "Goto Function..." +msgstr "PÅ™ejÃt na funkci..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "PÅ™ejÃt na řádek.." +msgid "Goto Line..." +msgstr "PÅ™ejÃt na řádek..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -4947,7 +4920,7 @@ msgstr "ZmÄ›nit skalárnà funkci" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Function" -msgstr "" +msgstr "ZmÄ›nit vektorovou funkci" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Uniform" @@ -5043,19 +5016,19 @@ msgstr "PerspektivnÃ" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Aborted." -msgstr "" +msgstr "Transformace zruÅ¡ena." #: editor/plugins/spatial_editor_plugin.cpp msgid "X-Axis Transform." -msgstr "" +msgstr "ZmÄ›nit osu X." #: editor/plugins/spatial_editor_plugin.cpp msgid "Y-Axis Transform." -msgstr "" +msgstr "ZmÄ›nit osu Y." #: editor/plugins/spatial_editor_plugin.cpp msgid "Z-Axis Transform." -msgstr "" +msgstr "ZmÄ›nit osu Z." #: editor/plugins/spatial_editor_plugin.cpp msgid "View Plane Transform." @@ -5063,7 +5036,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scaling: " -msgstr "" +msgstr "Å kálovánÃ: " #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy @@ -5080,7 +5053,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Animation Key Inserted." -msgstr "" +msgstr "AnimaÄnà klÃÄ vložen." #: editor/plugins/spatial_editor_plugin.cpp msgid "Objects Drawn" @@ -5103,9 +5076,8 @@ msgid "Draw Calls" msgstr "Vykreslovacà volánÃ" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Vertices" -msgstr "Vertexy" +msgstr "Vrcholy" #: editor/plugins/spatial_editor_plugin.cpp msgid "FPS" @@ -5188,9 +5160,8 @@ msgid "Display Unshaded" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "View Environment" -msgstr "Zobrazenà prostÅ™edÃ" +msgstr "Zobrazit prostÅ™edÃ" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Gizmos" @@ -5247,7 +5218,7 @@ msgstr "Rychlost volného pohledu" #: editor/plugins/spatial_editor_plugin.cpp msgid "XForm Dialog" -msgstr "" +msgstr "XForm Dialog" #: editor/plugins/spatial_editor_plugin.cpp msgid "Select Mode (Q)" @@ -5259,6 +5230,9 @@ msgid "" "Alt+Drag: Move\n" "Alt+RMB: Depth list selection" msgstr "" +"TáhnutÃ: OtoÄit\n" +"Alt+TáhnutÃ: PÅ™emÃstit\n" +"Alt+Pravé tlaÄÃko myÅ¡i: VýbÄ›r seznamu hloubky" #: editor/plugins/spatial_editor_plugin.cpp msgid "Move Mode (W)" @@ -5314,19 +5288,19 @@ msgstr "PÅ™epnout perspektivnÃ/ortogonálnà pohled" #: editor/plugins/spatial_editor_plugin.cpp msgid "Insert Animation Key" -msgstr "" +msgstr "Vložit animaÄnà klÃÄ" #: editor/plugins/spatial_editor_plugin.cpp msgid "Focus Origin" -msgstr "" +msgstr "Zaměřit poÄátek" #: editor/plugins/spatial_editor_plugin.cpp msgid "Focus Selection" -msgstr "" +msgstr "Zaměřit výbÄ›r" #: editor/plugins/spatial_editor_plugin.cpp msgid "Align Selection With View" -msgstr "" +msgstr "Zarovnat výbÄ›r s pohledem" #: editor/plugins/spatial_editor_plugin.cpp msgid "Tool Select" @@ -5353,44 +5327,40 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Nastavit pÅ™ichycenÃ.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" -msgstr "" +msgstr "1 výřez" #: editor/plugins/spatial_editor_plugin.cpp msgid "2 Viewports" -msgstr "" +msgstr "2 výřezy" #: editor/plugins/spatial_editor_plugin.cpp msgid "2 Viewports (Alt)" -msgstr "" +msgstr "2 výřezy (Alt)" #: editor/plugins/spatial_editor_plugin.cpp msgid "3 Viewports" -msgstr "" +msgstr "3 výřezy" #: editor/plugins/spatial_editor_plugin.cpp msgid "3 Viewports (Alt)" -msgstr "" +msgstr "3 výřezy (Alt)" #: editor/plugins/spatial_editor_plugin.cpp msgid "4 Viewports" -msgstr "" +msgstr "4 výřezy" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Origin" -msgstr "" +msgstr "Zobrazit poÄátek" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Grid" -msgstr "" +msgstr "Zobrazit mřÞku" #: editor/plugins/spatial_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp @@ -5444,11 +5414,11 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotate (deg.):" -msgstr "" +msgstr "OtoÄit (stupnÄ›):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale (ratio):" -msgstr "" +msgstr "ZmÄ›nit měřÃtko (pomÄ›r):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Type" @@ -5456,11 +5426,11 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Pre" -msgstr "" +msgstr "PÅ™ed" #: editor/plugins/spatial_editor_plugin.cpp msgid "Post" -msgstr "" +msgstr "Po" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "ERROR: Couldn't load frame resource!" @@ -5468,7 +5438,7 @@ msgstr "" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Frame" -msgstr "" +msgstr "PÅ™idat snÃmek" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Resource clipboard is empty or not a texture!" @@ -5476,7 +5446,7 @@ msgstr "" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Paste Frame" -msgstr "" +msgstr "Vložit snÃmek" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Empty" @@ -5488,27 +5458,27 @@ msgstr "" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Change Animation FPS" -msgstr "" +msgstr "ZmÄ›nit FPS animace" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "(empty)" -msgstr "" +msgstr "(prázdný)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Animations" -msgstr "" +msgstr "Animace" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Speed (FPS):" -msgstr "" +msgstr "Rychlost (FPS):" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Loop" -msgstr "" +msgstr "SmyÄka" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Animation Frames" -msgstr "" +msgstr "SnÃmky animace" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Insert Empty (Before)" @@ -5525,7 +5495,7 @@ msgstr "ZkopÃrovat uzly" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Move (After)" -msgstr "" +msgstr "PÅ™emÃstit (za)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "SpriteFrames" @@ -5549,7 +5519,7 @@ msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp msgid "<None>" -msgstr "" +msgstr "<Žádné>" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Pixel Snap" @@ -5566,25 +5536,25 @@ msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Offset:" -msgstr "" +msgstr "Offset:" #: editor/plugins/texture_region_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Step:" -msgstr "" +msgstr "Krok:" #: editor/plugins/texture_region_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Separation:" -msgstr "" +msgstr "OddÄ›lenÃ:" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Texture Region" -msgstr "" +msgstr "Oblast textury" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Texture Region Editor" -msgstr "" +msgstr "Editor oblasti textury" #: editor/plugins/theme_editor_plugin.cpp msgid "Can't save theme to file:" @@ -5592,29 +5562,28 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp msgid "Add All Items" -msgstr "" +msgstr "PÅ™idat vÅ¡echny položky" #: editor/plugins/theme_editor_plugin.cpp msgid "Add All" -msgstr "" +msgstr "PÅ™idat vÅ¡e" #: editor/plugins/theme_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Remove Item" -msgstr "" +msgstr "Odstranit položku" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Remove All Items" -msgstr "Odstranit výbÄ›r" +msgstr "Odstranit vÅ¡echny položky" #: editor/plugins/theme_editor_plugin.cpp msgid "Remove All" msgstr "Odebrat vÅ¡e" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "" +msgid "Edit theme..." +msgstr "Editovat téma..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5622,15 +5591,15 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp msgid "Add Class Items" -msgstr "" +msgstr "PÅ™idat položky tÅ™Ãdy" #: editor/plugins/theme_editor_plugin.cpp msgid "Remove Class Items" -msgstr "" +msgstr "Odstranit položky tÅ™Ãdy" #: editor/plugins/theme_editor_plugin.cpp msgid "Create Empty Template" -msgstr "" +msgstr "VytvoÅ™it prázdnou Å¡ablonu" #: editor/plugins/theme_editor_plugin.cpp msgid "Create Empty Editor Template" @@ -5642,19 +5611,20 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio1" -msgstr "" +msgstr "CheckBox Radio1" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio2" -msgstr "" +msgstr "CheckBox Radio2" #: editor/plugins/theme_editor_plugin.cpp msgid "Item" -msgstr "" +msgstr "Položka" #: editor/plugins/theme_editor_plugin.cpp +#, fuzzy msgid "Check Item" -msgstr "" +msgstr "Zkontrolovat položku" #: editor/plugins/theme_editor_plugin.cpp msgid "Checked Item" @@ -5678,61 +5648,60 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp msgid "Options" -msgstr "" +msgstr "Možnosti" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "Možnosti" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" -msgstr "" +msgstr "Tab 1" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 2" -msgstr "" +msgstr "Tab 2" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 3" -msgstr "" +msgstr "Tab 3" #: editor/plugins/theme_editor_plugin.cpp msgid "Data Type:" -msgstr "" +msgstr "Datový typ:" #: editor/plugins/theme_editor_plugin.cpp msgid "Icon" -msgstr "" +msgstr "Ikona" #: editor/plugins/theme_editor_plugin.cpp msgid "Style" -msgstr "" +msgstr "Styl" #: editor/plugins/theme_editor_plugin.cpp msgid "Font" -msgstr "" +msgstr "Font" #: editor/plugins/theme_editor_plugin.cpp msgid "Color" -msgstr "" +msgstr "Barva" #: editor/plugins/theme_editor_plugin.cpp msgid "Theme" msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp -#, fuzzy msgid "Erase Selection" -msgstr "ZmÄ›nit měřÃtko výbÄ›ru" +msgstr "Vymazat oznaÄené" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Paint TileMap" msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp -#, fuzzy msgid "Line Draw" -msgstr "LineárnÃ" +msgstr "Nakreslit Äáru" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rectangle Paint" @@ -5760,11 +5729,11 @@ msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Mirror X" -msgstr "" +msgstr "Zrcadlit X" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Mirror Y" -msgstr "" +msgstr "Zrcadlit Y" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Paint Tile" @@ -5776,19 +5745,19 @@ msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 0 degrees" -msgstr "" +msgstr "OtoÄit o 0 stupňů" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 90 degrees" -msgstr "" +msgstr "OtoÄit o 90 stupňů" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 180 degrees" -msgstr "" +msgstr "OtoÄit o 180 stupňů" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 270 degrees" -msgstr "" +msgstr "OtoÄit o 270 stupňů" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Could not find tile:" @@ -5796,15 +5765,15 @@ msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Item name or ID:" -msgstr "" +msgstr "Název položky nebo ID:" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from scene?" -msgstr "" +msgstr "VytvoÅ™it ze scény?" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Merge from scene?" -msgstr "" +msgstr "SlouÄit ze scény?" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy @@ -5813,15 +5782,15 @@ msgstr "Soubor:" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" -msgstr "" +msgstr "VytvoÅ™it ze scény" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Merge from Scene" -msgstr "" +msgstr "SlouÄit ze scény" #: editor/plugins/tile_set_editor_plugin.cpp editor/script_editor_debugger.cpp msgid "Error" -msgstr "" +msgstr "Chyba" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Autotiles" @@ -5864,7 +5833,7 @@ msgstr "Odstranit" #: editor/project_export.cpp msgid "Delete preset '%s'?" -msgstr "Odstranit preset '%s'?" +msgstr "Odstranit pÅ™edvolbu '%s'?" #: editor/project_export.cpp msgid "Export templates for this platform are missing/corrupted: " @@ -5872,35 +5841,35 @@ msgstr "Exportnà šablony pro tuto platformu chybà nebo jsou poÅ¡kozené: " #: editor/project_export.cpp msgid "Presets" -msgstr "" +msgstr "PÅ™edvolby" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "" +msgid "Add..." +msgstr "PÅ™idat..." #: editor/project_export.cpp msgid "Resources" -msgstr "" +msgstr "Zdroje" #: editor/project_export.cpp msgid "Export all resources in the project" -msgstr "" +msgstr "Exportovat vÄechny zdroje tohoto projektu" #: editor/project_export.cpp msgid "Export selected scenes (and dependencies)" -msgstr "" +msgstr "Exportovat vybrané scény (a závislosti)" #: editor/project_export.cpp msgid "Export selected resources (and dependencies)" -msgstr "" +msgstr "Exportovat vybrané zdroje (a závislosti)" #: editor/project_export.cpp msgid "Export Mode:" -msgstr "" +msgstr "Expertnà režim:" #: editor/project_export.cpp msgid "Resources to export:" -msgstr "" +msgstr "Zdroje k exportu:" #: editor/project_export.cpp msgid "" @@ -5966,6 +5935,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "Jméno projektu:" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Nelze vytvoÅ™it složku." @@ -6013,11 +5987,11 @@ msgstr "" #: editor/project_manager.cpp msgid "Import & Edit" -msgstr "" +msgstr "Importovat a upravit" #: editor/project_manager.cpp msgid "Create New Project" -msgstr "" +msgstr "VytvoÅ™it nový projekt" #: editor/project_manager.cpp msgid "Create & Edit" @@ -6025,7 +5999,7 @@ msgstr "VytvoÅ™it a editovat" #: editor/project_manager.cpp msgid "Install Project:" -msgstr "" +msgstr "Instalovat projekt:" #: editor/project_manager.cpp msgid "Install & Edit" @@ -6033,7 +6007,7 @@ msgstr "Instalovat a editovat" #: editor/project_manager.cpp msgid "Project Name:" -msgstr "" +msgstr "Jméno projektu:" #: editor/project_manager.cpp msgid "Create folder" @@ -6041,15 +6015,15 @@ msgstr "VytvoÅ™it složku" #: editor/project_manager.cpp msgid "Project Path:" -msgstr "" +msgstr "Cesta k projektu:" #: editor/project_manager.cpp msgid "Browse" -msgstr "" +msgstr "Procházet" #: editor/project_manager.cpp msgid "Unnamed Project" -msgstr "" +msgstr "Nepojmenovaný projekt" #: editor/project_manager.cpp msgid "Can't open project" @@ -6057,7 +6031,7 @@ msgstr "Nelze otevÅ™Ãt projekt" #: editor/project_manager.cpp msgid "Are you sure to open more than one project?" -msgstr "" +msgstr "Jste si jisti, že chcete otevÅ™it vÃce než jeden projekt?" #: editor/project_manager.cpp msgid "" @@ -6074,11 +6048,11 @@ msgstr "" #: editor/project_manager.cpp msgid "Are you sure to run more than one project?" -msgstr "" +msgstr "Jste si jisti, že chcete spustit vÃce než jeden projekt?" #: editor/project_manager.cpp msgid "Remove project from the list? (Folder contents will not be modified)" -msgstr "" +msgstr "Odstranit projekt ze seznamu? (Obsah složky zůstane nedotÄen)" #: editor/project_manager.cpp msgid "" @@ -6102,15 +6076,15 @@ msgstr "Seznam projektů" #: editor/project_manager.cpp msgid "Scan" -msgstr "" +msgstr "Skenovat" #: editor/project_manager.cpp msgid "Select a Folder to Scan" -msgstr "" +msgstr "Vyberte složku pro skenovánÃ" #: editor/project_manager.cpp msgid "New Project" -msgstr "" +msgstr "Nový projekt" #: editor/project_manager.cpp msgid "Templates" @@ -6118,11 +6092,11 @@ msgstr "Å ablony" #: editor/project_manager.cpp msgid "Exit" -msgstr "" +msgstr "UkonÄit" #: editor/project_manager.cpp msgid "Restart Now" -msgstr "" +msgstr "Restartovat nynÃ" #: editor/project_manager.cpp msgid "Can't run project" @@ -6148,17 +6122,20 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "Mouse Button" -msgstr "" +msgstr "TlaÄÃtko myÅ¡i" #: editor/project_settings_editor.cpp +#, fuzzy msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Neplatné jméno akce. Nesmà být prázdné nebo obsahovat '/', ':', '=', '\\' " +"nebo '\"'" #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" -msgstr "" +msgstr "Akce '%s' již existuje!" #: editor/project_settings_editor.cpp msgid "Rename Input Action Event" @@ -6178,51 +6155,51 @@ msgstr "Alt+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "Control+" -msgstr "" +msgstr "Ctrl+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "" +msgid "Press a Key..." +msgstr "StisknÄ›te klávesu..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" -msgstr "" +msgstr "Index tlaÄÃtka myÅ¡i:" #: editor/project_settings_editor.cpp msgid "Left Button" -msgstr "" +msgstr "Levé tlaÄÃtko" #: editor/project_settings_editor.cpp msgid "Right Button" -msgstr "" +msgstr "Pravé tlaÄÃtko" #: editor/project_settings_editor.cpp msgid "Middle Button" -msgstr "" +msgstr "ProstÅ™ednà tlaÄÃtko" #: editor/project_settings_editor.cpp msgid "Wheel Up Button" -msgstr "" +msgstr "KoleÄko nahoru" #: editor/project_settings_editor.cpp msgid "Wheel Down Button" -msgstr "" +msgstr "KoleÄko dolů" #: editor/project_settings_editor.cpp msgid "Button 6" -msgstr "" +msgstr "TlaÄÃtko Ä. 6" #: editor/project_settings_editor.cpp msgid "Button 7" -msgstr "" +msgstr "TlaÄÃtko Ä. 7" #: editor/project_settings_editor.cpp msgid "Button 8" -msgstr "" +msgstr "TlaÄÃtko Ä. 8" #: editor/project_settings_editor.cpp msgid "Button 9" -msgstr "" +msgstr "TlaÄÃtko Ä. 9" #: editor/project_settings_editor.cpp msgid "Joypad Axis Index:" @@ -6247,7 +6224,7 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "Add Event" -msgstr "" +msgstr "PÅ™idat akci" #: editor/project_settings_editor.cpp msgid "Device" @@ -6287,7 +6264,7 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "No property '%s' exists." -msgstr "" +msgstr "Vlastnost '%s' neexistuje." #: editor/project_settings_editor.cpp msgid "Setting '%s' is internal, and it can't be deleted." @@ -6299,7 +6276,7 @@ msgstr "Odstranit položku" #: editor/project_settings_editor.cpp msgid "Already existing" -msgstr "" +msgstr "Již existujÃcÃ" #: editor/project_settings_editor.cpp msgid "Add Input Action" @@ -6307,11 +6284,11 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "Error saving settings." -msgstr "" +msgstr "Chyba pÅ™i ukládánà nastavenÃ." #: editor/project_settings_editor.cpp msgid "Settings saved OK." -msgstr "" +msgstr "Nastavenà úspěšnÄ› uloženo." #: editor/project_settings_editor.cpp msgid "Override for Feature" @@ -6319,11 +6296,11 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "Add Translation" -msgstr "" +msgstr "PÅ™idat pÅ™eklad" #: editor/project_settings_editor.cpp msgid "Remove Translation" -msgstr "" +msgstr "Odstranit pÅ™eklad" #: editor/project_settings_editor.cpp msgid "Add Remapped Path" @@ -6360,14 +6337,14 @@ msgstr "Nastavenà projektu (project.godot)" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "" +msgstr "VÅ¡eobecné" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" -msgstr "" +msgstr "Vlastnost:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6376,35 +6353,35 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "Action:" -msgstr "" +msgstr "Akce:" #: editor/project_settings_editor.cpp msgid "Device:" -msgstr "" +msgstr "ZaÅ™ÃzenÃ:" #: editor/project_settings_editor.cpp msgid "Index:" -msgstr "" +msgstr "Index:" #: editor/project_settings_editor.cpp msgid "Localization" -msgstr "" +msgstr "Lokalizace" #: editor/project_settings_editor.cpp msgid "Translations" -msgstr "" +msgstr "PÅ™eklady" #: editor/project_settings_editor.cpp msgid "Translations:" -msgstr "" +msgstr "PÅ™eklady:" #: editor/project_settings_editor.cpp msgid "Remaps" -msgstr "" +msgstr "PÅ™emapovánÃ" #: editor/project_settings_editor.cpp msgid "Resources:" -msgstr "" +msgstr "Zdroje:" #: editor/project_settings_editor.cpp msgid "Remaps by Locale:" @@ -6452,7 +6429,7 @@ msgstr "" #: editor/property_editor.cpp msgid "Zero" -msgstr "" +msgstr "Nula" #: editor/property_editor.cpp msgid "Easing In-Out" @@ -6463,37 +6440,36 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." -msgstr "" +msgid "File..." +msgstr "Soubor..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "" +msgid "Dir..." +msgstr "Složka..." #: editor/property_editor.cpp msgid "Assign" -msgstr "" +msgstr "PÅ™iÅ™adit" #: editor/property_editor.cpp -#, fuzzy msgid "Select Node" -msgstr "Vybrat vÅ¡e" +msgstr "Vybrat uzel" #: editor/property_editor.cpp msgid "New Script" -msgstr "" +msgstr "Nový skript" #: editor/property_editor.cpp msgid "New %s" -msgstr "" +msgstr "Nový %s" #: editor/property_editor.cpp msgid "Make Unique" -msgstr "" +msgstr "VytvoÅ™it unikátnÃ" #: editor/property_editor.cpp msgid "Show in File System" -msgstr "" +msgstr "Zobrazit v souborovém systému" #: editor/property_editor.cpp msgid "Convert To %s" @@ -6508,9 +6484,8 @@ msgid "Selected node is not a Viewport!" msgstr "" #: editor/property_editor.cpp -#, fuzzy msgid "Pick a Node" -msgstr "Vložit uzly" +msgstr "Vybrat uzel" #: editor/property_editor.cpp msgid "Bit %d, val %d." @@ -6522,20 +6497,19 @@ msgstr "" #: editor/property_editor.cpp msgid "[Empty]" -msgstr "" +msgstr "[Prázdné]" #: editor/property_editor.cpp modules/visual_script/visual_script_editor.cpp msgid "Set" -msgstr "" +msgstr "Nastavit" #: editor/property_editor.cpp msgid "Properties:" -msgstr "" +msgstr "Vlastnosti:" #: editor/property_selector.cpp -#, fuzzy msgid "Select Property" -msgstr "PÅ™idat vlastnost setter" +msgstr "Vybrat vlastnost" #: editor/property_selector.cpp msgid "Select Virtual Method" @@ -6575,15 +6549,15 @@ msgstr "" #: editor/run_settings_dialog.cpp msgid "Current Scene" -msgstr "" +msgstr "Aktuálnà scéna" #: editor/run_settings_dialog.cpp msgid "Main Scene" -msgstr "" +msgstr "Hlavnà scéna" #: editor/run_settings_dialog.cpp msgid "Main Scene Arguments:" -msgstr "" +msgstr "Argumenty hlavnà scény:" #: editor/run_settings_dialog.cpp msgid "Scene Run Settings" @@ -6600,7 +6574,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Error loading scene from %s" -msgstr "" +msgstr "Chyba pÅ™i naÄÃtánà scény z %s" #: editor/scene_tree_dock.cpp msgid "" @@ -6618,19 +6592,19 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Move Node In Parent" -msgstr "" +msgstr "PÅ™esunout uzel v rodiÄi" #: editor/scene_tree_dock.cpp msgid "Move Nodes In Parent" -msgstr "" +msgstr "PÅ™esunout uzly v rodiÄi" #: editor/scene_tree_dock.cpp msgid "Duplicate Node(s)" -msgstr "" +msgstr "Duplikovat uzel/uzly" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)?" -msgstr "" +msgstr "Odstranit uzel/uzly?" #: editor/scene_tree_dock.cpp msgid "Can not perform with the root node." @@ -6641,8 +6615,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "" +msgid "Save New Scene As..." +msgstr "Uložit novou scénu jako..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -6658,7 +6632,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Makes Sense!" -msgstr "" +msgstr "Dává smysl!" #: editor/scene_tree_dock.cpp msgid "Can't operate on nodes from a foreign scene!" @@ -6670,7 +6644,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Remove Node(s)" -msgstr "" +msgstr "Odstranit uzel/uzly" #: editor/scene_tree_dock.cpp msgid "" @@ -6680,7 +6654,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Error saving scene." -msgstr "" +msgstr "Chyba pÅ™i ukládánà scény." #: editor/scene_tree_dock.cpp msgid "Error duplicating scene to save it." @@ -6696,11 +6670,11 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)" -msgstr "" +msgstr "Odstranit uzel/uzly" #: editor/scene_tree_dock.cpp msgid "Add Child Node" -msgstr "" +msgstr "PÅ™idat podÅ™Ãzený uzel" #: editor/scene_tree_dock.cpp msgid "Instance Child Scene" @@ -6708,11 +6682,11 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Change Type" -msgstr "" +msgstr "ZmÄ›nit typ" #: editor/scene_tree_dock.cpp msgid "Attach Script" -msgstr "" +msgstr "PÅ™ipojit skript" #: editor/scene_tree_dock.cpp msgid "Clear Script" @@ -6720,16 +6694,15 @@ msgstr "Vymazat skript" #: editor/scene_tree_dock.cpp msgid "Merge From Scene" -msgstr "" +msgstr "SlouÄit ze scény" #: editor/scene_tree_dock.cpp msgid "Save Branch as Scene" -msgstr "" +msgstr "Uložit vÄ›tev jako scénu" #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Copy Node Path" -msgstr "KopÃrovat cestu uzlu" +msgstr "KopÃrovat cestu k uzlu" #: editor/scene_tree_dock.cpp msgid "Delete (No Confirm)" @@ -6737,7 +6710,7 @@ msgstr "Odstranit (bez potvrzenÃ)" #: editor/scene_tree_dock.cpp msgid "Add/Create a New Node" -msgstr "" +msgstr "PÅ™idat/VytvoÅ™it nový uzel" #: editor/scene_tree_dock.cpp msgid "" @@ -6746,26 +6719,24 @@ msgid "" msgstr "" #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Filter nodes" msgstr "Filtrovat uzly" #: editor/scene_tree_dock.cpp msgid "Attach a new or existing script for the selected node." -msgstr "" +msgstr "PÅ™ipojit nový, nebo existujÃcà skript k vybranému uzlu." #: editor/scene_tree_dock.cpp msgid "Clear a script for the selected node." msgstr "" #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Remote" msgstr "Vzdálený" #: editor/scene_tree_dock.cpp msgid "Local" -msgstr "" +msgstr "MÃstnÃ" #: editor/scene_tree_dock.cpp msgid "Clear Inheritance? (No Undo!)" @@ -6823,7 +6794,7 @@ msgstr "" #: editor/scene_tree_editor.cpp msgid "Toggle Visibility" -msgstr "" +msgstr "PÅ™epnout viditelnost" #: editor/scene_tree_editor.cpp msgid "Invalid node name, the following characters are not allowed:" @@ -6831,11 +6802,11 @@ msgstr "" #: editor/scene_tree_editor.cpp msgid "Rename Node" -msgstr "" +msgstr "PÅ™ejmenovat uzel" #: editor/scene_tree_editor.cpp msgid "Scene Tree (Nodes):" -msgstr "" +msgstr "Strom scény (uzly):" #: editor/scene_tree_editor.cpp msgid "Node Configuration Warning!" @@ -6843,7 +6814,7 @@ msgstr "" #: editor/scene_tree_editor.cpp msgid "Select a Node" -msgstr "" +msgstr "Vybrat uzel" #: editor/script_create_dialog.cpp msgid "Error loading template '%s'" @@ -6859,36 +6830,35 @@ msgstr "Chyba nahrávánà skriptu z %s" #: editor/script_create_dialog.cpp msgid "N/A" -msgstr "" +msgstr "N/A" #: editor/script_create_dialog.cpp msgid "Path is empty" -msgstr "" +msgstr "Cesta je prázdná" #: editor/script_create_dialog.cpp msgid "Path is not local" -msgstr "" +msgstr "Cesta nenà mÃstnÃ" #: editor/script_create_dialog.cpp msgid "Invalid base path" -msgstr "" +msgstr "Neplatná základnà cesta" #: editor/script_create_dialog.cpp msgid "Directory of the same name exists" -msgstr "" +msgstr "Složka se stejným jménem již existuje" #: editor/script_create_dialog.cpp -#, fuzzy msgid "File exists, will be reused" -msgstr "Soubor už existuje. PÅ™epsat?" +msgstr "Soubor již existuje, bude znovu použit" #: editor/script_create_dialog.cpp msgid "Invalid extension" -msgstr "" +msgstr "Neplatná pÅ™Ãpona" #: editor/script_create_dialog.cpp msgid "Wrong extension chosen" -msgstr "" +msgstr "Vybrána Å¡patná pÅ™Ãpona" #: editor/script_create_dialog.cpp msgid "Invalid Path" @@ -6896,7 +6866,7 @@ msgstr "Neplatná cesta" #: editor/script_create_dialog.cpp msgid "Invalid class name" -msgstr "" +msgstr "Neplatné jméno tÅ™Ãdy" #: editor/script_create_dialog.cpp #, fuzzy @@ -6905,11 +6875,11 @@ msgstr "Neplatné jméno vlastnosti." #: editor/script_create_dialog.cpp msgid "Script valid" -msgstr "" +msgstr "Skript je validnÃ" #: editor/script_create_dialog.cpp msgid "Allowed: a-z, A-Z, 0-9 and _" -msgstr "" +msgstr "Povoleno: a-z, A-Z, 0-9 a _" #: editor/script_create_dialog.cpp msgid "Built-in script (into scene file)" @@ -6925,15 +6895,15 @@ msgstr "" #: editor/script_create_dialog.cpp msgid "Language" -msgstr "" +msgstr "Jazyk" #: editor/script_create_dialog.cpp msgid "Inherits" -msgstr "" +msgstr "DÄ›dÃ" #: editor/script_create_dialog.cpp msgid "Class Name" -msgstr "" +msgstr "Jméno tÅ™Ãdy" #: editor/script_create_dialog.cpp msgid "Template" @@ -6948,29 +6918,28 @@ msgid "Attach Node Script" msgstr "" #: editor/script_editor_debugger.cpp -#, fuzzy msgid "Remote " msgstr "Vzdálený " #: editor/script_editor_debugger.cpp msgid "Bytes:" -msgstr "" +msgstr "Bajtů:" #: editor/script_editor_debugger.cpp msgid "Warning" -msgstr "" +msgstr "VarovánÃ" #: editor/script_editor_debugger.cpp msgid "Error:" -msgstr "" +msgstr "Chyba:" #: editor/script_editor_debugger.cpp msgid "Source:" -msgstr "" +msgstr "Zdroj:" #: editor/script_editor_debugger.cpp msgid "Function:" -msgstr "" +msgstr "Funkce:" #: editor/script_editor_debugger.cpp msgid "Pick one or more items from the list to display the graph." @@ -6978,16 +6947,15 @@ msgstr "" #: editor/script_editor_debugger.cpp modules/mono/editor/mono_bottom_panel.cpp msgid "Errors" -msgstr "" +msgstr "Chyby" #: editor/script_editor_debugger.cpp msgid "Child Process Connected" msgstr "" #: editor/script_editor_debugger.cpp -#, fuzzy msgid "Copy Error" -msgstr "PÅ™ipojit.." +msgstr "KopÃrovat chybu" #: editor/script_editor_debugger.cpp msgid "Inspect Previous Instance" @@ -7003,11 +6971,11 @@ msgstr "" #: editor/script_editor_debugger.cpp msgid "Variable" -msgstr "" +msgstr "PromÄ›nná" #: editor/script_editor_debugger.cpp msgid "Errors:" -msgstr "" +msgstr "Chyby:" #: editor/script_editor_debugger.cpp msgid "Stack Trace (if applicable):" @@ -7023,7 +6991,7 @@ msgstr "" #: editor/script_editor_debugger.cpp msgid "Value" -msgstr "" +msgstr "Hodnota" #: editor/script_editor_debugger.cpp msgid "Monitors" @@ -7035,7 +7003,7 @@ msgstr "" #: editor/script_editor_debugger.cpp msgid "Total:" -msgstr "" +msgstr "Celkem:" #: editor/script_editor_debugger.cpp msgid "Video Mem" @@ -7043,23 +7011,23 @@ msgstr "" #: editor/script_editor_debugger.cpp msgid "Resource Path" -msgstr "" +msgstr "Cesta ke zdroji" #: editor/script_editor_debugger.cpp msgid "Type" -msgstr "" +msgstr "Typ" #: editor/script_editor_debugger.cpp msgid "Format" -msgstr "" +msgstr "Formát" #: editor/script_editor_debugger.cpp msgid "Usage" -msgstr "" +msgstr "PoužÃvánÃ" #: editor/script_editor_debugger.cpp msgid "Misc" -msgstr "" +msgstr "Různé" #: editor/script_editor_debugger.cpp msgid "Clicked Control:" @@ -7079,7 +7047,7 @@ msgstr "" #: editor/settings_config_dialog.cpp msgid "Shortcuts" -msgstr "" +msgstr "Zkratky" #: editor/settings_config_dialog.cpp msgid "Binding" @@ -7087,7 +7055,7 @@ msgstr "" #: editor/spatial_editor_gizmos.cpp msgid "Change Light Radius" -msgstr "" +msgstr "ZmÄ›nit rádius svÄ›tla" #: editor/spatial_editor_gizmos.cpp msgid "Change AudioStreamPlayer3D Emission Angle" @@ -7095,11 +7063,11 @@ msgstr "" #: editor/spatial_editor_gizmos.cpp msgid "Change Camera FOV" -msgstr "" +msgstr "ZmÄ›nit zorné pole kamery" #: editor/spatial_editor_gizmos.cpp msgid "Change Camera Size" -msgstr "" +msgstr "ZmÄ›nit velikost kamery" #: editor/spatial_editor_gizmos.cpp msgid "Change Sphere Shape Radius" @@ -7152,15 +7120,15 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Platform:" -msgstr "" +msgstr "Platforma:" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Platform" -msgstr "" +msgstr "Platforma" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Dynamic Library" -msgstr "" +msgstr "Dynamická knihovna" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" @@ -7168,23 +7136,23 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "GDNativeLibrary" -msgstr "" +msgstr "GDNativeLibrary" #: modules/gdnative/gdnative_library_singleton_editor.cpp msgid "Library" -msgstr "" +msgstr "Knihovna" #: modules/gdnative/gdnative_library_singleton_editor.cpp msgid "Status" -msgstr "" +msgstr "Status" #: modules/gdnative/gdnative_library_singleton_editor.cpp msgid "Libraries: " -msgstr "" +msgstr "Knihovny: " #: modules/gdnative/register_types.cpp msgid "GDNative" -msgstr "" +msgstr "GDNative" #: modules/gdscript/gdscript_functions.cpp #: modules/visual_script/visual_script_builtin_funcs.cpp @@ -7260,14 +7228,12 @@ msgid "Floor:" msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Delete Selection" -msgstr "Smazat vybraný" +msgstr "GridMap Smazat výbÄ›r" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Duplicate Selection" -msgstr "Duplikovat výbÄ›r" +msgstr "GridMap Duplikovat výbÄ›r" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Grid Map" @@ -7292,15 +7258,15 @@ msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Edit X Axis" -msgstr "" +msgstr "Editovat osu X" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Edit Y Axis" -msgstr "" +msgstr "Editovat osu Y" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Edit Z Axis" -msgstr "" +msgstr "Editovat osu Z" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Cursor Rotate X" @@ -7331,9 +7297,8 @@ msgid "Cursor Clear Rotation" msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Create Area" -msgstr "VytvoÅ™it nový" +msgstr "VytvoÅ™it plochu" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Create Exterior Connector" @@ -7341,15 +7306,13 @@ msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Erase Area" -msgstr "" +msgstr "Vymazat oblast" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Clear Selection" -msgstr "ZmÄ›nit měřÃtko výbÄ›ru" +msgstr "Vymazat výbÄ›r" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Settings" msgstr "Nastavenà GridMap" @@ -7363,25 +7326,23 @@ msgstr "" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." -msgstr "" +msgstr "Generovánà řeÅ¡enÃ..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating C# project..." -msgstr "" +msgstr "Generovánà C# projektu..." #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Failed to create solution." msgstr "NepodaÅ™ilo se vytvoÅ™it Å™eÅ¡enÃ." #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Failed to save solution." msgstr "NepodaÅ™ilo se uložit Å™eÅ¡enÃ." #: modules/mono/editor/godotsharp_editor.cpp msgid "Done" -msgstr "" +msgstr "Hotovo" #: modules/mono/editor/godotsharp_editor.cpp msgid "Failed to create C# project." @@ -7389,14 +7350,13 @@ msgstr "" #: modules/mono/editor/godotsharp_editor.cpp msgid "Mono" -msgstr "" +msgstr "Mono" #: modules/mono/editor/godotsharp_editor.cpp msgid "About C# support" -msgstr "" +msgstr "O podpoÅ™e C#" #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Create C# solution" msgstr "VytvoÅ™it C# Å™eÅ¡enÃ" @@ -7410,7 +7370,7 @@ msgstr "Sestavit projekt" #: modules/mono/editor/mono_bottom_panel.cpp msgid "Warnings" -msgstr "" +msgstr "VarovánÃ" #: modules/mono/mono_gd/gd_mono_utils.cpp msgid "End of inner exception stack trace" @@ -7458,9 +7418,8 @@ msgid "Change Signal Arguments" msgstr "Upravit argumenty signálu" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Argument Type" -msgstr "ZmÄ›nit typ hodnot pole" +msgstr "ZmÄ›nit typ argumentu" #: modules/visual_script/visual_script_editor.cpp msgid "Change Argument name" @@ -7515,29 +7474,25 @@ msgid "Add Signal" msgstr "PÅ™idat signál" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Expression" -msgstr "Animace: zmÄ›na pÅ™echodu" +msgstr "ZmÄ›nit výraz" #: modules/visual_script/visual_script_editor.cpp msgid "Add Node" msgstr "PÅ™idat uzel" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Remove VisualScript Nodes" -msgstr "Odstranit neplatné klÃÄe" +msgstr "Odstranit uzly VisualScriptu" #: modules/visual_script/visual_script_editor.cpp msgid "Duplicate VisualScript Nodes" msgstr "" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Hold %s to drop a Getter. Hold Shift to drop a generic signature." msgstr "" -"Podržte Meta k uvolnÄ›nà getteru. Podržte Shift k uvolnÄ›nà generického " -"podpisu." +"Podržte %s k uvolnÄ›nà getteru. Podržte Shift k uvolnÄ›nà generického podpisu." #: modules/visual_script/visual_script_editor.cpp #, fuzzy @@ -7547,7 +7502,6 @@ msgstr "" "podpisu." #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Hold %s to drop a simple reference to the node." msgstr "Podržte %s k uvolnÄ›nà jednoduché reference na uzel." @@ -7627,11 +7581,11 @@ msgstr "Zavolat" #: modules/visual_script/visual_script_editor.cpp msgid "Get" -msgstr "" +msgstr "ZÃskat" #: modules/visual_script/visual_script_editor.cpp msgid "Script already has function '%s'" -msgstr "" +msgstr "Script již má funkci '%s'" #: modules/visual_script/visual_script_editor.cpp msgid "Change Input Value" @@ -7643,7 +7597,7 @@ msgstr "" #: modules/visual_script/visual_script_editor.cpp msgid "Clipboard is empty!" -msgstr "" +msgstr "Schránka je prázdná!" #: modules/visual_script/visual_script_editor.cpp msgid "Paste VisualScript Nodes" @@ -7775,11 +7729,11 @@ msgstr "" #: platform/javascript/export/export.cpp msgid "Run in Browser" -msgstr "" +msgstr "Spustit v prohlÞeÄi" #: platform/javascript/export/export.cpp msgid "Run exported HTML in the system's default browser." -msgstr "" +msgstr "Spustit vyexportované HTML ve výchozÃm prohlÞeÄi." #: platform/javascript/export/export.cpp msgid "Could not write file:" @@ -7845,7 +7799,7 @@ msgstr "" #: scene/2d/collision_polygon_2d.cpp msgid "An empty CollisionPolygon2D has no effect on collision." -msgstr "Prázdný CollisionPolygon2D nemá žádný efekt na kolizÃch." +msgstr "Prázdný CollisionPolygon2D nemá pÅ™i kolizi žádný efekt." #: scene/2d/collision_shape_2d.cpp msgid "" @@ -7930,6 +7884,8 @@ msgid "" "VisibilityEnable2D works best when used with the edited scene root directly " "as parent." msgstr "" +"VisibilityEnable2D funguje nejlépe, když je nastaven jako rodiÄ editované " +"scény." #: scene/3d/arvr_nodes.cpp msgid "ARVRCamera must have an ARVROrigin node as its parent" @@ -7961,11 +7917,11 @@ msgstr "" #: scene/3d/baked_lightmap.cpp msgid "%d%%" -msgstr "" +msgstr "%d%%" #: scene/3d/baked_lightmap.cpp msgid "(Time Left: %d:%02d s)" -msgstr "" +msgstr "(ZbývajÃcà Äas: %d:%02d s)" #: scene/3d/baked_lightmap.cpp msgid "Plotting Meshes: " @@ -8092,11 +8048,11 @@ msgstr "" #: scene/gui/color_picker.cpp msgid "Raw Mode" -msgstr "" +msgstr "RAW mód" #: scene/gui/color_picker.cpp msgid "Add current color as a preset" -msgstr "" +msgstr "PÅ™idat aktuálnà barvu jako pÅ™edvolbu" #: scene/gui/dialogs.cpp msgid "Alert!" @@ -8104,7 +8060,7 @@ msgstr "Pozor!" #: scene/gui/dialogs.cpp msgid "Please Confirm..." -msgstr "PotvrÄte prosÃm.." +msgstr "PotvrÄte prosÃm..." #: scene/gui/file_dialog.cpp msgid "Select this Folder" @@ -8168,6 +8124,13 @@ msgstr "Chyba nahrávánà fontu." msgid "Invalid font size." msgstr "Neplatná velikost fontu." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "PÅ™edchozà záložka" + +#~ msgid "Next" +#~ msgstr "DalÅ¡Ã" + #~ msgid "Can't contain '/' or ':'" #~ msgstr "Nesmà obsaovat '/' nebo ':'" @@ -8181,9 +8144,6 @@ msgstr "Neplatná velikost fontu." #~ msgid "Can't write file." #~ msgstr "Nelze zapsat soubor." -#~ msgid "Next" -#~ msgstr "DalÅ¡Ã" - #~ msgid "Not found!" #~ msgstr "Nenalezeno!" diff --git a/editor/translations/da.po b/editor/translations/da.po index 349706f6e0..3b5854334a 100644 --- a/editor/translations/da.po +++ b/editor/translations/da.po @@ -6,14 +6,14 @@ # Dankse Memes <purplelops@gmail.com>, 2018. # David Lamhauge <davidlamhauge@gmail.com>, 2016. # Esben Damkjær Sørensen <esben@damkjaergaard.com>, 2018. -# Kim Nielsen <kimmowich@stofanet.dk>, 2017. +# Kim Nielsen <kimmowich@stofanet.dk>, 2017, 2018. # Michael Madsen <mim@michael-madsen.dk>, 2017. # msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-22 07:35+0000\n" -"Last-Translator: Dankse Memes <purplelops@gmail.com>\n" +"PO-Revision-Date: 2018-05-17 19:35+0000\n" +"Last-Translator: Kim Nielsen <kimmowich@stofanet.dk>\n" "Language-Team: Danish <https://hosted.weblate.org/projects/godot-engine/" "godot/da/>\n" "Language: da\n" @@ -501,7 +501,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Afbryd '%s' fra '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Forbind..." #: editor/connections_dialog.cpp @@ -829,9 +829,8 @@ msgid "Rename Audio Bus" msgstr "Omdøb Audio Bus" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Change Audio Bus Volume" -msgstr "Skifter Audio Bus Solo" +msgstr "Skift Audio Bus Volume" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Solo" @@ -923,12 +922,12 @@ msgid "Move Audio Bus" msgstr "Flyt Audio Bus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Gem Audio Bus Layout Som.." +msgid "Save Audio Bus Layout As..." +msgstr "Gem Audio Bus Layout Som..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Placering for Ny Layout.." +msgid "Location for New Layout..." +msgstr "Placering for Ny Layout..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1069,12 +1068,12 @@ msgid "Updating Scene" msgstr "Opdatere Scene" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Gemmer lokale ændringer.." +msgid "Storing local changes..." +msgstr "Gemmer lokale ændringer..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Opdatere scene.." +msgid "Updating scene..." +msgstr "Opdatere scene..." #: editor/editor_data.cpp msgid "[empty]" @@ -1142,8 +1141,8 @@ msgid "Show In File Manager" msgstr "Vis I Fil Manager" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Opret mappe.." +msgid "New Folder..." +msgstr "Opret mappe..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1335,18 +1334,16 @@ msgid "Description" msgstr "Beskrivelse" #: editor/editor_help.cpp -#, fuzzy msgid "Online Tutorials:" -msgstr "Online Dokumentation:" +msgstr "Online Undervisning:" #: editor/editor_help.cpp -#, fuzzy msgid "" "There are currently no tutorials for this class, you can [color=$color][url=" "$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" "url][/color]." msgstr "" -"Der er i øjeblikket ingen beskrivelse af denne metode. Det vil være en stor " +"Der er i øjeblikket ingen beskrivelse af denne klasse. Det vil være en stor " "hjælp, hvis du kan [color=$color][url=$url]bidrage[/url][/color] med en " "beskrivelse!" @@ -1404,30 +1401,28 @@ msgid "Clear" msgstr "Clear" #: editor/editor_log.cpp -#, fuzzy msgid "Clear Output" -msgstr "Output" +msgstr "Ryd Output" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Projekt eksport fejlede med fejlkode %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Fejl, kan ikke gemme ressource!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Gem Ressource Som.." +msgid "Save Resource As..." +msgstr "Gem Ressource Som..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp #, fuzzy -msgid "I see.." -msgstr "Jeg ser.." +msgid "I see..." +msgstr "Jeg ser..." #: editor/editor_node.cpp -#, fuzzy msgid "Can't open file for writing:" msgstr "Kan ikke Ã¥bne fil til skrivning:" @@ -1448,7 +1443,6 @@ msgid "Error while parsing '%s'." msgstr "Error ved parsing af '%s'." #: editor/editor_node.cpp -#, fuzzy msgid "Unexpected end of file '%s'." msgstr "Uventet afslutning af fil '%s'." @@ -1478,12 +1472,11 @@ msgid "This operation can't be done without a tree root." msgstr "Denne handling kan ikke foretages uden tree root" #: editor/editor_node.cpp -#, fuzzy msgid "" "Couldn't save scene. Likely dependencies (instances or inheritance) couldn't " "be satisfied." msgstr "" -"Kunne ikke gemme scene. Der er nogle afhængigheder (forekomster) some ikke " +"Kunne ikke gemme scene. Der er nogle afhængigheder (forekomster) som ikke " "kunne opfyldes." #: editor/editor_node.cpp @@ -1660,12 +1653,12 @@ msgid "Open Base Scene" msgstr "Ã…bn Grund Scene" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Hurtig Ã…bn Scene.." +msgid "Quick Open Scene..." +msgstr "Hurtig Ã…bn Scene..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Hurtig Ã…bn Script.." +msgid "Quick Open Script..." +msgstr "Hurtig Ã…bn Script..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1676,8 +1669,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Gem ændringer til '%s' før lukning?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Gem Scene Som.." +msgid "Save Scene As..." +msgstr "Gem Scene Som..." #: editor/editor_node.cpp msgid "No" @@ -1728,8 +1721,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Denne handling kan ikke fortrydes. Vend tilbage alligevel?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Hurtig Kør Scene.." +msgid "Quick Run Scene..." +msgstr "Hurtig Kør Scene..." #: editor/editor_node.cpp msgid "Quit" @@ -1887,8 +1880,8 @@ msgid "Previous tab" msgstr "Forrige fane" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrer filer.." +msgid "Filter Files..." +msgstr "Filtrer filer..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1900,12 +1893,12 @@ msgstr "Ny Scene" #: editor/editor_node.cpp #, fuzzy -msgid "New Inherited Scene.." -msgstr "Ny Nedarvet Scene.." +msgid "New Inherited Scene..." +msgstr "Ny Nedarvet Scene..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Ã…bn Scene.." +msgid "Open Scene..." +msgstr "Ã…bn Scene..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1924,16 +1917,16 @@ msgid "Open Recent" msgstr "Ã…ben Seneste" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Konverter Til.." +msgid "Convert To..." +msgstr "Konverter Til..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MaskeBibliotek.." +msgid "MeshLibrary..." +msgstr "MaskeBibliotek..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2199,8 +2192,8 @@ msgid "Save the currently edited resource." msgstr "Gem den aktuelt redigerede ressource." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Gem Som.." +msgid "Save As..." +msgstr "Gem Som..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2308,8 +2301,8 @@ msgid "Creating Mesh Previews" msgstr "Opretter Maske ForhÃ¥ndsvisninger" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniature.." +msgid "Thumbnail..." +msgstr "Miniature..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2465,8 +2458,8 @@ msgid "(Current)" msgstr "(Nuværende)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Henter spejle, vent venligst .." +msgid "Retrieving mirrors, please wait..." +msgstr "Henter spejle, vent venligst ..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2548,7 +2541,7 @@ msgstr "Fejl i anmodning url: " #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "Forbinder..." #: editor/export_template_manager.cpp @@ -2566,8 +2559,8 @@ msgstr "Kan ikke Løses" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Forbinder.." +msgid "Connecting..." +msgstr "Forbinder..." #: editor/export_template_manager.cpp #, fuzzy @@ -2580,8 +2573,8 @@ msgstr "Tilsluttet" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Anmoder.." +msgid "Requesting..." +msgstr "Anmoder..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2722,12 +2715,12 @@ msgid "Collapse all" msgstr "Klap alle sammen" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Omdøb.." +msgid "Rename..." +msgstr "Omdøb..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Flyt Til.." +msgid "Move To..." +msgstr "Flyt Til..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2739,16 +2732,16 @@ msgid "Instance" msgstr "Instans" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Rediger Afhængigheder.." +msgid "Edit Dependencies..." +msgstr "Rediger Afhængigheder..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Vis Ejere.." +msgid "View Owners..." +msgstr "Vis Ejere..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Duplikere" #: editor/filesystem_dock.cpp @@ -2774,10 +2767,10 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Scanner Filer,\n" -"Vent Venligst.." +"Vent Venligst..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2842,20 +2835,20 @@ msgid "Import Scene" msgstr "Importer Scene" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Importerer Scene.." +msgid "Importing Scene..." +msgstr "Importerer Scene..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" -msgstr "" +msgstr "Generering af lightmaps" #: editor/import/resource_importer_scene.cpp msgid "Generating for Mesh: " -msgstr "" +msgstr "Generering til Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Kører Brugerdefineret Script.." +msgid "Running Custom Script..." +msgstr "Kører Brugerdefineret Script..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2870,8 +2863,8 @@ msgid "Error running post-import script:" msgstr "Fejl ved kørsel af efter-import script:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Gemmer.." +msgid "Saving..." +msgstr "Gemmer..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2890,8 +2883,8 @@ msgid "Import As:" msgstr "Importer Som:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Forudindstillet.." +msgid "Preset..." +msgstr "Forudindstillet..." #: editor/import_dock.cpp msgid "Reimport" @@ -2981,21 +2974,21 @@ msgstr "Fjern Animation" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Invalid animation name!" -msgstr "" +msgstr "FEJL: Ugyldig animationsnavn!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Animation name already exists!" -msgstr "" +msgstr "FEJL: Animationsnavn eksisterer allerede!" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Rename Animation" -msgstr "" +msgstr "Omdøb animation" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Animation" -msgstr "" +msgstr "Tilføj animation" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Blend Next Changed" @@ -3007,19 +3000,19 @@ msgstr "" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load Animation" -msgstr "" +msgstr "Indlæs animation" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Duplicate Animation" -msgstr "" +msgstr "Lav en kopi af animation" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to copy!" -msgstr "" +msgstr "FEJL: Der er ingen animation der kan kopieres!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation resource on clipboard!" -msgstr "" +msgstr "FEJL: Ingen animationsressource i udklipsholder!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Pasted Animation" @@ -3031,31 +3024,31 @@ msgstr "" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to edit!" -msgstr "" +msgstr "FEJL: Der er ingen animation som kan redigeres!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation backwards from current pos. (A)" -msgstr "" +msgstr "Afspil valgte animation baglæns fra nuværende position. (A)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation backwards from end. (Shift+A)" -msgstr "" +msgstr "Afspil valgt animation baglæns fra slutningen. (Shift+A)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Stop animation playback. (S)" -msgstr "" +msgstr "Stop animation afspilning. (S)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation from start. (Shift+D)" -msgstr "" +msgstr "Afspil valgt animation fra start. (Shift+D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation from current pos. (D)" -msgstr "" +msgstr "Afspil valgt animation fra nuværende position. (D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation position (in seconds)." -msgstr "" +msgstr "Animationsposition (i sekunder)." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Scale animation playback globally for the node." @@ -3310,7 +3303,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3318,7 +3311,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3387,7 +3380,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3454,7 +3447,7 @@ msgid "Site:" msgstr "Websted:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Støtte..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3642,6 +3635,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -4065,7 +4059,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4272,7 +4266,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4638,7 +4632,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4737,7 +4731,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4944,15 +4938,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5405,11 +5399,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5662,7 +5652,7 @@ msgid "Remove All" msgstr "Fjern Alt" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5730,7 +5720,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5856,7 +5846,7 @@ msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet.." +msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -5920,7 +5910,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -6011,6 +6001,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Ugyldigt navn." + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Kunne ikke oprette mappe." @@ -6201,8 +6196,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6230,7 +6225,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6415,7 +6410,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6511,11 +6506,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6686,7 +6681,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8197,6 +8192,13 @@ msgstr "Error loading skrifttype." msgid "Invalid font size." msgstr "Ugyldig skriftstørrelse." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Forrige fane" + +#~ msgid "Next" +#~ msgstr "Næste" + #~ msgid "Can't contain '/' or ':'" #~ msgstr "Kan ikke indeholde '/' eller ':'" @@ -8210,9 +8212,6 @@ msgstr "Ugyldig skriftstørrelse." #~ msgid "Can't write file." #~ msgstr "Kan ikke skrive til fil." -#~ msgid "Next" -#~ msgstr "Næste" - #~ msgid "Not found!" #~ msgstr "Ikke fundet!" diff --git a/editor/translations/de.po b/editor/translations/de.po index 2087c7f4b6..d5d63f654b 100644 --- a/editor/translations/de.po +++ b/editor/translations/de.po @@ -2,7 +2,6 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Alexander Mahr <alex.mahr@gmail.com>, 2016. # Andreas Esau <andreasesau@gmail.com>, 2016. # Andreas Haas <liu.gam3@gmail.com>, 2016. @@ -12,10 +11,13 @@ # CitrusEdition <mariankloesler@web.de>, 2017. # danjo <atze@libra.uberspace.de>, 2016. # Eurocloud KnowHow <tobias.kloy@werde-volunteer.info>, 2017. +# HugeGameArt <hugegameartgd@gmail.com>, 2018. # hyperglow <greensoma@web.de>, 2016. # Jan Groß <jan@grossit.de>, 2016. # Kim <github@aggsol.de>, 2017. +# Metin Celik <metincelik88@gmail.com>, 2018. # Neicul <neicul@gmx.de>, 2018. +# nimradium <nimra242001@gmail.com>, 2018. # Oliver Ruehl <oliver@ruehldesign.co>, 2016-2017. # Paul-Vincent Roll <paviro@me.com>, 2016. # Peter Friedland <peter_friedland@gmx.de>, 2016. @@ -25,13 +27,12 @@ # Tim Schellenberg <smwleod@gmail.com>, 2017. # Timo Schwarzer <account@timoschwarzer.com>, 2016-2018. # viernullvier <hannes.breul+github@gmail.com>, 2016. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-04-18 15:38+0000\n" -"Last-Translator: Neicul <neicul@gmx.de>\n" +"PO-Revision-Date: 2018-06-19 19:38+0000\n" +"Last-Translator: nimradium <nimra242001@gmail.com>\n" "Language-Team: German <https://hosted.weblate.org/projects/godot-engine/" "godot/de/>\n" "Language: de\n" @@ -39,7 +40,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -330,7 +331,8 @@ msgstr "Optimieren" #: editor/animation_editor.cpp msgid "Select an AnimationPlayer from the Scene Tree to edit animations." msgstr "" -"AnimationPlayer aus dem Szenenbaum auswählen um Animationen zu bearbeiten." +"Wählen Sie einen AnimationPlayer aus dem Szenenbaum aus, um Animationen zu " +"bearbeiten." #: editor/animation_editor.cpp msgid "Key" @@ -520,8 +522,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "'%s' von '%s' trennen" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Verbinden.." +msgid "Connect..." +msgstr "Verbinden..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -946,12 +948,12 @@ msgid "Move Audio Bus" msgstr "Audiobus verschieben" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Audiobus-Layout speichern als.." +msgid "Save Audio Bus Layout As..." +msgstr "Audiobus-Layout speichern als..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Speicherort für neues Layout.." +msgid "Location for New Layout..." +msgstr "Speicherort für neues Layout..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1092,11 +1094,11 @@ msgid "Updating Scene" msgstr "Aktualisiere Szene" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Speichere lokale Änderungen.." +msgid "Storing local changes..." +msgstr "Speichere lokale Änderungen..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "Aktualisiere Szene..." #: editor/editor_data.cpp @@ -1165,8 +1167,8 @@ msgid "Show In File Manager" msgstr "Zeige im Dateimanager" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Neuer Ordner.." +msgid "New Folder..." +msgstr "Neuer Ordner..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1427,19 +1429,19 @@ msgstr "Ausgabe löschen" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Projekt-Export ist fehlgeschlagen mit Fehlercode %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Fehler beim speichern der Ressource!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Speichere Ressource als.." +msgid "Save Resource As..." +msgstr "Speichere Ressource als..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Verstehe..." #: editor/editor_node.cpp @@ -1671,12 +1673,12 @@ msgid "Open Base Scene" msgstr "Basisszene öffnen" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Schnell Szenen öffnen.." +msgid "Quick Open Scene..." +msgstr "Schnell Szenen öffnen..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Schnell Skripte öffnen.." +msgid "Quick Open Script..." +msgstr "Schnell Skripte öffnen..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1687,8 +1689,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Änderungen in ‚%s‘ vor dem Schließen speichern?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Szene speichern als.." +msgid "Save Scene As..." +msgstr "Szene speichern als..." #: editor/editor_node.cpp msgid "No" @@ -1741,8 +1743,8 @@ msgstr "" "Diese Aktion kann nicht rückgängig gemacht werden. Trotzdem zurücksetzen?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Schnell Szene starten.." +msgid "Quick Run Scene..." +msgstr "Schnell Szene starten..." #: editor/editor_node.cpp msgid "Quit" @@ -1905,8 +1907,8 @@ msgid "Previous tab" msgstr "Vorheriger Tab" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Dateien filtern.." +msgid "Filter Files..." +msgstr "Dateien filtern..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1917,12 +1919,12 @@ msgid "New Scene" msgstr "Neue Szene" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Neue gererbte Szene.." +msgid "New Inherited Scene..." +msgstr "Neue geerbte Szene..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Szene öffnen.." +msgid "Open Scene..." +msgstr "Szene öffnen..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1941,16 +1943,16 @@ msgid "Open Recent" msgstr "Zuletzt benutzte Szenen" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Umwandeln zu.." +msgid "Convert To..." +msgstr "Umwandeln zu..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2214,8 +2216,8 @@ msgid "Save the currently edited resource." msgstr "Speichere die so eben bearbeitete Ressource." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Speichern als.." +msgid "Save As..." +msgstr "Speichern als..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2323,8 +2325,8 @@ msgid "Creating Mesh Previews" msgstr "Mesh-Vorschauen erzeugen" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Vorschau.." +msgid "Thumbnail..." +msgstr "Vorschau..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2476,12 +2478,12 @@ msgid "(Current)" msgstr "(Aktuell)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Mirrors werden geladen, bitte warten..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" -msgstr "Template-Version ‚%s‘ entfernen?" +msgstr "Template-Version '%s' entfernen?" #: editor/export_template_manager.cpp msgid "Can't open export templates zip." @@ -2554,8 +2556,8 @@ msgid "Error requesting url: " msgstr "Fehler beim Abrufen der URL: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Verbinde mit Mirror.." +msgid "Connecting to Mirror..." +msgstr "Verbinde mit Mirror..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2571,8 +2573,8 @@ msgstr "Kann nicht aufgelöst werden" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Verbinde.." +msgid "Connecting..." +msgstr "Verbinde..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2584,8 +2586,8 @@ msgstr "Verbunden" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Frage an.." +msgid "Requesting..." +msgstr "Frage an..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2721,12 +2723,12 @@ msgid "Collapse all" msgstr "Alle einklappen" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Umbenennen.." +msgid "Rename..." +msgstr "Umbenennen..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Verschiebe zu.." +msgid "Move To..." +msgstr "Verschiebe zu..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2737,16 +2739,16 @@ msgid "Instance" msgstr "Instanz" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Abhängigkeiten bearbeiten.." +msgid "Edit Dependencies..." +msgstr "Abhängigkeiten bearbeiten..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Zeige Besitzer.." +msgid "View Owners..." +msgstr "Zeige Besitzer..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplizieren.." +msgid "Duplicate..." +msgstr "Duplizieren..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2771,10 +2773,10 @@ msgstr "Instantiiere gewählte Szene(n) als Unterobjekt des ausgewählten Nodes. #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Lese Dateien,\n" -"Bitte warten.." +"Bitte warten..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2839,8 +2841,8 @@ msgid "Import Scene" msgstr "Szene importieren" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Szene wird importiert.." +msgid "Importing Scene..." +msgstr "Szene wird importiert..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2851,8 +2853,8 @@ msgid "Generating for Mesh: " msgstr "Generierung für Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Angepasstes Skript wird ausgeführt.." +msgid "Running Custom Script..." +msgstr "Angepasstes Skript wird ausgeführt..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2867,8 +2869,8 @@ msgid "Error running post-import script:" msgstr "Fehler beim ausführen des Post-Import Skripts:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Speichere.." +msgid "Saving..." +msgstr "Speichere..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2887,8 +2889,8 @@ msgid "Import As:" msgstr "Importiere als:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Voreinstellungen.." +msgid "Preset..." +msgstr "Voreinstellungen..." #: editor/import_dock.cpp msgid "Reimport" @@ -3305,16 +3307,16 @@ msgid "Transition Node" msgstr "Übergangs-Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Animationen importieren.." +msgid "Import Animations..." +msgstr "Animationen importieren..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Nodefilter bearbeiten" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filter.." +msgid "Filters..." +msgstr "Filter..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3381,8 +3383,8 @@ msgid "Fetching:" msgstr "Hole:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Löse auf.." +msgid "Resolving..." +msgstr "Löse auf..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3448,8 +3450,8 @@ msgid "Site:" msgstr "Seite:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Stabilität.." +msgid "Support..." +msgstr "Stabilität..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3648,6 +3650,7 @@ msgid "Use Rotation Snap" msgstr "Rotationsraster benutzen" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Einrasten konfigurieren..." @@ -3745,14 +3748,12 @@ msgid "Show Guides" msgstr "Hilfslinien anzeigen" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "Zeige Ursprung" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "Eine Ansicht" +msgstr "Zeige Ansichtsfenster (Viewport)" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4046,7 +4047,7 @@ msgstr "Mesh hat keine Oberfläche von der Umrisse erzeugt werden könnten!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Der Mesh-Grundtyp ist nicht ist nicht PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4077,8 +4078,8 @@ msgid "Create Convex Collision Sibling" msgstr "Konvexes Kollisionselement erzeugen" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Umriss-Mesh erzeugen.." +msgid "Create Outline Mesh..." +msgstr "Umriss-Mesh erzeugen..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4285,8 +4286,8 @@ msgid "Error loading image:" msgstr "Fehler beim Laden des Bilds:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Keine Pixel mit einer Transparenz > 128 im Bild.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Keine Pixel mit einer Transparenz > 128 im Bild..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4646,8 +4647,8 @@ msgid "Import Theme" msgstr "Motiv importieren" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Motiv speichern als.." +msgid "Save Theme As..." +msgstr "Motiv speichern als..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4743,8 +4744,8 @@ msgstr "Seitenleiste umschalten" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Finde.." +msgid "Find..." +msgstr "Finde..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4953,16 +4954,16 @@ msgid "Find Previous" msgstr "Finde Vorheriges" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Ersetzen.." +msgid "Replace..." +msgstr "Ersetzen..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Springe zu Funktion.." +msgid "Goto Function..." +msgstr "Springe zu Funktion..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Springe zu Zeile.." +msgid "Goto Line..." +msgstr "Springe zu Zeile..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5415,12 +5416,8 @@ msgid "Transform" msgstr "Transformation" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Einrasten konfigurieren.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Transformationsdialog.." +msgid "Transform Dialog..." +msgstr "Transformationsdialog..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5672,8 +5669,8 @@ msgid "Remove All" msgstr "Alles entfernen" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Thema bearbeiten.." +msgid "Edit theme..." +msgstr "Thema bearbeiten..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5720,14 +5717,12 @@ msgid "Checked Item" msgstr "Überprüftes Element" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Element hinzufügen" +msgstr "Element der Auswahl" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Überprüftes Element" +msgstr "Markiertes Element der Auswahl" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5742,8 +5737,8 @@ msgid "Options" msgstr "Optionen" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Enthalten,Viele,Einige,Optionen!" +msgid "Has,Many,Options" +msgstr "Einstellungen" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5934,8 +5929,8 @@ msgid "Presets" msgstr "Vorlagen" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Hinzufügen.." +msgid "Add..." +msgstr "Hinzufügen..." #: editor/project_export.cpp msgid "Resources" @@ -6028,6 +6023,10 @@ msgid "Imported Project" msgstr "Importiertes Projekt" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Ungültiger Projektname." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Ordner konnte nicht erstellt werden." @@ -6230,9 +6229,11 @@ msgstr "Maustaste" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Ungültiger Aktionsname. Er kann weder leer sein noch ‚/‘, ‚:‘, ‚=‘, ‘\\‘ " +"oder ‚\"‘ enthalten." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6259,8 +6260,8 @@ msgid "Control+" msgstr "Steuerung+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Drücke eine Taste.." +msgid "Press a Key..." +msgstr "Drücke eine Taste..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6443,8 +6444,8 @@ msgid "Property:" msgstr "Eigenschaft:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Überschreiben für.." +msgid "Override For..." +msgstr "Überschreiben für..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6539,12 +6540,12 @@ msgid "Easing Out-In" msgstr "Glätten Aus-Ein" #: editor/property_editor.cpp -msgid "File.." -msgstr "Datei.." +msgid "File..." +msgstr "Datei..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Verzeichnis.." +msgid "Dir..." +msgstr "Verzeichnis..." #: editor/property_editor.cpp msgid "Assign" @@ -6719,8 +6720,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "Diese Aktion kann nicht auf instantiierten Szenen ausgeführt werden." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Speichere neue Szene als.." +msgid "Save New Scene As..." +msgstr "Speichere neue Szene als..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7439,7 +7440,7 @@ msgstr "Auswahlradius:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Der Klassenname kann nicht ein reserviertes Schlüsselwort sein" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8164,7 +8165,7 @@ msgstr "Die Pfad-Eigenschaft muss auf ein gültiges Spatial-Node verweisen." #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "Ein WorldEnvironment benötigt eine Environment-Ressource." #: scene/3d/scenario_fx.cpp msgid "" @@ -8178,6 +8179,9 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Dieses WorldEnvironment wird ignoriert. Entweder füge eine Kamera (für 3D-" +"Szenen) hinzu oder setze den Hintergrund-Modus des Environments nach Canvas " +"(für 2D-Szenen)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8280,6 +8284,13 @@ msgstr "Fehler beim Laden der Schriftart." msgid "Invalid font size." msgstr "Ungültige Schriftgröße." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Vorheriger Tab" + +#~ msgid "Next" +#~ msgstr "Nächste" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "" #~ "Ungültiger Name für Aktion (alle Zeichen außer ‚/‘ und ‚:‘ möglich)." @@ -8306,9 +8317,6 @@ msgstr "Ungültige Schriftgröße." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "project.godot konnte nicht im Projektpfad gefunden werden." -#~ msgid "Next" -#~ msgstr "Nächste" - #~ msgid "Not found!" #~ msgstr "Nicht gefunden!" @@ -8457,7 +8465,7 @@ msgstr "Ungültige Schriftgröße." #~ msgid "Exporting for %s" #~ msgstr "Exportiere für %s" -#~ msgid "Setting Up.." +#~ msgid "Setting Up..." #~ msgstr "Bereite vor..." #~ msgid "Error loading scene." @@ -8520,8 +8528,8 @@ msgstr "Ungültige Schriftgröße." #~ msgid "Info" #~ msgstr "Info" -#~ msgid "Re-Import.." -#~ msgstr "Neuimport.." +#~ msgid "Re-Import..." +#~ msgstr "Neuimport..." #~ msgid "No bit masks to import!" #~ msgstr "Keine Bitmasken zu importieren!" @@ -8915,14 +8923,14 @@ msgstr "Ungültige Schriftgröße." #~ msgid "Zoom (%):" #~ msgstr "Vergrößerung (%):" -#~ msgid "Skeleton.." -#~ msgstr "Skelett.." +#~ msgid "Skeleton..." +#~ msgstr "Skelett..." #~ msgid "Zoom Reset" #~ msgstr "Vergrößerung zurücksetzen" -#~ msgid "Zoom Set.." -#~ msgstr "Vergrößerung setzen.." +#~ msgid "Zoom Set..." +#~ msgstr "Vergrößerung setzen..." #~ msgid "Set a Value" #~ msgstr "Einen Wert setzen" @@ -9392,8 +9400,8 @@ msgstr "Ungültige Schriftgröße." #~ msgid "Export Project PCK" #~ msgstr "Exportiere Projekt-PCK" -#~ msgid "Export.." -#~ msgstr "Exportieren.." +#~ msgid "Export..." +#~ msgstr "Exportieren..." #~ msgid "Project Export" #~ msgstr "Projekt exportieren" @@ -9458,8 +9466,8 @@ msgstr "Ungültige Schriftgröße." #~ msgid "Method In Node:" #~ msgstr "Methode in Node:" -#~ msgid "Edit Connections.." -#~ msgstr "Bearbeite Verbindungen.." +#~ msgid "Edit Connections..." +#~ msgstr "Bearbeite Verbindungen..." #~ msgid "Plugin List:" #~ msgstr "Plugin Liste:" diff --git a/editor/translations/de_CH.po b/editor/translations/de_CH.po index ea942bb7c2..26f824bc4b 100644 --- a/editor/translations/de_CH.po +++ b/editor/translations/de_CH.po @@ -495,7 +495,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -916,11 +916,11 @@ msgid "Move Audio Bus" msgstr "Bild bewegen/einfügen" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1056,11 +1056,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1130,7 +1130,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1394,12 +1394,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1608,11 +1608,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1625,7 +1625,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1680,7 +1680,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1828,7 +1828,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1840,11 +1840,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1865,15 +1865,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2123,7 +2123,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2235,7 +2235,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2386,7 +2386,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2465,7 +2465,7 @@ msgstr "Szene kann nicht gespeichert werden." #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "Connections editieren" #: editor/export_template_manager.cpp @@ -2483,7 +2483,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Connecting.." +msgid "Connecting..." msgstr "Connections editieren" #: editor/export_template_manager.cpp @@ -2498,7 +2498,7 @@ msgstr "Verbindung zu Node:" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2639,11 +2639,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2656,16 +2656,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Node(s) duplizieren" #: editor/filesystem_dock.cpp @@ -2691,7 +2691,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2757,7 +2757,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2769,7 +2769,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2785,7 +2785,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2806,7 +2806,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3231,7 +3231,7 @@ msgid "Transition Node" msgstr "Transition-Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3239,7 +3239,7 @@ msgid "Edit Node Filters" msgstr "Node Filter editieren" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3310,7 +3310,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3378,7 +3378,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3571,6 +3571,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3998,7 +3999,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4206,7 +4207,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4575,7 +4576,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4672,7 +4673,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4881,15 +4882,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5347,11 +5348,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5607,7 +5604,7 @@ msgid "Remove All" msgstr "Ungültige Bilder löschen" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5675,7 +5672,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5865,7 +5862,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5961,6 +5958,11 @@ msgstr "Importierte Projekte" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Projektname:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Node erstellen" @@ -6157,8 +6159,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6186,8 +6188,8 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Taste drücken.." +msgid "Press a Key..." +msgstr "Taste drücken..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6373,7 +6375,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6470,11 +6472,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6651,7 +6653,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Das funktioniert nicht bei einer instanzierten Szene." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Neue Szene speichern als..." #: editor/scene_tree_dock.cpp diff --git a/editor/translations/editor.pot b/editor/translations/editor.pot index 687c517180..1cb31e0ee9 100644 --- a/editor/translations/editor.pot +++ b/editor/translations/editor.pot @@ -3491,6 +3491,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -5240,10 +5241,6 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap..." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Dialog..." msgstr "" @@ -5565,7 +5562,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5843,6 +5840,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6029,8 +6030,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp diff --git a/editor/translations/el.po b/editor/translations/el.po index 2bf8d790ab..b3275b4647 100644 --- a/editor/translations/el.po +++ b/editor/translations/el.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-03-05 16:04+0000\n" +"PO-Revision-Date: 2018-05-20 09:37+0000\n" "Last-Translator: George Tsiamasiotis <gtsiam@windowslive.com>\n" "Language-Team: Greek <https://hosted.weblate.org/projects/godot-engine/godot/" "el/>\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.20-dev\n" +"X-Generator: Weblate 3.0-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -498,8 +498,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "ΑποσÏνδεση του '%s' απο το '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "ΣÏνδεση.." +msgid "Connect..." +msgstr "ΣÏνδεση..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -919,12 +919,12 @@ msgid "Move Audio Bus" msgstr "Μετακίνηση διαÏλου ήχου" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Αποθήκευση διάταξης διαÏλων ήχου ÏŽÏ‚.." +msgid "Save Audio Bus Layout As..." +msgstr "Αποθήκευση διάταξης διαÏλων ήχου ÏŽÏ‚..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Τοποθεσία για νÎα διάταξη.." +msgid "Location for New Layout..." +msgstr "Τοποθεσία για νÎα διάταξη..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1061,12 +1061,12 @@ msgid "Updating Scene" msgstr "ΕνημÎÏωση σκηνής" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Αποθήκευση τοπικών αλλαγών.." +msgid "Storing local changes..." +msgstr "Αποθήκευση τοπικών αλλαγών..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "ΕνημÎÏωση σκηνής.." +msgid "Updating scene..." +msgstr "ΕνημÎÏωση σκηνής..." #: editor/editor_data.cpp msgid "[empty]" @@ -1134,7 +1134,7 @@ msgid "Show In File Manager" msgstr "Εμφάνιση στη διαχείÏιση αÏχείων" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "ÎÎος φάκελος" #: editor/editor_file_dialog.cpp @@ -1396,20 +1396,20 @@ msgstr "ΕκκαθάÏιση εξόδου" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Η εξαγωγή του ÎÏγου απÎτυχε με κωδικό %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Σφάλμα κατά την αποθήκευση πόÏου!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Αποθήκευση πόÏου ως.." +msgid "Save Resource As..." +msgstr "Αποθήκευση πόÏου ως..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Εντάξει.." +msgid "I see..." +msgstr "Εντάξει..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1643,11 +1643,11 @@ msgid "Open Base Scene" msgstr "Άνοιγμα σκηνής βάσης" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "ΓÏήγοÏο άνοιγμα σκηνής..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "ΓÏήγοÏη άνοιγμα δεσμής ενεÏγειών..." #: editor/editor_node.cpp @@ -1659,7 +1659,7 @@ msgid "Save changes to '%s' before closing?" msgstr "Αποθήκευση αλλαγών στο '%s' Ï€Ïιν το κλείσιμο;" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "Αποθήκευση σκηνή ως..." #: editor/editor_node.cpp @@ -1714,7 +1714,7 @@ msgstr "" "επαναφοÏά;" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "ΓÏήγοÏη εκτÎλεση σκηνής..." #: editor/editor_node.cpp @@ -1877,7 +1877,7 @@ msgid "Previous tab" msgstr "Î ÏοηγοÏμενη καÏÏ„Îλα" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "ΦιλτÏάÏισμα αÏχείων..." #: editor/editor_node.cpp @@ -1889,12 +1889,12 @@ msgid "New Scene" msgstr "ÎÎα σκηνή" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "ÎÎα κληÏονομημÎνη σκηνή.." +msgid "New Inherited Scene..." +msgstr "ÎÎα κληÏονομημÎνη σκηνή..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Άνοιγμα σκηνής.." +msgid "Open Scene..." +msgstr "Άνοιγμα σκηνής..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1913,15 +1913,15 @@ msgid "Open Recent" msgstr "Άνοιγμα Ï€Ïόσφατων" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "ΜετατÏοπή σε..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "Βιβλιοθήκη πλεγμάτων..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2186,7 +2186,7 @@ msgid "Save the currently edited resource." msgstr "Αποθήκευσε το Ï„ÏÎχων επεξεÏγαζόμενο πόÏο." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Αποθήκευση ως..." #: editor/editor_node.cpp @@ -2295,8 +2295,8 @@ msgid "Creating Mesh Previews" msgstr "ΔημιουÏγία Ï€Ïοεπισκοπήσεων πλεγμάτων" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "ΜικÏογÏαφία.." +msgid "Thumbnail..." +msgstr "ΜικÏογÏαφία..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2448,8 +2448,8 @@ msgid "(Current)" msgstr "(ΤÏÎχων)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Ανάκτηση δεδοÎνων κατοπτÏισμοÏ, παÏακαλώ πεÏιμÎνετε.." +msgid "Retrieving mirrors, please wait..." +msgstr "Ανάκτηση δεδοÎνων κατοπτÏισμοÏ, παÏακαλώ πεÏιμÎνετε..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2526,8 +2526,8 @@ msgid "Error requesting url: " msgstr "Σφάλμα κατά Ï„o αίτημα για διεÏθηνση url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "ΣÏνδεση σε διακομιστή κατοπτÏισμοÏ.." +msgid "Connecting to Mirror..." +msgstr "ΣÏνδεση σε διακομιστή κατοπτÏισμοÏ..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2543,8 +2543,8 @@ msgstr "Δεν είναι δυνατή η επίλυση" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "ΣÏνδεση.." +msgid "Connecting..." +msgstr "ΣÏνδεση..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2556,8 +2556,8 @@ msgstr "ΣυνδÎθηκε" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Γίνεται αίτημα.." +msgid "Requesting..." +msgstr "Γίνεται αίτημα..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2693,11 +2693,11 @@ msgid "Collapse all" msgstr "ΣÏμπτηξη όλων" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "Μετονομασία..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Μετακίνηση σε" #: editor/filesystem_dock.cpp @@ -2709,15 +2709,15 @@ msgid "Instance" msgstr "Στιγμιότυπο" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "ΕπεξεÏγασία εξαÏτήσεων" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Î Ïοβολή ιδιοκτητών" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "ΑναπαÏαγωγή" #: editor/filesystem_dock.cpp @@ -2745,10 +2745,10 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "ΣάÏωση αÏχείων,\n" -"ΠαÏακαλώ πεÏιμÎνετε.." +"ΠαÏακαλώ πεÏιμÎνετε..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2813,7 +2813,7 @@ msgid "Import Scene" msgstr "Εισαγωγή σκηνής" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "Εισαγωγή σκηνής..." #: editor/import/resource_importer_scene.cpp @@ -2825,7 +2825,7 @@ msgid "Generating for Mesh: " msgstr "ΔημιουÏία για πλÎγμα: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "ΕκτÎλεση Ï€ÏοσαÏμοσμÎνης δÎσμης ενεÏγειών..." #: editor/import/resource_importer_scene.cpp @@ -2843,7 +2843,7 @@ msgid "Error running post-import script:" msgstr "Σφάλμα κατά την εκτÎλεση της δÎσμης ενεÏγειών μετ-εισαγωγής:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Αποθήκευση..." #: editor/import_dock.cpp @@ -2863,7 +2863,7 @@ msgid "Import As:" msgstr "Εισαγωγή ÏŽÏ‚:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "ΔιαμόÏφωση..." #: editor/import_dock.cpp @@ -3281,16 +3281,16 @@ msgid "Transition Node" msgstr "Κόμβος μετάβασης" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Εισαγωγή κινήσεων.." +msgid "Import Animations..." +msgstr "Εισαγωγή κινήσεων..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "ΕπεξεÏγασία φίλτÏων κόμβων" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "ΦίλτÏα.." +msgid "Filters..." +msgstr "ΦίλτÏα..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3358,7 +3358,7 @@ msgid "Fetching:" msgstr "Λήψη:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "Επίλυση..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3425,8 +3425,8 @@ msgid "Site:" msgstr "ΔιεÏθυνση:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "ΥποστήÏιξη.." +msgid "Support..." +msgstr "ΥποστήÏιξη..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3624,6 +3624,7 @@ msgid "Use Rotation Snap" msgstr "ΧÏήση κουμπώματος πεÏιστÏοφής" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "ΔιαμόÏφωση κουμπώματος..." @@ -3720,14 +3721,12 @@ msgid "Show Guides" msgstr "Εμφάνιση οδηγών" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Î Ïοβολή ΑÏχής" +msgstr "Î Ïοβολή πηγής" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Οπτική γωνία" +msgstr "Î Ïοβολή οπτικής γωνίας" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4020,7 +4019,7 @@ msgstr "Το πλÎγμα δεν Îχει επιφάνει από την οποΠ#: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "O Ï€ÏωταÏχικός Ï„Ïπος δεν είναι PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4051,8 +4050,8 @@ msgid "Create Convex Collision Sibling" msgstr "ΔημιουÏγία Î±Î´ÎµÎ»Ï†Î¿Ï ÏƒÏγκÏουσης κυÏÏ„Î¿Ï ÏƒÏŽÎ¼Î±Ï„Î¿Ï‚" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "ΔημιουÏγία πλÎγματος πεÏιγÏάμματος.." +msgid "Create Outline Mesh..." +msgstr "ΔημιουÏγία πλÎγματος πεÏιγÏάμματος..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4260,8 +4259,8 @@ msgid "Error loading image:" msgstr "Σφάλμα κατά την φόÏτωση εικόνας:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Δεν υπάÏχουν εικονοστοιχεία με διαφάνεια >128 στην εικόνα.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Δεν υπάÏχουν εικονοστοιχεία με διαφάνεια >128 στην εικόνα..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4621,8 +4620,8 @@ msgid "Import Theme" msgstr "Εισαγωγή θÎματος" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Αποθήκευση θÎματος ως.." +msgid "Save Theme As..." +msgstr "Αποθήκευση θÎματος ως..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4718,8 +4717,8 @@ msgstr "Εναλλαγή πλαισίου δεσμών ενεÏγειών" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "ΕÏÏεση.." +msgid "Find..." +msgstr "ΕÏÏεση..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4928,16 +4927,16 @@ msgid "Find Previous" msgstr "ΈυÏεση Ï€ÏοηγοÏμενου" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Αντικατάσταση.." +msgid "Replace..." +msgstr "Αντικατάσταση..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Πήγαινε σε συνάÏτηση.." +msgid "Goto Function..." +msgstr "Πήγαινε σε συνάÏτηση..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Πήγαινε σε γÏαμμή.." +msgid "Goto Line..." +msgstr "Πήγαινε σε γÏαμμή..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5392,12 +5391,8 @@ msgid "Transform" msgstr "Μετασχηματισμός" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "ΔιαμόÏφωση κουμπώματος.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Διάλογος μετασχηματισμοÏ.." +msgid "Transform Dialog..." +msgstr "Διάλογος μετασχηματισμοÏ..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5649,8 +5644,8 @@ msgid "Remove All" msgstr "ΑφαίÏεση όλων" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "ΕπεξεÏγασία θÎματος.." +msgid "Edit theme..." +msgstr "ΕπεξεÏγασία θÎματος..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5697,14 +5692,12 @@ msgid "Checked Item" msgstr "ΕπιλεγμÎνο στοιχείο" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Î Ïοσθήκη στοιχείου" +msgstr "Στοιχείο επιλογής" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "ΕπιλεγμÎνο στοιχείο" +msgstr "ΕπιλεγμÎνο στοιχείο επιλογής" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5719,7 +5712,8 @@ msgid "Options" msgstr "ΕπιλογÎÏ‚" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +#, fuzzy +msgid "Has,Many,Options" msgstr "Έχει,ΠάÏα,ΠολλÎÏ‚,ΕπιλογÎÏ‚!" #: editor/plugins/theme_editor_plugin.cpp @@ -5912,8 +5906,8 @@ msgid "Presets" msgstr "ΔιαμοÏφώσεις" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Î Ïοσθήκη.." +msgid "Add..." +msgstr "Î Ïοσθήκη..." #: editor/project_export.cpp msgid "Resources" @@ -6007,6 +6001,11 @@ msgid "Imported Project" msgstr "ΕισαγμÎνο ÎÏγο" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "Όνομα ÎÏγου:" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "ΑδÏνατη η δημιουÏγία φακÎλου." @@ -6189,7 +6188,7 @@ msgid "" "Would you like to explore the official example projects in the Asset Library?" msgstr "" "Δεν Îχετε κανÎνα ÎÏγο.\n" -"Θα θÎλατε να εξεÏευνήσετε μεÏικά παÏαδείγματα στην βιβλιοθήκη πόÏων;" +"ΘÎλετε να εξεÏευνήσετε μεÏικά παÏαδείγματα στην βιβλιοθήκη πόÏων;" #: editor/project_settings_editor.cpp msgid "Key " @@ -6208,10 +6207,13 @@ msgid "Mouse Button" msgstr "Κουμπί ποντικιοÏ" #: editor/project_settings_editor.cpp +#, fuzzy msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"ΆκυÏο όνομα ενÎÏγειας. Δεν μποÏεί να είναι άδειο ή να πεÏιÎχει '/', ':', " +"'=', '\\' ή '\"'" #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6238,8 +6240,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Πατήστε Îνα κουμπί.." +msgid "Press a Key..." +msgstr "Πατήστε Îνα κουμπί..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6422,7 +6424,7 @@ msgid "Property:" msgstr "Ιδιότητα:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "ΠαÏάκαμψη για..." #: editor/project_settings_editor.cpp @@ -6518,12 +6520,12 @@ msgid "Easing Out-In" msgstr "Ομαλή κίνηση από Îξω Ï€Ïος τα μÎσα" #: editor/property_editor.cpp -msgid "File.." -msgstr "ΑÏχείο.." +msgid "File..." +msgstr "ΑÏχείο..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Κατάλογος.." +msgid "Dir..." +msgstr "Κατάλογος..." #: editor/property_editor.cpp msgid "Assign" @@ -6699,8 +6701,8 @@ msgstr "" "δημιουÏγηθεί στιγμιότυπα." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Αποθήκευση νÎας σκηνής ως.." +msgid "Save New Scene As..." +msgstr "Αποθήκευση νÎας σκηνής ως..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7422,7 +7424,7 @@ msgstr "Επιλογή απόστασης:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Το όνομα της κλάσης δεν μποÏεί να είναι λÎξη-κλειδί" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8144,7 +8146,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "Το WorldEnvironment χÏειάζεται Îναν πόÏο Environment." #: scene/3d/scenario_fx.cpp msgid "" @@ -8158,6 +8160,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Αυτό το WorldEnvironment θα αγνοηθεί. Î ÏοσθÎστε μια κάμεÏα (για 3d) ή οÏίστε " +"το Background Mode Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… πεÏιβάλλοντος σε Canvas (για 2d)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8256,6 +8260,13 @@ msgstr "Σφάλμα κατά την φόÏτωση της γÏαμματοσεΠmsgid "Invalid font size." msgstr "Μη ÎγκυÏο μÎγεθος γÏαμματοσειÏάς." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Î ÏοηγοÏμενη καÏÏ„Îλα" + +#~ msgid "Next" +#~ msgstr "Επόμενο" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Μη ÎγκυÏη ενÎÏγεια (Όλα επιτÏÎποντα εκτός από το '/' και το ':')." @@ -8283,9 +8294,6 @@ msgstr "Μη ÎγκυÏο μÎγεθος γÏαμματοσειÏάς." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Δεν βÏÎθηκε το project.godot στη διαδÏομή του ÎÏγου." -#~ msgid "Next" -#~ msgstr "Επόμενο" - #~ msgid "Not found!" #~ msgstr "Δεν βÏÎθηκε!" @@ -8431,8 +8439,8 @@ msgstr "Μη ÎγκυÏο μÎγεθος γÏαμματοσειÏάς." #~ msgid "Exporting for %s" #~ msgstr "Εξαγωγή για %s" -#~ msgid "Setting Up.." -#~ msgstr "ΑÏχικοποίηση.." +#~ msgid "Setting Up..." +#~ msgstr "ΑÏχικοποίηση..." #~ msgid "Error loading scene." #~ msgstr "Σφάλμα κατά τη φόÏτωση σκηνής." @@ -8494,7 +8502,7 @@ msgstr "Μη ÎγκυÏο μÎγεθος γÏαμματοσειÏάς." #~ msgid "Info" #~ msgstr "ΠληÏοφοÏίες" -#~ msgid "Re-Import.." +#~ msgid "Re-Import..." #~ msgstr "Εκ νÎου εισαγωγή..." #~ msgid "No bit masks to import!" @@ -8894,14 +8902,14 @@ msgstr "Μη ÎγκυÏο μÎγεθος γÏαμματοσειÏάς." #~ msgid "Zoom (%):" #~ msgstr "ΜεγÎθυνση (%):" -#~ msgid "Skeleton.." -#~ msgstr "Σκελετός.." +#~ msgid "Skeleton..." +#~ msgstr "Σκελετός..." #~ msgid "Zoom Reset" #~ msgstr "ΕπαναφοÏά μεγÎθυνσης" -#~ msgid "Zoom Set.." -#~ msgstr "ΟÏισμός μεγÎθυνσης.." +#~ msgid "Zoom Set..." +#~ msgstr "ΟÏισμός μεγÎθυνσης..." #~ msgid "Set a Value" #~ msgstr "ΟÏισμός τιμής" diff --git a/editor/translations/es.po b/editor/translations/es.po index 86188201c1..89118d2501 100644 --- a/editor/translations/es.po +++ b/editor/translations/es.po @@ -2,9 +2,8 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Addiel Lucena Perez <addiell2017@gmail.com>, 2017. -# Aleix Sanchis <aleixsanchis@hotmail.com>, 2017. +# Aleix Sanchis <aleixsanchis@hotmail.com>, 2017, 2018. # Alejandro Alvarez <eliluminado00@gmail.com>, 2017. # Avocado <avocadosan42@gmail.com>, 2018. # BLaDoM GUY <simplybladom@gmail.com>, 2017. @@ -13,27 +12,29 @@ # David Couto <davidcouto@gmail.com>, 2017. # Dharkael <izhe@hotmail.es>, 2017. # Diego López <diegodario21@gmail.com>, 2017. +# eon-s <emanuel.segretin@gmail.com>, 2018. # Gustavo Leon <gleondiaz@gmail.com>, 2017-2018. # Javier Ocampos <xavier.ocampos@gmail.com>, 2018. +# Jose Maria Martinez <josemar1992@hotmail.com>, 2018. # Juan Quiroga <juanquiroga9@gmail.com>, 2017. # Kiji Pixel <raccoon.fella@gmail.com>, 2017. # Lisandro Lorea <lisandrolorea@gmail.com>, 2016-2017. # Lonsfor <lotharw@protonmail.com>, 2017-2018. # Mario Nachbaur <manachbaur@gmail.com>, 2018. # Oscar Carballal <oscar.carballal@protonmail.com>, 2017-2018. -# Rabid Orange <theorangerabid@gmail.com>, 2017. +# R. Joshua Seville <rjoshua@protonmail.com>, 2018. +# Rabid Orange <theorangerabid@gmail.com>, 2017, 2018. # Roger Blanco Ribera <roger.blancoribera@gmail.com>, 2016-2018. # Sebastian Silva <sebastian@fuentelibre.org>, 2016. # Swyter <swyterzone@gmail.com>, 2016-2017. # Vazquinhos <vazquinhos@gmail.com>, 2018. # Yovani Damián <blackblex@gmail.com>, 2018. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-05-03 02:11+0000\n" -"Last-Translator: Javier Ocampos <xavier.ocampos@gmail.com>\n" +"PO-Revision-Date: 2018-06-22 08:31+0000\n" +"Last-Translator: R. Joshua Seville <rjoshua@protonmail.com>\n" "Language-Team: Spanish <https://hosted.weblate.org/projects/godot-engine/" "godot/es/>\n" "Language: es\n" @@ -41,7 +42,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -53,11 +54,11 @@ msgstr "Toda la Selección" #: editor/animation_editor.cpp msgid "Anim Change Keyframe Time" -msgstr "Cambiar el tiempo de la clave de animación" +msgstr "Cambiar el tiempo del Fotograma Clave de Animación" #: editor/animation_editor.cpp msgid "Anim Change Transition" -msgstr "Cambiar transición de animación" +msgstr "Cambiar Transición de Animación" #: editor/animation_editor.cpp msgid "Anim Change Transform" @@ -65,76 +66,76 @@ msgstr "Cambiar transformación de animación" #: editor/animation_editor.cpp msgid "Anim Change Keyframe Value" -msgstr "Cambiar valor de la clave de animación" +msgstr "Cambiar valor del Fotograma Clave de Animación" #: editor/animation_editor.cpp msgid "Anim Change Call" -msgstr "Cambiar llamada de animación" +msgstr "Cambiar Llamada de Animación" #: editor/animation_editor.cpp msgid "Anim Add Track" -msgstr "Añadir pista de animación" +msgstr "Añadir Pista de Animación" #: editor/animation_editor.cpp msgid "Anim Duplicate Keys" -msgstr "Duplicar claves de animación" +msgstr "Duplicar Claves de Animación" #: editor/animation_editor.cpp msgid "Move Anim Track Up" -msgstr "Subir pista de animación" +msgstr "Subir Pista de Animación" #: editor/animation_editor.cpp msgid "Move Anim Track Down" -msgstr "Bajar pista de animación" +msgstr "Bajar Pista de Animación" #: editor/animation_editor.cpp msgid "Remove Anim Track" -msgstr "Quitar pista de animación" +msgstr "Quitar Pista de Animación" #: editor/animation_editor.cpp msgid "Set Transitions to:" -msgstr "Establecer transiciones en:" +msgstr "Establecer Transiciones en:" #: editor/animation_editor.cpp msgid "Anim Track Rename" -msgstr "Renombrar pista de animación" +msgstr "Renombrar Pista de Animación" #: editor/animation_editor.cpp msgid "Anim Track Change Interpolation" -msgstr "Cambiar interpolación de pista de animación" +msgstr "Cambiar Interpolación de Pista de Animación" #: editor/animation_editor.cpp msgid "Anim Track Change Value Mode" -msgstr "Cambiar modo de valor de pista de animación" +msgstr "Cambiar Modo de Valor de Pista de Animación" #: editor/animation_editor.cpp msgid "Anim Track Change Wrap Mode" -msgstr "Cambiar modo de ciclo de pista de animación" +msgstr "Cambiar Modo de Ciclo de Pista de Animación" #: editor/animation_editor.cpp msgid "Edit Node Curve" -msgstr "Editar nodo de curva" +msgstr "Editar Nodo de Curva" #: editor/animation_editor.cpp msgid "Edit Selection Curve" -msgstr "Editar curva de selección" +msgstr "Editar Curva de Selección" #: editor/animation_editor.cpp msgid "Anim Delete Keys" -msgstr "Borrar claves de animación" +msgstr "Borrar Claves de Animación" #: editor/animation_editor.cpp editor/plugins/tile_map_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Duplicate Selection" -msgstr "Duplicar selección" +msgstr "Duplicar Selección" #: editor/animation_editor.cpp msgid "Duplicate Transposed" -msgstr "Duplicar transpuesto" +msgstr "Duplicar Transpuesto" #: editor/animation_editor.cpp msgid "Remove Selection" -msgstr "Quitar selección" +msgstr "Quitar Selección" #: editor/animation_editor.cpp msgid "Continuous" @@ -150,15 +151,15 @@ msgstr "Trigger" #: editor/animation_editor.cpp msgid "Anim Add Key" -msgstr "Añadir clave de animación" +msgstr "Añadir Clave de Animación" #: editor/animation_editor.cpp msgid "Anim Move Keys" -msgstr "Mover claves de animación" +msgstr "Mover Claves de Animación" #: editor/animation_editor.cpp msgid "Scale Selection" -msgstr "Escalar selección" +msgstr "Escalar Selección" #: editor/animation_editor.cpp msgid "Scale From Cursor" @@ -166,11 +167,11 @@ msgstr "Escalar desde cursor" #: editor/animation_editor.cpp msgid "Goto Next Step" -msgstr "Ir al siguiente paso" +msgstr "Ir al Siguiente Paso" #: editor/animation_editor.cpp msgid "Goto Prev Step" -msgstr "Ir al paso anterior" +msgstr "Ir al Paso Anterior" #: editor/animation_editor.cpp editor/plugins/curve_editor_plugin.cpp #: editor/property_editor.cpp @@ -203,11 +204,11 @@ msgstr "Transiciones" #: editor/animation_editor.cpp msgid "Optimize Animation" -msgstr "Optimizar animación" +msgstr "Optimizar Animación" #: editor/animation_editor.cpp msgid "Clean-Up Animation" -msgstr "Limpiar animación" +msgstr "Limpiar Animación" #: editor/animation_editor.cpp msgid "Create NEW track for %s and insert key?" @@ -235,19 +236,19 @@ msgstr "Insertar Pista y Clave de Animación" #: editor/animation_editor.cpp msgid "Anim Insert Key" -msgstr "Insertar clave de Animación" +msgstr "Insertar Clave de Animación" #: editor/animation_editor.cpp msgid "Change Anim Len" -msgstr "Cambiar duración de Animación" +msgstr "Cambiar Duración de Animación" #: editor/animation_editor.cpp msgid "Change Anim Loop" -msgstr "Cambiar bucle de Animación" +msgstr "Cambiar Bucle de Animación" #: editor/animation_editor.cpp msgid "Anim Create Typed Value Key" -msgstr "Crear clave de valor tipado para Animación" +msgstr "Crear Clave de Valor Tipado para Animación" #: editor/animation_editor.cpp msgid "Anim Insert" @@ -267,7 +268,7 @@ msgstr "Zoom de Animación." #: editor/animation_editor.cpp msgid "Length (s):" -msgstr "Duración (seg):" +msgstr "Duración (segs.):" #: editor/animation_editor.cpp msgid "Animation length (in seconds)." @@ -275,7 +276,7 @@ msgstr "Duración de la Animación (en segundos)." #: editor/animation_editor.cpp msgid "Step (s):" -msgstr "Paso (s):" +msgstr "Paso(s):" #: editor/animation_editor.cpp msgid "Cursor step snap (in seconds)." @@ -307,7 +308,7 @@ msgstr "Herramientas de pistas" #: editor/animation_editor.cpp msgid "Enable editing of individual keys by clicking them." -msgstr "Editar claves individuales al hacer clic." +msgstr "Habilitar la edición de claves individuales al hacer clic." #: editor/animation_editor.cpp msgid "Anim. Optimizer" @@ -345,11 +346,11 @@ msgstr "Transición" #: editor/animation_editor.cpp msgid "Scale Ratio:" -msgstr "Relación de Escalado:" +msgstr "Relación de Escala:" #: editor/animation_editor.cpp msgid "Call Functions in Which Node?" -msgstr "¿Desde que nodo quieres realizar llamadas a funciones?" +msgstr "¿Desde que Nodo quieres realizar Llamadas a Funciones?" #: editor/animation_editor.cpp msgid "Remove invalid keys" @@ -441,19 +442,19 @@ msgstr "Columna:" #: editor/connections_dialog.cpp msgid "Method in target Node must be specified!" -msgstr "¡Debes establecer un método en el nodo seleccionado!" +msgstr "¡Debes establecer un método en el Nodo seleccionado!" #: editor/connections_dialog.cpp msgid "" "Target method not found! Specify a valid method or attach a script to target " "Node." msgstr "" -"No se ha encontrado el método objetivo. Especifica un método válido o " -"adjunta un script en el Nodo objetivo." +"No se encontró el método del objetivo! Especifica un método válido o adjunta " +"un script al Nodo objetivo." #: editor/connections_dialog.cpp msgid "Connect To Node:" -msgstr "Conectar a nodo:" +msgstr "Conectar a Nodo:" #: editor/connections_dialog.cpp editor/editor_autoload_settings.cpp #: editor/groups_editor.cpp editor/plugins/item_list_editor_plugin.cpp @@ -478,7 +479,7 @@ msgstr "Argumentos extras de llamada:" #: editor/connections_dialog.cpp msgid "Path to Node:" -msgstr "Ruta al nodo:" +msgstr "Ruta al Nodo:" #: editor/connections_dialog.cpp msgid "Make Function" @@ -490,7 +491,7 @@ msgstr "Diferido" #: editor/connections_dialog.cpp msgid "Oneshot" -msgstr "Una vez" +msgstr "OneShot" #: editor/connections_dialog.cpp editor/dependency_editor.cpp #: editor/export_template_manager.cpp @@ -516,15 +517,15 @@ msgstr "Conectar «%s» a «%s»" #: editor/connections_dialog.cpp msgid "Connecting Signal:" -msgstr "Conectando señal:" +msgstr "Conectando Señal:" #: editor/connections_dialog.cpp msgid "Disconnect '%s' from '%s'" msgstr "Desconectar '%s' de '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Conectar.." +msgid "Connect..." +msgstr "Conectar..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -546,7 +547,7 @@ msgstr "Cambiar" #: editor/create_dialog.cpp msgid "Create New %s" -msgstr "Crear nuevo %s" +msgstr "Crear Nuevo %s" #: editor/create_dialog.cpp editor/editor_file_dialog.cpp #: editor/filesystem_dock.cpp @@ -625,7 +626,7 @@ msgstr "Arreglar rota(s)" #: editor/dependency_editor.cpp msgid "Dependency Editor" -msgstr "Editor de dependencias" +msgstr "Editor de Dependencias" #: editor/dependency_editor.cpp msgid "Search Replacement Resource:" @@ -716,7 +717,7 @@ msgstr "Eliminar" #: editor/dictionary_property_edit.cpp msgid "Change Dictionary Key" -msgstr "Cambiar Clave de Diccionario" +msgstr "Cambiar Clave del Diccionario" #: editor/dictionary_property_edit.cpp msgid "Change Dictionary Value" @@ -736,7 +737,7 @@ msgstr "Contribuidores de Godot" #: editor/editor_about.cpp msgid "Project Founders" -msgstr "Fundadores del proyecto" +msgstr "Fundadores del Proyecto" #: editor/editor_about.cpp msgid "Lead Developer" @@ -756,11 +757,11 @@ msgstr "Autores" #: editor/editor_about.cpp msgid "Platinum Sponsors" -msgstr "Patrocinadores Platino" +msgstr "Patrocinadores Platinum" #: editor/editor_about.cpp msgid "Gold Sponsors" -msgstr "Patrocinadores Oro" +msgstr "Patrocinadores Gold" #: editor/editor_about.cpp msgid "Mini Sponsors" @@ -768,11 +769,11 @@ msgstr "Mini Patrocinadores" #: editor/editor_about.cpp msgid "Gold Donors" -msgstr "Donantes Oro" +msgstr "Donantes Gold" #: editor/editor_about.cpp msgid "Silver Donors" -msgstr "Donantes Plata" +msgstr "Donantes Silver" #: editor/editor_about.cpp msgid "Bronze Donors" @@ -946,12 +947,12 @@ msgid "Move Audio Bus" msgstr "Mover Bus de Audio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Guardar configuración de los Buses de Audio como..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Ruta para nueva configuración.." +msgid "Location for New Layout..." +msgstr "Ubicación para Nueva Configuración..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1047,19 +1048,19 @@ msgstr "¡El fichero «%s» ya existe!" #: editor/editor_autoload_settings.cpp msgid "Rename Autoload" -msgstr "Renombrar «Autoload»" +msgstr "Renombrar Autoload" #: editor/editor_autoload_settings.cpp msgid "Toggle AutoLoad Globals" -msgstr "Des/activar globales de «Autoload»" +msgstr "Des/Activar Globales de Autoload" #: editor/editor_autoload_settings.cpp msgid "Move Autoload" -msgstr "Mover «Autoload»" +msgstr "Mover Autoload" #: editor/editor_autoload_settings.cpp msgid "Remove Autoload" -msgstr "Quitar «Autoload»" +msgstr "Quitar Autoload" #: editor/editor_autoload_settings.cpp msgid "Enable" @@ -1067,7 +1068,7 @@ msgstr "Activar" #: editor/editor_autoload_settings.cpp msgid "Rearrange Autoloads" -msgstr "Reordenar «Autoloads»" +msgstr "Reordenar Autoloads" #: editor/editor_autoload_settings.cpp editor/editor_file_dialog.cpp #: scene/gui/file_dialog.cpp @@ -1076,7 +1077,7 @@ msgstr "Ruta:" #: editor/editor_autoload_settings.cpp msgid "Node Name:" -msgstr "Nombre del nodo:" +msgstr "Nombre del Nodo:" #: editor/editor_autoload_settings.cpp editor/editor_profiler.cpp #: editor/project_manager.cpp editor/settings_config_dialog.cpp @@ -1085,19 +1086,19 @@ msgstr "Nombre" #: editor/editor_autoload_settings.cpp msgid "Singleton" -msgstr "«Singleton»" +msgstr "Singleton" #: editor/editor_data.cpp msgid "Updating Scene" -msgstr "Actualizando escena" +msgstr "Actualizando Escena" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Guardando cambios locales.." +msgid "Storing local changes..." +msgstr "Guardando cambios locales..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Actualizando escena.." +msgid "Updating scene..." +msgstr "Actualizando escena..." #: editor/editor_data.cpp msgid "[empty]" @@ -1109,7 +1110,7 @@ msgstr "[no guardado]" #: editor/editor_dir_dialog.cpp msgid "Please select a base directory first" -msgstr "Por favor, primero seleccione un directorio base" +msgstr "Por favor, selecciona primero un directorio base" #: editor/editor_dir_dialog.cpp msgid "Choose a Directory" @@ -1165,7 +1166,7 @@ msgid "Show In File Manager" msgstr "Mostrar en el navegador de archivos" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "Nueva carpeta..." #: editor/editor_file_dialog.cpp @@ -1427,20 +1428,20 @@ msgstr "Borrar salida" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "La exportación del proyecto falló con el código de error %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "¡Hubo un error al guardar el recurso!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Guardar recurso como.." +msgid "Save Resource As..." +msgstr "Guardar Recurso Como..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Ya veo.." +msgid "I see..." +msgstr "Ya veo..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1476,7 +1477,7 @@ msgstr "Error al cargar '%s'." #: editor/editor_node.cpp msgid "Saving Scene" -msgstr "Guardar escena" +msgstr "Guardar Escena" #: editor/editor_node.cpp msgid "Analyzing" @@ -1589,7 +1590,7 @@ msgstr "Expandir todas las propiedades" #: editor/editor_node.cpp msgid "Collapse all properties" -msgstr "Colapsar todo" +msgstr "Ocultar todas las propiedades" #: editor/editor_node.cpp msgid "Copy Params" @@ -1613,7 +1614,7 @@ msgstr "Convertirlo en integrado" #: editor/editor_node.cpp msgid "Make Sub-Resources Unique" -msgstr "Hacer sub-recursos únicos" +msgstr "Creación de Subrecursos Únicos" #: editor/editor_node.cpp msgid "Open in Help" @@ -1665,31 +1666,31 @@ msgstr "¡No se pudo comenzar el subproceso!" #: editor/editor_node.cpp msgid "Open Scene" -msgstr "Abrir escena" +msgstr "Abrir Escena" #: editor/editor_node.cpp msgid "Open Base Scene" -msgstr "Abrir escena base" +msgstr "Abrir Escena Base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Apertura rápida de escena.." +msgid "Quick Open Scene..." +msgstr "Apertura Rápida de Escena..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Apertura rápida de script.." +msgid "Quick Open Script..." +msgstr "Apertura Rápida de Script..." #: editor/editor_node.cpp msgid "Save & Close" -msgstr "Guardar & Cerrar" +msgstr "Guardar y Cerrar" #: editor/editor_node.cpp msgid "Save changes to '%s' before closing?" msgstr "¿Guardar cambios de '%s' antes de cerrar?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Guardar escena como.." +msgid "Save Scene As..." +msgstr "Guardar Escena Como..." #: editor/editor_node.cpp msgid "No" @@ -1743,8 +1744,8 @@ msgstr "" "modos?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Ejecución rápida de escena.." +msgid "Quick Run Scene..." +msgstr "Ejecución Rápida de Escena..." #: editor/editor_node.cpp msgid "Quit" @@ -1782,7 +1783,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Pick a Main Scene" -msgstr "Elige una escena principal" +msgstr "Elige una Escena Principal" #: editor/editor_node.cpp msgid "Unable to enable addon plugin at: '%s' parsing of config failed." @@ -1845,11 +1846,11 @@ msgstr "Limpiar Escenas Recientes" #: editor/editor_node.cpp msgid "Save Layout" -msgstr "Guardar ajustes" +msgstr "Guardar Ajustes" #: editor/editor_node.cpp msgid "Delete Layout" -msgstr "Borrar ajustes" +msgstr "Borrar Ajustes" #: editor/editor_node.cpp editor/import_dock.cpp #: editor/script_create_dialog.cpp @@ -1858,7 +1859,7 @@ msgstr "Predeterminado" #: editor/editor_node.cpp msgid "Switch Scene Tab" -msgstr "Cambiar pestaña de escena" +msgstr "Cambiar Pestaña de Escena" #: editor/editor_node.cpp msgid "%d more files or folders" @@ -1886,11 +1887,11 @@ msgstr "Alternar modo sin distracciones." #: editor/editor_node.cpp msgid "Add a new scene." -msgstr "Añadir nueva Escena." +msgstr "Añadir nueva escena." #: editor/editor_node.cpp msgid "Scene" -msgstr "Escena" +msgstr "Escenas" #: editor/editor_node.cpp msgid "Go to previously opened scene." @@ -1905,8 +1906,8 @@ msgid "Previous tab" msgstr "Pestaña anterior" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrado de archivos.." +msgid "Filter Files..." +msgstr "Filtrado de Archivos..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1914,19 +1915,19 @@ msgstr "Operaciones con archivos de escena." #: editor/editor_node.cpp msgid "New Scene" -msgstr "Nueva escena" +msgstr "Nueva Escena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Nueva escena heredada.." +msgid "New Inherited Scene..." +msgstr "Nueva Escena Heredada..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Abrir escena.." +msgid "Open Scene..." +msgstr "Abrir Escena..." #: editor/editor_node.cpp msgid "Save Scene" -msgstr "Guardar escena" +msgstr "Guardar Escena" #: editor/editor_node.cpp msgid "Save all Scenes" @@ -1941,16 +1942,16 @@ msgid "Open Recent" msgstr "Abrir reciente" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Convertir a.." +msgid "Convert To..." +msgstr "Convertir a..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "LibrerÃa de mallas.." +msgid "MeshLibrary..." +msgstr "LibrerÃa de mallas..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "\"TileSet\".." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1964,7 +1965,7 @@ msgstr "Rehacer" #: editor/editor_node.cpp msgid "Revert Scene" -msgstr "Revertir escena" +msgstr "Revertir Escena" #: editor/editor_node.cpp msgid "Miscellaneous project or scene-wide tools." @@ -1976,19 +1977,19 @@ msgstr "Proyecto" #: editor/editor_node.cpp msgid "Project Settings" -msgstr "Ajustes del proyecto" +msgstr "Ajustes del Proyecto" #: editor/editor_node.cpp msgid "Run Script" -msgstr "Ejecutar script" +msgstr "Ejecutar Script" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export" -msgstr "Export" +msgstr "Exportar" #: editor/editor_node.cpp msgid "Tools" -msgstr "Herramientas" +msgstr "Tools" #: editor/editor_node.cpp msgid "Quit to Project List" @@ -2091,11 +2092,11 @@ msgstr "Editor" #: editor/editor_node.cpp editor/settings_config_dialog.cpp msgid "Editor Settings" -msgstr "Ajustes del editor" +msgstr "Ajustes del Editor" #: editor/editor_node.cpp msgid "Editor Layout" -msgstr "Ajustes de diseño del editor" +msgstr "Ajustes de Diseño del Editor" #: editor/editor_node.cpp msgid "Toggle Fullscreen" @@ -2142,7 +2143,7 @@ msgstr "Acerca de" #: editor/editor_node.cpp msgid "Play the project." -msgstr "Inicia el proyecto para poder jugarlo." +msgstr "Reproducir el proyecto." #: editor/editor_node.cpp msgid "Play" @@ -2213,8 +2214,8 @@ msgid "Save the currently edited resource." msgstr "Guardar el recurso editado actualmente." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Guardar como.." +msgid "Save As..." +msgstr "Guardar Como..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2243,11 +2244,11 @@ msgstr "Importar" #: editor/editor_node.cpp msgid "Node" -msgstr "Nodo" +msgstr "Nodos" #: editor/editor_node.cpp msgid "FileSystem" -msgstr "SistDeArchivos" +msgstr "Sistema de Archivos" #: editor/editor_node.cpp msgid "Output" @@ -2263,7 +2264,7 @@ msgstr "Importar plantillas desde un archivo ZIP" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export Project" -msgstr "Exportar proyecto" +msgstr "Exportar Proyecto" #: editor/editor_node.cpp msgid "Export Library" @@ -2283,7 +2284,7 @@ msgstr "Abrir y ejecutar un script" #: editor/editor_node.cpp msgid "New Inherited" -msgstr "Nueva escena heredada" +msgstr "Nueva Escena Heredada" #: editor/editor_node.cpp msgid "Load Errors" @@ -2322,8 +2323,8 @@ msgid "Creating Mesh Previews" msgstr "Creando vistas previas de las mallas" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatura.." +msgid "Thumbnail..." +msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2425,7 +2426,7 @@ msgstr "No se pudo instanciar el script:" #: editor/editor_run_script.cpp msgid "Did you forget the 'tool' keyword?" -msgstr "Has olvidado la palabra clave 'tool'?" +msgstr "¿Olvidaste la palabra clave 'tool'?" #: editor/editor_run_script.cpp msgid "Couldn't run script:" @@ -2437,19 +2438,19 @@ msgstr "Te olvidaste del método '_run'?" #: editor/editor_settings.cpp msgid "Default (Same as Editor)" -msgstr "Predeterminado (Igual que el editor)" +msgstr "Predeterminado (Igual que el Editor)" #: editor/editor_sub_scene.cpp msgid "Select Node(s) to Import" -msgstr "Selecciona nodos a importar" +msgstr "Selecciona Nodos a importar" #: editor/editor_sub_scene.cpp msgid "Scene Path:" -msgstr "Ruta a la escena:" +msgstr "Ruta de la Escena:" #: editor/editor_sub_scene.cpp msgid "Import From Node:" -msgstr "Importar desde nodo:" +msgstr "Importar desde Nodo:" #: editor/export_template_manager.cpp msgid "Re-Download" @@ -2476,7 +2477,7 @@ msgid "(Current)" msgstr "(Actual)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Obteniendo mirrors, por favor espere..." #: editor/export_template_manager.cpp @@ -2554,8 +2555,8 @@ msgid "Error requesting url: " msgstr "Error al solicitar url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Intentando conexión alternativa.." +msgid "Connecting to Mirror..." +msgstr "Intentando conexión alternativa..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2571,8 +2572,8 @@ msgstr "No se puede resolver" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Conectando.." +msgid "Connecting..." +msgstr "Conectando..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2584,8 +2585,8 @@ msgstr "Conectado" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Solicitando.." +msgid "Requesting..." +msgstr "Solicitando..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2722,32 +2723,32 @@ msgid "Collapse all" msgstr "Colapsar todo" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Renombrar.." +msgid "Rename..." +msgstr "Renombrar..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Mover a.." +msgid "Move To..." +msgstr "Mover a..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" -msgstr "Abrir escena/s" +msgstr "Abrir Escena(s)" #: editor/filesystem_dock.cpp msgid "Instance" msgstr "Instanciar" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Editar dependencias.." +msgid "Edit Dependencies..." +msgstr "Editar Dependencias..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Ver propietarios.." +msgid "View Owners..." +msgstr "Ver Propietarios..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplicar.." +msgid "Duplicate..." +msgstr "Duplicar..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2759,7 +2760,7 @@ msgstr "Carpeta siguiente" #: editor/filesystem_dock.cpp msgid "Re-Scan Filesystem" -msgstr "Reanalizar sistema de archivos" +msgstr "Reanalizar Sistema de Archivos" #: editor/filesystem_dock.cpp msgid "Toggle folder status as Favorite" @@ -2773,7 +2774,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Escaneando archivos,\n" "Por favor, espere..." @@ -2841,8 +2842,8 @@ msgid "Import Scene" msgstr "Importar escena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Importando escena.." +msgid "Importing Scene..." +msgstr "Importando Escena..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2853,8 +2854,8 @@ msgid "Generating for Mesh: " msgstr "Generando para modelo: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Ejecutando script personalizado.." +msgid "Running Custom Script..." +msgstr "Ejecutando Script Personalizado..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2870,8 +2871,8 @@ msgid "Error running post-import script:" msgstr "Error ejecutando el script de posimportacion:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Guardando.." +msgid "Saving..." +msgstr "Guardando..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2890,8 +2891,8 @@ msgid "Import As:" msgstr "Importar como:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Ajuste.." +msgid "Preset..." +msgstr "Ajuste..." #: editor/import_dock.cpp msgid "Reimport" @@ -2907,7 +2908,7 @@ msgstr "Grupos" #: editor/node_dock.cpp msgid "Select a Node to edit Signals and Groups." -msgstr "Selecciona un nodo para editar señales y grupos." +msgstr "Selecciona un Nodo para editar Señales y Grupos." #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp @@ -2946,9 +2947,9 @@ msgid "" "RMB: Erase Point." msgstr "" "Editar polÃgono existente:\n" -"Click izquierdo: Mover punto.\n" -"Control + Click izquierdo: Dividir segmento.\n" -"Click derecho: Borrar punto." +"Clic izquierdo: Mover punto.\n" +"Control + Clic izquierdo: Dividir segmento.\n" +"Clic derecho: Borrar punto." #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Delete points" @@ -3085,7 +3086,7 @@ msgstr "Mostrar la lista de animaciones en el reproductor." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Autoplay on Load" -msgstr "Autoreproducir al cargar" +msgstr "Autoreproducir al Cargar" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Edit Target Blend Times" @@ -3206,7 +3207,7 @@ msgstr "Mezcla" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix" -msgstr "Mezclar" +msgstr "Mix" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Auto Restart:" @@ -3251,7 +3252,7 @@ msgstr "Actual:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Add Input" -msgstr "Añadir entrada" +msgstr "Añadir Entrada" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Clear Auto-Advance" @@ -3263,7 +3264,7 @@ msgstr "Establecer autoavanzar" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Delete Input" -msgstr "Eliminar entrada" +msgstr "Eliminar Entrada" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation tree is valid." @@ -3275,15 +3276,15 @@ msgstr "El árbol de animación no es correcto." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation Node" -msgstr "Nodo de animación" +msgstr "Nodo de Animación" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "OneShot Node" -msgstr "Nodo UnaVez" +msgstr "Nodo OneShot" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix Node" -msgstr "Nodo Mezcla" +msgstr "Nodo Mix" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend2 Node" @@ -3299,7 +3300,7 @@ msgstr "Nodo Blend4" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeScale Node" -msgstr "Nodo TimeScale (Escala de tiempo)" +msgstr "Nodo TimeScale" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeSeek Node" @@ -3307,19 +3308,19 @@ msgstr "Nodo TimeSeek" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Transition Node" -msgstr "Nodo de transición" +msgstr "Nodo Transition" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importar animaciones.." +msgid "Import Animations..." +msgstr "Importar Animaciones..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" -msgstr "Editar filtros de nodo" +msgstr "Editar Filtros de Nodo" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filtros.." +msgid "Filters..." +msgstr "Filtros..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3386,7 +3387,7 @@ msgid "Fetching:" msgstr "Buscando:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "Resolviendo..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3453,8 +3454,8 @@ msgid "Site:" msgstr "Sitio:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Soporte.." +msgid "Support..." +msgstr "Soporte..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3494,7 +3495,7 @@ msgstr "" #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "Bake Lightmaps" -msgstr "Calculando «lightmaps»" +msgstr "Calculando Lightmaps" #: editor/plugins/camera_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp @@ -3508,20 +3509,20 @@ msgstr "Configurar ajuste" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid Offset:" -msgstr "Desplazamiento de rejilla:" +msgstr "Desplazamiento de CuadrÃcula:" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid Step:" -msgstr "Pasos de rejilla:" +msgstr "Paso de CuadrÃcula:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotation Offset:" -msgstr "Desplazamiento de rotación:" +msgstr "Desplazamiento de Rotación:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotation Step:" -msgstr "Cantidad de rotaciones:" +msgstr "Cantidad de Rotaciones:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Pivot" @@ -3603,7 +3604,7 @@ msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Alt+RMB: Depth list selection" -msgstr "Alt+Click Der.: Selección en listado de solapamientos" +msgstr "Alt + Clic Derecho: Selección en listado de solapamientos" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Mode" @@ -3619,12 +3620,13 @@ msgid "" "Show a list of all objects at the position clicked\n" "(same as Alt+RMB in select mode)." msgstr "" -"Mostrar una lista de todos los objetos en la posición cliqueada\n" -"(igual que Alt+Click Der. en modo selección)." +"Mostrar una lista de todos los objetos en la posición en la que se ha hecho " +"clic\n" +"(igual que Alt + Clic Derecho en modo selección)." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Click to change object's rotation pivot." -msgstr "Click para cambiar el pivote de rotación de un objeto." +msgstr "Haz clic para cambiar el pivote de rotación de un objeto." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Pan Mode" @@ -3651,8 +3653,9 @@ msgid "Use Rotation Snap" msgstr "Ajustar rotación" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "Configurar CuadrÃcula..." +msgstr "Configurar Ajuste..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3672,7 +3675,7 @@ msgstr "Ajustar al padre" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to node anchor" -msgstr "Ajustar al anclado del nodo" +msgstr "Ajustar al anclaje del nodo" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to node sides" @@ -3732,7 +3735,7 @@ msgstr "Ver" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Show Grid" -msgstr "Mostrar rejilla" +msgstr "Mostrar CuadrÃcula" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Helpers" @@ -3747,14 +3750,12 @@ msgid "Show Guides" msgstr "Mostrar guÃas" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Ver origen" +msgstr "Ver Origen" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 visor" +msgstr "Ver Viewport" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3774,11 +3775,11 @@ msgstr "Insertar claves" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Key" -msgstr "Insertar clave" +msgstr "Insertar Clave" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Key (Existing Tracks)" -msgstr "Insertar clave (pistas existentes)" +msgstr "Insertar Clave (Pistas Existentes)" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Copy Pose" @@ -3823,7 +3824,7 @@ msgstr "No se pueden instanciar varios nodos sin un nodo raÃz." #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Create Node" -msgstr "Crear nodo" +msgstr "Crear Nodo" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp @@ -4049,7 +4050,7 @@ msgstr "¡La malla no tiene superficie de la que crear contornos!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "El tipo de la malla primitiva no es PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4080,8 +4081,8 @@ msgid "Create Convex Collision Sibling" msgstr "Crear colisión hermanada convexa" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Crear contorno de malla.." +msgid "Create Outline Mesh..." +msgstr "Crear Contorno de Malla..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4227,7 +4228,7 @@ msgstr "Calculando tamaño de cuadrÃcula..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating heightfield..." -msgstr "Creando octree de luces (\"heigfield\")..." +msgstr "Creando heightfield..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Marking walkable triangles..." @@ -4251,7 +4252,7 @@ msgstr "Creando contornos..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating polymesh..." -msgstr "Crear malla 3D de contorno (\"polymesh\")..." +msgstr "Crear polymesh..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Converting to native navigation mesh..." @@ -4263,7 +4264,7 @@ msgstr "Configuración del Generador de Mallas de Navegación:" #: editor/plugins/navigation_mesh_generator.cpp msgid "Parsing Geometry..." -msgstr "Analizando geometrÃa..." +msgstr "Analizando GeometrÃa..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Done!" @@ -4288,8 +4289,8 @@ msgid "Error loading image:" msgstr "Error al cargar la imagen:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "No hay pÃxeles con transparencia > 128 en la imagen.." +msgid "No pixels with transparency > 128 in image..." +msgstr "No hay pÃxeles con transparencia > 128 en la imagen..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4331,11 +4332,11 @@ msgstr "Colores de emisión" #: editor/plugins/particles_editor_plugin.cpp msgid "Node does not contain geometry." -msgstr "El nodo no contiene geometrÃa." +msgstr "El nodo no posee geometrÃa." #: editor/plugins/particles_editor_plugin.cpp msgid "Node does not contain geometry (faces)." -msgstr "El nodo no contiene geometrÃa (caras)." +msgstr "El nodo no posee geometrÃa (caras)." #: editor/plugins/particles_editor_plugin.cpp msgid "A processor material of type 'ParticlesMaterial' is required." @@ -4359,7 +4360,7 @@ msgstr "Crear puntos de emisión desde malla" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Node" -msgstr "Crear puntos de emisión desde el nodo" +msgstr "Crear Puntos de Emisión desde el Nodo" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emitter" @@ -4431,12 +4432,12 @@ msgstr "Mayús + arrastrar: Seleccionar puntos de control" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Click: Add Point" -msgstr "Click: Añadir punto" +msgstr "Clic: Añadir Punto" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp msgid "Right Click: Delete Point" -msgstr "Clic derecho: Eliminar punto" +msgstr "Clic Derecho: Eliminar Punto" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Select Control Points (Shift+Drag)" @@ -4565,7 +4566,7 @@ msgstr "Habilitar fijado" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid" -msgstr "Rejilla" +msgstr "CuadrÃcula" #: editor/plugins/resource_preloader_editor_plugin.cpp msgid "ERROR: Couldn't load resource!" @@ -4591,7 +4592,7 @@ msgstr "¡El portapapeles de recursos está vacÃo!" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/scene_tree_dock.cpp editor/scene_tree_editor.cpp msgid "Open in Editor" -msgstr "Abrir en el editor" +msgstr "Abrir en el Editor" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/scene_tree_editor.cpp @@ -4649,8 +4650,8 @@ msgid "Import Theme" msgstr "Importar tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Guardar tema como.." +msgid "Save Theme As..." +msgstr "Guardar Tema Como..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4746,8 +4747,8 @@ msgstr "Alternar panel de scripts" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Buscar.." +msgid "Find..." +msgstr "Buscar..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4856,7 +4857,7 @@ msgstr "Minúscula" #: editor/plugins/script_text_editor.cpp msgid "Capitalize" -msgstr "Letra Capital" +msgstr "Poner en mayúsculas" #: editor/plugins/script_text_editor.cpp scene/gui/line_edit.cpp #: scene/gui/text_edit.cpp @@ -4920,7 +4921,7 @@ msgstr "Convertir Indentación a Espacios" #: editor/plugins/script_text_editor.cpp msgid "Convert Indent To Tabs" -msgstr "Convertir indentación a tabuladores" +msgstr "Convertir Indentación a Tabuladores" #: editor/plugins/script_text_editor.cpp msgid "Auto Indent" @@ -4956,16 +4957,16 @@ msgid "Find Previous" msgstr "Buscar anterior" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Reemplazar.." +msgid "Replace..." +msgstr "Reemplazar..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Ir a función.." +msgid "Goto Function..." +msgstr "Ir a función..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Ir a lÃnea.." +msgid "Goto Line..." +msgstr "Ir a lÃnea..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -4973,7 +4974,7 @@ msgstr "Ayuda contextual" #: editor/plugins/shader_editor_plugin.cpp msgid "Shader" -msgstr "\"Shader\"" +msgstr "Shader" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Constant" @@ -5073,11 +5074,11 @@ msgstr "Desconectar Nodos Gráficos" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Remove Shader Graph Node" -msgstr "Borrar Nodo Gráfico de Shader" +msgstr "Eliminar el Nodo Gráfico del Shader" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Move Shader Graph Node" -msgstr "Mover Nodo Gráfico de Shader" +msgstr "Mover el Nodo Gráfico del Shader" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Duplicate Graph Node(s)" @@ -5085,7 +5086,7 @@ msgstr "Duplicar Nodo(s) Gráfico" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Delete Shader Graph Node(s)" -msgstr "Borrar Nodo(s) Gráfico(s) de Shader" +msgstr "Eliminar Nodo(s) Gráfico(s) del Shader" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Error: Cyclic Connection Link" @@ -5097,7 +5098,7 @@ msgstr "Error: Conexiones de Entrada Faltantes" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Add Shader Graph Node" -msgstr "Añadir nodo gráfico de Shader" +msgstr "Añadir Nodo Gráfico del Shader" #: editor/plugins/spatial_editor_plugin.cpp msgid "Orthogonal" @@ -5125,7 +5126,7 @@ msgstr "Transformación en el eje Z." #: editor/plugins/spatial_editor_plugin.cpp msgid "View Plane Transform." -msgstr "Ver transformación en plano." +msgstr "Ver Transformación de Plano." #: editor/plugins/spatial_editor_plugin.cpp msgid "Scaling: " @@ -5145,7 +5146,7 @@ msgstr "Insertar claves está desactivado (no se insertaron claves)." #: editor/plugins/spatial_editor_plugin.cpp msgid "Animation Key Inserted." -msgstr "Clave de animación insertada." +msgstr "Clave de Animación Insertada." #: editor/plugins/spatial_editor_plugin.cpp msgid "Objects Drawn" @@ -5157,7 +5158,7 @@ msgstr "Cambios del material" #: editor/plugins/spatial_editor_plugin.cpp msgid "Shader Changes" -msgstr "Cambios del shader" +msgstr "Cambios del Shader" #: editor/plugins/spatial_editor_plugin.cpp msgid "Surface Changes" @@ -5177,11 +5178,11 @@ msgstr "FPS" #: editor/plugins/spatial_editor_plugin.cpp msgid "Top View." -msgstr "Vista superior." +msgstr "Vista Superior." #: editor/plugins/spatial_editor_plugin.cpp msgid "Bottom View." -msgstr "Vista inferior." +msgstr "Vista Inferior." #: editor/plugins/spatial_editor_plugin.cpp msgid "Bottom" @@ -5189,7 +5190,7 @@ msgstr "Fondo" #: editor/plugins/spatial_editor_plugin.cpp msgid "Left View." -msgstr "Vista izquierda." +msgstr "Vista Izquierda." #: editor/plugins/spatial_editor_plugin.cpp msgid "Left" @@ -5197,7 +5198,7 @@ msgstr "Izquierda" #: editor/plugins/spatial_editor_plugin.cpp msgid "Right View." -msgstr "Vista derecha." +msgstr "Vista Derecha." #: editor/plugins/spatial_editor_plugin.cpp msgid "Right" @@ -5205,7 +5206,7 @@ msgstr "Derecha" #: editor/plugins/spatial_editor_plugin.cpp msgid "Front View." -msgstr "Vista frontal." +msgstr "Vista Frontal." #: editor/plugins/spatial_editor_plugin.cpp msgid "Front" @@ -5213,7 +5214,7 @@ msgstr "Frente" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rear View." -msgstr "Vista anterior." +msgstr "Vista Posterior." #: editor/plugins/spatial_editor_plugin.cpp msgid "Rear" @@ -5281,31 +5282,31 @@ msgstr "Activar Doppler" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Left" -msgstr "Vista libre izquierda" +msgstr "Vista Libre Izquierda" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Right" -msgstr "Vista libre derecha" +msgstr "Vista Libre Derecha" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Forward" -msgstr "Vista libre frente" +msgstr "Vista Libre Frontal" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Backwards" -msgstr "Vista libre atrás" +msgstr "Vista Libre Posterior" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Up" -msgstr "Vista libre arriba" +msgstr "Vista Libre Arriba" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Down" -msgstr "Vista libre abajo" +msgstr "Vista Libre Abajo" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Speed Modifier" -msgstr "Modificador de velocidad de \"vista libre\"" +msgstr "Modificador de Velocidad de Vista Libre" #: editor/plugins/spatial_editor_plugin.cpp msgid "XForm Dialog" @@ -5323,7 +5324,7 @@ msgid "" msgstr "" "Arrastrar: Rotar\n" "Alt + Arrastrar: Mover\n" -"Alt + Click derecho: Selección en la lista de superposición" +"Alt + Clic Derecho: Selección en la lista de superposición" #: editor/plugins/spatial_editor_plugin.cpp msgid "Move Mode (W)" @@ -5339,7 +5340,7 @@ msgstr "Modo escalado (R)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Coords" -msgstr "Coordenadas locales" +msgstr "Local Coords (Coordenadas Locales)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Space Mode (%s)" @@ -5351,35 +5352,35 @@ msgstr "Modo de ajuste (%s)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Bottom View" -msgstr "Vista inferior" +msgstr "Vista Inferior" #: editor/plugins/spatial_editor_plugin.cpp msgid "Top View" -msgstr "Vista superior" +msgstr "Vista Superior" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rear View" -msgstr "Vista anterior" +msgstr "Vista Posterior" #: editor/plugins/spatial_editor_plugin.cpp msgid "Front View" -msgstr "Vista frontal" +msgstr "Vista Frontal" #: editor/plugins/spatial_editor_plugin.cpp msgid "Left View" -msgstr "Vista izquierda" +msgstr "Vista Izquierda" #: editor/plugins/spatial_editor_plugin.cpp msgid "Right View" -msgstr "Vista derecha" +msgstr "Vista Derecha" #: editor/plugins/spatial_editor_plugin.cpp msgid "Switch Perspective/Orthogonal view" -msgstr "Intercambiar entre vista de perspectiva y ortogonal" +msgstr "Intercambiar vista Perspectiva/Ortogonal" #: editor/plugins/spatial_editor_plugin.cpp msgid "Insert Animation Key" -msgstr "Insertar clave de animación" +msgstr "Insertar Clave de Animación" #: editor/plugins/spatial_editor_plugin.cpp msgid "Focus Origin" @@ -5411,23 +5412,19 @@ msgstr "Escalar" #: editor/plugins/spatial_editor_plugin.cpp msgid "Toggle Freelook" -msgstr "Alternar vista libre" +msgstr "Activar Vista Libre" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform" -msgstr "Transformar" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configurar ajuste.." +msgstr "Transform" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Ventana de transformación.." +msgid "Transform Dialog..." +msgstr "Ventana de transformación..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" -msgstr "1 visor" +msgstr "1 Viewport" #: editor/plugins/spatial_editor_plugin.cpp msgid "2 Viewports" @@ -5455,7 +5452,7 @@ msgstr "Ver origen" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Grid" -msgstr "Ver rejilla" +msgstr "Ver CuadrÃcula" #: editor/plugins/spatial_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp @@ -5484,7 +5481,7 @@ msgstr "Ajuste de escala (%):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Viewport Settings" -msgstr "Ajustes del visor" +msgstr "Ajustes del Viewport" #: editor/plugins/spatial_editor_plugin.cpp msgid "Perspective FOV (deg.):" @@ -5592,7 +5589,7 @@ msgstr "Mover (Después)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "SpriteFrames" -msgstr "Fotogramas del sprite" +msgstr "SpriteFrames" #: editor/plugins/style_box_editor_plugin.cpp msgid "StyleBox Preview:" @@ -5620,7 +5617,7 @@ msgstr "Ajustar a pÃxeles" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Grid Snap" -msgstr "Ajustar a cuadrÃcula" +msgstr "Ajustar a CuadrÃcula" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Auto Slice" @@ -5675,8 +5672,8 @@ msgid "Remove All" msgstr "Quitar todos" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Editar tema.." +msgid "Edit theme..." +msgstr "Editar tema..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5723,14 +5720,12 @@ msgid "Checked Item" msgstr "Casilla de verificación activa" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Añadir elemento" +msgstr "Radio Item" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Casilla de verificación activa" +msgstr "Ratio Item Activo" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5745,8 +5740,8 @@ msgid "Options" msgstr "Opciones" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "¡Tienes,Muchas,Y,Variadas,Opciones!" +msgid "Has,Many,Options" +msgstr "Tienes, Muchas, Opciones" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5870,7 +5865,7 @@ msgstr "¿Mezclar desde escena?" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Tile Set" -msgstr "\"Tile Set\"" +msgstr "Tile Set" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -5886,7 +5881,7 @@ msgstr "Error" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Autotiles" -msgstr "\"Autotiles\"" +msgstr "Autotiles" #: editor/plugins/tile_set_editor_plugin.cpp msgid "" @@ -5901,8 +5896,8 @@ msgid "" "LMB: set bit on.\n" "RMB: set bit off." msgstr "" -"Click izquierdo: habilitar bit.\n" -"Click derecho: deshabilitar bit." +"Clic Izquierdo: habilitar bit.\n" +"Clic Derecho: deshabilitar bit." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Select current edited sub-tile." @@ -5938,8 +5933,8 @@ msgid "Presets" msgstr "Preajustes" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Añadir.." +msgid "Add..." +msgstr "Añadir..." #: editor/project_export.cpp msgid "Resources" @@ -6014,7 +6009,7 @@ msgstr "" #: editor/project_export.cpp msgid "Export With Debug" -msgstr "Exportar con depuración" +msgstr "Exportar con Depuración" #: editor/project_manager.cpp msgid "The path does not exist." @@ -6030,7 +6025,11 @@ msgstr "Por favor elija una carpeta vacÃa." #: editor/project_manager.cpp msgid "Imported Project" -msgstr "Proyecto importado" +msgstr "Proyecto Importado" + +#: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Nombre de Proyecto Inválido." #: editor/project_manager.cpp msgid "Couldn't create folder." @@ -6074,11 +6073,11 @@ msgstr "Renombrar proyecto" #: editor/project_manager.cpp msgid "New Game Project" -msgstr "Nuevo proyecto de juego" +msgstr "Nuevo Proyecto de Juego" #: editor/project_manager.cpp msgid "Import Existing Project" -msgstr "Importar proyecto existente" +msgstr "Importar Proyecto Existente" #: editor/project_manager.cpp msgid "Import & Edit" @@ -6086,7 +6085,7 @@ msgstr "Importar y editar" #: editor/project_manager.cpp msgid "Create New Project" -msgstr "Crear proyecto nuevo" +msgstr "Crear Nuevo Proyecto" #: editor/project_manager.cpp msgid "Create & Edit" @@ -6094,7 +6093,7 @@ msgstr "Crear y editar" #: editor/project_manager.cpp msgid "Install Project:" -msgstr "Instalar proyecto:" +msgstr "Instalar Proyecto:" #: editor/project_manager.cpp msgid "Install & Edit" @@ -6102,7 +6101,7 @@ msgstr "Instalar y editar" #: editor/project_manager.cpp msgid "Project Name:" -msgstr "Nombre del proyecto:" +msgstr "Nombre del Proyecto:" #: editor/project_manager.cpp msgid "Create folder" @@ -6110,7 +6109,7 @@ msgstr "Crear carpeta" #: editor/project_manager.cpp msgid "Project Path:" -msgstr "Ruta del proyecto:" +msgstr "Ruta del Proyecto:" #: editor/project_manager.cpp msgid "Browse" @@ -6118,7 +6117,7 @@ msgstr "Examinar" #: editor/project_manager.cpp msgid "Unnamed Project" -msgstr "Proyecto sin nombre" +msgstr "Proyecto sin Nombre" #: editor/project_manager.cpp msgid "Can't open project" @@ -6135,8 +6134,8 @@ msgid "" "the \"Application\" category." msgstr "" "No hay una escena principal definida para ejecutar el proyecto.\n" -"Por favor elija la escena principal en \"Ajustes del proyecto\" en la " -"categorÃa \"Aplicación\"." +"Por favor elija la escena principal en \"Ajustes del Proyecto\" en la " +"categorÃa \"Application\"." #: editor/project_manager.cpp msgid "" @@ -6153,8 +6152,8 @@ msgstr "¿Seguro que quieres ejecutar más de un proyecto?" #: editor/project_manager.cpp msgid "Remove project from the list? (Folder contents will not be modified)" msgstr "" -"¿Quieres quitar proyecto de la lista? (El contenido de la carpeta no se " -"modificarán)" +"¿Quieres quitar el proyecto de la lista? (El contenido de la carpeta no se " +"modificará)" #: editor/project_manager.cpp msgid "" @@ -6191,7 +6190,7 @@ msgstr "Selecciona la carpeta a analizar" #: editor/project_manager.cpp msgid "New Project" -msgstr "Proyecto nuevo" +msgstr "Nuevo Proyecto" #: editor/project_manager.cpp msgid "Templates" @@ -6214,8 +6213,9 @@ msgid "" "You don't currently have any projects.\n" "Would you like to explore the official example projects in the Asset Library?" msgstr "" -"Ahora mismo no tiene ningún proyecto.\n" -"¿Le gustarÃa explorar los proyectos ejemplo oficiales del Asset Library?" +"Actualmente no tienes ningún proyecto.\n" +"¿Quieres explorar los proyectos de ejemplo oficiales en la Biblioteca de " +"Assets?" #: editor/project_settings_editor.cpp msgid "Key " @@ -6235,9 +6235,11 @@ msgstr "Botón del ratón" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Nombre de acción inválido. No puede estar vacÃo ni contener '/', ':', '=', " +"'\\' o '\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6245,11 +6247,11 @@ msgstr "¡La acción «%s» ya existe!" #: editor/project_settings_editor.cpp msgid "Rename Input Action Event" -msgstr "Renombrar evento de acción de entrada" +msgstr "Renombrar Evento de Acción de Entrada" #: editor/project_settings_editor.cpp msgid "Add Input Action Event" -msgstr "Añadir evento de acción de entrada" +msgstr "Añadir Evento de Acción de Entrada" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "Shift+" @@ -6264,8 +6266,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Presiona una tecla.." +msgid "Press a Key..." +msgstr "Presiona una tecla..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6321,11 +6323,11 @@ msgstr "Ãndice de boton del mando:" #: editor/project_settings_editor.cpp msgid "Erase Input Action" -msgstr "Borrar \"Input Action\"" +msgstr "Borrar Acción de Entrada" #: editor/project_settings_editor.cpp msgid "Erase Input Action Event" -msgstr "Borrar evento de acción de entrada" +msgstr "Borrar Evento de Acción de Entrada" #: editor/project_settings_editor.cpp msgid "Add Event" @@ -6385,7 +6387,7 @@ msgstr "Ya existe" #: editor/project_settings_editor.cpp msgid "Add Input Action" -msgstr "Añadir acción de entrada" +msgstr "Añadir Acción de Entrada" #: editor/project_settings_editor.cpp msgid "Error saving settings." @@ -6397,7 +6399,7 @@ msgstr "Los ajustes se han guardado correctamente." #: editor/project_settings_editor.cpp msgid "Override for Feature" -msgstr "Sobreescribir para esta caracterÃstica" +msgstr "Sobrescribir la CaracterÃstica" #: editor/project_settings_editor.cpp msgid "Add Translation" @@ -6448,12 +6450,12 @@ msgid "Property:" msgstr "Propiedad:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Sobre escribir por.." +msgid "Override For..." +msgstr "Sustituir por..." #: editor/project_settings_editor.cpp msgid "Input Map" -msgstr "Mapa de entradas" +msgstr "Mapa de Entradas" #: editor/project_settings_editor.cpp msgid "Action:" @@ -6521,7 +6523,7 @@ msgstr "AutoLoad" #: editor/property_editor.cpp msgid "Pick a Viewport" -msgstr "Selecciona un visor" +msgstr "Selecciona un Viewport" #: editor/property_editor.cpp msgid "Ease In" @@ -6544,12 +6546,12 @@ msgid "Easing Out-In" msgstr "Transición salida-entrada" #: editor/property_editor.cpp -msgid "File.." -msgstr "Archivo.." +msgid "File..." +msgstr "Archivo..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Directorio.." +msgid "Dir..." +msgstr "Directorio..." #: editor/property_editor.cpp msgid "Assign" @@ -6557,7 +6559,7 @@ msgstr "Asignar" #: editor/property_editor.cpp msgid "Select Node" -msgstr "Seleccionar nodo" +msgstr "Seleccionar Nodo" #: editor/property_editor.cpp msgid "New Script" @@ -6585,11 +6587,11 @@ msgstr "Error al cargar el archivo: ¡No es un recurso!" #: editor/property_editor.cpp msgid "Selected node is not a Viewport!" -msgstr "¡El nodo seleccionado no es un visor!" +msgstr "¡El nodo seleccionado no es un Viewport!" #: editor/property_editor.cpp msgid "Pick a Node" -msgstr "Selecciona un nodo" +msgstr "Selecciona un Nodo" #: editor/property_editor.cpp msgid "Bit %d, val %d." @@ -6634,7 +6636,7 @@ msgstr "" #: editor/reparent_dialog.cpp editor/scene_tree_dock.cpp msgid "Reparent Node" -msgstr "Reemparentar nodo" +msgstr "Reemparentar Nodo" #: editor/reparent_dialog.cpp msgid "Reparent Location (Select new Parent):" @@ -6707,11 +6709,11 @@ msgstr "Mover Nodos Dentro del Padre" #: editor/scene_tree_dock.cpp msgid "Duplicate Node(s)" -msgstr "Duplicar nodos" +msgstr "Duplicar Nodo(s)" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)?" -msgstr "¿Quieres borrar los nodos?" +msgstr "¿Eliminar Nodo(s)?" #: editor/scene_tree_dock.cpp msgid "Can not perform with the root node." @@ -6722,20 +6724,20 @@ msgid "This operation can't be done on instanced scenes." msgstr "Esta operación no puede realizarse en escenas instanciadas." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Guardar nueva escena como.." +msgid "Save New Scene As..." +msgstr "Guardar Nueva Escena Como..." #: editor/scene_tree_dock.cpp msgid "Editable Children" -msgstr "Hijos editables" +msgstr "Hijos Editables" #: editor/scene_tree_dock.cpp msgid "Load As Placeholder" -msgstr "Cargar como temporal" +msgstr "Cargar como Temporal" #: editor/scene_tree_dock.cpp msgid "Discard Instancing" -msgstr "Descartar instancia" +msgstr "Descartar Instancia" #: editor/scene_tree_dock.cpp msgid "Makes Sense!" @@ -6751,7 +6753,7 @@ msgstr "¡No se puede operar sobre los nodos heredados por la escena actual!" #: editor/scene_tree_dock.cpp msgid "Remove Node(s)" -msgstr "Borrar nodos" +msgstr "Eliminar Nodo(s)" #: editor/scene_tree_dock.cpp msgid "" @@ -6771,27 +6773,27 @@ msgstr "Error al duplicar escena para guardarla." #: editor/scene_tree_dock.cpp msgid "Sub-Resources" -msgstr "Sub-recursos" +msgstr "Sub-Recursos" #: editor/scene_tree_dock.cpp msgid "Clear Inheritance" -msgstr "Limpiar heredado" +msgstr "Limpiar Heredado" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)" -msgstr "Borrar nodos" +msgstr "Eliminar Nodo(s)" #: editor/scene_tree_dock.cpp msgid "Add Child Node" -msgstr "Añadir nodo hijo" +msgstr "Añadir Nodo Hijo" #: editor/scene_tree_dock.cpp msgid "Instance Child Scene" -msgstr "Instanciar escena hija" +msgstr "Instanciar Escena Hija" #: editor/scene_tree_dock.cpp msgid "Change Type" -msgstr "Cambiar tipo" +msgstr "Cambiar Tipo" #: editor/scene_tree_dock.cpp msgid "Attach Script" @@ -6803,7 +6805,7 @@ msgstr "Quitar script" #: editor/scene_tree_dock.cpp msgid "Merge From Scene" -msgstr "Unir desde escena" +msgstr "Unir Desde Escena" #: editor/scene_tree_dock.cpp msgid "Save Branch as Scene" @@ -6811,22 +6813,22 @@ msgstr "Guardar Rama como Escena" #: editor/scene_tree_dock.cpp msgid "Copy Node Path" -msgstr "Copiar ruta del nodo" +msgstr "Copiar Ruta del Nodo" #: editor/scene_tree_dock.cpp msgid "Delete (No Confirm)" -msgstr "Eliminar (sin confirmar)" +msgstr "Eliminar (Sin Confirmar)" #: editor/scene_tree_dock.cpp msgid "Add/Create a New Node" -msgstr "Añadir/crear nodo nuevo" +msgstr "Añadir/Crear un Nuevo Nodo" #: editor/scene_tree_dock.cpp msgid "" "Instance a scene file as a Node. Creates an inherited scene if no root node " "exists." msgstr "" -"Instanciar un archivo de escena como Nodo. Crear una escena heredada si no " +"Instanciar un archivo de escena como Nodo. Crea una escena heredada si no " "existe ningún nodo raÃz." #: editor/scene_tree_dock.cpp @@ -6839,7 +6841,7 @@ msgstr "Añadir un script nuevo o existente al nodo seleccionado." #: editor/scene_tree_dock.cpp msgid "Clear a script for the selected node." -msgstr "Borra el script del nodo seleccionado." +msgstr "Borrar el script del nodo seleccionado." #: editor/scene_tree_dock.cpp msgid "Remote" @@ -6874,8 +6876,8 @@ msgid "" "Node has connection(s) and group(s)\n" "Click to show signals dock." msgstr "" -"El nodo tiene conexión/es y grupo/s\n" -"Haz click para mostrar el panel de señales." +"El nodo tiene conexión(es) y grupo(s)\n" +"Haz clic para mostrar el panel de señales." #: editor/scene_tree_editor.cpp msgid "" @@ -6883,7 +6885,7 @@ msgid "" "Click to show signals dock." msgstr "" "El nodo tiene conexiones.\n" -"Haz click para mostrar el panel de señales." +"Haz clic para mostrar el panel de señales." #: editor/scene_tree_editor.cpp msgid "" @@ -6891,7 +6893,7 @@ msgid "" "Click to show groups dock." msgstr "" "El nodo está en el/los grupo(s).\n" -"Click para mostrar el panel de grupos." +"Haz clic para mostrar el panel de grupos." #: editor/scene_tree_editor.cpp msgid "Open script" @@ -6903,7 +6905,7 @@ msgid "" "Click to unlock" msgstr "" "El nodo está bloqueado.\n" -"Click para desbloquear" +"Haz clic para desbloquear" #: editor/scene_tree_editor.cpp msgid "" @@ -6911,7 +6913,7 @@ msgid "" "Click to make selectable" msgstr "" "Los hijos no son seleccionables.\n" -"Haz click para hacerlos seleccionables" +"Haz clic para hacerlos seleccionables" #: editor/scene_tree_editor.cpp msgid "Toggle Visibility" @@ -6924,7 +6926,7 @@ msgstr "" #: editor/scene_tree_editor.cpp msgid "Rename Node" -msgstr "Renombrar nodo" +msgstr "Renombrar Nodo" #: editor/scene_tree_editor.cpp msgid "Scene Tree (Nodes):" @@ -6932,11 +6934,11 @@ msgstr "Ãrbol de escenas (nodos):" #: editor/scene_tree_editor.cpp msgid "Node Configuration Warning!" -msgstr "¡Alerta de configuración de nodos!" +msgstr "¡Alerta de Configuración de Nodos!" #: editor/scene_tree_editor.cpp msgid "Select a Node" -msgstr "Selecciona un nodo" +msgstr "Selecciona un Nodo" #: editor/script_create_dialog.cpp msgid "Error loading template '%s'" @@ -6944,7 +6946,7 @@ msgstr "Error al cargar la plantilla '%s'" #: editor/script_create_dialog.cpp msgid "Error - Could not create script in filesystem." -msgstr "Error - No se pudo crear script en el sistema." +msgstr "Error - No se pudo crear script en el sistema de archivos." #: editor/script_create_dialog.cpp msgid "Error loading script from %s" @@ -6980,7 +6982,7 @@ msgstr "La extensión no es correcta" #: editor/script_create_dialog.cpp msgid "Wrong extension chosen" -msgstr "Extensión seleccionada errónea" +msgstr "Se ha elegido una extensión incorrecta" #: editor/script_create_dialog.cpp msgid "Invalid Path" @@ -7036,7 +7038,7 @@ msgstr "Script integrado" #: editor/script_create_dialog.cpp msgid "Attach Node Script" -msgstr "Añadir script de nodo" +msgstr "Añadir Script de Nodo" #: editor/script_editor_debugger.cpp msgid "Remote " @@ -7140,7 +7142,7 @@ msgstr "Tipo" #: editor/script_editor_debugger.cpp msgid "Format" -msgstr "Format" +msgstr "Formato" #: editor/script_editor_debugger.cpp msgid "Usage" @@ -7152,11 +7154,11 @@ msgstr "Otros" #: editor/script_editor_debugger.cpp msgid "Clicked Control:" -msgstr "Controles seleccionados:" +msgstr "Controles Seleccionados:" #: editor/script_editor_debugger.cpp msgid "Clicked Control Type:" -msgstr "Tipo de controles seleccionados:" +msgstr "Tipo de Controles Seleccionados:" #: editor/script_editor_debugger.cpp msgid "Live Edit Root:" @@ -7236,7 +7238,7 @@ msgstr "Borrar entrada actual" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Double click to create a new entry" -msgstr "Doble click para crear una nueva entrada" +msgstr "Haz doble clic para crear una nueva entrada" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Platform:" @@ -7248,15 +7250,15 @@ msgstr "Plataforma" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Dynamic Library" -msgstr "LibrerÃa dinámica" +msgstr "LibrerÃa Dinámica" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" -msgstr "Añadir entrada de arquitectura" +msgstr "Añadir una entrada de arquitectura" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "GDNativeLibrary" -msgstr "\"GDNativeLibrary\"" +msgstr "GDNativeLibrary" #: modules/gdnative/gdnative_library_singleton_editor.cpp msgid "Library" @@ -7290,7 +7292,7 @@ msgstr "" #: modules/gdscript/gdscript_functions.cpp msgid "step argument is zero!" -msgstr "¡El argumento «step» es cero!" +msgstr "¡el argumento del paso es cero!" #: modules/gdscript/gdscript_functions.cpp msgid "Not a script with an instance" @@ -7330,7 +7332,7 @@ msgstr "El objeto no puede proporcionar una longitud." #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Next Plane" -msgstr "Plano siguiente" +msgstr "Siguiente Plano" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Previous Plane" @@ -7342,7 +7344,7 @@ msgstr "Plano:" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Next Floor" -msgstr "Suelo Posterior" +msgstr "Siguiente Piso" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Previous Floor" @@ -7362,7 +7364,7 @@ msgstr "GridMap Duplicar selección" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Grid Map" -msgstr "Rejilla" +msgstr "Mapa de CuadrÃcula" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Snap View" @@ -7446,7 +7448,7 @@ msgstr "Seleccionar distancia:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "El nombre de la clase no puede ser una palabra reservada" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -7490,7 +7492,7 @@ msgstr "Compilaciones" #: modules/mono/editor/mono_bottom_panel.cpp msgid "Build Project" -msgstr "Compilar proyecto" +msgstr "Compilar Proyecto" #: modules/mono/editor/mono_bottom_panel.cpp msgid "Warnings" @@ -7505,7 +7507,7 @@ msgid "" "A node yielded without working memory, please read the docs on how to yield " "properly!" msgstr "" -"¡Un nodo ejecutó un «yield» sin memoria de trabajo. Prueba leyendo la " +"¡Un nodo ejecutó un yield sin memoria de trabajo. Prueba leyendo la " "documentación sobre cómo utilizar yield!" #: modules/visual_script/visual_script.cpp @@ -7513,8 +7515,8 @@ msgid "" "Node yielded, but did not return a function state in the first working " "memory." msgstr "" -"Un nodo ejecutó un «yield» pero no devolvió un estado de función en la " -"memoria de trabajo original." +"Un nodo ejecutó un yield pero no devolvió un estado de función en la memoria " +"de trabajo original." #: modules/visual_script/visual_script.cpp msgid "" @@ -7540,7 +7542,7 @@ msgstr "Desbordamiento de pila en el nivel: " #: modules/visual_script/visual_script_editor.cpp msgid "Change Signal Arguments" -msgstr "Cambiar argumentos de la señal" +msgstr "Cambiar Argumentos de la Señal" #: modules/visual_script/visual_script_editor.cpp msgid "Change Argument Type" @@ -7576,35 +7578,35 @@ msgstr "Otra función/variable/señal ya utiliza este nombre:" #: modules/visual_script/visual_script_editor.cpp msgid "Rename Function" -msgstr "Renombrar función" +msgstr "Renombrar Función" #: modules/visual_script/visual_script_editor.cpp msgid "Rename Variable" -msgstr "Renombrar variable" +msgstr "Renombrar Variable" #: modules/visual_script/visual_script_editor.cpp msgid "Rename Signal" -msgstr "Renombrar señal" +msgstr "Renombrar Señal" #: modules/visual_script/visual_script_editor.cpp msgid "Add Function" -msgstr "Añadir función" +msgstr "Añadir Función" #: modules/visual_script/visual_script_editor.cpp msgid "Add Variable" -msgstr "Añadir variable" +msgstr "Añadir Variable" #: modules/visual_script/visual_script_editor.cpp msgid "Add Signal" -msgstr "Añadir señal" +msgstr "Añadir Señal" #: modules/visual_script/visual_script_editor.cpp msgid "Change Expression" -msgstr "Cambiar expresión" +msgstr "Cambiar Expresión" #: modules/visual_script/visual_script_editor.cpp msgid "Add Node" -msgstr "Añadir nodo" +msgstr "Añadir Nodo" #: modules/visual_script/visual_script_editor.cpp msgid "Remove VisualScript Nodes" @@ -7612,7 +7614,7 @@ msgstr "Quitar nodos de VisualScript" #: modules/visual_script/visual_script_editor.cpp msgid "Duplicate VisualScript Nodes" -msgstr "Duplicar nodos de VisualScript" +msgstr "Duplicar Nodos de VisualScript" #: modules/visual_script/visual_script_editor.cpp msgid "Hold %s to drop a Getter. Hold Shift to drop a generic signature." @@ -7632,7 +7634,7 @@ msgstr "Mantén pulsado %s para quitar una referencia simple del nodo." #: modules/visual_script/visual_script_editor.cpp msgid "Hold Ctrl to drop a simple reference to the node." -msgstr "Mantén pulsado Ctrl para soltar una referencia al nodo." +msgstr "Mantén pulsado Ctrl para soltar una referencia simple al nodo." #: modules/visual_script/visual_script_editor.cpp msgid "Hold %s to drop a Variable Setter." @@ -7644,19 +7646,19 @@ msgstr "Mantén pulsado Ctrl para soltar un «Setter» de variable." #: modules/visual_script/visual_script_editor.cpp msgid "Add Preload Node" -msgstr "Añadir nodo «Preload»" +msgstr "Añadir Nodo Preload" #: modules/visual_script/visual_script_editor.cpp msgid "Add Node(s) From Tree" -msgstr "Añadir nodo/s desde árbol" +msgstr "Añadir Nodo(s) desde Ãrbol" #: modules/visual_script/visual_script_editor.cpp msgid "Add Getter Property" -msgstr "Añadir propiedad «Getter»" +msgstr "Añadir propiedad Getter" #: modules/visual_script/visual_script_editor.cpp msgid "Add Setter Property" -msgstr "Añadir propiedad «Setter»" +msgstr "Añadir propiedad Setter" #: modules/visual_script/visual_script_editor.cpp msgid "Change Base Type" @@ -7664,15 +7666,15 @@ msgstr "Cambiar tipo base" #: modules/visual_script/visual_script_editor.cpp msgid "Move Node(s)" -msgstr "Mover nodo/s" +msgstr "Mover Nodo(s)" #: modules/visual_script/visual_script_editor.cpp msgid "Remove VisualScript Node" -msgstr "Quitar nodo de VisualScript" +msgstr "Quitar Nodo de VisualScript" #: modules/visual_script/visual_script_editor.cpp msgid "Connect Nodes" -msgstr "Conectar nodos" +msgstr "Conectar Nodos" #: modules/visual_script/visual_script_editor.cpp msgid "Condition" @@ -7700,7 +7702,7 @@ msgstr "Devuelve (\"Return\")" #: modules/visual_script/visual_script_editor.cpp msgid "Call" -msgstr "Call" +msgstr "Llamada (\"Call\")" #: modules/visual_script/visual_script_editor.cpp msgid "Get" @@ -7712,7 +7714,7 @@ msgstr "El script ya contiene la función '%s'" #: modules/visual_script/visual_script_editor.cpp msgid "Change Input Value" -msgstr "Cambiar valor de entrada" +msgstr "Cambiar Valor de Entrada" #: modules/visual_script/visual_script_editor.cpp msgid "Can't copy the function node." @@ -7728,35 +7730,35 @@ msgstr "Pegar nodos de VisualScript" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Function" -msgstr "Quitar función" +msgstr "Quitar Función" #: modules/visual_script/visual_script_editor.cpp msgid "Edit Variable" -msgstr "Editar variable" +msgstr "Editar Variable" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Variable" -msgstr "Quitar variable" +msgstr "Quitar Variable" #: modules/visual_script/visual_script_editor.cpp msgid "Edit Signal" -msgstr "Editar señal" +msgstr "Editar Señal" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Signal" -msgstr "Quitar señal" +msgstr "Quitar Señal" #: modules/visual_script/visual_script_editor.cpp msgid "Editing Variable:" -msgstr "Editando variable:" +msgstr "Editando Variable:" #: modules/visual_script/visual_script_editor.cpp msgid "Editing Signal:" -msgstr "Editando señal:" +msgstr "Editando Señal:" #: modules/visual_script/visual_script_editor.cpp msgid "Base Type:" -msgstr "Tipo base:" +msgstr "Tipo Base:" #: modules/visual_script/visual_script_editor.cpp msgid "Available Nodes:" @@ -7772,7 +7774,7 @@ msgstr "Editar argumentos de la señal:" #: modules/visual_script/visual_script_editor.cpp msgid "Edit Variable:" -msgstr "Editar variable:" +msgstr "Editar Variable:" #: modules/visual_script/visual_script_editor.cpp msgid "Delete Selected" @@ -7780,19 +7782,19 @@ msgstr "Quitar seleccionados" #: modules/visual_script/visual_script_editor.cpp msgid "Find Node Type" -msgstr "Buscar tipo de nodo" +msgstr "Buscar Tipo de Nodo" #: modules/visual_script/visual_script_editor.cpp msgid "Copy Nodes" -msgstr "Copiar nodos" +msgstr "Copiar Nodos" #: modules/visual_script/visual_script_editor.cpp msgid "Cut Nodes" -msgstr "Cortar nodos" +msgstr "Cortar Nodos" #: modules/visual_script/visual_script_editor.cpp msgid "Paste Nodes" -msgstr "Pegar nodos" +msgstr "Pegar Nodos" #: modules/visual_script/visual_script_flow_control.cpp msgid "Input type not iterable: " @@ -7812,11 +7814,11 @@ msgstr "Ãndice del nombre de la propiedad inválido." #: modules/visual_script/visual_script_func_nodes.cpp msgid "Base object is not a Node!" -msgstr "¡El objeto base no es un nodo!" +msgstr "¡El objeto base no es un Nodo!" #: modules/visual_script/visual_script_func_nodes.cpp msgid "Path does not lead Node!" -msgstr "¡La ruta no apunta a un nodo!" +msgstr "¡La ruta no apunta a un Nodo!" #: modules/visual_script/visual_script_func_nodes.cpp msgid "Invalid index property name '%s' in node %s." @@ -7858,7 +7860,7 @@ msgstr "Ejecutar en navegador" #: platform/javascript/export/export.cpp msgid "Run exported HTML in the system's default browser." -msgstr "Ejecutar HTML exportado en el navegador por defecto del sistema." +msgstr "Ejecutar HTML exportado en el navegador predeterminado del sistema." #: platform/javascript/export/export.cpp msgid "Could not write file:" @@ -7889,8 +7891,8 @@ msgid "" "A SpriteFrames resource must be created or set in the 'Frames' property in " "order for AnimatedSprite to display frames." msgstr "" -"Se debe crear un recurso \"SpriteFrames\" o asignar uno en la propiedad " -"'Frames' para que AnimatedSprite pueda mostrar fotogramas." +"Se debe crear un recurso SpriteFrames o asignar uno en la propiedad 'Frames' " +"para que AnimatedSprite pueda mostrar fotogramas." #: scene/2d/canvas_modulate.cpp msgid "" @@ -7934,9 +7936,8 @@ msgid "" "StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape." msgstr "" "CollisionShape2D solo sirve para proveer de una forma de colisión a un nodo " -"derivado de \"CollisionObject2D\". Por favor, úsalo solo como hijo de " -"Area2D, StaticBody2D, RigidBody2D, KinematicBody2D, etc... para dotarlos de " -"forma." +"derivado de CollisionObject2D. Por favor, úsalo solo como hijo de Area2D, " +"StaticBody2D, RigidBody2D, KinematicBody2D, etc. para dotarlos de forma." #: scene/2d/collision_shape_2d.cpp msgid "" @@ -7979,15 +7980,15 @@ msgid "" "NavigationPolygonInstance must be a child or grandchild to a Navigation2D " "node. It only provides navigation data." msgstr "" -"NavigationPolygonInstance debe ser hijo o nieto de un nodo \"Navigation2D\". " +"NavigationPolygonInstance debe ser hijo o nieto de un nodo Navigation2D. " "Solo provee datos de navegación." #: scene/2d/parallax_layer.cpp msgid "" "ParallaxLayer node only works when set as child of a ParallaxBackground node." msgstr "" -"En nodo \"ParallaxLayer\" solo funciona cuando esta posicionado como hijo de " -"un nodo \"ParallaxBackground\"." +"En nodo ParallaxLayer solo funciona cuando esta posicionado como hijo de un " +"nodo ParallaxBackground." #: scene/2d/particles_2d.cpp scene/3d/particles.cpp msgid "" @@ -8000,8 +8001,7 @@ msgstr "" #: scene/2d/path_2d.cpp msgid "PathFollow2D only works when set as a child of a Path2D node." msgstr "" -"\"PathFollow2D\" solo funciona cuando está posicionado como hijo de un nodo " -"\"Path2D\"." +"PathFollow2D solo funciona cuando está colocado como hijo de un nodo Path2D." #: scene/2d/physics_body_2d.cpp msgid "" @@ -8090,8 +8090,8 @@ msgid "" msgstr "" "Este nodo no tiene formas hijas, por lo que no puede interactuar con el " "espacio.\n" -"Considera añadir un \"CollisionShape\" o \"CollisionPolygon\" como hijos de " -"este nodo para dotarlo de una forma." +"Considera añadir un CollisionShape o CollisionPolygon como hijos de este " +"nodo para dotarlo de una forma." #: scene/3d/collision_polygon.cpp msgid "" @@ -8114,8 +8114,8 @@ msgid "" "KinematicBody, etc. to give them a shape." msgstr "" "CollisionShape solo sirve para proveer de una forma a un nodo derivado de un " -"CollisionObject. Por favor, úsalo solo como hijo de \"Area\", \"StaticBody" -"\", \"RigidBody\", \"KinematicBody\", etc. para darles dicha forma." +"CollisionObject. Por favor, úsalo solo como hijo de Area, StaticBody, " +"RigidBody, KinematicBody, etc. para darles dicha forma." #: scene/3d/collision_shape.cpp msgid "" @@ -8166,7 +8166,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment necesita un recurso Environment." #: scene/3d/scenario_fx.cpp msgid "" @@ -8180,6 +8180,9 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Este WorldEnvironment está siendo ignorado. Agrega un nodo Camera (para " +"escenas 3D) o configura el Background Mode de este entorno en modo Canvas " +"(para escenas 2D)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8203,7 +8206,7 @@ msgstr "Modo Raw" #: scene/gui/color_picker.cpp msgid "Add current color as a preset" -msgstr "Añadir el color actual como predefinido" +msgstr "Añadir el color actual como predeterminado" #: scene/gui/dialogs.cpp msgid "Alert!" @@ -8211,7 +8214,7 @@ msgstr "¡Alerta!" #: scene/gui/dialogs.cpp msgid "Please Confirm..." -msgstr "Confirmar decisión…" +msgstr "Por favor, Confirma..." #: scene/gui/file_dialog.cpp msgid "Select this Folder" @@ -8246,8 +8249,8 @@ msgid "" "Default Environment as specified in Project Settings (Rendering -> " "Environment -> Default Environment) could not be loaded." msgstr "" -"El entorno especificado por defecto en los Ajustes del Proyecto (Renderizado " -"-> Ventana -> Entorno por Defecto) no se ha podido cargar." +"El Entorno por Defecto como se especifica en los Ajustes del Proyecto " +"(Rendering -> Environment -> Default Environment) no se ha podido cargar." #: scene/main/viewport.cpp msgid "" @@ -8256,10 +8259,10 @@ msgid "" "obtain a size. Otherwise, make it a RenderTarget and assign its internal " "texture to some node for display." msgstr "" -"Este viewport no está configurado como \"render target\". Si tienes " -"intención de que muestre su contenido directamente en la pantalla, hazlo " -"hijo de un Control para que pueda obtener un tamaño. Alternativamente, hazlo " -"un RenderTarget y asigna su textura interna a algún otro nodo para mostrar." +"Este viewport no está configurado como render target. Si quieres que muestre " +"su contenido directamente en la pantalla, hazlo hijo de un Control para que " +"pueda obtener un tamaño. De lo contrario, conviértelo en un RenderTarget y " +"asigna su textura interna a algún nodo para mostrarlo." #: scene/resources/dynamic_font.cpp msgid "Error initializing FreeType." @@ -8277,6 +8280,13 @@ msgstr "Error al cargar la tipografÃa." msgid "Invalid font size." msgstr "Tamaño de tipografÃa incorrecto." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Pestaña anterior" + +#~ msgid "Next" +#~ msgstr "Siguiente" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "La acción no es correcta (no puedes utilizar «/» o «:»)." @@ -8303,9 +8313,6 @@ msgstr "Tamaño de tipografÃa incorrecto." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "No se encontró project.godot en la ruta del proyecto." -#~ msgid "Next" -#~ msgstr "Siguiente" - #~ msgid "Not found!" #~ msgstr "¡No se ha encontrado!" @@ -8459,8 +8466,8 @@ msgstr "Tamaño de tipografÃa incorrecto." #~ msgid "Exporting for %s" #~ msgstr "Exportando para %s" -#~ msgid "Setting Up.." -#~ msgstr "Configurando.." +#~ msgid "Setting Up..." +#~ msgstr "Configurando..." #~ msgid "Error loading scene." #~ msgstr "Hubo un error al cargar la escena." @@ -8525,8 +8532,8 @@ msgstr "Tamaño de tipografÃa incorrecto." #~ msgid "Info" #~ msgstr "Info" -#~ msgid "Re-Import.." -#~ msgstr "Reimportar.." +#~ msgid "Re-Import..." +#~ msgstr "Reimportar..." #~ msgid "No bit masks to import!" #~ msgstr "¡Sin máscaras de bits para importar!" @@ -8925,14 +8932,14 @@ msgstr "Tamaño de tipografÃa incorrecto." #~ msgid "Zoom (%):" #~ msgstr "Zoom (%):" -#~ msgid "Skeleton.." -#~ msgstr "Esqueleto.." +#~ msgid "Skeleton..." +#~ msgstr "Esqueleto..." #~ msgid "Zoom Reset" #~ msgstr "Restablecer zoom" -#~ msgid "Zoom Set.." -#~ msgstr "Ajustar zoom.." +#~ msgid "Zoom Set..." +#~ msgstr "Ajustar zoom..." #~ msgid "Set a Value" #~ msgstr "Establecer valor" @@ -9443,7 +9450,7 @@ msgstr "Tamaño de tipografÃa incorrecto." #~ msgid "Export Project PCK" #~ msgstr "Exportar PCK del proyecto" -#~ msgid "Export.." +#~ msgid "Export..." #~ msgstr "Exportar…" #~ msgid "Project Export" @@ -9557,8 +9564,8 @@ msgstr "Tamaño de tipografÃa incorrecto." #~ msgid "Reload Tool Script (Soft)" #~ msgstr "Volver a Cargar Script de Herramientas (Soft)" -#~ msgid "Edit Connections.." -#~ msgstr "Editar Conecciones.." +#~ msgid "Edit Connections..." +#~ msgstr "Editar Conecciones..." #~ msgid "Set Params" #~ msgstr "Setear Params" diff --git a/editor/translations/es_AR.po b/editor/translations/es_AR.po index 304fc7dbc5..64ee2404f1 100644 --- a/editor/translations/es_AR.po +++ b/editor/translations/es_AR.po @@ -2,17 +2,15 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Diego López <diegodario21@gmail.com>, 2017. # Lisandro Lorea <lisandrolorea@gmail.com>, 2016-2018. # Roger Blanco Ribera <roger.blancoribera@gmail.com>, 2016-2018. # Sebastian Silva <sebastian@sugarlabs.org>, 2016. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-03-04 06:03+0000\n" +"PO-Revision-Date: 2018-06-06 13:28+0000\n" "Last-Translator: Lisandro Lorea <lisandrolorea@gmail.com>\n" "Language-Team: Spanish (Argentina) <https://hosted.weblate.org/projects/" "godot-engine/godot/es_AR/>\n" @@ -21,7 +19,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -502,8 +500,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Desconectar '%s' de '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Conectar.." +msgid "Connect..." +msgstr "Conectar..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -922,12 +920,12 @@ msgid "Move Audio Bus" msgstr "Mover Bus de Audio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Guardar Layout de Bus de Audio Como.." +msgid "Save Audio Bus Layout As..." +msgstr "Guardar Layout de Bus de Audio Como..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Ubicación para el Nuevo Layout.." +msgid "Location for New Layout..." +msgstr "Ubicación para el Nuevo Layout..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1068,12 +1066,12 @@ msgid "Updating Scene" msgstr "Actualizando Escena" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Guardando cambios locales.." +msgid "Storing local changes..." +msgstr "Guardando cambios locales..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Actualizando escena.." +msgid "Updating scene..." +msgstr "Actualizando escena..." #: editor/editor_data.cpp msgid "[empty]" @@ -1141,8 +1139,8 @@ msgid "Show In File Manager" msgstr "Mostrar en Gestor de Archivos" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nueva Carpeta.." +msgid "New Folder..." +msgstr "Nueva Carpeta..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1403,20 +1401,20 @@ msgstr "Limpiar Salida" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "La exportación del proyecto falló con el código de error %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Error al guardar el recurso!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Guardar Recurso Como.." +msgid "Save Resource As..." +msgstr "Guardar Recurso Como..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Ya Veo.." +msgid "I see..." +msgstr "Ya Veo..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1649,12 +1647,12 @@ msgid "Open Base Scene" msgstr "Abrir Escena Base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Abrir Escena Rapido.." +msgid "Quick Open Scene..." +msgstr "Abrir Escena Rapido..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Abrir Script Rapido.." +msgid "Quick Open Script..." +msgstr "Abrir Script Rapido..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1665,8 +1663,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Guardar cambios a '%s' antes de cerrar?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Guardar Escena Como.." +msgid "Save Scene As..." +msgstr "Guardar Escena Como..." #: editor/editor_node.cpp msgid "No" @@ -1717,8 +1715,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Esta acción no se puede deshacer. Revertir de todos modos?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Ejecutar Escena Rapido.." +msgid "Quick Run Scene..." +msgstr "Ejecutar Escena Rapido..." #: editor/editor_node.cpp msgid "Quit" @@ -1880,8 +1878,8 @@ msgid "Previous tab" msgstr "Pestaña anterior" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrar Archivos.." +msgid "Filter Files..." +msgstr "Filtrar Archivos..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1892,12 +1890,12 @@ msgid "New Scene" msgstr "Nueva Escena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Nueva Escena Heredada.." +msgid "New Inherited Scene..." +msgstr "Nueva Escena Heredada..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Abrir Escena.." +msgid "Open Scene..." +msgstr "Abrir Escena..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1916,16 +1914,16 @@ msgid "Open Recent" msgstr "Abrir Reciente" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Convertir A.." +msgid "Convert To..." +msgstr "Convertir A..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2189,8 +2187,8 @@ msgid "Save the currently edited resource." msgstr "Guardar el recurso editado actualmente." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Guardar Como.." +msgid "Save As..." +msgstr "Guardar Como..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2298,8 +2296,8 @@ msgid "Creating Mesh Previews" msgstr "Creando Vistas Previas de Mesh/es" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatura.." +msgid "Thumbnail..." +msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2452,8 +2450,8 @@ msgid "(Current)" msgstr "(Actual)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Recuperando mirrors, esperá, por favor.." +msgid "Retrieving mirrors, please wait..." +msgstr "Recuperando mirrors, esperá, por favor..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2530,8 +2528,8 @@ msgid "Error requesting url: " msgstr "Error al pedir el url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Conectando al Mirror.." +msgid "Connecting to Mirror..." +msgstr "Conectando al Mirror..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2547,8 +2545,8 @@ msgstr "No se ha podido resolver" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Conectando.." +msgid "Connecting..." +msgstr "Conectando..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2560,8 +2558,8 @@ msgstr "Conectado" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Solicitando.." +msgid "Requesting..." +msgstr "Solicitando..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2697,12 +2695,12 @@ msgid "Collapse all" msgstr "Colapsar todos" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Renombrar.." +msgid "Rename..." +msgstr "Renombrar..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Mover A.." +msgid "Move To..." +msgstr "Mover A..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2713,16 +2711,16 @@ msgid "Instance" msgstr "Instancia" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Editar Dependencias.." +msgid "Edit Dependencies..." +msgstr "Editar Dependencias..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Ver Dueños.." +msgid "View Owners..." +msgstr "Ver Dueños..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplicar.." +msgid "Duplicate..." +msgstr "Duplicar..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2748,7 +2746,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Examinando Archivos,\n" "Aguardá, por favor." @@ -2816,8 +2814,8 @@ msgid "Import Scene" msgstr "Importar Escena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Importando Escena.." +msgid "Importing Scene..." +msgstr "Importando Escena..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2828,8 +2826,8 @@ msgid "Generating for Mesh: " msgstr "Generando para Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Ejecutando Script Personalizado.." +msgid "Running Custom Script..." +msgstr "Ejecutando Script Personalizado..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2844,8 +2842,8 @@ msgid "Error running post-import script:" msgstr "Error ejecutando el script de post-importacion:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Guardando.." +msgid "Saving..." +msgstr "Guardando..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2864,8 +2862,8 @@ msgid "Import As:" msgstr "Importar Como:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Preseteo.." +msgid "Preset..." +msgstr "Preseteo..." #: editor/import_dock.cpp msgid "Reimport" @@ -3284,16 +3282,16 @@ msgid "Transition Node" msgstr "Nodo Transición" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importar Animaciones.." +msgid "Import Animations..." +msgstr "Importar Animaciones..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Editar Filtros de Nodo" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filtros.." +msgid "Filters..." +msgstr "Filtros..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3360,8 +3358,8 @@ msgid "Fetching:" msgstr "Obteniendo:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Resolviendo.." +msgid "Resolving..." +msgstr "Resolviendo..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3427,8 +3425,8 @@ msgid "Site:" msgstr "Sitio:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Soporte.." +msgid "Support..." +msgstr "Soporte..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3625,8 +3623,9 @@ msgid "Use Rotation Snap" msgstr "Usar Snap de Rotación" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "Configurar alineado.." +msgstr "Configurar Snap..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3721,14 +3720,12 @@ msgid "Show Guides" msgstr "Mostrar guÃas" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Ver Origen" +msgstr "Mostrar OrÃgen" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Viewport" +msgstr "Mostrar Viewport" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4021,7 +4018,7 @@ msgstr "El mesh no tiene una superficie de donde crear contornos(outlines)!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "El tipo de la malla primitiva no es PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4052,8 +4049,8 @@ msgid "Create Convex Collision Sibling" msgstr "Crear Collision Sibling Convexo" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Crear Outline Mesh.." +msgid "Create Outline Mesh..." +msgstr "Crear Outline Mesh..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4259,8 +4256,8 @@ msgid "Error loading image:" msgstr "Error al cargar la imagen:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Sin pixeles con transparencia > 128 en imagen.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Sin pixeles con transparencia > 128 en imagen..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4620,8 +4617,8 @@ msgid "Import Theme" msgstr "Importar Tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Guardar Tema Como.." +msgid "Save Theme As..." +msgstr "Guardar Tema Como..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4717,8 +4714,8 @@ msgstr "Act/Desact. Panel de Scripts" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Encontrar.." +msgid "Find..." +msgstr "Encontrar..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4927,16 +4924,16 @@ msgid "Find Previous" msgstr "Encontrar Anterior" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Reemplazar.." +msgid "Replace..." +msgstr "Reemplazar..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Ir a Función.." +msgid "Goto Function..." +msgstr "Ir a Función..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Ir a LÃnea.." +msgid "Goto Line..." +msgstr "Ir a LÃnea..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5389,12 +5386,8 @@ msgid "Transform" msgstr "Transformar" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configurar Snap.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Dialogo de Transformación.." +msgid "Transform Dialog..." +msgstr "Dialogo de Transformación..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5646,8 +5639,8 @@ msgid "Remove All" msgstr "Quitar Todos" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Editar tema.." +msgid "Edit theme..." +msgstr "Editar tema..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5694,14 +5687,12 @@ msgid "Checked Item" msgstr "Item Tildado" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Agregar Item" +msgstr "Radio Item" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Item Tildado" +msgstr "Radio Item Tildado" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5716,8 +5707,8 @@ msgid "Options" msgstr "Opciones" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Tienes, Muchas, Variadas, Opciones!" +msgid "Has,Many,Options" +msgstr "Tiene,Muchas,Opciones" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5910,8 +5901,8 @@ msgid "Presets" msgstr "Presets" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Agregar.." +msgid "Add..." +msgstr "Agregar..." #: editor/project_export.cpp msgid "Resources" @@ -6006,6 +5997,10 @@ msgid "Imported Project" msgstr "Proyecto Importado" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Nombre de Proyecto Inválido." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "No se pudo crear la carpeta." @@ -6209,9 +6204,11 @@ msgstr "Botón de Mouse" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Nombre de acción inválido. No puede estar vacÃo o contener '/', ':', '=', " +"'\\' o '\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6238,8 +6235,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Presionar una Tecla.." +msgid "Press a Key..." +msgstr "Presionar una Tecla..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6422,8 +6419,8 @@ msgid "Property:" msgstr "Propiedad:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Sobreescribir Para.." +msgid "Override For..." +msgstr "Sobreescribir Para..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6518,12 +6515,12 @@ msgid "Easing Out-In" msgstr "Easing Out-In" #: editor/property_editor.cpp -msgid "File.." -msgstr "Archivo.." +msgid "File..." +msgstr "Archivo..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Dir.." +msgid "Dir..." +msgstr "Dir..." #: editor/property_editor.cpp msgid "Assign" @@ -6696,8 +6693,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "Esta operación no puede ser realizada en escenas instanciadas." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Guardar Nueva Escena Como.." +msgid "Save New Scene As..." +msgstr "Guardar Nueva Escena Como..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7417,7 +7414,7 @@ msgstr "Elegir Instancia:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "El nombre de la clase no puede ser una palabra reservada" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8128,7 +8125,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment necesita un recurso Environment." #: scene/3d/scenario_fx.cpp msgid "" @@ -8142,6 +8139,9 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Este WorldEnvironment esta siendo ignorado. Agregá un nodo Camera (para " +"escenas 3D) o configurá el Background Mode de este entorno en modo Canvas " +"(para escenas 2D)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8239,6 +8239,13 @@ msgstr "Error cargando tipografÃa." msgid "Invalid font size." msgstr "Tamaño de tipografÃa inválido." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Pestaña anterior" + +#~ msgid "Next" +#~ msgstr "Siguiente" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Acción Invalida (cualquier cosa va menos '/' o ':')." @@ -8265,9 +8272,6 @@ msgstr "Tamaño de tipografÃa inválido." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "No se pudo obtener project.godot en la ruta de proyecto." -#~ msgid "Next" -#~ msgstr "Siguiente" - #~ msgid "Not found!" #~ msgstr "No se encontró!" @@ -8413,8 +8417,8 @@ msgstr "Tamaño de tipografÃa inválido." #~ msgid "Exporting for %s" #~ msgstr "Exportando para %s" -#~ msgid "Setting Up.." -#~ msgstr "Configurando.." +#~ msgid "Setting Up..." +#~ msgstr "Configurando..." #~ msgid "Error loading scene." #~ msgstr "Error al cargar la escena." @@ -8476,8 +8480,8 @@ msgstr "Tamaño de tipografÃa inválido." #~ msgid "Info" #~ msgstr "Info" -#~ msgid "Re-Import.." -#~ msgstr "Reimportando.." +#~ msgid "Re-Import..." +#~ msgstr "Reimportando..." #~ msgid "No bit masks to import!" #~ msgstr "Sin máscaras de bits para importar!" @@ -8873,14 +8877,14 @@ msgstr "Tamaño de tipografÃa inválido." #~ msgid "Zoom (%):" #~ msgstr "Zoom (%):" -#~ msgid "Skeleton.." -#~ msgstr "Esqueleto.." +#~ msgid "Skeleton..." +#~ msgstr "Esqueleto..." #~ msgid "Zoom Reset" #~ msgstr "Resetear Zoom" -#~ msgid "Zoom Set.." -#~ msgstr "Setear Zoom.." +#~ msgid "Zoom Set..." +#~ msgstr "Setear Zoom..." #~ msgid "Set a Value" #~ msgstr "Setear un Valor" @@ -9362,8 +9366,8 @@ msgstr "Tamaño de tipografÃa inválido." #~ msgid "Export Project PCK" #~ msgstr "Exportar PCK de Proyecto" -#~ msgid "Export.." -#~ msgstr "Exportar.." +#~ msgid "Export..." +#~ msgstr "Exportar..." #~ msgid "Project Export" #~ msgstr "Exportar Proyecto" @@ -9483,8 +9487,8 @@ msgstr "Tamaño de tipografÃa inválido." #~ msgid "Reload Tool Script (Soft)" #~ msgstr "Volver a Cargar Script de Herramientas (Soft)" -#~ msgid "Edit Connections.." -#~ msgstr "Editar Conecciones.." +#~ msgid "Edit Connections..." +#~ msgstr "Editar Conecciones..." #~ msgid "Set Params" #~ msgstr "Setear Params" diff --git a/editor/translations/fa.po b/editor/translations/fa.po index 8b7fdcbb79..f674ef99cc 100644 --- a/editor/translations/fa.po +++ b/editor/translations/fa.po @@ -502,7 +502,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "'s%' را از 's%' جدا Ú©Ù†" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "در ØØ§Ù„ اتصال..." #: editor/connections_dialog.cpp @@ -928,11 +928,11 @@ msgid "Move Audio Bus" msgstr "کلید Add را جابجا Ú©Ù†" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1068,11 +1068,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1143,8 +1143,8 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "ساختن پوشه.." +msgid "New Folder..." +msgstr "ساختن پوشه..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1406,12 +1406,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "ذخیره منبع از ..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "من میبینم ..." #: editor/editor_node.cpp @@ -1619,11 +1619,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1635,7 +1635,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "ذخیره صØÙ†Ù‡ در ..." #: editor/editor_node.cpp @@ -1687,7 +1687,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1834,7 +1834,7 @@ msgid "Previous tab" msgstr "زبانه قبلی" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1846,11 +1846,11 @@ msgid "New Scene" msgstr "صØÙ†Ù‡ جدید" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1870,15 +1870,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2124,7 +2124,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "ذخیره در..." #: editor/editor_node.cpp @@ -2233,7 +2233,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2386,7 +2386,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2443,7 +2443,7 @@ msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy msgid "Request Failed." -msgstr "در ØØ§Ù„ درخواست.." +msgstr "در ØØ§Ù„ درخواست..." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp @@ -2465,7 +2465,7 @@ msgstr "خطای آدرس درخواستی: " #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "در ØØ§Ù„ اتصال..." #: editor/export_template_manager.cpp @@ -2482,8 +2482,8 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "در ØØ§Ù„ اتصال.." +msgid "Connecting..." +msgstr "در ØØ§Ù„ اتصال..." #: editor/export_template_manager.cpp #, fuzzy @@ -2496,8 +2496,8 @@ msgstr "وصل شده" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "در ØØ§Ù„ درخواست.." +msgid "Requesting..." +msgstr "در ØØ§Ù„ درخواست..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2634,11 +2634,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "تغییر نام.." +msgid "Rename..." +msgstr "تغییر نام..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2651,16 +2651,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "انتخاب شده را به دو تا تکثیر Ú©Ù†" #: editor/filesystem_dock.cpp @@ -2686,7 +2686,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2752,7 +2752,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2764,7 +2764,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2780,7 +2780,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2800,7 +2800,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3217,7 +3217,7 @@ msgid "Transition Node" msgstr "گره جابجای" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3225,7 +3225,7 @@ msgid "Edit Node Filters" msgstr "ویرایش صاÙÛŒ های گره" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3295,7 +3295,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3363,8 +3363,8 @@ msgid "Site:" msgstr "تارنما:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "پشتیبانی.." +msgid "Support..." +msgstr "پشتیبانی..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3553,6 +3553,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3977,7 +3978,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4184,7 +4185,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4550,7 +4551,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4651,7 +4652,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4862,15 +4863,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5330,11 +5331,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5590,7 +5587,7 @@ msgid "Remove All" msgstr "برداشتن" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5659,7 +5656,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5853,7 +5850,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5947,6 +5944,11 @@ msgstr "پروژه واردشده" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "نام پروژه:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "ناتوان در ساختن پوشه." @@ -6138,8 +6140,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6167,7 +6169,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6352,7 +6354,7 @@ msgid "Property:" msgstr "ویژگی:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6448,11 +6450,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6628,7 +6630,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8183,15 +8185,19 @@ msgstr "خطای بارگذاری قلم." msgid "Invalid font size." msgstr "اندازهٔ قلم نامعتبر." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "زبانه قبلی" + +#~ msgid "Next" +#~ msgstr "بعدی" + #~ msgid "Can't contain '/' or ':'" #~ msgstr "نمی‌تواند شامل '/' یا ':' باشد" #~ msgid "Can't write file." #~ msgstr "ناتوان در نوشتن پرونده." -#~ msgid "Next" -#~ msgstr "بعدی" - #~ msgid "Not found!" #~ msgstr "چیزی ÛŒØ§ÙØª نشد!" diff --git a/editor/translations/fi.po b/editor/translations/fi.po index 139983464e..f80efffd42 100644 --- a/editor/translations/fi.po +++ b/editor/translations/fi.po @@ -2,25 +2,25 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # basse <basse@roiske.org>, 2017. -# Bastian Salmela <bastian.salmela@gmail.com>, 2017. +# Bastian Salmela <bastian.salmela@gmail.com>, 2017, 2018. # ekeimaja <ekeimaja@gmail.com>, 2017-2018. # Jarmo Riikonen <amatrelan@gmail.com>, 2017. +# Nuutti Varvikko <nvarvikko@gmail.com>, 2018. # Sami Lehtilä <sami.lehtila@gmail.com>, 2018. -# +# Tapani Niemi <tapani.niemi@kapsi.fi>, 2018. msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-01-31 04:36+0000\n" -"Last-Translator: Sami Lehtilä <sami.lehtila@gmail.com>\n" +"PO-Revision-Date: 2018-06-14 20:37+0000\n" +"Last-Translator: Tapani Niemi <tapani.niemi@kapsi.fi>\n" "Language-Team: Finnish <https://hosted.weblate.org/projects/godot-engine/" "godot/fi/>\n" "Language: fi\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.19-dev\n" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -31,26 +31,24 @@ msgid "All Selection" msgstr "Koko valinta" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Time" -msgstr "Animaatio: muuta arvoa" +msgstr "Animaatio: muuta avainruudun aikaa" #: editor/animation_editor.cpp msgid "Anim Change Transition" -msgstr "Vaihda animaation siirtymää" +msgstr "Animaatio: muuta siirtymää" #: editor/animation_editor.cpp msgid "Anim Change Transform" -msgstr "Animaatio: muuta siirtymää" +msgstr "Animaatio: muuta muunnosta" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Value" -msgstr "Animaatio: muuta arvoa" +msgstr "Animaatio: muuta avainruudun arvoa" #: editor/animation_editor.cpp msgid "Anim Change Call" -msgstr "Anmaatio: Muuta kutsua" +msgstr "Animaatio: muuta kutsua" #: editor/animation_editor.cpp msgid "Anim Add Track" @@ -70,7 +68,7 @@ msgstr "Siirrä animaatioraita alas" #: editor/animation_editor.cpp msgid "Remove Anim Track" -msgstr "Poista animaation raita" +msgstr "Poista animaatioraita" #: editor/animation_editor.cpp msgid "Set Transitions to:" @@ -78,24 +76,23 @@ msgstr "Aseta siirtymät:" #: editor/animation_editor.cpp msgid "Anim Track Rename" -msgstr "Nimeä animaatioraita uudelleen" +msgstr "Animaatioraita: nimeä uudelleen" #: editor/animation_editor.cpp msgid "Anim Track Change Interpolation" -msgstr "Animaatio: Vaihda raidan interpolaatiota" +msgstr "Animaatioraita: muuta interpolaatiota" #: editor/animation_editor.cpp msgid "Anim Track Change Value Mode" -msgstr "Animaatio: Muuta avainta tila" +msgstr "Animaatioraita: muuta arvon tilaa" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Track Change Wrap Mode" -msgstr "Animaatio: Muuta toisto tila" +msgstr "Animaatioraita: muuta kierron tilaa" #: editor/animation_editor.cpp msgid "Edit Node Curve" -msgstr "Muokkaa noden käyrää" +msgstr "Muokkaa solmun käyrää" #: editor/animation_editor.cpp msgid "Edit Selection Curve" @@ -103,16 +100,16 @@ msgstr "Muokkaa valinnan käyrää" #: editor/animation_editor.cpp msgid "Anim Delete Keys" -msgstr "Poista avaimet" +msgstr "Animaatio: poista avaimet" #: editor/animation_editor.cpp editor/plugins/tile_map_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Duplicate Selection" -msgstr "Monista valinta" +msgstr "Kahdenna valinta" #: editor/animation_editor.cpp msgid "Duplicate Transposed" -msgstr "Monista käänteisesti" +msgstr "Kahdenna käänteisesti" #: editor/animation_editor.cpp msgid "Remove Selection" @@ -132,11 +129,11 @@ msgstr "Liipaisin" #: editor/animation_editor.cpp msgid "Anim Add Key" -msgstr "Lisää avain" +msgstr "Animaatio: lisää avain" #: editor/animation_editor.cpp msgid "Anim Move Keys" -msgstr "SIirrä avaimia" +msgstr "Animaatio: siirrä avaimia" #: editor/animation_editor.cpp msgid "Scale Selection" @@ -193,7 +190,7 @@ msgstr "Siivoa animaatio" #: editor/animation_editor.cpp msgid "Create NEW track for %s and insert key?" -msgstr "Luo UUSI raita %lle ja lisää avain?" +msgstr "Luo kohteelle %s UUSI raita ja lisää avain?" #: editor/animation_editor.cpp msgid "Create %d NEW tracks and insert keys?" @@ -209,7 +206,7 @@ msgstr "Luo" #: editor/animation_editor.cpp msgid "Anim Create & Insert" -msgstr "Animaatio: Luo ja lisää" +msgstr "Animaatio: luo ja lisää" #: editor/animation_editor.cpp msgid "Anim Insert Track & Key" @@ -221,7 +218,7 @@ msgstr "Animaatio: Lisää avain" #: editor/animation_editor.cpp msgid "Change Anim Len" -msgstr "Vaihda animaation pituutta" +msgstr "Muuta animaation pituutta" #: editor/animation_editor.cpp msgid "Change Anim Loop" @@ -233,7 +230,7 @@ msgstr "Animaatio: Luo tyypitetty arvoavain" #: editor/animation_editor.cpp msgid "Anim Insert" -msgstr "Animaatio: Lisää" +msgstr "Animaatio: lisää" #: editor/animation_editor.cpp msgid "Anim Scale Keys" @@ -245,7 +242,7 @@ msgstr "Animaatio: Lisää kutsuraita" #: editor/animation_editor.cpp msgid "Animation zoom." -msgstr "Animaation zoom." +msgstr "Animaation lähennystaso." #: editor/animation_editor.cpp msgid "Length (s):" @@ -257,7 +254,7 @@ msgstr "Animaation pituus (sekunteina)." #: editor/animation_editor.cpp msgid "Step (s):" -msgstr "Askellus:" +msgstr "Askellus (s):" #: editor/animation_editor.cpp msgid "Cursor step snap (in seconds)." @@ -265,7 +262,7 @@ msgstr "Kohdistimen askelrajoitin (sekunneissa)." #: editor/animation_editor.cpp msgid "Enable/Disable looping in animation." -msgstr "Ota käyttöön/poista käytöstä animaation toisto." +msgstr "Ota käyttöön tai poista käytöstä animaation toisto." #: editor/animation_editor.cpp msgid "Add new tracks." @@ -289,7 +286,7 @@ msgstr "Raidan työkalut" #: editor/animation_editor.cpp msgid "Enable editing of individual keys by clicking them." -msgstr "Mahdollistaa avainten muokkaamisen klikkaamalla." +msgstr "Mahdollistaa avainten muokkaamisen napsauttamalla niitä." #: editor/animation_editor.cpp msgid "Anim. Optimizer" @@ -301,11 +298,11 @@ msgstr "Max. lineaarinen virhe:" #: editor/animation_editor.cpp msgid "Max. Angular Error:" -msgstr "Max. Kulmavirhe:" +msgstr "Max. kulmavirhe:" #: editor/animation_editor.cpp msgid "Max Optimizable Angle:" -msgstr "Max. Optimoitava kulma:" +msgstr "Max. optimoitava kulma:" #: editor/animation_editor.cpp msgid "Optimize" @@ -313,7 +310,7 @@ msgstr "Optimoi" #: editor/animation_editor.cpp msgid "Select an AnimationPlayer from the Scene Tree to edit animations." -msgstr "Valitse AnimationPlayer Scenepuusta muokataksesi animaatioita." +msgstr "Valitse AnimationPlayer skenen puusta muokataksesi animaatioita." #: editor/animation_editor.cpp msgid "Key" @@ -329,7 +326,7 @@ msgstr "Skaalaussuhde:" #: editor/animation_editor.cpp msgid "Call Functions in Which Node?" -msgstr "Mistä nodesta kutsutaan funktiota?" +msgstr "Mistä solmusta kutsutaan funktiota?" #: editor/animation_editor.cpp msgid "Remove invalid keys" @@ -345,7 +342,7 @@ msgstr "Siivoa kaikki animaatiot" #: editor/animation_editor.cpp msgid "Clean-Up Animation(s) (NO UNDO!)" -msgstr "Siivoa animaatio(t) (EI VOI KUMOTA)" +msgstr "Siivoa animaatio(t) (EI VOI KUMOTA!)" #: editor/animation_editor.cpp msgid "Clean-Up" @@ -369,7 +366,7 @@ msgstr "Mene riville" #: editor/code_editor.cpp msgid "Line Number:" -msgstr "RIvinumero:" +msgstr "Rivinumero:" #: editor/code_editor.cpp msgid "No Matches" @@ -409,7 +406,7 @@ msgstr "Loitonna" #: editor/code_editor.cpp msgid "Reset Zoom" -msgstr "Nollaa lähennys" +msgstr "Palauta oletuslähennystaso" #: editor/code_editor.cpp editor/script_editor_debugger.cpp msgid "Line:" @@ -417,23 +414,23 @@ msgstr "Rivi:" #: editor/code_editor.cpp msgid "Col:" -msgstr "Kolumni:" +msgstr "Sarake:" #: editor/connections_dialog.cpp msgid "Method in target Node must be specified!" -msgstr "Kohdenoden metodi täytyy määrittää!" +msgstr "Kohdesolmun metodi täytyy määrittää!" #: editor/connections_dialog.cpp msgid "" "Target method not found! Specify a valid method or attach a script to target " "Node." msgstr "" -"Kohde metodia ei löytynyt! Määrittele voimassa oleva metodi tai kiinnitä " -"skripti nodeen." +"Kohdemetodia ei löytynyt! Määrittele voimassa oleva metodi tai kiinnitä " +"skripti solmuun." #: editor/connections_dialog.cpp msgid "Connect To Node:" -msgstr "Yhdistä Nodeen:" +msgstr "Yhdistä solmuun:" #: editor/connections_dialog.cpp editor/editor_autoload_settings.cpp #: editor/groups_editor.cpp editor/plugins/item_list_editor_plugin.cpp @@ -458,7 +455,7 @@ msgstr "Ylimääräiset argumentit:" #: editor/connections_dialog.cpp msgid "Path to Node:" -msgstr "Polku Nodeen:" +msgstr "Polku solmuun:" #: editor/connections_dialog.cpp msgid "Make Function" @@ -492,7 +489,7 @@ msgstr "Yhdistä" #: editor/connections_dialog.cpp msgid "Connect '%s' to '%s'" -msgstr "Yhdistä '%s' '%s':n" +msgstr "Yhdistä solmu '%s' solmuun '%s'" #: editor/connections_dialog.cpp msgid "Connecting Signal:" @@ -500,10 +497,10 @@ msgstr "Yhdistävä signaali:" #: editor/connections_dialog.cpp msgid "Disconnect '%s' from '%s'" -msgstr "Katkaise yhteys '%s' '%s':n" +msgstr "Katkaise yhteys solmusta '%s' solmuun '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Yhdistä..." #: editor/connections_dialog.cpp @@ -569,7 +566,7 @@ msgid "" "Scene '%s' is currently being edited.\n" "Changes will not take effect unless reloaded." msgstr "" -"Sceneä '%s' muokataan parhaillaan.\n" +"Skeneä '%s' muokataan parhaillaan.\n" "Muutokset tulevat voimaan vasta päivityksen jälkeen." #: editor/dependency_editor.cpp @@ -619,9 +616,8 @@ msgid "Open" msgstr "Avaa" #: editor/dependency_editor.cpp -#, fuzzy msgid "Owners Of:" -msgstr "Omistajat:" +msgstr "Omistajat kohteelle:" #: editor/dependency_editor.cpp msgid "Remove selected files from the project? (no undo)" @@ -638,7 +634,6 @@ msgstr "" "Poistetaanko silti? (ei mahdollisuutta kumota)" #: editor/dependency_editor.cpp -#, fuzzy msgid "Cannot remove:" msgstr "Ei voida poistaa:" @@ -648,7 +643,7 @@ msgstr "Virhe ladatessa:" #: editor/dependency_editor.cpp msgid "Scene failed to load due to missing dependencies:" -msgstr "Scenen lataaminen epäonnistui puuttuvan riippuvuuden takia:" +msgstr "Skenen lataaminen epäonnistui puuttuvan riippuvuuden takia:" #: editor/dependency_editor.cpp editor/editor_node.cpp msgid "Open Anyway" @@ -667,9 +662,8 @@ msgid "Errors loading!" msgstr "Virheitä ladatessa!" #: editor/dependency_editor.cpp -#, fuzzy msgid "Permanently delete %d item(s)? (No undo!)" -msgstr "Poista pysyvästi %d ? (Ei voi kumota!)" +msgstr "Poista pysyvästi %d kohdetta? (Ei voi kumota!)" #: editor/dependency_editor.cpp msgid "Owns" @@ -680,9 +674,8 @@ msgid "Resources Without Explicit Ownership:" msgstr "Resurssit, joilla ei ole selvää omistajaa:" #: editor/dependency_editor.cpp editor/editor_node.cpp -#, fuzzy msgid "Orphan Resource Explorer" -msgstr "Orpojen resurssien selain" +msgstr "Irrallisten resurssien hallinta" #: editor/dependency_editor.cpp msgid "Delete selected files?" @@ -697,14 +690,12 @@ msgid "Delete" msgstr "Poista" #: editor/dictionary_property_edit.cpp -#, fuzzy msgid "Change Dictionary Key" -msgstr "Vaihda taulukon avainta" +msgstr "Vaihda hakurakenteen avainta" #: editor/dictionary_property_edit.cpp -#, fuzzy msgid "Change Dictionary Value" -msgstr "Vaihda taulukon arvoa" +msgstr "Vaihda hakurakenteen arvoa" #: editor/editor_about.cpp msgid "Thanks from the Godot community!" @@ -727,9 +718,8 @@ msgid "Lead Developer" msgstr "Pääkehittäjä" #: editor/editor_about.cpp -#, fuzzy msgid "Project Manager " -msgstr "Projektinhallinta" +msgstr "Projektipäällikkö " #: editor/editor_about.cpp msgid "Developers" @@ -741,27 +731,27 @@ msgstr "Tekijät" #: editor/editor_about.cpp msgid "Platinum Sponsors" -msgstr "Platinum sponsorit" +msgstr "Platinasponsorit" #: editor/editor_about.cpp msgid "Gold Sponsors" -msgstr "Kulta sponsorit" +msgstr "Kultasponsorit" #: editor/editor_about.cpp msgid "Mini Sponsors" -msgstr "Mini sponsorit" +msgstr "Minisponsorit" #: editor/editor_about.cpp msgid "Gold Donors" -msgstr "Kulta lahjoittajat" +msgstr "Kultalahjoittajat" #: editor/editor_about.cpp msgid "Silver Donors" -msgstr "Hopea lahjoittajat" +msgstr "Hopealahjoittajat" #: editor/editor_about.cpp msgid "Bronze Donors" -msgstr "Pronssi lahjoittajat" +msgstr "Pronssilahjoittajat" #: editor/editor_about.cpp msgid "Donors" @@ -784,8 +774,8 @@ msgid "" msgstr "" "Godot moottori käyttää useita kolmannen osapuolen ilmaisia ja avoimia " "kirjastoja, jotka kaikki ovat yhteensopivia sen MIT lisenssin kanssa. " -"Seuraava tyhjentävä listaus sisältää kaikki tälläiset kolmannen osapuolen " -"komponentit ja niiden vastaavat copyright ja lisenssi määritelmät." +"Seuraava tyhjentävä listaus sisältää kaikki tällaiset kolmannen osapuolen " +"komponentit ja niiden vastaavat tekijänoikeustiedot ja käyttöoikeusehdot." #: editor/editor_about.cpp msgid "All Components" @@ -801,7 +791,7 @@ msgstr "Lisenssit" #: editor/editor_asset_installer.cpp editor/project_manager.cpp msgid "Error opening package file, not in zip format." -msgstr "Virhe avattaessa pakettia, ei zip muotoinen." +msgstr "Virhe avattaessa pakettitiedostoa, ei zip-muodossa." #: editor/editor_asset_installer.cpp msgid "Uncompressing Assets" @@ -835,12 +825,11 @@ msgstr "Lisää efekti" #: editor/editor_audio_buses.cpp msgid "Rename Audio Bus" -msgstr "Nimeä väylä uudelleen" +msgstr "Nimeä ääniväylä uudelleen" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Change Audio Bus Volume" -msgstr "Ääniväylä sooloksi" +msgstr "Muuta ääniväylän voimakkuutta" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Solo" @@ -856,7 +845,7 @@ msgstr "Käytä ääniväylän efektejä" #: editor/editor_audio_buses.cpp msgid "Select Audio Bus Send" -msgstr "" +msgstr "Valitse ääniväylän lähtö" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus Effect" @@ -905,7 +894,7 @@ msgstr "Poista efekti" #: editor/editor_audio_buses.cpp msgid "Audio" -msgstr "" +msgstr "Äänet" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus" @@ -925,18 +914,18 @@ msgstr "Monista ääniväylä" #: editor/editor_audio_buses.cpp msgid "Reset Bus Volume" -msgstr "Palauta äänenvoimakkuus" +msgstr "Palauta väylän äänenvoimakkuus" #: editor/editor_audio_buses.cpp msgid "Move Audio Bus" msgstr "Siirrä ääniväylää" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Tallenna ääniväylän asettelu nimellä..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "Sijainti uudelle asettelulle..." #: editor/editor_audio_buses.cpp @@ -1020,18 +1009,16 @@ msgid "File does not exist." msgstr "Tiedostoa ei ole olemassa." #: editor/editor_autoload_settings.cpp -#, fuzzy msgid "Not in resource path." msgstr "Ei löytynyt resurssipolusta." #: editor/editor_autoload_settings.cpp -#, fuzzy msgid "Add AutoLoad" msgstr "Lisää automaattisesti ladattava" #: editor/editor_autoload_settings.cpp msgid "Autoload '%s' already exists!" -msgstr "Automaattisesti ladattava '%s' löytyi jo!" +msgstr "Automaattisesti ladattava '%s' on jo olemassa!" #: editor/editor_autoload_settings.cpp msgid "Rename Autoload" @@ -1039,10 +1026,9 @@ msgstr "Nimeä automaattisesti ladattava uudelleen" #: editor/editor_autoload_settings.cpp msgid "Toggle AutoLoad Globals" -msgstr "" +msgstr "Aseta globaalien automaattilataus" #: editor/editor_autoload_settings.cpp -#, fuzzy msgid "Move Autoload" msgstr "Siirrä automaattisesti ladattavaa" @@ -1065,7 +1051,7 @@ msgstr "Polku:" #: editor/editor_autoload_settings.cpp msgid "Node Name:" -msgstr "Noden nimi:" +msgstr "Solmun nimi:" #: editor/editor_autoload_settings.cpp editor/editor_profiler.cpp #: editor/project_manager.cpp editor/settings_config_dialog.cpp @@ -1073,30 +1059,28 @@ msgid "Name" msgstr "Nimi" #: editor/editor_autoload_settings.cpp -#, fuzzy msgid "Singleton" -msgstr "Ainokainen" +msgstr "Singleton" #: editor/editor_data.cpp msgid "Updating Scene" msgstr "Päivitetään skeneä" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Varastoidaan paikalliset muutokset..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "Päivitetään skeneä..." #: editor/editor_data.cpp -#, fuzzy msgid "[empty]" -msgstr "(tyhjä)" +msgstr "[tyhjä]" #: editor/editor_data.cpp msgid "[unsaved]" -msgstr "[ei tallennettu]" +msgstr "[tallentamaton]" #: editor/editor_dir_dialog.cpp msgid "Please select a base directory first" @@ -1136,18 +1120,16 @@ msgid "Packing" msgstr "Pakataan" #: editor/editor_export.cpp platform/javascript/export/export.cpp -#, fuzzy msgid "Template file not found:" -msgstr "Mallitiedostoa ei löytynyt:\n" +msgstr "Mallitiedostoa ei löytynyt:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "File Exists, Overwrite?" msgstr "Tiedosto on jo olemassa, korvaa?" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp -#, fuzzy msgid "Select Current Folder" -msgstr "Luo kansio" +msgstr "Valitse nykyinen kansio" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp msgid "Copy Path" @@ -1158,8 +1140,8 @@ msgid "Show In File Manager" msgstr "Näytä tiedostonhallinnassa" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Luo kansio..." +msgid "New Folder..." +msgstr "Uusi kansio..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1216,19 +1198,16 @@ msgid "Toggle Hidden Files" msgstr "Näytä piilotiedostot" #: editor/editor_file_dialog.cpp -#, fuzzy msgid "Toggle Favorite" -msgstr "Näytä suosikit" +msgstr "Aseta suosikiksi" #: editor/editor_file_dialog.cpp -#, fuzzy msgid "Toggle Mode" -msgstr "Näytä/piilota" +msgstr "Aseta tila" #: editor/editor_file_dialog.cpp -#, fuzzy msgid "Focus Path" -msgstr "Kohdista polku" +msgstr "Kohdista polkuun" #: editor/editor_file_dialog.cpp msgid "Move Favorite Up" @@ -1239,13 +1218,12 @@ msgid "Move Favorite Down" msgstr "Siirrä suosikkia alas" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp -#, fuzzy msgid "Go to parent folder" -msgstr "Kansiota ei voitu luoda." +msgstr "Siirry yläkansioon" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Directories & Files:" -msgstr "Hakemistot & tiedostot:" +msgstr "Hakemistot ja tiedostot:" #: editor/editor_file_dialog.cpp msgid "Preview:" @@ -1257,23 +1235,21 @@ msgid "File:" msgstr "Tiedosto:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp -#, fuzzy msgid "Must use a valid extension." -msgstr "Käytä sopivaa laajennusta" +msgstr "Käytä sopivaa tiedostopäätettä." #: editor/editor_file_system.cpp msgid "ScanSources" -msgstr "" +msgstr "Selaa lähdetiedostoja" #: editor/editor_file_system.cpp msgid "(Re)Importing Assets" -msgstr "Tuodaan (uudelleen) Assetteja" +msgstr "Tuodaan (uudelleen) assetteja" #: editor/editor_help.cpp editor/editor_node.cpp #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Search Help" -msgstr "Hae oppaasta" +msgstr "Etsi ohjeesta" #: editor/editor_help.cpp msgid "Class List:" @@ -1285,7 +1261,7 @@ msgstr "Etsi luokkia" #: editor/editor_help.cpp editor/plugins/spatial_editor_plugin.cpp msgid "Top" -msgstr "Pinta" +msgstr "Yläpuoli" #: editor/editor_help.cpp editor/property_editor.cpp msgid "Class:" @@ -1296,27 +1272,24 @@ msgid "Inherits:" msgstr "Perii:" #: editor/editor_help.cpp -#, fuzzy msgid "Inherited by:" -msgstr "Peritty:" +msgstr "Perivät:" #: editor/editor_help.cpp msgid "Brief Description:" msgstr "Lyhyt kuvaus:" #: editor/editor_help.cpp -#, fuzzy msgid "Members" -msgstr "Jäsenet:" +msgstr "Jäsenet" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Members:" msgstr "Jäsenet:" #: editor/editor_help.cpp -#, fuzzy msgid "Public Methods" -msgstr "Julkiset metodit:" +msgstr "Julkiset metodit" #: editor/editor_help.cpp msgid "Public Methods:" @@ -1324,66 +1297,59 @@ msgstr "Julkiset metodit:" #: editor/editor_help.cpp msgid "GUI Theme Items" -msgstr "GUI teeman osat" +msgstr "Käyttöliittymäteeman osat" #: editor/editor_help.cpp msgid "GUI Theme Items:" -msgstr "GUI teeman osat:" +msgstr "Käyttöliittymäteeman osat:" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Signals:" msgstr "Signaalit:" #: editor/editor_help.cpp -#, fuzzy msgid "Enumerations" -msgstr "Animaatiot" +msgstr "Enumeraatiot" #: editor/editor_help.cpp -#, fuzzy msgid "Enumerations:" -msgstr "Animaatiot" +msgstr "Enumeraatiot:" #: editor/editor_help.cpp msgid "enum " -msgstr "enum" +msgstr "enum " #: editor/editor_help.cpp -#, fuzzy msgid "Constants" -msgstr "Vakiot:" +msgstr "Vakiot" #: editor/editor_help.cpp msgid "Constants:" msgstr "Vakiot:" #: editor/editor_help.cpp -#, fuzzy msgid "Description" -msgstr "Kuvaus:" +msgstr "Kuvaus" #: editor/editor_help.cpp -#, fuzzy msgid "Online Tutorials:" -msgstr "Oppaat" +msgstr "Online-oppaat:" #: editor/editor_help.cpp -#, fuzzy msgid "" "There are currently no tutorials for this class, you can [color=$color][url=" "$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" "url][/color]." msgstr "" -"Tälle metodille ei vielä löydy kuvailua. Voit auttaa meitä [color=$color]" -"[url=$url]kirjoittamalla sellaisen[/url][/color]!" +"Tälle luokalle ei vielä löydy kuvausta. Voit [color=$color][url=$url]auttaa " +"luomalla sellaisen[/url][/color] tai [color=$color][url=$url2]pyytää " +"sellaisen[/url][/color]." #: editor/editor_help.cpp -#, fuzzy msgid "Properties" -msgstr "Ominaisuudet:" +msgstr "Ominaisuudet" #: editor/editor_help.cpp -#, fuzzy msgid "Property Description:" msgstr "Ominaisuuden kuvaus:" @@ -1392,13 +1358,12 @@ msgid "" "There is currently no description for this property. Please help us by " "[color=$color][url=$url]contributing one[/url][/color]!" msgstr "" -"Tälle ei vielä löydy kuvailua. Voit auttaa meitä [color=$color][url=" -"$url]kirjoittamalla sellaisen[/url][/color]!" +"Tälle ominaisuudelle ei vielä löydy kuvausta. Voit auttaa meitä [color=" +"$color][url=$url]kirjoittamalla sellaisen[/url][/color]!" #: editor/editor_help.cpp -#, fuzzy msgid "Methods" -msgstr "Metodilista:" +msgstr "Metodit" #: editor/editor_help.cpp msgid "Method Description:" @@ -1409,7 +1374,7 @@ msgid "" "There is currently no description for this method. Please help us by [color=" "$color][url=$url]contributing one[/url][/color]!" msgstr "" -"Tälle metodille ei vielä löydy kuvailua. Voit auttaa meitä [color=$color]" +"Tälle metodille ei vielä löydy kuvausta. Voit auttaa meitä [color=$color]" "[url=$url]kirjoittamalla sellaisen[/url][/color]!" #: editor/editor_help.cpp @@ -1421,9 +1386,8 @@ msgid "Find" msgstr "Etsi" #: editor/editor_log.cpp -#, fuzzy msgid "Output:" -msgstr " Tuloste:" +msgstr "Tuloste:" #: editor/editor_log.cpp editor/plugins/animation_tree_editor_plugin.cpp #: editor/property_editor.cpp editor/script_editor_debugger.cpp @@ -1433,25 +1397,24 @@ msgid "Clear" msgstr "Tyhjennä" #: editor/editor_log.cpp -#, fuzzy msgid "Clear Output" -msgstr "Tuloste" +msgstr "Tyhjennä tuloste" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Projektin vienti epäonnistui virhekoodilla %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Virhe tallennettaessa resurssia!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Tallenna resurssi nimellä..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Ymmärrän..." #: editor/editor_node.cpp @@ -1467,32 +1430,28 @@ msgid "Error while saving." msgstr "Virhe tallennettaessa." #: editor/editor_node.cpp -#, fuzzy msgid "Can't open '%s'." -msgstr "Yhdistä..." +msgstr "Ei voida avata tiedostoa '%s'." #: editor/editor_node.cpp -#, fuzzy msgid "Error while parsing '%s'." -msgstr "Virhe tallennettaessa." +msgstr "Virhe jäsennettäessä tiedostoa '%s'." #: editor/editor_node.cpp msgid "Unexpected end of file '%s'." msgstr "Odottamaton loppu tiedostossa '%s'." #: editor/editor_node.cpp -#, fuzzy msgid "Missing '%s' or its dependencies." -msgstr "Scenellä '%s' on rikkinäisiä riippuvuuksia:" +msgstr "Tiedosto '%s' tai jokin sen riippuvuuksista puuttuu." #: editor/editor_node.cpp -#, fuzzy msgid "Error while loading '%s'." -msgstr "Virhe tallennettaessa." +msgstr "Virhe ladattaessa tiedostoa '%s'." #: editor/editor_node.cpp msgid "Saving Scene" -msgstr "Tallennetaan sceneä" +msgstr "Tallennetaan skeneä" #: editor/editor_node.cpp msgid "Analyzing" @@ -1503,16 +1462,16 @@ msgid "Creating Thumbnail" msgstr "Luodaan pienoiskuvaa" #: editor/editor_node.cpp -#, fuzzy msgid "This operation can't be done without a tree root." -msgstr "Tätä toimintoa ei voi tehdä ilman Sceneä." +msgstr "Tätä toimintoa ei voi tehdä ilman että puun juuri on olemassa." #: editor/editor_node.cpp -#, fuzzy msgid "" "Couldn't save scene. Likely dependencies (instances or inheritance) couldn't " "be satisfied." -msgstr "Sceneä ei voitu tallentaa. Riippuvuuksia ei voitu tyydyttää." +msgstr "" +"Skeneä ei voitu tallentaa. Mahdollisia riippuvuuksia (ilmentymiä tai " +"perintää) ei voida toteuttaa." #: editor/editor_node.cpp msgid "Failed to load resource." @@ -1520,19 +1479,19 @@ msgstr "Resurssin lataaminen epäonnistui." #: editor/editor_node.cpp msgid "Can't load MeshLibrary for merging!" -msgstr "MalliKirjastojen yhdistäminen ei onnistunut!" +msgstr "Ei voitu ladata MeshLibrary resurssia yhdistämistä varten!" #: editor/editor_node.cpp msgid "Error saving MeshLibrary!" -msgstr "Virhe tallennettaessa MeshLibrarya!" +msgstr "Virhe tallennettaessa MeshLibrary resurssia!" #: editor/editor_node.cpp msgid "Can't load TileSet for merging!" -msgstr "Ei voida ladata tilesetiä tuontia varten!" +msgstr "Ei voida ladata ruutuvalikoimaa yhdistämistä varten!" #: editor/editor_node.cpp msgid "Error saving TileSet!" -msgstr "Virhe tallennettaessa tilesetiä!" +msgstr "Virhe tallennettaessa ruutuvalikoimaa!" #: editor/editor_node.cpp msgid "Error trying to save layout!" @@ -1540,16 +1499,15 @@ msgstr "Virhe tallennettaessa asettelua!" #: editor/editor_node.cpp msgid "Default editor layout overridden." -msgstr "Editorin oletusulkoasu ylikirjoitettu." +msgstr "Editorin oletusasettelu ylikirjoitettu." #: editor/editor_node.cpp msgid "Layout name not found!" -msgstr "Layoutin nimeä ei löytynyt!" +msgstr "Asettelun nimeä ei löytynyt!" #: editor/editor_node.cpp -#, fuzzy msgid "Restored default layout to base settings." -msgstr "Palautettiin oletusasettelu alkuperäiseen muotoonsa." +msgstr "Palautettiin oletusasettelu alkuperäisiin asetuksiinsa." #: editor/editor_node.cpp msgid "" @@ -1558,16 +1516,16 @@ msgid "" "understand this workflow." msgstr "" "Tämä resurssi kuuluu tuotuun skeneen, joten sitä ei voi suoraan muokata.\n" -"Lue ohjeet skenejen tuomisesta, jotta ymmärrät paremmin tämän työkulun." +"Lue ohjeet skenejen tuomisesta, jotta ymmärrät paremmin tämän työnkulun." #: editor/editor_node.cpp msgid "" "This resource belongs to a scene that was instanced or inherited.\n" "Changes to it will not be kept when saving the current scene." msgstr "" -"Tämä resurssi kuuluu skeneen josta on luotu instanssi, tai joka on " +"Tämä resurssi kuuluu skeneen, josta on luotu ilmentymä, tai joka on " "periytyvä.\n" -"Muutokset tähän eivät ole pysyviä, kun tallennat nykyisen skenen." +"Muutokset siihen eivät ole pysyviä, kun tallennat nykyisen skenen." #: editor/editor_node.cpp msgid "" @@ -1585,9 +1543,10 @@ msgid "" "understand this workflow." msgstr "" "Tämä skene on tuotu, joten siihen tehtyjä muutoksia ei säilytetä.\n" -"Instanssin, tai periytyvän skenen luominen mahdollistaa tämän.\n" -"Ole hyvä ja lue tarkemmat ohjeet skenejen tuomisesta jotta ymmärrät paremmin " -"tämän työnkulun." +"Ilmentymän tai periytyvän skenen luominen siitä mahdollistaa muutoksien " +"tekemisen siihen.\n" +"Ole hyvä ja lue dokumentaatiosta tarkemmat ohjeet skenejen tuomisesta, jotta " +"ymmärrät paremmin tämän työnkulun." #: editor/editor_node.cpp msgid "" @@ -1599,14 +1558,12 @@ msgstr "" "Ole hyvä ja lue ohjeet testaamisesta ymmärtääksesi paremmin tämän työnkulun." #: editor/editor_node.cpp -#, fuzzy msgid "Expand all properties" -msgstr "Laajenna kaikki" +msgstr "Laajenna kaikki ominaisuudet" #: editor/editor_node.cpp -#, fuzzy msgid "Collapse all properties" -msgstr "Pienennä kaikki" +msgstr "Tiivistä kaikki ominaisuudet" #: editor/editor_node.cpp msgid "Copy Params" @@ -1625,14 +1582,12 @@ msgid "Copy Resource" msgstr "Kopioi resurssi" #: editor/editor_node.cpp -#, fuzzy msgid "Make Built-In" msgstr "Tee sisäänrakennettu" #: editor/editor_node.cpp -#, fuzzy msgid "Make Sub-Resources Unique" -msgstr "Tee ali-resursseista yksilöllisiä." +msgstr "Tee aliresursseista yksilöllisiä" #: editor/editor_node.cpp msgid "Open in Help" @@ -1640,17 +1595,16 @@ msgstr "Avaa ohjeessa" #: editor/editor_node.cpp msgid "There is no defined scene to run." -msgstr "Suoritettavaa sceneä ei ole määritetty." +msgstr "Suoritettavaa skeneä ei ole määritetty." #: editor/editor_node.cpp -#, fuzzy msgid "" "No main scene has ever been defined, select one?\n" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" -"Pääsceneä ei ole määritetty, haluatko valita sen?\n" -"Voit muuttaa sitä myöhemmin projektin asetuksista." +"Pääskeneä ei ole määritetty, haluatko valita sen?\n" +"Voit muuttaa sen myöhemmin projektin asetuksista, kohdasta 'Application'." #: editor/editor_node.cpp msgid "" @@ -1658,8 +1612,8 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" -"Valittua sceneä '%s' ei ole olemassa, valitse kelvollinen?\n" -"Voit muuttaa sitä myöhemmin projektin asetuksista." +"Valittua skeneä '%s' ei ole olemassa, valitse kelvollinen?\n" +"Voit muuttaa sitä myöhemmin projektin asetuksista, kohdasta 'Application'." #: editor/editor_node.cpp msgid "" @@ -1667,13 +1621,13 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" -"Valittu scene '%s' ei ole scene-tiedosto, valitse kelvollinen?\n" -"Voit muuttaa sitä myöhemmin projektin asetuksista." +"Valittu skene '%s' ei ole scene-tiedosto, valitse kelvollinen?\n" +"Voit muuttaa sitä myöhemmin projektin asetuksista, kohdasta 'Application'." #: editor/editor_node.cpp msgid "Current scene was never saved, please save it prior to running." msgstr "" -"Nykyistä sceneä ei ole vielä tallennettu. Tallenna se ennen suorittamista." +"Nykyistä skeneä ei ole vielä tallennettu. Tallenna se ennen suorittamista." #: editor/editor_node.cpp msgid "Could not start subprocess!" @@ -1681,32 +1635,31 @@ msgstr "Aliprosessia ei voitu käynnistää!" #: editor/editor_node.cpp msgid "Open Scene" -msgstr "Avaa scene" +msgstr "Avaa skene" #: editor/editor_node.cpp msgid "Open Base Scene" -msgstr "Avaa kantascene" +msgstr "Avaa kantaskene" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Nopea skenen avaus..." +msgid "Quick Open Scene..." +msgstr "Skenen pika-avaus..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Nopea skriptin avaus..." +msgid "Quick Open Script..." +msgstr "Skriptin pika-avaus..." #: editor/editor_node.cpp msgid "Save & Close" msgstr "Tallenna ja sulje" #: editor/editor_node.cpp -#, fuzzy msgid "Save changes to '%s' before closing?" -msgstr "Tallennetaanko muutokset '%s' ennen sulkemista?" +msgstr "Tallennetaanko muutokset tiedostoon '%s' ennen sulkemista?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Tallenna scene nimellä..." +msgid "Save Scene As..." +msgstr "Tallenna skene nimellä..." #: editor/editor_node.cpp msgid "No" @@ -1718,35 +1671,35 @@ msgstr "Kyllä" #: editor/editor_node.cpp msgid "This scene has never been saved. Save before running?" -msgstr "Tätä sceneä ei ole koskaan tallennettu. Tallenna ennen suorittamista?" +msgstr "Tätä skeneä ei ole koskaan tallennettu. Tallenna ennen suorittamista?" #: editor/editor_node.cpp editor/scene_tree_dock.cpp msgid "This operation can't be done without a scene." -msgstr "Tätä toimintoa ei voi tehdä ilman sceneä." +msgstr "Tätä toimintoa ei voi tehdä ilman skeneä." #: editor/editor_node.cpp msgid "Export Mesh Library" -msgstr "Vie malli kirjasto" +msgstr "Vie mesh-kirjasto" #: editor/editor_node.cpp msgid "This operation can't be done without a root node." -msgstr "Tätä toimintoa ei voida suorittaa ilman päänodea." +msgstr "Tätä toimintoa ei voida suorittaa ilman juurisolmua." #: editor/editor_node.cpp msgid "Export Tile Set" -msgstr "Vie tileset" +msgstr "Vie ruutuvalikoima" #: editor/editor_node.cpp msgid "This operation can't be done without a selected node." -msgstr "Tätä toimintoa ei voi tehdä ilman valittua nodea." +msgstr "Tätä toimintoa ei voi tehdä ilman valittua solmua." #: editor/editor_node.cpp msgid "Current scene not saved. Open anyway?" -msgstr "Nykyistä sceneä ei ole tallennettu. Avaa joka tapauksessa?" +msgstr "Nykyistä skeneä ei ole tallennettu. Avaa joka tapauksessa?" #: editor/editor_node.cpp msgid "Can't reload a scene that was never saved." -msgstr "Ei voida uudelleen ladata skeneä jota ei ole vielä tallennettu." +msgstr "Ei voida ladata uudelleen skeneä, jota ei ole koskaan tallennettu." #: editor/editor_node.cpp msgid "Revert" @@ -1757,8 +1710,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Tätä toimintoa ei voida peruttaa. Palauta joka tapauksessa?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Nopea skenen käynnistys..." +msgid "Quick Run Scene..." +msgstr "Skenen pikakäynnistys..." #: editor/editor_node.cpp msgid "Quit" @@ -1770,7 +1723,7 @@ msgstr "Poistu editorista?" #: editor/editor_node.cpp msgid "Open Project Manager?" -msgstr "Projektienhallinta" +msgstr "Avataanko projektinhallinta?" #: editor/editor_node.cpp msgid "Save & Quit" @@ -1796,7 +1749,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Pick a Main Scene" -msgstr "Valitse pääscene" +msgstr "Valitse pääskene" #: editor/editor_node.cpp msgid "Unable to enable addon plugin at: '%s' parsing of config failed." @@ -1804,7 +1757,7 @@ msgstr "Lisäosan '%s' aktivointi epäonnistui, virheellinen asetustiedosto." #: editor/editor_node.cpp msgid "Unable to find script field for addon plugin at: 'res://addons/%s'." -msgstr "" +msgstr "Skriptikenttää ei löytynyt lisäosan tiedostosta: 'res://addons/%s'." #: editor/editor_node.cpp msgid "Unable to load addon script from path: '%s'." @@ -1825,8 +1778,8 @@ msgid "" "Scene '%s' was automatically imported, so it can't be modified.\n" "To make changes to it, a new inherited scene can be created." msgstr "" -"Scene '%s' tuotiin automaattisesti, joten sitä ei voida muokata.\n" -"Muokataksesi sitä voit luoda uuden perityn Scenen." +"Skene '%s' tuotiin automaattisesti, joten sitä ei voida muokata.\n" +"Muokataksesi sitä voit luoda uuden perityn skenen." #: editor/editor_node.cpp editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp @@ -1838,20 +1791,20 @@ msgid "" "Error loading scene, it must be inside the project path. Use 'Import' to " "open the scene, then save it inside the project path." msgstr "" -"Virhe Scenen latauksessa, sen täytyy sijaita projektin polussa. Käytä 'Tuo' -" -"toimintoa avataksesi Scenen ja tallenna se projektin polkuun." +"Virhe skenen latauksessa, sen täytyy sijaita projektin polussa. Käytä 'Tuo'-" +"toimintoa avataksesi skenen ja tallenna se projektin polkuun." #: editor/editor_node.cpp msgid "Scene '%s' has broken dependencies:" -msgstr "Scenellä '%s' on rikkinäisiä riippuvuuksia:" +msgstr "Skenellä '%s' on rikkinäisiä riippuvuuksia:" #: editor/editor_node.cpp msgid "Clear Recent Scenes" -msgstr "Tyhjennä viimeiset scenet" +msgstr "Tyhjennä viimeisimmät skenet" #: editor/editor_node.cpp msgid "Save Layout" -msgstr "Tallenna asettelut" +msgstr "Tallenna asettelu" #: editor/editor_node.cpp msgid "Delete Layout" @@ -1864,7 +1817,7 @@ msgstr "Oletus" #: editor/editor_node.cpp msgid "Switch Scene Tab" -msgstr "Vaihda Scenen välilehteä" +msgstr "Vaihda skenen välilehteä" #: editor/editor_node.cpp msgid "%d more files or folders" @@ -1911,23 +1864,23 @@ msgid "Previous tab" msgstr "Edellinen välilehti" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Suodata tiedostot..." #: editor/editor_node.cpp msgid "Operations with scene files." -msgstr "Toiminnot skene tiedostoille." +msgstr "Toiminnot skenetiedostoille." #: editor/editor_node.cpp msgid "New Scene" msgstr "Uusi skene" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Uusi peritty skene..." #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "Avaa skene..." #: editor/editor_node.cpp @@ -1947,16 +1900,16 @@ msgid "Open Recent" msgstr "Avaa viimeaikainen" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "Muunna..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MalliKirjasto..." +msgid "MeshLibrary..." +msgstr "Mesh-kirjastoksi..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "" +msgid "TileSet..." +msgstr "Ruutuvalikoimaksi..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1977,9 +1930,8 @@ msgid "Miscellaneous project or scene-wide tools." msgstr "Sekalaiset projekti- tai skenetyökalut." #: editor/editor_node.cpp -#, fuzzy msgid "Project" -msgstr "Uusi projekti" +msgstr "Projekti" #: editor/editor_node.cpp msgid "Project Settings" @@ -2003,23 +1955,23 @@ msgstr "Lopeta ja palaa projektiluetteloon" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Debug" -msgstr "Testaa" +msgstr "Virheenkorjaus" #: editor/editor_node.cpp msgid "Deploy with Remote Debug" -msgstr "Julkaise etätestauksen kasnsa" +msgstr "Julkaise etätestauksen kanssa" #: editor/editor_node.cpp msgid "" "When exporting or deploying, the resulting executable will attempt to " "connect to the IP of this computer in order to be debugged." msgstr "" -"Vietäessä tai julkaistaessa, käynnistystiedosto yrittää ottaa yhteyden tämän " -"tietokoneen IP osoitteeseen testaamista varten." +"Vietäessä tai julkaistaessa, käynnistettävä ohjelma yrittää ottaa yhteyden " +"tämän tietokoneen IP-osoitteeseen testaamista varten." #: editor/editor_node.cpp msgid "Small Deploy with Network FS" -msgstr "" +msgstr "Kevyt käyttöönotto verkkolevyn avulla" #: editor/editor_node.cpp msgid "" @@ -2038,16 +1990,15 @@ msgstr "" "kohdalla." #: editor/editor_node.cpp -#, fuzzy msgid "Visible Collision Shapes" -msgstr "Näytä osuma-alueet" +msgstr "Näytä törmäysmuodot" #: editor/editor_node.cpp msgid "" "Collision shapes and raycast nodes (for 2D and 3D) will be visible on the " "running game if this option is turned on." msgstr "" -"Osuma-alueet ja raycast nodet (2D ja 3D) ovat näkyvillä peliä ajettaessa " +"Törmäysmuodot ja raycast-solmut (2D ja 3D) ovat näkyvillä peliä ajettaessa " "tämän ollessa valittuna." #: editor/editor_node.cpp @@ -2059,8 +2010,8 @@ msgid "" "Navigation meshes and polygons will be visible on the running game if this " "option is turned on." msgstr "" -"Navigaatiomuodot ja -polygonit ovat näkyvillä peliä ajettaessa tämän ollessa " -"valittuna." +"Navigointiverkot ja niiden polygonit ovat näkyvillä peliä ajettaessa tämän " +"ollessa valittuna." #: editor/editor_node.cpp msgid "Sync Scene Changes" @@ -2075,15 +2026,13 @@ msgid "" msgstr "" "Tämän ollessa valittuna, kaikki skeneen tehdyt muutokset toteutetaan myös " "käynnissä olevassa pelissä.\n" -"Tämä on tehokkainta verkkotiedostojärjestelmän kanssa mikäli käytössä on " -"etälaite." +"Mikäli peliä ajetaan etälaitteella, on tehokkaampaa käyttää verkkolevyä." #: editor/editor_node.cpp msgid "Sync Script Changes" msgstr "Synkronoi skriptin muutokset" #: editor/editor_node.cpp -#, fuzzy msgid "" "When this option is turned on, any script that is saved will be reloaded on " "the running game.\n" @@ -2092,13 +2041,11 @@ msgid "" msgstr "" "Jos tämä on valittu, kaikki tallennetut skriptit ladataan uudelleen pelin " "käynnistyessä.\n" -"Mikäli peli ajetaan etälaitteella, on tehokkaampaa käyttää " -"verkkotiedostojärjestelmää ." +"Mikäli peliä ajetaan etälaitteella, on tehokkaampaa käyttää verkkolevyä." #: editor/editor_node.cpp -#, fuzzy msgid "Editor" -msgstr "Muokkaa" +msgstr "Editori" #: editor/editor_node.cpp editor/settings_config_dialog.cpp msgid "Editor Settings" @@ -2114,7 +2061,7 @@ msgstr "Siirry koko näytön tilaan" #: editor/editor_node.cpp editor/project_export.cpp msgid "Manage Export Templates" -msgstr "Hallitse vietäviä Templateja" +msgstr "Hallinnoi vientimalleja" #: editor/editor_node.cpp msgid "Help" @@ -2136,9 +2083,8 @@ msgid "Online Docs" msgstr "Dokumentaatio" #: editor/editor_node.cpp -#, fuzzy msgid "Q&A" -msgstr "Kysy&Vastaa" +msgstr "Kysymykset ja vastaukset" #: editor/editor_node.cpp msgid "Issue Tracker" @@ -2154,24 +2100,23 @@ msgstr "Tietoja" #: editor/editor_node.cpp msgid "Play the project." -msgstr "Käynnistä projekti" +msgstr "Käynnistä projekti." #: editor/editor_node.cpp -#, fuzzy msgid "Play" -msgstr "Toista" +msgstr "Pelaa" #: editor/editor_node.cpp msgid "Pause the scene" -msgstr "Pysäytä Scene" +msgstr "Keskeytä skenen suorittaminen hetkellisesti" #: editor/editor_node.cpp msgid "Pause Scene" -msgstr "Pysäytä Scene" +msgstr "Keskeytä skene" #: editor/editor_node.cpp msgid "Stop the scene." -msgstr "Lopeta Scene." +msgstr "Lopeta skenen suorittaminen." #: editor/editor_node.cpp msgid "Stop" @@ -2179,11 +2124,11 @@ msgstr "Pysäytä" #: editor/editor_node.cpp msgid "Play the edited scene." -msgstr "Käynnistä muokattu skene." +msgstr "Käynnistä muokattavana oleva skene." #: editor/editor_node.cpp msgid "Play Scene" -msgstr "Toista Scene" +msgstr "Toista skene" #: editor/editor_node.cpp msgid "Play custom scene" @@ -2211,7 +2156,7 @@ msgstr "Poista päivitysanimaatio" #: editor/editor_node.cpp msgid "Inspector" -msgstr "Tarkastaja" +msgstr "Tarkastelu" #: editor/editor_node.cpp msgid "Create a new resource in memory and edit it." @@ -2226,7 +2171,7 @@ msgid "Save the currently edited resource." msgstr "Tallenna tällä hetkellä muokattu resurssi." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Tallenna nimellä..." #: editor/editor_node.cpp @@ -2256,7 +2201,7 @@ msgstr "Tuo" #: editor/editor_node.cpp msgid "Node" -msgstr "Node" +msgstr "Solmu" #: editor/editor_node.cpp msgid "FileSystem" @@ -2292,16 +2237,15 @@ msgstr "Salasana:" #: editor/editor_node.cpp msgid "Open & Run a Script" -msgstr "Avaa & suorita skripti" +msgstr "Avaa ja suorita skripti" #: editor/editor_node.cpp -#, fuzzy msgid "New Inherited" -msgstr "Uusi peritty Scene..." +msgstr "Uusi peritty skene" #: editor/editor_node.cpp msgid "Load Errors" -msgstr "Lataa virheet" +msgstr "Latausvirheet" #: editor/editor_node.cpp editor/plugins/tile_map_editor_plugin.cpp msgid "Select" @@ -2321,7 +2265,7 @@ msgstr "Avaa skriptieditori" #: editor/editor_node.cpp editor/project_manager.cpp msgid "Open Asset Library" -msgstr "Avaa Asset-kirjasto" +msgstr "Avaa asset-kirjasto" #: editor/editor_node.cpp msgid "Open the next Editor" @@ -2333,10 +2277,10 @@ msgstr "Avaa edellinen editori" #: editor/editor_plugin.cpp msgid "Creating Mesh Previews" -msgstr "Luodaan mallien esikatseluita" +msgstr "Luodaan meshien esikatseluita" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "Pienoiskuva..." #: editor/editor_plugin_settings.cpp @@ -2369,13 +2313,12 @@ msgid "Start Profiling" msgstr "Aloita profilointi" #: editor/editor_profiler.cpp -#, fuzzy msgid "Measure:" -msgstr "Mittayksikkö:" +msgstr "Mittaa:" #: editor/editor_profiler.cpp msgid "Frame Time (sec)" -msgstr "Framen aika (sek)" +msgstr "Kuvaruudun aika (sek)" #: editor/editor_profiler.cpp msgid "Average Time (sec)" @@ -2383,12 +2326,11 @@ msgstr "Keskimääräinen aika (sek)" #: editor/editor_profiler.cpp msgid "Frame %" -msgstr "Frame %" +msgstr "Kuvaruutujen %" #: editor/editor_profiler.cpp -#, fuzzy msgid "Physics Frame %" -msgstr "Kiinteä Frame %" +msgstr "Fysiikkaruutujen %" #: editor/editor_profiler.cpp editor/script_editor_debugger.cpp msgid "Time:" @@ -2396,7 +2338,7 @@ msgstr "Aika:" #: editor/editor_profiler.cpp msgid "Inclusive" -msgstr "" +msgstr "Sisältävä" #: editor/editor_profiler.cpp msgid "Self" @@ -2407,14 +2349,12 @@ msgid "Frame #:" msgstr "Ruutu #:" #: editor/editor_profiler.cpp -#, fuzzy msgid "Time" -msgstr "Aika:" +msgstr "Aika" #: editor/editor_profiler.cpp -#, fuzzy msgid "Calls" -msgstr "Kutsu" +msgstr "Kutsuja" #: editor/editor_run_native.cpp msgid "Select device from the list" @@ -2426,15 +2366,15 @@ msgid "" "Please add a runnable preset in the export menu." msgstr "" "Käynnistettävää vientipohjaa ei löytynyt tälle alustalle.\n" -"Lisää sellainen vienti-valikosta." +"Lisää sellainen vientivalikosta." #: editor/editor_run_script.cpp msgid "Write your logic in the _run() method." -msgstr "Kirjoita logiikka _run() -metodiin." +msgstr "Kirjoita logiikka _run() metodiin." #: editor/editor_run_script.cpp msgid "There is an edited scene already." -msgstr "Muokattu Scene on jo olemassa." +msgstr "Muokattu skene on jo olemassa." #: editor/editor_run_script.cpp msgid "Couldn't instance script:" @@ -2442,7 +2382,7 @@ msgstr "Ei voitu luoda instanssia skriptistä:" #: editor/editor_run_script.cpp msgid "Did you forget the 'tool' keyword?" -msgstr "Unohditko 'tool' hakusanan?" +msgstr "Unohditko 'tool' avainsanan?" #: editor/editor_run_script.cpp msgid "Couldn't run script:" @@ -2450,7 +2390,7 @@ msgstr "Skriptiä ei voitu suorittaa:" #: editor/editor_run_script.cpp msgid "Did you forget the '_run' method?" -msgstr "Unohditko '_run' -metodin?" +msgstr "Unohditko '_run' metodin?" #: editor/editor_settings.cpp msgid "Default (Same as Editor)" @@ -2458,15 +2398,15 @@ msgstr "Oletus (sama kuin editori)" #: editor/editor_sub_scene.cpp msgid "Select Node(s) to Import" -msgstr "Valitse tuotava(t) node(t)" +msgstr "Valitse tuotavat solmut" #: editor/editor_sub_scene.cpp msgid "Scene Path:" -msgstr "Scenen polku:" +msgstr "Skenen polku:" #: editor/editor_sub_scene.cpp msgid "Import From Node:" -msgstr "Tuo Nodesta:" +msgstr "Tuo solmusta:" #: editor/export_template_manager.cpp msgid "Re-Download" @@ -2493,7 +2433,7 @@ msgid "(Current)" msgstr "(Nykyinen)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Noudetaan peilipalvelimia, hetkinen..." #: editor/export_template_manager.cpp @@ -2502,24 +2442,23 @@ msgstr "Poista mallin versio '%s'?" #: editor/export_template_manager.cpp msgid "Can't open export templates zip." -msgstr "Vientipohjien zip-tiedostoa ei voitu avata." +msgstr "Vientimallien zip-tiedostoa ei voitu avata." #: editor/export_template_manager.cpp msgid "Invalid version.txt format inside templates." -msgstr "Paketti sisältää viallisen version.txt tiedoston." +msgstr "Vientimalli sisältää virheellisen version.txt tiedoston." #: editor/export_template_manager.cpp msgid "No version.txt found inside templates." -msgstr "version.txt -tiedostoa ei löytynyt." +msgstr "Vientimalleista ei löytynyt version.txt tiedostoa." #: editor/export_template_manager.cpp -#, fuzzy msgid "Error creating path for templates:" -msgstr "Virhe luotaessa polkua mallille:\n" +msgstr "Virhe luotaessa polkua malleille:" #: editor/export_template_manager.cpp msgid "Extracting Export Templates" -msgstr "Puretaan vientipohjia." +msgstr "Puretaan vientimalleja" #: editor/export_template_manager.cpp msgid "Importing:" @@ -2550,7 +2489,6 @@ msgstr "Ei vastausta." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Request Failed." msgstr "Pyyntö epäonnistui." @@ -2565,29 +2503,24 @@ msgid "Failed:" msgstr "Epäonnistui:" #: editor/export_template_manager.cpp -#, fuzzy msgid "Download Complete." -msgstr "Lataa" +msgstr "Lataus valmis." #: editor/export_template_manager.cpp -#, fuzzy msgid "Error requesting url: " -msgstr "Virhe tallennettaessa atlas-kuvaa:" +msgstr "Virhe pyydettäessä osoitetta: " #: editor/export_template_manager.cpp -#, fuzzy -msgid "Connecting to Mirror.." -msgstr "Yhdistä..." +msgid "Connecting to Mirror..." +msgstr "Yhdistetään peilipalvelimeen..." #: editor/export_template_manager.cpp -#, fuzzy msgid "Disconnected" -msgstr "Katkaise yhteys" +msgstr "Yhteys katkaistu" #: editor/export_template_manager.cpp -#, fuzzy msgid "Resolving" -msgstr "Tallennetaan..." +msgstr "Selvitetään" #: editor/export_template_manager.cpp msgid "Can't Resolve" @@ -2595,29 +2528,25 @@ msgstr "Yhdistäminen epäonnistui" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy -msgid "Connecting.." -msgstr "Yhdistä..." +msgid "Connecting..." +msgstr "Yhdistetään..." #: editor/export_template_manager.cpp -#, fuzzy msgid "Can't Connect" msgstr "Ei voitu yhdistää" #: editor/export_template_manager.cpp -#, fuzzy msgid "Connected" -msgstr "Yhdistä" +msgstr "Yhdistetty" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "Pyydetään..." #: editor/export_template_manager.cpp -#, fuzzy msgid "Downloading" -msgstr "Lataa" +msgstr "Ladataan" #: editor/export_template_manager.cpp msgid "Connection Error" @@ -2648,14 +2577,12 @@ msgid "Select template file" msgstr "Valitse mallin tiedosto" #: editor/export_template_manager.cpp -#, fuzzy msgid "Export Template Manager" msgstr "Vientimallien hallinta" #: editor/export_template_manager.cpp -#, fuzzy msgid "Download Templates" -msgstr "Poista malli" +msgstr "Lataa mallit" #: editor/export_template_manager.cpp msgid "Select mirror from list: " @@ -2665,7 +2592,7 @@ msgstr "Valitse peilipalvelin listasta: " msgid "Can't open file_type_cache.cch for writing, not saving file type cache!" msgstr "" "Tiedostoa file_type_cache.cch ei voitu avata kirjoittamista varten. " -"Välimuistia ei tallenneta. " +"Välimuistia ei tallenneta!" #: editor/filesystem_dock.cpp msgid "Cannot navigate to '%s' as it has not been found in the file system!" @@ -2682,79 +2609,65 @@ msgid "View items as a list" msgstr "Listanäkymä" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Status: Import of file failed. Please fix file and reimport manually." msgstr "" -"\n" -"Tila: Tuonti epäonnistui. Ole hyvä, korjaa tiedosto, ja tuo uudelleen." +"Tila: Tuonti epäonnistui. Ole hyvä, korjaa tiedosto ja tuo se uudelleen." #: editor/filesystem_dock.cpp msgid "Cannot move/rename resources root." msgstr "Ei voitu siirtää/nimetä uudelleen resurssien päätasoa." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Cannot move a folder into itself." -msgstr "Hakemistoa ei voi siirtää itsensä sisään.\n" +msgstr "Kansiota ei voi siirtää itsensä sisään." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Error moving:" -msgstr "Virhe tuotaessa:" +msgstr "Virhe siirrettäessä:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Error duplicating:" -msgstr "Virhe ladatessa:" +msgstr "Virhe kahdennettaessa:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Unable to update dependencies:" -msgstr "Scenellä '%s' on rikkinäisiä riippuvuuksia:" +msgstr "Ei voida päivittää riippuvuuksia:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "No name provided" msgstr "Nimeä ei annettu" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Provided name contains invalid characters" -msgstr "Annettu nimi sisältää laittomia kirjaimia" +msgstr "Annettu nimi sisältää virheellisiä kirjainmerkkejä" #: editor/filesystem_dock.cpp -#, fuzzy msgid "No name provided." -msgstr "Nimeä uudelleen tai siirrä..." +msgstr "Nimeä ei annettu." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Name contains invalid characters." -msgstr "Kelvolliset merkit:" +msgstr "Nimi sisältää virheellisiä kirjainmerkkejä." #: editor/filesystem_dock.cpp msgid "A file or folder with this name already exists." msgstr "Tällä nimellä löytyy jo kansio tai tiedosto." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Renaming file:" -msgstr "Nimeä muuttuja uudelleen" +msgstr "Nimetään tiedosto uudelleen:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Renaming folder:" -msgstr "Nimeä Node uudelleen" +msgstr "Nimetään kansio uudelleen:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Duplicating file:" -msgstr "Monista" +msgstr "Kahdennetaan tiedosto:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Duplicating folder:" -msgstr "Nimeä Node uudelleen" +msgstr "Kahdennetaan kansio:" #: editor/filesystem_dock.cpp msgid "Expand all" @@ -2765,35 +2678,32 @@ msgid "Collapse all" msgstr "Pienennä kaikki" #: editor/filesystem_dock.cpp -#, fuzzy -msgid "Rename.." -msgstr "Nimeä uudelleen" +msgid "Rename..." +msgstr "Nimeä uudelleen..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Siirrä..." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Open Scene(s)" -msgstr "Avaa scene" +msgstr "Avaa skene tai skenejä" #: editor/filesystem_dock.cpp msgid "Instance" -msgstr "Instanssi" +msgstr "Luo ilmentymä" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "Muokkaa riippuvuuksia..." #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Tarkastele omistajia..." #: editor/filesystem_dock.cpp -#, fuzzy -msgid "Duplicate.." -msgstr "Monista" +msgid "Duplicate..." +msgstr "Kahdenna..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2805,7 +2715,7 @@ msgstr "Seuraava hakemisto" #: editor/filesystem_dock.cpp msgid "Re-Scan Filesystem" -msgstr "" +msgstr "Skannaa tiedostojärjestelmä uudelleen" #: editor/filesystem_dock.cpp msgid "Toggle folder status as Favorite" @@ -2813,16 +2723,15 @@ msgstr "Merkitse kansio suosikkeihin" #: editor/filesystem_dock.cpp msgid "Instance the selected scene(s) as child of the selected node." -msgstr "Luo valituista skeneistä instanssi valitun noden alle." +msgstr "Luo valituista skeneistä ilmentymä valitun solmun alle." #: editor/filesystem_dock.cpp -#, fuzzy msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Selataan tiedostoja,\n" -"Hetkinen..." +"Hetkinen…" #: editor/filesystem_dock.cpp msgid "Move" @@ -2842,49 +2751,40 @@ msgid "Remove from Group" msgstr "Poista ryhmästä" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import as Single Scene" -msgstr "Tuodaan Scene..." +msgstr "Tuo yhtenä skenenä" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Animations" -msgstr "Tuo animaatiot..." +msgstr "Tuo erillisten animaatioiden kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Materials" msgstr "Tuo erillisten materiaalien kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Objects" msgstr "Tuo erillisten objektien kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Objects+Materials" msgstr "Tuo erillisten objektien ja materiaalien kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Objects+Animations" msgstr "Tuo erillisten objektien ja animaatioiden kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Materials+Animations" msgstr "Tuo erillisten materiaalien ja animaatioiden kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import with Separate Objects+Materials+Animations" msgstr "Tuo erillisten objektien, materiaalien ja animaatioiden kanssa" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Import as Multiple Scenes" -msgstr "Tuo 3D Scene" +msgstr "Tuo useina skeneinä" #: editor/import/resource_importer_scene.cpp msgid "Import as Multiple Scenes+Materials" @@ -2893,49 +2793,48 @@ msgstr "Tuo useina skeneinä ja materiaaleina" #: editor/import/resource_importer_scene.cpp #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import Scene" -msgstr "Tuo Scene" +msgstr "Tuo skene" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Tuodaan Scene..." +msgid "Importing Scene..." +msgstr "Tuodaan skene..." #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Generating Lightmaps" -msgstr "Muunna Lightmapiksi:" +msgstr "Luodaan Lightmappeja" #: editor/import/resource_importer_scene.cpp -#, fuzzy msgid "Generating for Mesh: " -msgstr "Luo AABB" +msgstr "Luodaan meshille: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "Suorita valitsemasi skripti..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" -msgstr "Ei voitu ladata tuonnin jälkeistä skriptiä: " +msgstr "Ei voitu ladata tuonnin jälkeistä skriptiä:" #: editor/import/resource_importer_scene.cpp msgid "Invalid/broken script for post-import (check console):" -msgstr "Viallinen tuonnin jälkeinen skripti (tarkista konsoli) : " +msgstr "" +"Virheellinen tai viallinen tuonnin jälkeinen skripti (tarkista konsoli):" #: editor/import/resource_importer_scene.cpp msgid "Error running post-import script:" -msgstr "Virhe ajettaessa tuonnin jälkeistä skriptiä: " +msgstr "Virhe ajettaessa tuonnin jälkeistä skriptiä:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Tallennetaan..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" -msgstr "" +msgstr "Aseta oletus valinnalle '%s'" #: editor/import_dock.cpp msgid "Clear Default for '%s'" -msgstr "" +msgstr "Poista oletus valinnalta '%s'" #: editor/import_dock.cpp msgid " Files" @@ -2946,7 +2845,7 @@ msgid "Import As:" msgstr "Tuo nimellä:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "Esiasetus..." #: editor/import_dock.cpp @@ -2955,7 +2854,7 @@ msgstr "Tuo uudelleen" #: editor/multi_node_edit.cpp msgid "MultiNode Set" -msgstr "" +msgstr "Aseta usealle solmulle" #: editor/node_dock.cpp msgid "Groups" @@ -2963,7 +2862,7 @@ msgstr "Ryhmät" #: editor/node_dock.cpp msgid "Select a Node to edit Signals and Groups." -msgstr "Valitse node jonka signaaleja ja ryhmiä haluat muokata." +msgstr "Valitse solmu, jonka signaaleja ja ryhmiä haluat muokata." #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp @@ -3055,11 +2954,11 @@ msgstr "Lisää animaatio" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Blend Next Changed" -msgstr "Sekoita seuraavaan vaihdettu" +msgstr "Sulauta seuraavaan vaihdettu" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Blend Time" -msgstr "" +msgstr "Muuta sulautusaikaa" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load Animation" @@ -3111,11 +3010,11 @@ msgstr "Toista valittu animaatio nykyisestä kohdasta. (D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation position (in seconds)." -msgstr "Animaation sijainti (sekunneissa)." +msgstr "Animaation kohta (sekunneissa)." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Scale animation playback globally for the node." -msgstr "" +msgstr "Skaalaa animaation toistoa globaalisti solmulle." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create new animation in player." @@ -3139,11 +3038,11 @@ msgstr "Näytä lista animaatioista soittimessa." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Autoplay on Load" -msgstr "Toista automaattisesti" +msgstr "Toista automaattisesti ladattaessa" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Edit Target Blend Times" -msgstr "" +msgstr "Muokkaa kohteen sulautusaikoja" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation Tools" @@ -3159,7 +3058,7 @@ msgstr "Onion skinning" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Enable Onion Skinning" -msgstr "Käytä Onion skinningiä" +msgstr "Käytä onion skinningiä" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Directions" @@ -3199,7 +3098,7 @@ msgstr "Pakota valkoisen modulaatio" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Include Gizmos (3D)" -msgstr "" +msgstr "Näytä 3D-muokkaimet" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create New Animation" @@ -3260,7 +3159,7 @@ msgstr "Sulauta" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix" -msgstr "Sekoitus" +msgstr "Sekoita" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Auto Restart:" @@ -3309,11 +3208,11 @@ msgstr "Lisää syöte" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Clear Auto-Advance" -msgstr "" +msgstr "Poista automaattinen eteneminen" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Set Auto-Advance" -msgstr "" +msgstr "Aseta automaattinen eteneminen" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Delete Input" @@ -3329,56 +3228,55 @@ msgstr "Animaatiopuu ei ole kelvollinen." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation Node" -msgstr "Animaationode" +msgstr "Animaatiosolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "OneShot Node" -msgstr "OneShot node" +msgstr "Vaiheistussolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix Node" -msgstr "Mix Node" +msgstr "Sekoitussolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend2 Node" -msgstr "Sulautus2 node" +msgstr "2-sulautussolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend3 Node" -msgstr "" +msgstr "3-sulautussolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend4 Node" -msgstr "" +msgstr "4-sulautussolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeScale Node" -msgstr "" +msgstr "Ajanskaalaussolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeSeek Node" -msgstr "" +msgstr "Ajanhakusolmu" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Transition Node" -msgstr "Siirtymänode" +msgstr "Siirtymäsolmu" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "Tuo animaatiot..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" -msgstr "Muokkaa noden suodattimia" +msgstr "Muokkaa solmun suodattimia" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Suodattimet..." #: editor/plugins/animation_tree_editor_plugin.cpp -#, fuzzy msgid "AnimationTree" -msgstr "Animaatio" +msgstr "Animaatiopuu" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Free" @@ -3389,9 +3287,8 @@ msgid "Contents:" msgstr "Sisällöt:" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "View Files" -msgstr " Tiedostot" +msgstr "Näytä tiedostot" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't resolve hostname:" @@ -3402,18 +3299,16 @@ msgid "Connection error, please try again." msgstr "Yhteysvirhe, ole hyvä ja yritä uudelleen." #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Can't connect to host:" -msgstr "Yhdistä Nodeen:" +msgstr "Isäntään yhdistäminen epäonnistui:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "No response from host:" -msgstr "Ei vastausta isännältä: " +msgstr "Ei vastausta isännältä:" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Request failed, return code:" -msgstr "Pyydetty tiedostomuoto tuntematon:" +msgstr "Pyyntö epäonnistui, virhekoodi:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request failed, too many redirects" @@ -3444,14 +3339,12 @@ msgid "Fetching:" msgstr "Noudetaan:" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy -msgid "Resolving.." -msgstr "Tallennetaan..." +msgid "Resolving..." +msgstr "Selvitetään..." #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Error making request" -msgstr "Virhe tallennettaessa resurssia!" +msgstr "Virhe pyynnön luonnissa" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Idle" @@ -3462,9 +3355,8 @@ msgid "Retry" msgstr "Yritä uudelleen" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Download Error" -msgstr "Lataa" +msgstr "Latausvirhe" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Download for this asset is already in progress!" @@ -3514,7 +3406,7 @@ msgid "Site:" msgstr "Sivu:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Tuki..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3522,7 +3414,6 @@ msgid "Official" msgstr "Virallinen" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Testing" msgstr "Testaus" @@ -3536,21 +3427,27 @@ msgid "" "Save your scene (for images to be saved in the same dir), or pick a save " "path from the BakedLightmap properties." msgstr "" +"Lightmap-kuvien tallennuspolun määrittäminen ei onnistu.\n" +"Tallenna skenesi (jotta kuvat tallentuisivat samaan hakemistoon), tai " +"valitse tallennuspolku BakedLightmapin asetuksista." #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "" "No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake " "Light' flag is on." msgstr "" +"Ei meshejä kehitettävänä. Varmista, että ne sisältävät UV2-kanavan, ja että " +"'Bake Light' asetus on päällä." #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "Failed creating lightmap images, make sure path is writable." msgstr "" +"Lightmap-kuvien luonti epäonnistui, varmista, että polku on " +"kirjoituskelpoinen." #: editor/plugins/baked_lightmap_editor_plugin.cpp -#, fuzzy msgid "Bake Lightmaps" -msgstr "Muunna Lightmapiksi:" +msgstr "Kehitä Lightmapit" #: editor/plugins/camera_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp @@ -3585,36 +3482,31 @@ msgstr "Siirrä keskikohtaa" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Action" -msgstr "Siirrä " +msgstr "Siirrä" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move vertical guide" msgstr "Siirrä pystysuuntaista apuviivaa" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Create new vertical guide" -msgstr "Luo uusi skripti" +msgstr "Luo uusi pystysuora apuviiva" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Remove vertical guide" -msgstr "Poista muuttuja" +msgstr "Poista pystysuora apuviiva" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Move horizontal guide" -msgstr "Siirrä pistettä käyrällä" +msgstr "Siirrä vaakasuoraa apuviivaa" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Create new horizontal guide" -msgstr "Luo uusi skripti" +msgstr "Luo uusi vaakasuora apuviiva" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Remove horizontal guide" -msgstr "Poista virheelliset avaimet" +msgstr "Poista vaakasuora apuviiva" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Create new horizontal and vertical guides" @@ -3629,14 +3521,12 @@ msgid "Edit CanvasItem" msgstr "Muokkaa CanvasItemiä" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Anchors only" -msgstr "Ankkuri" +msgstr "Vain ankkurit" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Change Anchors and Margins" -msgstr "Muuta ankkureita" +msgstr "Muuta ankkureita ja marginaaleja" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Change Anchors" @@ -3666,7 +3556,7 @@ msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Alt+RMB: Depth list selection" -msgstr "" +msgstr "Alt + Hiiren oikea painike: Syvyyslistan valinta" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Mode" @@ -3682,6 +3572,8 @@ msgid "" "Show a list of all objects at the position clicked\n" "(same as Alt+RMB in select mode)." msgstr "" +"Näytä lista kaikista napsautetussa kohdassa olevista objekteista\n" +"(sama kuin Alt + Hiiren oikea painike valintatilassa)." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Click to change object's rotation pivot." @@ -3689,7 +3581,7 @@ msgstr "Klikkaa vaihtaaksesi objektin kääntökeskiötä." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Pan Mode" -msgstr "" +msgstr "Panorointitila" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Toggles snapping" @@ -3700,9 +3592,8 @@ msgid "Use Snap" msgstr "Käytä tarttumista" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Snapping options" -msgstr "Animaation asetukset" +msgstr "Tarttumisen asetukset" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to grid" @@ -3713,6 +3604,7 @@ msgid "Use Rotation Snap" msgstr "Tartu käännettäessä" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Määrittele tarttuminen..." @@ -3729,28 +3621,24 @@ msgid "Smart snapping" msgstr "Älykäs tarttuminen" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Snap to parent" -msgstr "Laajenna Parentiin" +msgstr "Tartu isäntään" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to node anchor" -msgstr "Tartu noden ankkuriin" +msgstr "Tartu solmun ankkuriin" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Snap to node sides" -msgstr "Tartu noden sivustoihin" +msgstr "Tartu solmun reunoihin" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Snap to other nodes" -msgstr "Tartu muihin nodeihin" +msgstr "Tartu muihin solmuihin" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Snap to guides" -msgstr "Laajenna Parentiin" +msgstr "Tartu apuviivoihin" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp @@ -3768,7 +3656,7 @@ msgstr "Varmistaa ettei objektin lapsia voi valita." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Restores the object's children's ability to be selected." -msgstr "" +msgstr "Palauttaa objektin aliobjektien mahdollisuuden tulla valituksi." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Make Bones" @@ -3792,9 +3680,8 @@ msgstr "Tyhjennä IK ketju" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "View" -msgstr "Näytä/Tarkastele" +msgstr "Näytä" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp @@ -3802,9 +3689,8 @@ msgid "Show Grid" msgstr "Näytä ruudukko" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Helpers" -msgstr "Näytä luut" +msgstr "Näytä avustimet" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Rulers" @@ -3815,14 +3701,12 @@ msgid "Show Guides" msgstr "Näytä apuviivat" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "Näytä origo" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 näyttöruutu" +msgstr "Näytä näyttöikkuna" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3838,7 +3722,7 @@ msgstr "Asettelu" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Keys" -msgstr "Lisää keyframeja" +msgstr "Lisää avainruutuja" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Key" @@ -3846,7 +3730,7 @@ msgstr "Lisää keyframe" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Key (Existing Tracks)" -msgstr "Lisää keyframe (olemassaolevalle raidalle)" +msgstr "Lisää avainruutu (olemassa olevat raidat)" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Copy Pose" @@ -3857,9 +3741,8 @@ msgid "Clear Pose" msgstr "Tyhjennä asento" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Drag pivot from mouse position" -msgstr "Rahaa pistettä hiiren sijainnista" +msgstr "Vedä keskipistettä hiiren sijainnista" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Set pivot at mouse position" @@ -3883,21 +3766,21 @@ msgstr "Lisätään %s..." #: editor/plugins/canvas_item_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ok" -msgstr "" +msgstr "Ok" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Cannot instantiate multiple nodes without root." -msgstr "" +msgstr "Ei voida luoda ilmentymiä useasta solmusta ilman juurta." #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Create Node" -msgstr "Luo Node" +msgstr "Luo solmu" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Error instancing scene from %s" -msgstr "Virhe luotaessa instanssia kohteesta %s" +msgstr "Virhe luotaessa ilmentymää kohteesta %s" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Change default type" @@ -3908,8 +3791,8 @@ msgid "" "Drag & drop + Shift : Add node as sibling\n" "Drag & drop + Alt : Change node type" msgstr "" -"Vedä & pudota + Shift: Lisää Node sisarena\n" -"Vedä & pudota + Alt: Muuta Noden tyyppiä" +"Vedä & pudota + Shift: Lisää solmu sisarena\n" +"Vedä & pudota + Alt: Muuta solmun tyyppiä" #: editor/plugins/collision_polygon_editor_plugin.cpp msgid "Create Poly3D" @@ -3921,48 +3804,45 @@ msgstr "Aseta kahva" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Remove item %d?" -msgstr "" +msgstr "Poistetaanko kohde %d?" #: editor/plugins/cube_grid_theme_editor_plugin.cpp #: editor/plugins/theme_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp -#, fuzzy msgid "Add Item" -msgstr "Lisää" +msgstr "Lisää kohde" #: editor/plugins/cube_grid_theme_editor_plugin.cpp -#, fuzzy msgid "Remove Selected Item" -msgstr "Poista valitut" +msgstr "Poista valitut kohteet" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import from Scene" -msgstr "Tuo Scenestä" +msgstr "Tuo skenestä" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Update from Scene" -msgstr "Päivitä Scenestä" +msgstr "Päivitä skenestä" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat0" -msgstr "" +msgstr "Tasainen0" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat1" -msgstr "" +msgstr "Tasainen1" #: editor/plugins/curve_editor_plugin.cpp -#, fuzzy msgid "Ease in" -msgstr "Framen valinta" +msgstr "Kiihdytä alussa" #: editor/plugins/curve_editor_plugin.cpp msgid "Ease out" -msgstr "" +msgstr "Hidasta lopussa" #: editor/plugins/curve_editor_plugin.cpp msgid "Smoothstep" -msgstr "" +msgstr "Pehmeä askellus" #: editor/plugins/curve_editor_plugin.cpp msgid "Modify Curve Point" @@ -4002,7 +3882,7 @@ msgstr "Poista käyrän piste" #: editor/plugins/curve_editor_plugin.cpp msgid "Toggle Curve Linear Tangent" -msgstr "" +msgstr "Aseta käyrälle lineaarinen tangentti" #: editor/plugins/curve_editor_plugin.cpp msgid "Hold Shift to edit tangents individually" @@ -4010,7 +3890,7 @@ msgstr "Pidä shift pohjassa muokataksesi tangentteja yksitellen" #: editor/plugins/gi_probe_editor_plugin.cpp msgid "Bake GI Probe" -msgstr "" +msgstr "Kehitä GI Probe" #: editor/plugins/gradient_editor_plugin.cpp msgid "Add/Remove Color Ramp Point" @@ -4023,27 +3903,27 @@ msgstr "Muokkaa väriliukumaa" #: editor/plugins/item_list_editor_plugin.cpp msgid "Item %d" -msgstr "" +msgstr "Kohde %d" #: editor/plugins/item_list_editor_plugin.cpp msgid "Items" -msgstr "" +msgstr "Sisältö" #: editor/plugins/item_list_editor_plugin.cpp msgid "Item List Editor" -msgstr "" +msgstr "Sisällön muokkaus" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "" "No OccluderPolygon2D resource on this node.\n" "Create and assign one?" msgstr "" -"Tälle nodelle ei ole OccluderPolygon2D:tä.\n" -"Luodaanko sellainen?" +"Tälle solmulle ei ole OccluderPolygon2D resurssia.\n" +"Luodaanko ja asetetaanko sellainen?" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create Occluder Polygon" -msgstr "Luo Occluder polygooni" +msgstr "Luo peittävä polygoni" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create a new polygon from scratch." @@ -4059,7 +3939,7 @@ msgstr "VHP: Siirrä pistettä." #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Ctrl+LMB: Split Segment." -msgstr "Ctrl+Vasen hiirennappi: Puolita osa" +msgstr "Ctrl+Vasen hiirennappi: Puolita osa." #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "RMB: Erase Point." @@ -4067,60 +3947,59 @@ msgstr "OHP: Pyyhi piste." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh is empty!" -msgstr "" +msgstr "Mesh on tyhjä!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Static Trimesh Body" -msgstr "" +msgstr "Luo konkaavi staattinen kappale" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Static Convex Body" -msgstr "" +msgstr "Luo konveksi staattinen kappale" #: editor/plugins/mesh_instance_editor_plugin.cpp -#, fuzzy msgid "This doesn't work on scene root!" -msgstr "Tämä ei toimi root-Scenessä!" +msgstr "Tämä ei toimi skenen juuressa!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Shape" -msgstr "" +msgstr "Luo konkaavi muoto" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Shape" -msgstr "" +msgstr "Luo konveksi muoto" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Navigation Mesh" -msgstr "" +msgstr "Luo navigointiverkko" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Contained Mesh is not of type ArrayMesh." -msgstr "" +msgstr "Sisällytetty Mesh ei ole tyyppiä ArrayMesh." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "UV Unwrap failed, mesh may not be manifold?" -msgstr "" +msgstr "UV-aukaisu epäonnistui, mesh ei ehkä ole jaettavissa osiin?" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "No mesh to debug." -msgstr "" +msgstr "Ei meshiä debugattavaksi." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Model has no UV in this layer" -msgstr "" +msgstr "Mallilla ei ole UV-kanavaa tällä kerroksella" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "MeshInstance lacks a Mesh!" -msgstr "" +msgstr "MeshInstance solmulta puuttuu Mesh!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh has not surface to create outlines from!" -msgstr "" +msgstr "Meshillä ei ole pintaa, josta ääriviivat voitaisiin luoda!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Meshin primitiivityyppi ei ole PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4132,90 +4011,89 @@ msgstr "Luo ääriviivat" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh" -msgstr "" +msgstr "Mesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Static Body" -msgstr "" +msgstr "Luo konkaavi staattinen kappale" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Static Body" -msgstr "" +msgstr "Luo konveksi staattinen kappale" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Collision Sibling" -msgstr "" +msgstr "Luo konkaavi törmäysmuoto sisareksi" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Collision Sibling" -msgstr "" +msgstr "Luo konveksi törmäysmuoto sisareksi" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "" +msgid "Create Outline Mesh..." +msgstr "Luo reunoista Mesh..." #: editor/plugins/mesh_instance_editor_plugin.cpp -#, fuzzy msgid "View UV1" -msgstr "Näytä/Tarkastele" +msgstr "Näytä UV1" #: editor/plugins/mesh_instance_editor_plugin.cpp -#, fuzzy msgid "View UV2" -msgstr "Näytä/Tarkastele" +msgstr "Näytä UV2" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Unwrap UV2 for Lightmap/AO" -msgstr "" +msgstr "Aukaise UV2 Lightmapille tai AO:lle" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Outline Mesh" -msgstr "" +msgstr "Luo reunoista Mesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Outline Size:" msgstr "Ääriviivojen koko:" #: editor/plugins/multimesh_editor_plugin.cpp -#, fuzzy msgid "No mesh source specified (and no MultiMesh set in node)." -msgstr "Mesh:in lähdettä ei määritetty" +msgstr "" +"Meshin lähdettä ei ole määritetty (ja MultiMesh ei ole asetettu solmulle)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "No mesh source specified (and MultiMesh contains no Mesh)." msgstr "" +"Meshin lähdettä ei ole määritetty (ja MultiMesh ei sisällä Mesh solmua)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh source is invalid (invalid path)." -msgstr "Virheellinen Mesh:in lähde (virheellinen polku)." +msgstr "Meshin lähde on virheellinen (virheellinen polku)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh source is invalid (not a MeshInstance)." -msgstr "" +msgstr "Meshin lähde on virheellinen (ei MeshInstance)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh source is invalid (contains no Mesh resource)." -msgstr "" +msgstr "Meshin lähde on virheellinen (ei sisällä Mesh resurssia)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "No surface source specified." -msgstr "" +msgstr "Pinnan lähdettä ei ole määritelty." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Surface source is invalid (invalid path)." -msgstr "" +msgstr "Pinnan lähde on virheellinen (virheellinen polku)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Surface source is invalid (no geometry)." -msgstr "" +msgstr "Pinnan lähde on virheellinen (geometria puuttuu)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Surface source is invalid (no faces)." -msgstr "" +msgstr "Pinnan lähde on virheellinen (tahkot puuttuvat)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Parent has no solid faces to populate." -msgstr "" +msgstr "Lähteellä ei ole kiinteitä tahkoja täytettäväksi." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Couldn't map area." @@ -4223,27 +4101,27 @@ msgstr "Aluetta ei voitu kartoittaa." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Select a Source Mesh:" -msgstr "" +msgstr "Valitse lähdemesh:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Select a Target Surface:" -msgstr "" +msgstr "Valitse kohdepinta:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate Surface" -msgstr "" +msgstr "Täytä pinta" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate MultiMesh" -msgstr "" +msgstr "Täytä MultiMesh" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Target Surface:" -msgstr "" +msgstr "Kohdepinta:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Source Mesh:" -msgstr "" +msgstr "Lähde Mesh:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "X-Axis" @@ -4259,7 +4137,7 @@ msgstr "Z-akseli" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh Up Axis:" -msgstr "" +msgstr "Meshin ylös-akseli:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Random Rotation:" @@ -4275,19 +4153,19 @@ msgstr "Satunnainen skaalaus:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate" -msgstr "" +msgstr "Täytä" #: editor/plugins/navigation_mesh_editor_plugin.cpp msgid "Bake!" -msgstr "" +msgstr "Kehitä!" #: editor/plugins/navigation_mesh_editor_plugin.cpp msgid "Bake the navigation mesh." -msgstr "" +msgstr "Kehitä navigointiverkko." #: editor/plugins/navigation_mesh_editor_plugin.cpp msgid "Clear the navigation mesh." -msgstr "" +msgstr "Tyhjennä navigointiverkko." #: editor/plugins/navigation_mesh_generator.cpp msgid "Setting up Configuration..." @@ -4299,45 +4177,43 @@ msgstr "Lasketaan ruudukon kokoa..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating heightfield..." -msgstr "" +msgstr "Luodaan korkeuskenttää..." #: editor/plugins/navigation_mesh_generator.cpp -#, fuzzy msgid "Marking walkable triangles..." -msgstr "Varastoidaan paikalliset muutokset..." +msgstr "Merkitään kuljettavat kolmiot..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." -msgstr "" +msgstr "Rakennetaan tiivistä korkeuskenttää..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Eroding walkable area..." -msgstr "" +msgstr "Syövytetään kuljettavaa aluetta..." #: editor/plugins/navigation_mesh_generator.cpp -#, fuzzy msgid "Partitioning..." -msgstr "Varoitus" +msgstr "Ositetaan..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating contours..." -msgstr "" +msgstr "Luodaan korkeuskäyriä..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating polymesh..." -msgstr "" +msgstr "Luodaan polymesh..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Converting to native navigation mesh..." -msgstr "" +msgstr "Muunnetaan alkuperäiseksi navigointiverkoksi..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Navigation Mesh Generator Setup:" -msgstr "" +msgstr "Navigointiverkon generaattorin asetukset:" #: editor/plugins/navigation_mesh_generator.cpp msgid "Parsing Geometry..." -msgstr "" +msgstr "Jäsentää geometriaa…" #: editor/plugins/navigation_mesh_generator.cpp msgid "Done!" @@ -4345,29 +4221,29 @@ msgstr "Valmis!" #: editor/plugins/navigation_polygon_editor_plugin.cpp msgid "Create Navigation Polygon" -msgstr "" +msgstr "Luo navigointipolygoni" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp -#, fuzzy msgid "Generating AABB" -msgstr "Luo AABB" +msgstr "Luodaan AABB" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Can only set point into a ParticlesMaterial process material" msgstr "" +"Piste voidaan asettaa ainoastaan ParticlesMaterial käsittelyn materiaaliin" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Error loading image:" msgstr "Virhe ladattaessa kuvaa:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "" +msgid "No pixels with transparency > 128 in image..." +msgstr "Kuvassa ei ole pikseleitä, joiden läpinäkyvyys on enemmän kuin 128…" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" -msgstr "" +msgstr "Kartoita näkyvä alue" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Load Emission Mask" @@ -4375,7 +4251,7 @@ msgstr "Lataa emissiomaski" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Clear Emission Mask" -msgstr "" +msgstr "Tyhjennä emissiomaski" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp @@ -4388,18 +4264,16 @@ msgstr "Luotujen pisteiden määrä:" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp -#, fuzzy msgid "Generation Time (sec):" -msgstr "Keskimääräinen aika (sek)" +msgstr "Luontiaika (s):" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Emission Mask" msgstr "Emission maski" #: editor/plugins/particles_2d_editor_plugin.cpp -#, fuzzy msgid "Capture from Pixel" -msgstr "Luo Scenestä" +msgstr "Nappaa pikselistä" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Emission Colors" @@ -4407,15 +4281,15 @@ msgstr "Emission väri" #: editor/plugins/particles_editor_plugin.cpp msgid "Node does not contain geometry." -msgstr "Node ei sisällä geometriaa." +msgstr "Solmu ei sisällä geometriaa." #: editor/plugins/particles_editor_plugin.cpp msgid "Node does not contain geometry (faces)." -msgstr "" +msgstr "Solmulta puuttuu geometria (tahkot)." #: editor/plugins/particles_editor_plugin.cpp msgid "A processor material of type 'ParticlesMaterial' is required." -msgstr "" +msgstr "Tarvitaan 'ParticlesMaterial' tyyppinen prosessorimateriaali." #: editor/plugins/particles_editor_plugin.cpp msgid "Faces contain no area!" @@ -4431,14 +4305,13 @@ msgstr "Luo AABB" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Mesh" -msgstr "" +msgstr "Luo säteilypisteet meshistä" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Node" -msgstr "" +msgstr "Luo säteilypisteet solmusta" #: editor/plugins/particles_editor_plugin.cpp -#, fuzzy msgid "Create Emitter" msgstr "Luo säteilijä/lähetin" @@ -4460,26 +4333,23 @@ msgstr "Äänenvoimakkuus" #: editor/plugins/particles_editor_plugin.cpp msgid "Emission Source: " -msgstr "Emission lähde:" +msgstr "Emission lähde: " #: editor/plugins/particles_editor_plugin.cpp -#, fuzzy msgid "Generate Visibility AABB" -msgstr "Luo AABB" +msgstr "Kartoita näkyvä alue" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Remove Point from Curve" msgstr "Poista pisteet käyrästä" #: editor/plugins/path_2d_editor_plugin.cpp -#, fuzzy msgid "Remove Out-Control from Curve" -msgstr "Poista pisteet käyrästä" +msgstr "Poista lähtöohjain käyrästä" #: editor/plugins/path_2d_editor_plugin.cpp -#, fuzzy msgid "Remove In-Control from Curve" -msgstr "Poista pisteet käyrästä" +msgstr "Poista tulo-ohjain käyrästä" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp @@ -4492,11 +4362,11 @@ msgstr "Siirrä pistettä käyrällä" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move In-Control in Curve" -msgstr "" +msgstr "Siirrä tulo-ohjainta käyrällä" #: editor/plugins/path_2d_editor_plugin.cpp msgid "Move Out-Control in Curve" -msgstr "" +msgstr "Siirrä lähtöohjainta käyrällä" #: editor/plugins/path_2d_editor_plugin.cpp #: editor/plugins/path_editor_plugin.cpp @@ -4547,19 +4417,16 @@ msgid "Curve Point #" msgstr "Käyrän piste #" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve Point Position" -msgstr "Siirrä pistettä" +msgstr "Aseta käyräpisteen sijainti" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve In Position" -msgstr "Siirrä pistettä" +msgstr "Aseta käyrän aloitussijainti" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve Out Position" -msgstr "Siirrä pistettä" +msgstr "Aseta käyrän lopetussijainti" #: editor/plugins/path_editor_plugin.cpp msgid "Split Path" @@ -4570,14 +4437,12 @@ msgid "Remove Path Point" msgstr "Poista polun piste" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Remove Out-Control Point" -msgstr "Poista polygoni ja piste" +msgstr "Poista lähtöohjaimen piste" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Remove In-Control Point" -msgstr "Poista polygoni ja piste" +msgstr "Poista tulo-ohjaimen piste" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Create UV Map" @@ -4589,16 +4454,15 @@ msgstr "Muunna UV kartta" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Polygon 2D UV Editor" -msgstr "" +msgstr "Polygon 2D UV-editori" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Move Point" msgstr "Siirrä pistettä" #: editor/plugins/polygon_2d_editor_plugin.cpp -#, fuzzy msgid "Ctrl: Rotate" -msgstr "Ctrl: Pyöritä/kierrä" +msgstr "Ctrl: Kierrä" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Shift: Move All" @@ -4614,7 +4478,7 @@ msgstr "Siirrä polygonia" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Rotate Polygon" -msgstr "Käännä polygonia" +msgstr "Kierrä polygonia" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Scale Polygon" @@ -4630,11 +4494,11 @@ msgstr "Muokkaa" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Polygon->UV" -msgstr "Polygooni->UV" +msgstr "Polygoni->UV" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "UV->Polygon" -msgstr "UV->Polygooni" +msgstr "UV->Polygoni" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Clear UV" @@ -4682,7 +4546,7 @@ msgstr "Avaa editorissa" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/scene_tree_editor.cpp msgid "Instance:" -msgstr "" +msgstr "Ilmentymä:" #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/plugins/theme_editor_plugin.cpp editor/project_settings_editor.cpp @@ -4703,21 +4567,16 @@ msgid "Paste" msgstr "Liitä" #: editor/plugins/resource_preloader_editor_plugin.cpp -#, fuzzy msgid "ResourcePreloader" -msgstr "Resurssi" +msgstr "Resurssien esilataaja" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Clear Recent Files" -msgstr "Tyhjennä luut" +msgstr "Tyhjennä viimeisimpien tiedostojen luettelo" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Close and save changes?" -msgstr "" -"Sulje ja tallenna muutokset?\n" -"\"" +msgstr "Sulje ja tallenna muutokset?" #: editor/plugins/script_editor_plugin.cpp msgid "Error while saving theme" @@ -4740,7 +4599,7 @@ msgid "Import Theme" msgstr "Tuo teema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Tallenna teema nimellä..." #: editor/plugins/script_editor_plugin.cpp @@ -4748,9 +4607,8 @@ msgid " Class Reference" msgstr " Luokan referenssi" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Sort" -msgstr "Lajittele:" +msgstr "Lajittele" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp editor/scene_tree_dock.cpp @@ -4786,15 +4644,13 @@ msgstr "Tallenna kaikki" #: editor/plugins/script_editor_plugin.cpp msgid "Soft Reload Script" -msgstr "" +msgstr "Lataa skripti uudelleen kevyesti" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Copy Script Path" -msgstr "Kopioi polku" +msgstr "Kopioi skriptin polku" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Show In File System" msgstr "Näytä tiedostojärjestelmässä" @@ -4840,7 +4696,7 @@ msgstr "Näytä/piilota skriptipaneeli" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "Etsi..." #: editor/plugins/script_editor_plugin.cpp @@ -4849,13 +4705,12 @@ msgid "Find Next" msgstr "Etsi seuraava" #: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp -#, fuzzy msgid "Step Over" -msgstr "Ohita" +msgstr "Siirry seuraavaan" #: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp msgid "Step Into" -msgstr "Siirry" +msgstr "Siirry sisään" #: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp msgid "Break" @@ -4880,7 +4735,7 @@ msgstr "Avaa Godotin online-dokumentaatio" #: editor/plugins/script_editor_plugin.cpp msgid "Search the class hierarchy." -msgstr "Etsi luokkahierarkia." +msgstr "Etsi luokkahierarkiasta." #: editor/plugins/script_editor_plugin.cpp msgid "Search the reference documentation." @@ -4926,21 +4781,20 @@ msgstr "Debuggeri" msgid "" "Built-in scripts can only be edited when the scene they belong to is loaded" msgstr "" -"Sisäänrakennettuja skriptejä voi muokata ainoastaan kun Scene, johon ne " +"Sisäänrakennettuja skriptejä voi muokata ainoastaan, kun skene, johon ne " "kuuluvat, on ladattu" #: editor/plugins/script_text_editor.cpp msgid "Only resources from filesystem can be dropped." -msgstr "" +msgstr "Vain tiedostojärjestelmän resursseja voi raahata ja pudottaa." #: editor/plugins/script_text_editor.cpp msgid "Pick Color" msgstr "Poimi väri" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Convert Case" -msgstr "Muunnetaan kuvia" +msgstr "Muunna aakkoslaji" #: editor/plugins/script_text_editor.cpp msgid "Uppercase" @@ -4952,7 +4806,7 @@ msgstr "Pienet kirjaimet" #: editor/plugins/script_text_editor.cpp msgid "Capitalize" -msgstr "" +msgstr "Isot alkukirjaimet" #: editor/plugins/script_text_editor.cpp scene/gui/line_edit.cpp #: scene/gui/text_edit.cpp @@ -4971,9 +4825,8 @@ msgid "Select All" msgstr "Valitse kaikki" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Delete Line" -msgstr "Poista piste" +msgstr "Poista rivi" #: editor/plugins/script_text_editor.cpp msgid "Indent Left" @@ -4988,9 +4841,8 @@ msgid "Toggle Comment" msgstr "Näytä/Piilota kommentit" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Fold/Unfold Line" -msgstr "Avaa rivi" +msgstr "Laskosta tai avaa rivi" #: editor/plugins/script_text_editor.cpp msgid "Fold All Lines" @@ -5010,7 +4862,7 @@ msgstr "Täydennä symbooli" #: editor/plugins/script_text_editor.cpp msgid "Trim Trailing Whitespace" -msgstr "" +msgstr "Poista välilyönnit lopusta" #: editor/plugins/script_text_editor.cpp msgid "Convert Indent To Spaces" @@ -5027,7 +4879,7 @@ msgstr "Automaattinen sisennys" #: editor/plugins/script_text_editor.cpp #: modules/visual_script/visual_script_editor.cpp msgid "Toggle Breakpoint" -msgstr "" +msgstr "Aseta tai poista breakpoint" #: editor/plugins/script_text_editor.cpp msgid "Remove All Breakpoints" @@ -5042,106 +4894,104 @@ msgid "Goto Previous Breakpoint" msgstr "Mene edelliseen breakpointiin" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Convert To Uppercase" -msgstr "Muunna..." +msgstr "Muunna isoiksi kirjaimiksi" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Convert To Lowercase" -msgstr "Muunna..." +msgstr "Muunna pieniksi kirjaimiksi" #: editor/plugins/script_text_editor.cpp msgid "Find Previous" msgstr "Etsi edellinen" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "Korvaa..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "Mene funktioon..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "Mene riville..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" -msgstr "" +msgstr "Asiayhteydellinen ohje" #: editor/plugins/shader_editor_plugin.cpp msgid "Shader" -msgstr "" +msgstr "Sävytin" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Constant" -msgstr "" +msgstr "Muuta skalaarivakiota" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Constant" -msgstr "" +msgstr "Muuta vektorivakiota" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change RGB Constant" -msgstr "" +msgstr "Muuta RGB-värivakiota" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Operator" -msgstr "" +msgstr "Muuta skalaarioperaattoria" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Operator" -msgstr "" +msgstr "Muuta vektorioperaattoria" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Scalar Operator" -msgstr "" +msgstr "Muuta vektori- ja skalaarioperaattoria" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change RGB Operator" -msgstr "" +msgstr "Muuta RGB-värioperaattoria" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Toggle Rot Only" -msgstr "" +msgstr "Vain kierto" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Function" -msgstr "" +msgstr "Muuta skalaarifunktiota" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Function" -msgstr "" +msgstr "Muuta vektorifunktiota" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Uniform" -msgstr "" +msgstr "Muuta skalaariuniformia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Uniform" -msgstr "" +msgstr "Muuta vektoriuniformia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change RGB Uniform" -msgstr "" +msgstr "Muuta RGB-uniformia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Default Value" -msgstr "" +msgstr "Muuta oletusarvoa" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change XForm Uniform" -msgstr "" +msgstr "Muuta XForm-uniformia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Texture Uniform" -msgstr "" +msgstr "Muuta tekstuuriuniformia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Cubemap Uniform" -msgstr "" +msgstr "Muuta Cubemap-uniformia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Comment" @@ -5149,15 +4999,15 @@ msgstr "Vaihda kommenttia" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Add/Remove to Color Ramp" -msgstr "" +msgstr "Lisää tai poista väriluiskalta" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Add/Remove to Curve Map" -msgstr "" +msgstr "Lisää tai poista käyräkartalta" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Modify Curve Map" -msgstr "" +msgstr "Muokkaa käyräkarttaa" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Input Name" @@ -5165,39 +5015,39 @@ msgstr "Vaihda syötteen nimi" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Connect Graph Nodes" -msgstr "" +msgstr "Yhdistä graafin solmut" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Disconnect Graph Nodes" -msgstr "" +msgstr "Erota graafin solmut" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Remove Shader Graph Node" -msgstr "" +msgstr "Poista sävytingraafin solmu" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Move Shader Graph Node" -msgstr "" +msgstr "Siirrä sävytingraafin solmua" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Duplicate Graph Node(s)" -msgstr "" +msgstr "Kahdenna graafin solmut(t)" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Delete Shader Graph Node(s)" -msgstr "" +msgstr "Poista sävytingraafin solmuja" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Error: Cyclic Connection Link" -msgstr "" +msgstr "Virhe: syklinen kytkentä" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Error: Missing Input Connections" -msgstr "" +msgstr "Virhe: syöteliitännät puuttuvat" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Add Shader Graph Node" -msgstr "" +msgstr "Lisää sävytingraafin solmu" #: editor/plugins/spatial_editor_plugin.cpp msgid "Orthogonal" @@ -5213,29 +5063,27 @@ msgstr "Muunnos keskeytetty." #: editor/plugins/spatial_editor_plugin.cpp msgid "X-Axis Transform." -msgstr "" +msgstr "X-akselin muunnos." #: editor/plugins/spatial_editor_plugin.cpp msgid "Y-Axis Transform." -msgstr "" +msgstr "Y-akselin muunnos." #: editor/plugins/spatial_editor_plugin.cpp msgid "Z-Axis Transform." -msgstr "" +msgstr "Z-akselin muunnos." #: editor/plugins/spatial_editor_plugin.cpp msgid "View Plane Transform." -msgstr "" +msgstr "Näkymätason muunnos." #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Scaling: " -msgstr "Skaalaus:" +msgstr "Skaalataan: " #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Translating: " -msgstr "Siirtymä" +msgstr "Siirretään: " #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotating %s degrees." @@ -5243,7 +5091,7 @@ msgstr "Kierto %s astetta." #: editor/plugins/spatial_editor_plugin.cpp msgid "Keying is disabled (no key inserted)." -msgstr "" +msgstr "Animaation avainnus on pois päältä (avainta ei lisätty)." #: editor/plugins/spatial_editor_plugin.cpp msgid "Animation Key Inserted." @@ -5251,35 +5099,31 @@ msgstr "Animaatioavain lisätty." #: editor/plugins/spatial_editor_plugin.cpp msgid "Objects Drawn" -msgstr "" +msgstr "Objekteja piirretty" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Material Changes" -msgstr "Päivitä muutokset" +msgstr "Materiaalimuutokset" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Shader Changes" -msgstr "Päivitä muutokset" +msgstr "Sävytinmuutokset" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Surface Changes" -msgstr "Päivitä muutokset" +msgstr "Pintamuutokset" #: editor/plugins/spatial_editor_plugin.cpp msgid "Draw Calls" -msgstr "" +msgstr "Piirtokutsuja" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Vertices" -msgstr "Ominaisuudet:" +msgstr "Kärkipisteet" #: editor/plugins/spatial_editor_plugin.cpp msgid "FPS" -msgstr "" +msgstr "FPS" #: editor/plugins/spatial_editor_plugin.cpp msgid "Top View." @@ -5322,9 +5166,8 @@ msgid "Rear View." msgstr "Takanäkymä." #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Rear" -msgstr "Taka/perä" +msgstr "Taka" #: editor/plugins/spatial_editor_plugin.cpp msgid "Align with view" @@ -5336,11 +5179,11 @@ msgstr "Asia kunnossa :(" #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "No parent to instance a child at." -msgstr "" +msgstr "Isäntää, jonka alle ilmentymä luodaan, ei ole valittu." #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "This operation requires a single selected node." -msgstr "Tämä toiminto vaatii yhden valitun noden." +msgstr "Tämä toiminto vaatii yhden valitun solmun." #: editor/plugins/spatial_editor_plugin.cpp msgid "Display Normal" @@ -5352,85 +5195,75 @@ msgstr "Näytä rautalankamalli" #: editor/plugins/spatial_editor_plugin.cpp msgid "Display Overdraw" -msgstr "" +msgstr "Näytä ylipiirto" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Display Unshaded" -msgstr "Näytä varjoton" +msgstr "Näytä sävyttämätön" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "View Environment" -msgstr "Ympäristö" +msgstr "Näytä ympäristö" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "View Gizmos" -msgstr "Näytä ruudukko" +msgstr "Näytä muokkaimet" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Information" msgstr "Näytä tiedot" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "View FPS" -msgstr " Tiedostot" +msgstr "Näytä FPS" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Half Resolution" -msgstr "Skaalaa valintaa" +msgstr "Puolikas näyttötarkkuus" #: editor/plugins/spatial_editor_plugin.cpp msgid "Audio Listener" -msgstr "" +msgstr "Äänikuuntelija" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Doppler Enable" -msgstr "Ota käyttöön" +msgstr "Doppler käytössä" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Left" -msgstr "" +msgstr "Liiku vasemmalle" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Right" -msgstr "" +msgstr "Liiku oikealle" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Freelook Forward" -msgstr "Mene eteenpäin" +msgstr "Liiku eteenpäin" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Freelook Backwards" -msgstr "Taaksepäin" +msgstr "Liiku taaksepäin" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Up" -msgstr "" +msgstr "Liiku ylös" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Freelook Down" -msgstr "Rulla alas." +msgstr "Liiku alas" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Speed Modifier" -msgstr "" +msgstr "Liikkumisen nopeussäädin" #: editor/plugins/spatial_editor_plugin.cpp msgid "XForm Dialog" -msgstr "" +msgstr "XForm-ikkuna" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Select Mode (Q)" -msgstr "Valitse tila" +msgstr "Valintatila (Q)" #: editor/plugins/spatial_editor_plugin.cpp msgid "" @@ -5438,6 +5271,9 @@ msgid "" "Alt+Drag: Move\n" "Alt+RMB: Depth list selection" msgstr "" +"Vedä: Kierrä\n" +"Alt + Vedä: Siirrä\n" +"Alt + Hiiren oikea painike: Syvyyslistan valinta" #: editor/plugins/spatial_editor_plugin.cpp msgid "Move Mode (W)" @@ -5456,14 +5292,12 @@ msgid "Local Coords" msgstr "Paikalliset koordinaatit" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Local Space Mode (%s)" -msgstr "Skaalaustila (R)" +msgstr "Paikallisavaruuden tila (%s)" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Snap Mode (%s)" -msgstr "Tarttumisen tila:" +msgstr "Tarttumisen tila (%s)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Bottom View" @@ -5510,41 +5344,32 @@ msgid "Align Selection With View" msgstr "Kohdista valinta näkymään" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Tool Select" -msgstr "Valitse" +msgstr "Valintatyökalu" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Tool Move" -msgstr "Siirrä" +msgstr "Siirtotyökalu" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Tool Rotate" -msgstr "Ctrl: Pyöritä/kierrä" +msgstr "Kiertotyökalu" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Tool Scale" -msgstr "Skaalaus:" +msgstr "Skaalaustyökalu" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Toggle Freelook" -msgstr "Siirry koko näytön tilaan" +msgstr "Kytke liikkuminen päälle/pois" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform" msgstr "Muunna" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Määrittele tarttuminen..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "" +msgid "Transform Dialog..." +msgstr "Muunnosikkuna..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5585,7 +5410,7 @@ msgstr "Asetukset" #: editor/plugins/spatial_editor_plugin.cpp msgid "Skeleton Gizmo visibility" -msgstr "" +msgstr "Luurankomuokkaimen näkyvyys" #: editor/plugins/spatial_editor_plugin.cpp msgid "Snap Settings" @@ -5613,19 +5438,19 @@ msgstr "Näkökentän perspektiivi (ast.):" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Z-Near:" -msgstr "Minimi etäisyys:" +msgstr "Pienin etäisyys:" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Z-Far:" -msgstr "Maksimi etäisyys:" +msgstr "Suurin etäisyys:" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Change" -msgstr "" +msgstr "Muunnoksen muutos" #: editor/plugins/spatial_editor_plugin.cpp msgid "Translate:" -msgstr "Käännä:" +msgstr "Siirrä:" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotate (deg.):" @@ -5637,7 +5462,7 @@ msgstr "Skaalaa (kuvasuhde):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Type" -msgstr "" +msgstr "Muunnoksen tyyppi" #: editor/plugins/spatial_editor_plugin.cpp msgid "Pre" @@ -5657,7 +5482,7 @@ msgstr "Lisää frame" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Resource clipboard is empty or not a texture!" -msgstr "" +msgstr "Resurssileikepöytä on tyhjä tai ei sisällä tekstuuria!" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Paste Frame" @@ -5688,9 +5513,8 @@ msgid "Speed (FPS):" msgstr "Nopeus (FPS):" #: editor/plugins/sprite_frames_editor_plugin.cpp -#, fuzzy msgid "Loop" -msgstr "Toisto" +msgstr "Toista" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Animation Frames" @@ -5705,33 +5529,28 @@ msgid "Insert Empty (After)" msgstr "Syötä tyhjä (jälkeen)" #: editor/plugins/sprite_frames_editor_plugin.cpp -#, fuzzy msgid "Move (Before)" -msgstr "Poista Node(t)" +msgstr "Siirrä (ennen)" #: editor/plugins/sprite_frames_editor_plugin.cpp -#, fuzzy msgid "Move (After)" -msgstr "Siirry vasemmalle" +msgstr "Siirrä (jälkeen)" #: editor/plugins/sprite_frames_editor_plugin.cpp -#, fuzzy msgid "SpriteFrames" -msgstr "Pinoa Framet" +msgstr "SpriteFrames" #: editor/plugins/style_box_editor_plugin.cpp msgid "StyleBox Preview:" -msgstr "StyleBox:in esikatselu:" +msgstr "StyleBoxin esikatselu:" #: editor/plugins/style_box_editor_plugin.cpp -#, fuzzy msgid "StyleBox" -msgstr "Tyyli" +msgstr "StyleBox" #: editor/plugins/texture_region_editor_plugin.cpp -#, fuzzy msgid "Set Region Rect" -msgstr "Tekstuurialue" +msgstr "Aseta alueen suorakulmio" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Snap Mode:" @@ -5781,7 +5600,6 @@ msgid "Can't save theme to file:" msgstr "Teemaa ei voi tallentaa tiedostoon:" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Add All Items" msgstr "Lisää kaikki" @@ -5795,29 +5613,28 @@ msgid "Remove Item" msgstr "Poista" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Remove All Items" -msgstr "Poista valitut" +msgstr "Poista kaikki" #: editor/plugins/theme_editor_plugin.cpp msgid "Remove All" msgstr "Poista kaikki" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Muokkaa teemaa..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." -msgstr "Teeman muokkaus." +msgstr "Teeman muokkausvalikko." #: editor/plugins/theme_editor_plugin.cpp msgid "Add Class Items" -msgstr "" +msgstr "Lisää luokka" #: editor/plugins/theme_editor_plugin.cpp msgid "Remove Class Items" -msgstr "" +msgstr "Poista luokka" #: editor/plugins/theme_editor_plugin.cpp msgid "Create Empty Template" @@ -5833,15 +5650,15 @@ msgstr "Luo nykyisestä editorin teemasta" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio1" -msgstr "" +msgstr "Valintaruudun valinta 1" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio2" -msgstr "" +msgstr "Valintaruudun valinta 2" #: editor/plugins/theme_editor_plugin.cpp msgid "Item" -msgstr "" +msgstr "Osanen" #: editor/plugins/theme_editor_plugin.cpp msgid "Check Item" @@ -5852,32 +5669,28 @@ msgid "Checked Item" msgstr "Valittu" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Lisää" +msgstr "Valintapainike" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Valittu" +msgstr "Valittu valintapainike" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" msgstr "On" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Many" -msgstr "Moni(a)/Monta" +msgstr "Useita" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp msgid "Options" -msgstr "Asetukset" +msgstr "Asetuksia" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy -msgid "Have,Many,Several,Options!" -msgstr "On,Monia,Useita,Asetuksia" +msgid "Has,Many,Options" +msgstr "On,Useita,Asetuksia" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5916,13 +5729,12 @@ msgid "Theme" msgstr "Teema" #: editor/plugins/tile_map_editor_plugin.cpp -#, fuzzy msgid "Erase Selection" -msgstr "Framen valinta" +msgstr "Tyhjennä valittu alue" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Paint TileMap" -msgstr "" +msgstr "Täytä ruudukko" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Line Draw" @@ -5930,7 +5742,7 @@ msgstr "Viivan piirto" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rectangle Paint" -msgstr "" +msgstr "Suorakaidetäyttö" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Bucket Fill" @@ -5938,19 +5750,19 @@ msgstr "Täyttö" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Erase TileMap" -msgstr "" +msgstr "Tyhjennä ruudukko" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Erase selection" -msgstr "" +msgstr "Tyhjennä valinta" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Find tile" -msgstr "Etsi tile" +msgstr "Etsi ruutu" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Transpose" -msgstr "" +msgstr "Transponoi" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Mirror X" @@ -5961,13 +5773,12 @@ msgid "Mirror Y" msgstr "Peilaa Y" #: editor/plugins/tile_map_editor_plugin.cpp -#, fuzzy msgid "Paint Tile" -msgstr "Poimi tile" +msgstr "Maalaa ruutu" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Pick Tile" -msgstr "Poimi tile" +msgstr "Poimi ruutu" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 0 degrees" @@ -5987,7 +5798,7 @@ msgstr "Käännä 270 astetta" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Could not find tile:" -msgstr "Tileä ei löytynyt:" +msgstr "Ruutua ei löytynyt:" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Item name or ID:" @@ -6002,9 +5813,8 @@ msgid "Merge from scene?" msgstr "Yhdistä skenestä?" #: editor/plugins/tile_set_editor_plugin.cpp -#, fuzzy msgid "Tile Set" -msgstr "Vie tileset" +msgstr "Ruutuvalikoima" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -6012,37 +5822,39 @@ msgstr "Luo skenestä" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Merge from Scene" -msgstr "" +msgstr "Yhdistä skenestä" #: editor/plugins/tile_set_editor_plugin.cpp editor/script_editor_debugger.cpp msgid "Error" msgstr "Virhe" #: editor/plugins/tile_set_editor_plugin.cpp -#, fuzzy msgid "Autotiles" -msgstr "Jaa automaattisesti" +msgstr "Automaattiruudutus" #: editor/plugins/tile_set_editor_plugin.cpp msgid "" "Select sub-tile to use as icon, this will be also used on invalid autotile " "bindings." msgstr "" +"Valitse aliruutu, jota käytetään ikonina ja myös virheellisten " +"automaattiruudutusten ilmaisemiseen." #: editor/plugins/tile_set_editor_plugin.cpp msgid "" "LMB: set bit on.\n" "RMB: set bit off." msgstr "" +"Hiiren vasen: aseta bitti päälle.\n" +"Hiiren oikea: aseta bitti pois päältä." #: editor/plugins/tile_set_editor_plugin.cpp -#, fuzzy msgid "Select current edited sub-tile." -msgstr "Tallenna tällä hetkellä muokattu resurssi." +msgstr "Valitse muokattavana oleva aliruutu." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Select sub-tile to change its priority." -msgstr "" +msgstr "Valitse aliruutu muuttaaksesi sen tärkeyttä." #: editor/progress_dialog.cpp scene/gui/dialogs.cpp msgid "Cancel" @@ -6054,7 +5866,7 @@ msgstr "Suoritettava" #: editor/project_export.cpp msgid "Delete patch '%s' from list?" -msgstr "" +msgstr "Poista päivitys '%s' listasta?" #: editor/project_export.cpp msgid "Delete preset '%s'?" @@ -6062,14 +5874,14 @@ msgstr "Poista esiasetus '%s'?" #: editor/project_export.cpp msgid "Export templates for this platform are missing/corrupted: " -msgstr "" +msgstr "Vientimallit tälle alustalle puuttuvat tai ovat viallisia: " #: editor/project_export.cpp msgid "Presets" msgstr "Esiasetukset" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Lisää..." #: editor/project_export.cpp @@ -6082,7 +5894,7 @@ msgstr "Vie kaikki projektin resurssit" #: editor/project_export.cpp msgid "Export selected scenes (and dependencies)" -msgstr "Vie valitut Scenet (ja riippuvuudet)" +msgstr "Vie valitut skenet (ja riippuvuudet)" #: editor/project_export.cpp msgid "Export selected resources (and dependencies)" @@ -6112,25 +5924,23 @@ msgstr "" #: editor/project_export.cpp msgid "Patches" -msgstr "" +msgstr "Päivitykset" #: editor/project_export.cpp msgid "Make Patch" -msgstr "Tee patchi" +msgstr "Luo päivitys" #: editor/project_export.cpp -#, fuzzy msgid "Features" -msgstr "Tekstuuri" +msgstr "Ominaisuudet" #: editor/project_export.cpp msgid "Custom (comma-separated):" -msgstr "" +msgstr "Mukautettu (pilkulla erotettu):" #: editor/project_export.cpp -#, fuzzy msgid "Feature List:" -msgstr "Metodilista:" +msgstr "Ominaisuuslista:" #: editor/project_export.cpp msgid "Export PCK/Zip" @@ -6142,7 +5952,7 @@ msgstr "Tälle alustalle ei löytynyt vientipohjia:" #: editor/project_export.cpp msgid "Export templates for this platform are missing/corrupted:" -msgstr "" +msgstr "Vientimallit tälle alustalle puuttuvat tai ovat viallisia:" #: editor/project_export.cpp msgid "Export With Debug" @@ -6157,22 +5967,24 @@ msgid "Please choose a 'project.godot' file." msgstr "Ole hyvä ja valitse 'project.godot' tiedosto." #: editor/project_manager.cpp -#, fuzzy msgid "Please choose an empty folder." -msgstr "Ole hyvä ja valitse 'project.godot' tiedosto." +msgstr "Ole hyvä ja valitse tyhjä kansio." #: editor/project_manager.cpp msgid "Imported Project" msgstr "Tuotu projekti" #: editor/project_manager.cpp -#, fuzzy +msgid "Invalid Project Name." +msgstr "Virheellinen projektin nimi." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Kansiota ei voitu luoda." #: editor/project_manager.cpp msgid "There is already a folder in this path with the specified name." -msgstr "" +msgstr "Polusta löytyy jo kansio annetulla nimellä." #: editor/project_manager.cpp msgid "It would be a good idea to name your project." @@ -6183,21 +5995,20 @@ msgid "Invalid project path (changed anything?)." msgstr "Virheellinen projektin polku (muuttuiko mikään?)." #: editor/project_manager.cpp -#, fuzzy msgid "" "Couldn't load project.godot in project path (error %d). It may be missing or " "corrupted." -msgstr "Ei voitu luoda godot.cfg -tiedostoa projektin polkuun." +msgstr "" +"Tiedoston project.godot lataus projektin polusta epäonnistui (virhe %d). Se " +"saattaa puuttua tai olla vioittunut." #: editor/project_manager.cpp -#, fuzzy msgid "Couldn't edit project.godot in project path." -msgstr "Ei voitu luoda godot.cfg -tiedostoa projektin polkuun." +msgstr "Ei voitu muokata project.godot tiedostoa projektin polussa." #: editor/project_manager.cpp -#, fuzzy msgid "Couldn't create project.godot in project path." -msgstr "Ei voitu luoda godot.cfg -tiedostoa projektin polkuun." +msgstr "Tiedoston project.godot luonti projektin polkuun epäonnistui." #: editor/project_manager.cpp msgid "The following files failed extraction from package:" @@ -6216,34 +6027,30 @@ msgid "Import Existing Project" msgstr "Tuo olemassaoleva projekti" #: editor/project_manager.cpp -#, fuzzy msgid "Import & Edit" -msgstr "Tuo & Avaa" +msgstr "Tuo ja muokkaa" #: editor/project_manager.cpp msgid "Create New Project" msgstr "Luo uusi projekti" #: editor/project_manager.cpp -#, fuzzy msgid "Create & Edit" -msgstr "Luo säteilijä/lähetin" +msgstr "Luo ja muokkaa" #: editor/project_manager.cpp msgid "Install Project:" msgstr "Asenna projekti:" #: editor/project_manager.cpp -#, fuzzy msgid "Install & Edit" -msgstr "Asenna" +msgstr "Asenna ja muokkaa" #: editor/project_manager.cpp msgid "Project Name:" msgstr "Projektin nimi:" #: editor/project_manager.cpp -#, fuzzy msgid "Create folder" msgstr "Luo kansio" @@ -6273,9 +6080,9 @@ msgid "" "Please edit the project and set the main scene in \"Project Settings\" under " "the \"Application\" category." msgstr "" -"Projektia ei voida suorittaa: pääsceneä ei ole määritetty.\n" -"Ole hyvä ja muokkaa projektia ja aseta pääscene projektin asetuksista " -"\"Application\" -kategoriasta." +"Projektia ei voida suorittaa: pääskeneä ei ole määritetty.\n" +"Ole hyvä ja muokkaa projektia ja aseta pääskene projektin asetuksista " +"\"Application\"-kategoriasta." #: editor/project_manager.cpp msgid "" @@ -6337,14 +6144,12 @@ msgid "Exit" msgstr "Poistu" #: editor/project_manager.cpp -#, fuzzy msgid "Restart Now" -msgstr "Käynnistä uudelleen (s):" +msgstr "Käynnistä uudelleen nyt" #: editor/project_manager.cpp -#, fuzzy msgid "Can't run project" -msgstr "Yhdistä..." +msgstr "Projektia ei voida käynnistää" #: editor/project_manager.cpp msgid "" @@ -6356,7 +6161,7 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "Key " -msgstr "Näppäin... " +msgstr "Näppäin " #: editor/project_settings_editor.cpp msgid "Joy Button" @@ -6372,9 +6177,11 @@ msgstr "Hiiren painike" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Virheellinen toiminnon nimi. Se ei voi olla tyhjä eikä voi sisältää merkkejä " +"'/', ':', '=', '\\' tai '\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6382,11 +6189,11 @@ msgstr "Tapahtuma '%s' on jo olemassa!" #: editor/project_settings_editor.cpp msgid "Rename Input Action Event" -msgstr "Nimeä syöttötapahtuma uudelleen" +msgstr "Nimeä syötetoiminto uudelleen" #: editor/project_settings_editor.cpp msgid "Add Input Action Event" -msgstr "Lisää syöttötapahtuma" +msgstr "Lisää syötetoiminnon tapahtuma" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "Shift+" @@ -6401,7 +6208,7 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "Paina näppäintä..." #: editor/project_settings_editor.cpp @@ -6457,18 +6264,16 @@ msgid "Joypad Button Index:" msgstr "Ohjaimen painikkeen indeksi:" #: editor/project_settings_editor.cpp -#, fuzzy msgid "Erase Input Action" -msgstr "Tyhjennä syöttötapahtuma" +msgstr "Tyhjennä syötetoiminto" #: editor/project_settings_editor.cpp msgid "Erase Input Action Event" -msgstr "Tyhjennä syöttötapahtuma" +msgstr "Tyhjennä syötetoiminnon tapahtuma" #: editor/project_settings_editor.cpp -#, fuzzy msgid "Add Event" -msgstr "Lisää tyhjä" +msgstr "Lisää tapahtuma" #: editor/project_settings_editor.cpp msgid "Device" @@ -6504,7 +6309,7 @@ msgstr "Lisää yleinen ominaisuus" #: editor/project_settings_editor.cpp msgid "Select a setting item first!" -msgstr "" +msgstr "Valitse asetus ensin!" #: editor/project_settings_editor.cpp msgid "No property '%s' exists." @@ -6512,12 +6317,11 @@ msgstr "Ominaisuutta '%s' ei löytynyt." #: editor/project_settings_editor.cpp msgid "Setting '%s' is internal, and it can't be deleted." -msgstr "" +msgstr "Asetus '%s' on sisäinen, eikä sitä voi poistaa." #: editor/project_settings_editor.cpp -#, fuzzy msgid "Delete Item" -msgstr "Poista syöte" +msgstr "Poista kohde" #: editor/project_settings_editor.cpp msgid "Already existing" @@ -6525,7 +6329,7 @@ msgstr "On jo olemassa" #: editor/project_settings_editor.cpp msgid "Add Input Action" -msgstr "Lisää syöttötapahtuma" +msgstr "Lisää syötetapahtuma" #: editor/project_settings_editor.cpp msgid "Error saving settings." @@ -6537,7 +6341,7 @@ msgstr "Asetukset tallennettu onnistuneesti." #: editor/project_settings_editor.cpp msgid "Override for Feature" -msgstr "" +msgstr "Ominaisuuden ohitus" #: editor/project_settings_editor.cpp msgid "Add Translation" @@ -6549,53 +6353,51 @@ msgstr "Poista käännös" #: editor/project_settings_editor.cpp msgid "Add Remapped Path" -msgstr "" +msgstr "Lisää korvaavuuspolku" #: editor/project_settings_editor.cpp msgid "Resource Remap Add Remap" -msgstr "" +msgstr "Lisää resurssin korvaavuus" #: editor/project_settings_editor.cpp msgid "Change Resource Remap Language" -msgstr "" +msgstr "Vaihda resurssin korvaavuuskieli" #: editor/project_settings_editor.cpp msgid "Remove Resource Remap" -msgstr "" +msgstr "Poista resurssin korvaavuus" #: editor/project_settings_editor.cpp msgid "Remove Resource Remap Option" -msgstr "" +msgstr "Poista resurssin korvaavuusvalinta" #: editor/project_settings_editor.cpp -#, fuzzy msgid "Changed Locale Filter" -msgstr "Muuta kameran kokoa" +msgstr "Vaihdettu kielisuodatin" #: editor/project_settings_editor.cpp msgid "Changed Locale Filter Mode" -msgstr "" +msgstr "Vaihdettu kielisuodattimen tila" #: editor/project_settings_editor.cpp -#, fuzzy msgid "Project Settings (project.godot)" -msgstr "Projektin asetukset" +msgstr "Projektin asetukset (project.godot)" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "Yleinen" +msgstr "Yleistä" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "Ominaisuus:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "" +msgid "Override For..." +msgstr "Ohita alustalle..." #: editor/project_settings_editor.cpp msgid "Input Map" -msgstr "" +msgstr "Syötekartta" #: editor/project_settings_editor.cpp msgid "Action:" @@ -6623,7 +6425,7 @@ msgstr "Käännökset:" #: editor/project_settings_editor.cpp msgid "Remaps" -msgstr "" +msgstr "Korvaavuudet" #: editor/project_settings_editor.cpp msgid "Resources:" @@ -6631,11 +6433,11 @@ msgstr "Resurssit:" #: editor/project_settings_editor.cpp msgid "Remaps by Locale:" -msgstr "" +msgstr "Korvaavuudet kielikohtaisesti:" #: editor/project_settings_editor.cpp msgid "Locale" -msgstr "Kieli" +msgstr "Kielialue" #: editor/project_settings_editor.cpp msgid "Locales Filter" @@ -6659,7 +6461,7 @@ msgstr "Kielet:" #: editor/project_settings_editor.cpp msgid "AutoLoad" -msgstr "Lataa automaattisesti" +msgstr "Automaattilataus" #: editor/property_editor.cpp msgid "Pick a Viewport" @@ -6667,40 +6469,39 @@ msgstr "Valitse näyttöruutu" #: editor/property_editor.cpp msgid "Ease In" -msgstr "" +msgstr "Kiihdytä alussa" #: editor/property_editor.cpp msgid "Ease Out" -msgstr "" +msgstr "Hidasta lopussa" #: editor/property_editor.cpp msgid "Zero" -msgstr "" +msgstr "Nolla" #: editor/property_editor.cpp msgid "Easing In-Out" -msgstr "" +msgstr "Helpotus sisään-ulos" #: editor/property_editor.cpp msgid "Easing Out-In" -msgstr "" +msgstr "Helpotus ulos-sisään" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "Tiedosto..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "Hakemisto..." #: editor/property_editor.cpp msgid "Assign" -msgstr "" +msgstr "Aseta" #: editor/property_editor.cpp -#, fuzzy msgid "Select Node" -msgstr "Valitse Node" +msgstr "Valitse solmu" #: editor/property_editor.cpp msgid "New Script" @@ -6711,9 +6512,8 @@ msgid "New %s" msgstr "Uusi %s" #: editor/property_editor.cpp -#, fuzzy msgid "Make Unique" -msgstr "Tee luut" +msgstr "Tee yksilölliseksi" #: editor/property_editor.cpp msgid "Show in File System" @@ -6728,26 +6528,24 @@ msgid "Error loading file: Not a resource!" msgstr "Virhe ladattaessa tiedostoa: Ei ole resurssi!" #: editor/property_editor.cpp -#, fuzzy msgid "Selected node is not a Viewport!" -msgstr "Valitse tuotava(t) node(t)" +msgstr "Valittu solmu ei ole Viewport!" #: editor/property_editor.cpp msgid "Pick a Node" -msgstr "Poimi node" +msgstr "Poimi solmu" #: editor/property_editor.cpp msgid "Bit %d, val %d." -msgstr "" +msgstr "Bitti %d, arvo %d." #: editor/property_editor.cpp msgid "On" msgstr "Päällä" #: editor/property_editor.cpp -#, fuzzy msgid "[Empty]" -msgstr "Lisää tyhjä" +msgstr "[Tyhjä]" #: editor/property_editor.cpp modules/visual_script/visual_script_editor.cpp msgid "Set" @@ -6771,15 +6569,15 @@ msgstr "Valitse metodi" #: editor/pvrtc_compress.cpp msgid "Could not execute PVRTC tool:" -msgstr "" +msgstr "PVRTC-työkalun suoritus ei onnistunut:" #: editor/pvrtc_compress.cpp msgid "Can't load back converted image using PVRTC tool:" -msgstr "" +msgstr "Muunnettua kuva ei voitu ladata takaisin PVRTC-työkalulla:" #: editor/reparent_dialog.cpp editor/scene_tree_dock.cpp msgid "Reparent Node" -msgstr "Vaihda noden isäntää" +msgstr "Vaihda solmun isäntää" #: editor/reparent_dialog.cpp msgid "Reparent Location (Select new Parent):" @@ -6787,7 +6585,7 @@ msgstr "Valitse uusi isäntä:" #: editor/reparent_dialog.cpp msgid "Keep Global Transform" -msgstr "" +msgstr "Pidä globaali muunnos" #: editor/reparent_dialog.cpp editor/scene_tree_dock.cpp msgid "Reparent" @@ -6795,7 +6593,7 @@ msgstr "Uusi isäntä" #: editor/run_settings_dialog.cpp msgid "Run Mode:" -msgstr "" +msgstr "Käynnistystila:" #: editor/run_settings_dialog.cpp msgid "Current Scene" @@ -6811,74 +6609,76 @@ msgstr "Pääskenen argumentit:" #: editor/run_settings_dialog.cpp msgid "Scene Run Settings" -msgstr "Scenen suorittamisasetukset" +msgstr "Skenen suorittamisasetukset" #: editor/scene_tree_dock.cpp editor/script_create_dialog.cpp #: scene/gui/dialogs.cpp msgid "OK" -msgstr "" +msgstr "OK" #: editor/scene_tree_dock.cpp msgid "No parent to instance the scenes at." -msgstr "" +msgstr "Solmua, jonka alle skenen ilmentymä luodaan, ei ole valittu." #: editor/scene_tree_dock.cpp msgid "Error loading scene from %s" -msgstr "" +msgstr "Virhe ladattaessa skeneä paikasta %s" #: editor/scene_tree_dock.cpp msgid "" "Cannot instance the scene '%s' because the current scene exists within one " "of its nodes." msgstr "" +"Skenestä '%s' ei voida luoda ilmentymää, koska nykyinen skene on olemassa " +"jossakin sen solmuista." #: editor/scene_tree_dock.cpp msgid "Instance Scene(s)" -msgstr "" +msgstr "Luo ilmentymä skenestä tai skeneistä" #: editor/scene_tree_dock.cpp msgid "This operation can't be done on the tree root." -msgstr "" +msgstr "Tätä toimenpidettä ei voi tehdä puun juurelle." #: editor/scene_tree_dock.cpp msgid "Move Node In Parent" -msgstr "" +msgstr "Siirrä solmu isännän alle" #: editor/scene_tree_dock.cpp msgid "Move Nodes In Parent" -msgstr "" +msgstr "Siirrä solmut isännän alle" #: editor/scene_tree_dock.cpp msgid "Duplicate Node(s)" -msgstr "Monista node(t)" +msgstr "Kahdenna solmu(t)" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)?" -msgstr "Poista Node(t)?" +msgstr "Poista solmu(t)?" #: editor/scene_tree_dock.cpp msgid "Can not perform with the root node." -msgstr "" +msgstr "Ei voi tehdä juurisolmulle." #: editor/scene_tree_dock.cpp msgid "This operation can't be done on instanced scenes." -msgstr "" +msgstr "Tätä toimintoa ei voi tehdä skenejen ilmentymille." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Tallenna uusi scene nimellä..." +msgid "Save New Scene As..." +msgstr "Tallenna uusi skene nimellä..." #: editor/scene_tree_dock.cpp msgid "Editable Children" -msgstr "" +msgstr "Muokattavat alisolmut" #: editor/scene_tree_dock.cpp msgid "Load As Placeholder" -msgstr "" +msgstr "Lataa paikanpitäjäksi" #: editor/scene_tree_dock.cpp msgid "Discard Instancing" -msgstr "" +msgstr "Hylkää ilmentymä" #: editor/scene_tree_dock.cpp msgid "Makes Sense!" @@ -6886,50 +6686,51 @@ msgstr "Käy järkeen!" #: editor/scene_tree_dock.cpp msgid "Can't operate on nodes from a foreign scene!" -msgstr "Ei voida käyttää ulkopuolisen scenen nodeja!" +msgstr "Ei voida käyttää ulkopuolisen skenen solmuja!" #: editor/scene_tree_dock.cpp msgid "Can't operate on nodes the current scene inherits from!" -msgstr "Ei voida käyttää nodeja, jotka periytyvät nykyisestä scenestä!" +msgstr "Ei voida käyttää solmuja, joista nykyinen skene periytyy!" #: editor/scene_tree_dock.cpp msgid "Remove Node(s)" -msgstr "Poista Node(t)" +msgstr "Poista solmu(t)" #: editor/scene_tree_dock.cpp msgid "" "Couldn't save new scene. Likely dependencies (instances) couldn't be " "satisfied." msgstr "" +"Skeneä ei voitu tallentaa. Mahdollisia riippuvuuksia (ilmentymiä) ei voida " +"toteuttaa." #: editor/scene_tree_dock.cpp msgid "Error saving scene." -msgstr "Virhe tallennettaessa sceneä." +msgstr "Virhe tallennettaessa skeneä." #: editor/scene_tree_dock.cpp msgid "Error duplicating scene to save it." -msgstr "" +msgstr "Virhe kahdennettaessa skeneä sen tallentamiseksi." #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Sub-Resources" -msgstr "Resurssit" +msgstr "Aliresurssit" #: editor/scene_tree_dock.cpp msgid "Clear Inheritance" -msgstr "" +msgstr "Poista perintä" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)" -msgstr "Poista Node(t)" +msgstr "Poista solmu(t)" #: editor/scene_tree_dock.cpp msgid "Add Child Node" -msgstr "Lisää lapsinode" +msgstr "Lisää alisolmu" #: editor/scene_tree_dock.cpp msgid "Instance Child Scene" -msgstr "" +msgstr "Luo aliskenen ilmentymä" #: editor/scene_tree_dock.cpp msgid "Change Type" @@ -6945,15 +6746,15 @@ msgstr "Tyhjennä skripti" #: editor/scene_tree_dock.cpp msgid "Merge From Scene" -msgstr "Yhdistä scenestä" +msgstr "Yhdistä skenestä" #: editor/scene_tree_dock.cpp msgid "Save Branch as Scene" -msgstr "" +msgstr "Tallenna haara skenenä" #: editor/scene_tree_dock.cpp msgid "Copy Node Path" -msgstr "Kopioi Noden polku" +msgstr "Kopioi solmun polku" #: editor/scene_tree_dock.cpp msgid "Delete (No Confirm)" @@ -6961,40 +6762,39 @@ msgstr "Poista (ei varmistusta)" #: editor/scene_tree_dock.cpp msgid "Add/Create a New Node" -msgstr "Lisää/Luo uusi Node" +msgstr "Lisää/Luo uusi solmu" #: editor/scene_tree_dock.cpp msgid "" "Instance a scene file as a Node. Creates an inherited scene if no root node " "exists." msgstr "" +"Luo skenetiedostosta ilmentymän solmuksi. Luo periytetyn skenen jos " +"juurisolmua ei ole olemassa." #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Filter nodes" -msgstr "Suodattimet" +msgstr "Suodata solmuja" #: editor/scene_tree_dock.cpp msgid "Attach a new or existing script for the selected node." -msgstr "" +msgstr "Liitä uusi tai olemassa oleva skripti valitulle solmulle." #: editor/scene_tree_dock.cpp msgid "Clear a script for the selected node." -msgstr "" +msgstr "Poista skripti valitulta solmulta." #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Remote" -msgstr "Poista" +msgstr "Etäinen" #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Local" -msgstr "Skaalaus:" +msgstr "Paikallinen" #: editor/scene_tree_dock.cpp msgid "Clear Inheritance? (No Undo!)" -msgstr "" +msgstr "Poistetaanko perintä? (Ei voi perua!)" #: editor/scene_tree_dock.cpp msgid "Clear!" @@ -7002,84 +6802,91 @@ msgstr "Tyhjennä!" #: editor/scene_tree_editor.cpp msgid "Toggle Spatial Visible" -msgstr "" +msgstr "Aseta Spatial näkyvyys päälle/pois" #: editor/scene_tree_editor.cpp msgid "Toggle CanvasItem Visible" -msgstr "" +msgstr "Aseta CanvasItem näkyvyys päälle/pois" #: editor/scene_tree_editor.cpp msgid "Node configuration warning:" -msgstr "" +msgstr "Solmun konfiguroinnin varoitus:" #: editor/scene_tree_editor.cpp msgid "" "Node has connection(s) and group(s)\n" "Click to show signals dock." msgstr "" +"Solmulla on liitäntöjä ja ryhmiä\n" +"Napsauta näyttääksesi signaalitelakan." #: editor/scene_tree_editor.cpp msgid "" "Node has connections.\n" "Click to show signals dock." msgstr "" +"Solmulla on liitäntöjä.\n" +"Napsauta näyttääksesi signaalitelakan." #: editor/scene_tree_editor.cpp msgid "" "Node is in group(s).\n" "Click to show groups dock." msgstr "" +"Solmu kuuluu ryhmään.\n" +"Napsauta näyttääksesi ryhmätelakan." #: editor/scene_tree_editor.cpp -#, fuzzy msgid "Open script" -msgstr "Seuraava skripti" +msgstr "Avaa skripti" #: editor/scene_tree_editor.cpp msgid "" "Node is locked.\n" "Click to unlock" msgstr "" +"Solmu on lukittu.\n" +"Napsauta lukituksen avaamiseksi" #: editor/scene_tree_editor.cpp msgid "" "Children are not selectable.\n" "Click to make selectable" msgstr "" +"Alisolmut eivät ole valittavissa.\n" +"Napsauta niiden tekemiseksi valittavaksi" #: editor/scene_tree_editor.cpp msgid "Toggle Visibility" -msgstr "" +msgstr "Aseta näkyvyys" #: editor/scene_tree_editor.cpp msgid "Invalid node name, the following characters are not allowed:" -msgstr "" +msgstr "Virheellinen solmun nimi, seuraavat merkit eivät ole sallittuja:" #: editor/scene_tree_editor.cpp msgid "Rename Node" -msgstr "Nimeä Node uudelleen" +msgstr "Nimeä solmu uudelleen" #: editor/scene_tree_editor.cpp msgid "Scene Tree (Nodes):" -msgstr "" +msgstr "Skenepuu (solmut):" #: editor/scene_tree_editor.cpp msgid "Node Configuration Warning!" -msgstr "" +msgstr "Solmun konfigurointivaroitus!" #: editor/scene_tree_editor.cpp msgid "Select a Node" -msgstr "Valitse Node" +msgstr "Valitse solmu" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Error loading template '%s'" -msgstr "Virhe ladattaessa kuvaa:" +msgstr "Virhe ladattaessa mallia '%s'" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Error - Could not create script in filesystem." -msgstr "Ei voitu luoda skriptiä tiedostojärjestelmään." +msgstr "Virhe - Ei voitu luoda skriptiä tiedostojärjestelmään." #: editor/script_create_dialog.cpp msgid "Error loading script from %s" @@ -7087,7 +6894,7 @@ msgstr "Virhe ladattaessa skripti %s:stä" #: editor/script_create_dialog.cpp msgid "N/A" -msgstr "" +msgstr "Ei mitään" #: editor/script_create_dialog.cpp msgid "Path is empty" @@ -7099,16 +6906,15 @@ msgstr "Polku ei ole paikallinen" #: editor/script_create_dialog.cpp msgid "Invalid base path" -msgstr "" +msgstr "Virheellinen kantapolku" #: editor/script_create_dialog.cpp msgid "Directory of the same name exists" msgstr "Samanniminen hakemisto on jo olemassa" #: editor/script_create_dialog.cpp -#, fuzzy msgid "File exists, will be reused" -msgstr "Tiedosto on jo olemassa, korvaa?" +msgstr "Tiedosto on jo olemassa, käytetään uudelleen" #: editor/script_create_dialog.cpp msgid "Invalid extension" @@ -7116,12 +6922,11 @@ msgstr "Virheellinen laajennus" #: editor/script_create_dialog.cpp msgid "Wrong extension chosen" -msgstr "" +msgstr "Valittu väärä tiedostopääte" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Invalid Path" -msgstr "Virheellinen polku." +msgstr "Virheellinen polku" #: editor/script_create_dialog.cpp msgid "Invalid class name" @@ -7129,62 +6934,55 @@ msgstr "Virheellinen luokan nimi" #: editor/script_create_dialog.cpp msgid "Invalid inherited parent name or path" -msgstr "" +msgstr "Virheellinen peritty isännän nimi tai polku" #: editor/script_create_dialog.cpp msgid "Script valid" -msgstr "" +msgstr "Skripti kelpaa" #: editor/script_create_dialog.cpp msgid "Allowed: a-z, A-Z, 0-9 and _" -msgstr "" +msgstr "Sallittu: a-z, A-Z, 0-9 ja _" #: editor/script_create_dialog.cpp msgid "Built-in script (into scene file)" -msgstr "" +msgstr "Sisäänrakennettu skripti (skenetiedostoon)" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Create new script file" -msgstr "Luo uusi skripti" +msgstr "Luo uusi skriptitiedosto" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Load existing script file" -msgstr "Lataa olemassaoleva skripti" +msgstr "Lataa olemassaoleva skriptitiedosto" #: editor/script_create_dialog.cpp msgid "Language" msgstr "Kieli" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Inherits" -msgstr "Perii:" +msgstr "Perii" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Class Name" -msgstr "Luokan nimi:" +msgstr "Luokan nimi" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Template" -msgstr "Poista malli" +msgstr "Malli" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Built-in Script" msgstr "Sisäänrakennettu skripti" #: editor/script_create_dialog.cpp msgid "Attach Node Script" -msgstr "Liitä Noden skripti" +msgstr "Liitä solmun skripti" #: editor/script_editor_debugger.cpp -#, fuzzy msgid "Remote " -msgstr "Poista" +msgstr "Etäinen " #: editor/script_editor_debugger.cpp msgid "Bytes:" @@ -7208,7 +7006,7 @@ msgstr "Funktio:" #: editor/script_editor_debugger.cpp msgid "Pick one or more items from the list to display the graph." -msgstr "" +msgstr "Valitse yksi tai useampi kohde listasta näyttääksesi graafin." #: editor/script_editor_debugger.cpp modules/mono/editor/mono_bottom_panel.cpp msgid "Errors" @@ -7216,20 +7014,19 @@ msgstr "Virheet" #: editor/script_editor_debugger.cpp msgid "Child Process Connected" -msgstr "Lapsiprosessi yhdistetty" +msgstr "Aliprosessi yhdistetty" #: editor/script_editor_debugger.cpp -#, fuzzy msgid "Copy Error" -msgstr "Lataa virheet" +msgstr "Kopiointivirhe" #: editor/script_editor_debugger.cpp msgid "Inspect Previous Instance" -msgstr "Tarkastele edellistä instanssia" +msgstr "Tarkastele edellistä ilmentymää" #: editor/script_editor_debugger.cpp msgid "Inspect Next Instance" -msgstr "Tarkastele seuraavaa instanssia" +msgstr "Tarkastele seuraavaa ilmentymää" #: editor/script_editor_debugger.cpp msgid "Stack Frames" @@ -7245,15 +7042,15 @@ msgstr "Virheet:" #: editor/script_editor_debugger.cpp msgid "Stack Trace (if applicable):" -msgstr "" +msgstr "Metodipino (jos soveltuva):" #: editor/script_editor_debugger.cpp msgid "Profiler" -msgstr "" +msgstr "Profiloija" #: editor/script_editor_debugger.cpp msgid "Monitor" -msgstr "" +msgstr "Monitoroija" #: editor/script_editor_debugger.cpp msgid "Value" @@ -7261,11 +7058,11 @@ msgstr "Arvo" #: editor/script_editor_debugger.cpp msgid "Monitors" -msgstr "" +msgstr "Monitoroijat" #: editor/script_editor_debugger.cpp msgid "List of Video Memory Usage by Resource:" -msgstr "" +msgstr "Lista näyttömuistin käytöstä resurssikohtaisesti:" #: editor/script_editor_debugger.cpp msgid "Total:" @@ -7273,11 +7070,11 @@ msgstr "Yhteensä:" #: editor/script_editor_debugger.cpp msgid "Video Mem" -msgstr "" +msgstr "Näyttömuisti" #: editor/script_editor_debugger.cpp msgid "Resource Path" -msgstr "" +msgstr "Resurssipolku" #: editor/script_editor_debugger.cpp msgid "Type" @@ -7293,23 +7090,23 @@ msgstr "Käyttö" #: editor/script_editor_debugger.cpp msgid "Misc" -msgstr "" +msgstr "Sekalaista" #: editor/script_editor_debugger.cpp msgid "Clicked Control:" -msgstr "" +msgstr "Napsautettu kontrolli:" #: editor/script_editor_debugger.cpp msgid "Clicked Control Type:" -msgstr "" +msgstr "Napsautetun kontrollin tyyppi:" #: editor/script_editor_debugger.cpp msgid "Live Edit Root:" -msgstr "" +msgstr "Juuren suora muokkaus:" #: editor/script_editor_debugger.cpp msgid "Set From Tree" -msgstr "" +msgstr "Aseta puusta" #: editor/settings_config_dialog.cpp msgid "Shortcuts" @@ -7317,7 +7114,7 @@ msgstr "Pikakuvakkeet" #: editor/settings_config_dialog.cpp msgid "Binding" -msgstr "" +msgstr "Sidonta" #: editor/spatial_editor_gizmos.cpp msgid "Change Light Radius" @@ -7325,7 +7122,7 @@ msgstr "Muuta valon sädettä" #: editor/spatial_editor_gizmos.cpp msgid "Change AudioStreamPlayer3D Emission Angle" -msgstr "" +msgstr "Muuta AudioStreamPlayer3D solmun suuntausta" #: editor/spatial_editor_gizmos.cpp msgid "Change Camera FOV" @@ -7337,19 +7134,19 @@ msgstr "Muuta kameran kokoa" #: editor/spatial_editor_gizmos.cpp msgid "Change Sphere Shape Radius" -msgstr "Muuta pallon sädettä" +msgstr "Muuta pallomuodon sädettä" #: editor/spatial_editor_gizmos.cpp msgid "Change Box Shape Extents" -msgstr "" +msgstr "Muuta laatikkomuodon ulottuvuuksia" #: editor/spatial_editor_gizmos.cpp msgid "Change Capsule Shape Radius" -msgstr "" +msgstr "Muuta kapselimuodon sädettä" #: editor/spatial_editor_gizmos.cpp msgid "Change Capsule Shape Height" -msgstr "" +msgstr "Muuta kapselimuodon korkeutta" #: editor/spatial_editor_gizmos.cpp msgid "Change Ray Shape Length" @@ -7357,32 +7154,31 @@ msgstr "Vaihda säteen muodon pituutta" #: editor/spatial_editor_gizmos.cpp msgid "Change Notifier Extents" -msgstr "" +msgstr "Muuta ilmoittajan kattavuutta" #: editor/spatial_editor_gizmos.cpp msgid "Change Particles AABB" -msgstr "" +msgstr "Muuta partikkelien AABB" #: editor/spatial_editor_gizmos.cpp msgid "Change Probe Extents" -msgstr "" +msgstr "Muuta Proben ulottuvuuksia" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Select the dynamic library for this entry" -msgstr "" +msgstr "Valitse dynaaminen kirjasto tälle kohteelle" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Select dependencies of the library for this entry" -msgstr "" +msgstr "Valitse kirjaston riippuvuudet tälle kohteelle" #: modules/gdnative/gdnative_library_editor_plugin.cpp -#, fuzzy msgid "Remove current entry" -msgstr "Poista käyrän piste" +msgstr "Poista nykyinen kohde" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Double click to create a new entry" -msgstr "" +msgstr "Kaksoisnapsauta luodaksesi uuden kohteen" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Platform:" @@ -7393,28 +7189,24 @@ msgid "Platform" msgstr "Alusta" #: modules/gdnative/gdnative_library_editor_plugin.cpp -#, fuzzy msgid "Dynamic Library" -msgstr "Vie kirjasto" +msgstr "Dynaaminen kirjasto" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" -msgstr "" +msgstr "Lisää arkkitehtuurikohde" #: modules/gdnative/gdnative_library_editor_plugin.cpp -#, fuzzy msgid "GDNativeLibrary" -msgstr "Vie kirjasto" +msgstr "GDNativeLibrary" #: modules/gdnative/gdnative_library_singleton_editor.cpp -#, fuzzy msgid "Library" -msgstr "Vie kirjasto" +msgstr "Kirjasto" #: modules/gdnative/gdnative_library_singleton_editor.cpp -#, fuzzy msgid "Status" -msgstr "Tila:" +msgstr "Tila" #: modules/gdnative/gdnative_library_singleton_editor.cpp msgid "Libraries: " @@ -7422,113 +7214,110 @@ msgstr "Kirjastot: " #: modules/gdnative/register_types.cpp msgid "GDNative" -msgstr "" +msgstr "GDNative" #: modules/gdscript/gdscript_functions.cpp #: modules/visual_script/visual_script_builtin_funcs.cpp msgid "Invalid type argument to convert(), use TYPE_* constants." msgstr "" +"Virheellinen tyyppiargumentti convert() metodille, käytä TYPE_* vakioita." #: modules/gdscript/gdscript_functions.cpp modules/mono/glue/glue_header.h #: modules/visual_script/visual_script_builtin_funcs.cpp msgid "Not enough bytes for decoding bytes, or invalid format." -msgstr "" +msgstr "Ei tarpeeksi tavuja tavujen purkamiseksi tai virheellinen formaatti." #: modules/gdscript/gdscript_functions.cpp msgid "step argument is zero!" -msgstr "" +msgstr "askeleen argumentti on nolla!" #: modules/gdscript/gdscript_functions.cpp msgid "Not a script with an instance" -msgstr "" +msgstr "Ei ole skripti, jolla on ilmentymä" #: modules/gdscript/gdscript_functions.cpp msgid "Not based on a script" -msgstr "" +msgstr "Ei pohjaudu skriptiin" #: modules/gdscript/gdscript_functions.cpp msgid "Not based on a resource file" -msgstr "" +msgstr "Ei pohjaudu resurssitiedostoon" #: modules/gdscript/gdscript_functions.cpp msgid "Invalid instance dictionary format (missing @path)" -msgstr "" +msgstr "Virheellinen ilmentymän sanakirjaformaatti (puuttuu @path)" #: modules/gdscript/gdscript_functions.cpp msgid "Invalid instance dictionary format (can't load script at @path)" msgstr "" +"Virheellinen ilmentymän sanakirjaformaatti (ei voida ladata skriptiä polusta " +"@path)" #: modules/gdscript/gdscript_functions.cpp msgid "Invalid instance dictionary format (invalid script at @path)" msgstr "" +"Virheellinen ilmentymän sanakirjaformaatti (virheellinen skripti kohdassa " +"@path)" #: modules/gdscript/gdscript_functions.cpp msgid "Invalid instance dictionary (invalid subclasses)" -msgstr "" +msgstr "Virheellinen ilmentymän sanakirja (virheelliset aliluokat)" #: modules/gdscript/gdscript_functions.cpp msgid "Object can't provide a length." -msgstr "" +msgstr "Objektille ei voida määrittää pituutta." #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Next Plane" -msgstr "Seuraava välilehti" +msgstr "Seuraava taso" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Previous Plane" -msgstr "Edellinen välilehti" +msgstr "Edellinen taso" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Plane:" -msgstr "" +msgstr "Taso:" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Next Floor" -msgstr "" +msgstr "Seuraava kerros" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Previous Floor" -msgstr "Edellinen välilehti" +msgstr "Edellinen kerros" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Floor:" -msgstr "" +msgstr "Kerros:" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Delete Selection" -msgstr "Poista valitut" +msgstr "Poista valinta" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Duplicate Selection" -msgstr "Monista valinta" +msgstr "Kahdenna valinta" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Grid Map" msgstr "Ruudukko" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Snap View" -msgstr "Huippunäkymä" +msgstr "Tartu näkymään" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Clip Disabled" -msgstr "Poistettu käytöstä" +msgstr "Leikkaus pois käytöstä" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Clip Above" -msgstr "" +msgstr "Leikkaa yläpuolelta" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Clip Below" -msgstr "" +msgstr "Leikkaa alapuolelta" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Edit X Axis" @@ -7543,166 +7332,156 @@ msgid "Edit Z Axis" msgstr "Muokkaa Z-akselia" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Cursor Rotate X" -msgstr "Ctrl: Pyöritä/kierrä" +msgstr "Kierrä kohdistinta X-akselilla" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Cursor Rotate Y" -msgstr "Ctrl: Pyöritä/kierrä" +msgstr "Kierrä kohdistinta Y-akselilla" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Cursor Rotate Z" -msgstr "Ctrl: Pyöritä/kierrä" +msgstr "Kierrä kohdistinta Z-akselilla" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Cursor Back Rotate X" -msgstr "" +msgstr "Kierrä kohdistinta X-akselilla takaperin" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Cursor Back Rotate Y" -msgstr "" +msgstr "Kierrä kohdistinta Y-akselilla takaperin" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Cursor Back Rotate Z" -msgstr "" +msgstr "Kierrä kohdistinta Z-akselilla takaperin" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Cursor Clear Rotation" -msgstr "" +msgstr "Poista kohdistimen kierto" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Create Area" -msgstr "Luo uusi" +msgstr "Luo alue" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Create Exterior Connector" -msgstr "Luo uusi projekti" +msgstr "Luo ulkoliitin" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Erase Area" -msgstr "" +msgstr "Tyhjennä alue" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Clear Selection" -msgstr "Valinta keskikohtaan" +msgstr "Tyhjennä valinta" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Settings" -msgstr "Näyttöruudun asetukset" +msgstr "Ruudukon asetukset" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Pick Distance:" -msgstr "Poimi tile" +msgstr "Poimintaetäisyys:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Luokan nimi ei voi olla varattu avainsana" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." -msgstr "" +msgstr "Luodaan ratkaisua..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating C# project..." msgstr "Luodaan C# projekti..." #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Failed to create solution." -msgstr "Ääriviivoja ei voitu luoda!" +msgstr "Ratkaisun luonti epäonnistui." #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Failed to save solution." -msgstr "Resurssin lataaminen epäonnistui." +msgstr "Ratkaisun tallennus epäonnistui." #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Done" -msgstr "Valmis!" +msgstr "Valmis" #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Failed to create C# project." -msgstr "Resurssin lataaminen epäonnistui." +msgstr "C# projektin luonti epäonnistui." #: modules/mono/editor/godotsharp_editor.cpp msgid "Mono" -msgstr "" +msgstr "Mono" #: modules/mono/editor/godotsharp_editor.cpp msgid "About C# support" -msgstr "" +msgstr "Lisätietoja C# tuesta" #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "Create C# solution" -msgstr "Luo ääriviivat" +msgstr "Luo C# ratkaisu" #: modules/mono/editor/mono_bottom_panel.cpp msgid "Builds" -msgstr "" +msgstr "Käännökset" #: modules/mono/editor/mono_bottom_panel.cpp -#, fuzzy msgid "Build Project" -msgstr "Uusi projekti" +msgstr "Käännä projekti" #: modules/mono/editor/mono_bottom_panel.cpp -#, fuzzy msgid "Warnings" -msgstr "Varoitus" +msgstr "Varoitukset" #: modules/mono/mono_gd/gd_mono_utils.cpp msgid "End of inner exception stack trace" -msgstr "" +msgstr "Sisemmän poikkeuksen kutsupinon loppu" #: modules/visual_script/visual_script.cpp msgid "" "A node yielded without working memory, please read the docs on how to yield " "properly!" msgstr "" +"Solmu väisti ilman työmuistia, ole hyvä ja lue dokumentaatiosta kuinka " +"väistö (yield) on tehtävä!" #: modules/visual_script/visual_script.cpp msgid "" "Node yielded, but did not return a function state in the first working " "memory." msgstr "" +"Solmu väisti (yield), mutta ei palauttanut funktion tilaa ensimmäiselle " +"työmuistille." #: modules/visual_script/visual_script.cpp msgid "" "Return value must be assigned to first element of node working memory! Fix " "your node please." msgstr "" +"Paluuarvo täytyy sijoittaa työmuistin ensimmäiselle elementille! Ole hyvä ja " +"korjaa solmusi." #: modules/visual_script/visual_script.cpp msgid "Node returned an invalid sequence output: " -msgstr "" +msgstr "Solmu palautti virheellisen jakson tulosteen: " #: modules/visual_script/visual_script.cpp msgid "Found sequence bit but not the node in the stack, report bug!" -msgstr "" +msgstr "Jaksobitti löytyi, mutta solmua ei löydy pinosta, raportoi bugi!" #: modules/visual_script/visual_script.cpp msgid "Stack overflow with stack depth: " -msgstr "" +msgstr "Pinon ylivuoto pinosyvyydellä: " #: modules/visual_script/visual_script_editor.cpp msgid "Change Signal Arguments" -msgstr "" +msgstr "Muuta signaalin argumentit" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Argument Type" -msgstr "Vaihda taulukon arvon tyyppiä" +msgstr "Vaihda argumentin tyyppiä" #: modules/visual_script/visual_script_editor.cpp msgid "Change Argument name" @@ -7710,12 +7489,11 @@ msgstr "Vaihda argumentin nimi" #: modules/visual_script/visual_script_editor.cpp msgid "Set Variable Default Value" -msgstr "" +msgstr "Aseta muuttujan oletusarvo" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Set Variable Type" -msgstr "Muokkaa muuttujaa:" +msgstr "Aseta muuttujan tyyppi" #: modules/visual_script/visual_script_editor.cpp msgid "Functions:" @@ -7763,76 +7541,76 @@ msgstr "Vaihda lauseketta" #: modules/visual_script/visual_script_editor.cpp msgid "Add Node" -msgstr "Lisää Node" +msgstr "Lisää solmu" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Remove VisualScript Nodes" -msgstr "Poista virheelliset avaimet" +msgstr "Poista VisualScript solmut" #: modules/visual_script/visual_script_editor.cpp msgid "Duplicate VisualScript Nodes" -msgstr "" +msgstr "Kahdenna VisualScript solmut" #: modules/visual_script/visual_script_editor.cpp msgid "Hold %s to drop a Getter. Hold Shift to drop a generic signature." msgstr "" +"Pidä %s pohjassa pudottaaksesi Getterin. Pidä Shift pohjassa pudottaaksesi " +"yleisen tunnisteen." #: modules/visual_script/visual_script_editor.cpp msgid "Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature." msgstr "" +"Pidä Ctrl pohjassa pudottaaksesi Getterin. Pidä Shift pohjassa pudottaaksesi " +"yleisen tunnisteen." #: modules/visual_script/visual_script_editor.cpp msgid "Hold %s to drop a simple reference to the node." -msgstr "" +msgstr "Pidä %s pohjassa pudottaaksesi yksinkertaisen viittauksen solmuun." #: modules/visual_script/visual_script_editor.cpp msgid "Hold Ctrl to drop a simple reference to the node." -msgstr "" +msgstr "Pidä Ctrl pohjassa pudottaaksesi yksinkertaisen viittauksen solmuun." #: modules/visual_script/visual_script_editor.cpp msgid "Hold %s to drop a Variable Setter." -msgstr "" +msgstr "Pidä %s pohjassa pudottaaksesi muuttujan asettajan (Variable Setter)." #: modules/visual_script/visual_script_editor.cpp msgid "Hold Ctrl to drop a Variable Setter." msgstr "" +"Pidä Ctrl pohjassa pudottaaksesi muuttujan asettajan (Variable Setter)." #: modules/visual_script/visual_script_editor.cpp msgid "Add Preload Node" -msgstr "" +msgstr "Lisää esiladattu solmu" #: modules/visual_script/visual_script_editor.cpp msgid "Add Node(s) From Tree" -msgstr "Lisää Nodet puusta" +msgstr "Lisää solmut puusta" #: modules/visual_script/visual_script_editor.cpp msgid "Add Getter Property" -msgstr "" +msgstr "Lisää palauttajaominaisuus" #: modules/visual_script/visual_script_editor.cpp msgid "Add Setter Property" -msgstr "" +msgstr "Lisää asettajaominaisuus" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Base Type" -msgstr "Muuta tyyppiä" +msgstr "Muuta kantatyyppiä" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Move Node(s)" -msgstr "Poista Node(t)" +msgstr "Siirrä solmu(t)" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Remove VisualScript Node" -msgstr "Poista muuttuja" +msgstr "Poista VisualScript solmu" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Connect Nodes" -msgstr "Yhdistä Nodeen:" +msgstr "Kytke solmut" #: modules/visual_script/visual_script_editor.cpp msgid "Condition" @@ -7840,19 +7618,19 @@ msgstr "Ehtolause" #: modules/visual_script/visual_script_editor.cpp msgid "Sequence" -msgstr "" +msgstr "Sarja" #: modules/visual_script/visual_script_editor.cpp msgid "Switch" -msgstr "" +msgstr "Valinta (Switch)" #: modules/visual_script/visual_script_editor.cpp msgid "Iterator" -msgstr "" +msgstr "Iteraattori" #: modules/visual_script/visual_script_editor.cpp msgid "While" -msgstr "" +msgstr "Kun (While)" #: modules/visual_script/visual_script_editor.cpp msgid "Return" @@ -7864,48 +7642,43 @@ msgstr "Kutsu" #: modules/visual_script/visual_script_editor.cpp msgid "Get" -msgstr "" +msgstr "Get" #: modules/visual_script/visual_script_editor.cpp msgid "Script already has function '%s'" -msgstr "" +msgstr "Skriptillä on jo funktio '%s'" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Input Value" -msgstr "Vaihda syötteen nimi" +msgstr "Vaihda syötteen arvo" #: modules/visual_script/visual_script_editor.cpp msgid "Can't copy the function node." -msgstr "" +msgstr "Ei voida kopioida funktiosolmua." #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Clipboard is empty!" -msgstr "Resurssien leikepöytä on tyhjä!" +msgstr "Leikepöytä on tyhjä!" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Paste VisualScript Nodes" -msgstr "Liitä Nodet" +msgstr "Liitä VisualScript solmut" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Function" msgstr "Poista funktio" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Edit Variable" -msgstr "Muokkaa muuttujaa:" +msgstr "Muokkaa muuttujaa" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Variable" msgstr "Poista muuttuja" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Edit Signal" -msgstr "Muokataan signaalia:" +msgstr "Muokkaa signaalia" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Signal" @@ -7921,19 +7694,19 @@ msgstr "Muokataan signaalia:" #: modules/visual_script/visual_script_editor.cpp msgid "Base Type:" -msgstr "" +msgstr "Kantatyyppi:" #: modules/visual_script/visual_script_editor.cpp msgid "Available Nodes:" -msgstr "Saatavilla olevat Nodet:" +msgstr "Saatavilla olevat solmut:" #: modules/visual_script/visual_script_editor.cpp msgid "Select or create a function to edit graph" -msgstr "" +msgstr "Valitse tai luo funktio graafin muokkaamiseksi" #: modules/visual_script/visual_script_editor.cpp msgid "Edit Signal Arguments:" -msgstr "" +msgstr "Muokkaa signaalin argumentteja:" #: modules/visual_script/visual_script_editor.cpp msgid "Edit Variable:" @@ -7945,51 +7718,51 @@ msgstr "Poista valitut" #: modules/visual_script/visual_script_editor.cpp msgid "Find Node Type" -msgstr "Etsi Noden tyyppi" +msgstr "Etsi solmun tyyppi" #: modules/visual_script/visual_script_editor.cpp msgid "Copy Nodes" -msgstr "Kopioi Nodet" +msgstr "Kopioi solmut" #: modules/visual_script/visual_script_editor.cpp msgid "Cut Nodes" -msgstr "Leikkaa Nodet" +msgstr "Leikkaa solmut" #: modules/visual_script/visual_script_editor.cpp msgid "Paste Nodes" -msgstr "Liitä Nodet" +msgstr "Liitä solmut" #: modules/visual_script/visual_script_flow_control.cpp msgid "Input type not iterable: " -msgstr "" +msgstr "Syötetyyppi ei ole iteroitavissa: " #: modules/visual_script/visual_script_flow_control.cpp msgid "Iterator became invalid" -msgstr "" +msgstr "Iteraattori muuttui epäkelvoksi" #: modules/visual_script/visual_script_flow_control.cpp msgid "Iterator became invalid: " -msgstr "" +msgstr "Iteraattori muuttui epäkelvoksi: " #: modules/visual_script/visual_script_func_nodes.cpp msgid "Invalid index property name." -msgstr "" +msgstr "Virheellinen osoitinominaisuuden nimi." #: modules/visual_script/visual_script_func_nodes.cpp msgid "Base object is not a Node!" -msgstr "" +msgstr "Kantaobjekti ei ole solmu!" #: modules/visual_script/visual_script_func_nodes.cpp msgid "Path does not lead Node!" -msgstr "Polku ei vie Nodeen!" +msgstr "Polku ei johda solmuun!" #: modules/visual_script/visual_script_func_nodes.cpp msgid "Invalid index property name '%s' in node %s." -msgstr "" +msgstr "Virheellinen osoitinominaisuuden nimi '%s' solmussa %s." #: modules/visual_script/visual_script_nodes.cpp msgid ": Invalid argument of type: " -msgstr "" +msgstr ": Virheellinen argumentti tyyppiä: " #: modules/visual_script/visual_script_nodes.cpp msgid ": Invalid arguments: " @@ -7997,21 +7770,24 @@ msgstr ": Virheelliset argumentit: " #: modules/visual_script/visual_script_nodes.cpp msgid "VariableGet not found in script: " -msgstr "" +msgstr "VariableGet ei löytynyt skriptistä: " #: modules/visual_script/visual_script_nodes.cpp msgid "VariableSet not found in script: " -msgstr "" +msgstr "VariableSet ei löytynyt skriptistä: " #: modules/visual_script/visual_script_nodes.cpp msgid "Custom node has no _step() method, can't process graph." msgstr "" +"Mukautetulla solmulla ei ole _step() metodia, graafia ei voida käsitellä." #: modules/visual_script/visual_script_nodes.cpp msgid "" "Invalid return value from _step(), must be integer (seq out), or string " "(error)." msgstr "" +"Virheellinen paluuarvo _step() metodilta, täytyy olla kokonaisluku (seq out) " +"tai merkkijono (virhe)." #: platform/javascript/export/export.cpp msgid "Run in Browser" @@ -8022,46 +7798,44 @@ msgid "Run exported HTML in the system's default browser." msgstr "Suorita viety HTML järjestelmän oletusselaimessa." #: platform/javascript/export/export.cpp -#, fuzzy msgid "Could not write file:" -msgstr "Ei voitu kirjoittaa tiedostoa:\n" +msgstr "Ei voitu kirjoittaa tiedostoa:" #: platform/javascript/export/export.cpp -#, fuzzy msgid "Could not open template for export:" -msgstr "Kansiota ei voitu luoda." +msgstr "Mallin avaus vientiin epäonnistui:" #: platform/javascript/export/export.cpp -#, fuzzy msgid "Invalid export template:" -msgstr "Hallitse vietäviä Templateja" +msgstr "Virheellinen vientimalli:" #: platform/javascript/export/export.cpp -#, fuzzy msgid "Could not read custom HTML shell:" -msgstr "Ei voitu lukea tiedostoa:\n" +msgstr "Ei voitu lukea mukautettua HTML tulkkia:" #: platform/javascript/export/export.cpp -#, fuzzy msgid "Could not read boot splash image file:" -msgstr "Ei voitu lukea tiedostoa:\n" +msgstr "Ei voitu lukea käynnistyskuvan tiedostoa:" #: platform/javascript/export/export.cpp -#, fuzzy msgid "Using default boot splash image." -msgstr "Ei voitu lukea tiedostoa:\n" +msgstr "Käytetään oletuskäynnistyskuvaa." #: scene/2d/animated_sprite.cpp msgid "" "A SpriteFrames resource must be created or set in the 'Frames' property in " "order for AnimatedSprite to display frames." msgstr "" +"SpriteFrames resurssi on luotava tai asetettava 'Frames' ominaisuudelle, " +"jotta AnimatedSprite voi näyttää ruutuja." #: scene/2d/canvas_modulate.cpp msgid "" "Only one visible CanvasModulate is allowed per scene (or set of instanced " "scenes). The first created one will work, while the rest will be ignored." msgstr "" +"Vain yksi CanvasModulate on sallittu per skene (tai per skeneilmentymien " +"joukko). Ensimmäisenä luotu toimii ja loput jätetään huomioimatta." #: scene/2d/collision_object_2d.cpp msgid "" @@ -8069,6 +7843,10 @@ msgid "" "Consider adding CollisionShape2D or CollisionPolygon2D children nodes to " "define its shape." msgstr "" +"Tämän solmun alaisuudessa ei ole muotoja, joten se ei voi olla " +"vuorovaikutuksessa avaruuden kanssa.\n" +"Harkitse CollisionShape2D tai CollisionPolygon2D solmun lisäämistä " +"alisolmuksi muodon määrittämiseksi." #: scene/2d/collision_polygon_2d.cpp msgid "" @@ -8076,10 +7854,13 @@ msgid "" "CollisionObject2D derived node. Please only use it as a child of Area2D, " "StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape." msgstr "" +"CollisionPolygon2D toimii törmäysmuotona ainoastaan CollisionObject2D " +"solmusta perityille solmuille. Käytä sitä ainoastaan Area2D, StaticBody2D, " +"RigidBody2D, KinematicBody2D, jne. alla antaaksesi niille muodon." #: scene/2d/collision_polygon_2d.cpp msgid "An empty CollisionPolygon2D has no effect on collision." -msgstr "Tyhjällä CollisionPolygon2D:llä ei ole vaikutusta törmäyksessä." +msgstr "Tyhjällä CollisionPolygon2D solmulla ei ole vaikutusta törmäyksessä." #: scene/2d/collision_shape_2d.cpp msgid "" @@ -8087,56 +7868,73 @@ msgid "" "CollisionObject2D derived node. Please only use it as a child of Area2D, " "StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape." msgstr "" +"CollisionShape2D toimii törmäysmuotona ainoastaan CollisionObject2D solmusta " +"perityille solmuille. Käytä sitä ainoastaan Area2D, StaticBody2D, " +"RigidBody2D, KinematicBody2D, jne. alla antaaksesi niille muodon." #: scene/2d/collision_shape_2d.cpp msgid "" "A shape must be provided for CollisionShape2D to function. Please create a " "shape resource for it!" msgstr "" +"CollisionShape2D solmulla täytyy olla muoto, jotta se toimisi. Ole hyvä ja " +"luo sille muotoresurssi!" #: scene/2d/light_2d.cpp msgid "" "A texture with the shape of the light must be supplied to the 'texture' " "property." msgstr "" +"Tekstuuri, jolta löytyy valon muoto, täytyy antaa 'texture' ominaisuudella." #: scene/2d/light_occluder_2d.cpp msgid "" "An occluder polygon must be set (or drawn) for this occluder to take effect." msgstr "" +"Toimimiseksi tälle peittäjälle on asetettava (tai piirrettävä) peittävä " +"monikulmio." #: scene/2d/light_occluder_2d.cpp msgid "The occluder polygon for this occluder is empty. Please draw a polygon!" msgstr "" +"Peittävä monikulmio tälle peittäjälle on tyhjä. Ole hyvä ja piirrä " +"monikulmio!" #: scene/2d/navigation_polygon.cpp msgid "" "A NavigationPolygon resource must be set or created for this node to work. " "Please set a property or draw a polygon." msgstr "" +"Tälle solmulle on asetettava tai luotava NavigationPolygon resurssi, jotta " +"se toimisi. Ole hyvä ja aseta ominaisuus tai piirrä monikulmio." #: scene/2d/navigation_polygon.cpp msgid "" "NavigationPolygonInstance must be a child or grandchild to a Navigation2D " "node. It only provides navigation data." msgstr "" +"NavigationPolygonInstance solmun täytyy olla Navigation2D solmun " +"alaisuudessa. Se tarjoaa vain navigointidataa." #: scene/2d/parallax_layer.cpp msgid "" "ParallaxLayer node only works when set as child of a ParallaxBackground node." msgstr "" +"ParallaxLayer solmu toimii ainoastaan, jos se on ParallaxBackground solmun " +"alla." #: scene/2d/particles_2d.cpp scene/3d/particles.cpp msgid "" "A material to process the particles is not assigned, so no behavior is " "imprinted." msgstr "" +"Materiaalia partikkeleiden käsittelemiseksi ei ole määritetty, joten mitään " +"ei tapahdu." #: scene/2d/path_2d.cpp msgid "PathFollow2D only works when set as a child of a Path2D node." msgstr "" -"PathFollow2D toimii ainoastaan ollessaan asetettuna Path2D Node:n " -"lapsiolioksi." +"PathFollow2D toimii ainoastaan ollessaan asetettuna Path2D solmun alle." #: scene/2d/physics_body_2d.cpp msgid "" @@ -8144,68 +7942,78 @@ msgid "" "by the physics engine when running.\n" "Change the size in children collision shapes instead." msgstr "" +"Fysiikkamoottori ylikirjoittaa RigidBody2D kokomuutokset (hahmo- tai " +"jäykkätila) ajon aikana.\n" +"Muuta sen sijaan solmun alla olevia törmäysmuotoja." #: scene/2d/remote_transform_2d.cpp msgid "Path property must point to a valid Node2D node to work." -msgstr "Polku täytyy olla määritetty toimivaan Node2D solmuun toimiakseen." +msgstr "" +"Polkuominaisuuden täytyy osoittaa kelvolliseen Node2D solmuun toimiakseen." #: scene/2d/visibility_notifier_2d.cpp msgid "" "VisibilityEnable2D works best when used with the edited scene root directly " "as parent." msgstr "" +"VisibilityEnable2D toimii parhaiten, kun sitä käytetään suoraan muokatun " +"skenen juuren isäntänä." #: scene/3d/arvr_nodes.cpp msgid "ARVRCamera must have an ARVROrigin node as its parent" -msgstr "" +msgstr "ARVRCamera solmun isännän täytyy olla ARVROrigin solmu" #: scene/3d/arvr_nodes.cpp msgid "ARVRController must have an ARVROrigin node as its parent" -msgstr "" +msgstr "ARVRController solmun isännän täytyy olla ARVROrigin solmu" #: scene/3d/arvr_nodes.cpp msgid "" "The controller id must not be 0 or this controller will not be bound to an " "actual controller" msgstr "" +"Ohjaimen tunnus ei saa olla 0, tai tämä ohjain ei ole sidottu oikeaan " +"ohjaimeen" #: scene/3d/arvr_nodes.cpp msgid "ARVRAnchor must have an ARVROrigin node as its parent" -msgstr "" +msgstr "ARVRAnchor solmun isännän täytyy olla ARVROrigin solmu" #: scene/3d/arvr_nodes.cpp msgid "" "The anchor id must not be 0 or this anchor will not be bound to an actual " "anchor" msgstr "" +"Ankkurin tunnus ei saa olla 0, tai tämä ankkuri ei ole sidottu oikeaan " +"ankkuriin" #: scene/3d/arvr_nodes.cpp msgid "ARVROrigin requires an ARVRCamera child node" -msgstr "" +msgstr "ARVROrigin solmu tarvitsee ARVRCamera alisolmun" #: scene/3d/baked_lightmap.cpp msgid "%d%%" -msgstr "" +msgstr "%d%%" #: scene/3d/baked_lightmap.cpp msgid "(Time Left: %d:%02d s)" -msgstr "" +msgstr "(Aikaa jäljellä: %d:%02d s)" #: scene/3d/baked_lightmap.cpp msgid "Plotting Meshes: " -msgstr "" +msgstr "Piirretään meshejä: " #: scene/3d/baked_lightmap.cpp msgid "Plotting Lights:" -msgstr "" +msgstr "Piirretään valoja:" #: scene/3d/baked_lightmap.cpp scene/3d/gi_probe.cpp msgid "Finishing Plot" -msgstr "" +msgstr "Viimeistellään piirto" #: scene/3d/baked_lightmap.cpp msgid "Lighting Meshes: " -msgstr "" +msgstr "Valaistaan meshejä: " #: scene/3d/collision_object.cpp msgid "" @@ -8213,6 +8021,10 @@ msgid "" "Consider adding CollisionShape or CollisionPolygon children nodes to define " "its shape." msgstr "" +"Tällä solmulla ei ole alimuotoja, joten se ei voi olla vuorovaikutuksessa " +"avaruuden kanssa.\n" +"Harkitse CollisionShape tai CollisionPolygon solmun lisäämistä sen " +"alisolmuksi määritelläksesi sen muodon." #: scene/3d/collision_polygon.cpp msgid "" @@ -8220,10 +8032,13 @@ msgid "" "CollisionObject derived node. Please only use it as a child of Area, " "StaticBody, RigidBody, KinematicBody, etc. to give them a shape." msgstr "" +"CollisionPolygon solmu antaa ainoastaan törmäysmuodon CollisionObject " +"solmusta periytyville solmuille. Käytä sitä Area, StaticBody, RigidBody, " +"KinematicBody, jne. solmujen alla antaaksesi niille muodon." #: scene/3d/collision_polygon.cpp msgid "An empty CollisionPolygon has no effect on collision." -msgstr "Tyhjällä CollisionPolygon:illa ei ole vaikutusta törmäyksessä." +msgstr "Tyhjällä CollisionPolygon solmulla ei ole vaikutusta törmäyksessä." #: scene/3d/collision_shape.cpp msgid "" @@ -8231,31 +8046,42 @@ msgid "" "derived node. Please only use it as a child of Area, StaticBody, RigidBody, " "KinematicBody, etc. to give them a shape." msgstr "" +"CollisionShape solmu antaa ainoastaan törmäysmuodon CollisionObject solmusta " +"periytyville solmuille. Käytä sitä Area, StaticBody, RigidBody, " +"KinematicBody, jne. solmujen alla antaaksesi niille muodon." #: scene/3d/collision_shape.cpp msgid "" "A shape must be provided for CollisionShape to function. Please create a " "shape resource for it!" msgstr "" +"CollisionShape solmulle täytyy antaa muoto, jotta se toimisi. Ole hyvä ja " +"luo sille muotoresurssi!" #: scene/3d/gi_probe.cpp msgid "Plotting Meshes" -msgstr "" +msgstr "Piirretään meshejä" #: scene/3d/navigation_mesh.cpp msgid "A NavigationMesh resource must be set or created for this node to work." msgstr "" +"Tälle solmulle täytyy asettaa tai luoda NavigationMesh resurssi, jotta se " +"toimisi." #: scene/3d/navigation_mesh.cpp msgid "" "NavigationMeshInstance must be a child or grandchild to a Navigation node. " "It only provides navigation data." msgstr "" +"NavigationMeshInstance solmun täytyy olla Navigation solmun alaisuudessa. Se " +"tarjoaa vain navigointidataa." #: scene/3d/particles.cpp msgid "" "Nothing is visible because meshes have not been assigned to draw passes." msgstr "" +"Mitään ei näy, koska mesheille ei ole asetettu piirtopyyhkäisyjä (draw " +"passes)." #: scene/3d/physics_body.cpp msgid "" @@ -8263,42 +8089,53 @@ msgid "" "by the physics engine when running.\n" "Change the size in children collision shapes instead." msgstr "" +"Fysiikkamoottori ylikirjoittaa RigidBody kokomuutokset (hahmo- tai " +"jäykkätilassa) ajon aikana.\n" +"Muuta sen sijaan solmun alla olevia törmäysmuotoja." #: scene/3d/remote_transform.cpp msgid "Path property must point to a valid Spatial node to work." -msgstr "" +msgstr "Polkuominaisuuden täytyy osoittaa Spatial solmuun toimiakseen." #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment tarvitsee Environment resurssin." #: scene/3d/scenario_fx.cpp msgid "" "Only one WorldEnvironment is allowed per scene (or set of instanced scenes)." msgstr "" +"Vain yksi WorldEnvironment on sallittu per skene (tai per skeneilmentymien " +"joukko)." #: scene/3d/scenario_fx.cpp msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Tämä WorldEnvironment jätetään huomioimatta. Lisää joko Camera (3D-" +"skeneille) tai aseta tälle ympäristölle Background Mode asetukseksi Canvas " +"(2D-skeneille)." #: scene/3d/sprite_3d.cpp msgid "" "A SpriteFrames resource must be created or set in the 'Frames' property in " "order for AnimatedSprite3D to display frames." msgstr "" +"AnimatedSprite3D solmulle täytyy luoda tai asettaa 'Frames' ominaisuudeksi " +"SpriteFrames resurssi ruutujen näyttämiseksi." #: scene/3d/vehicle_body.cpp msgid "" "VehicleWheel serves to provide a wheel system to a VehicleBody. Please use " "it as a child of a VehicleBody." msgstr "" +"VehicleWheel solmu tarjoaa rengasjärjestelmän VehicleBody solmulle. Ole hyvä " +"ja käytä sitä VehicleBody solmun alla." #: scene/gui/color_picker.cpp -#, fuzzy msgid "Raw Mode" -msgstr "Kääntötila" +msgstr "Raakatila" #: scene/gui/color_picker.cpp msgid "Add current color as a preset" @@ -8313,9 +8150,8 @@ msgid "Please Confirm..." msgstr "Ole hyvä ja vahvista..." #: scene/gui/file_dialog.cpp -#, fuzzy msgid "Select this Folder" -msgstr "Valitse metodi" +msgstr "Valitse tämä kansio" #: scene/gui/popup.cpp msgid "" @@ -8342,13 +8178,12 @@ msgid "(Other)" msgstr "(Muu)" #: scene/main/scene_tree.cpp -#, fuzzy msgid "" "Default Environment as specified in Project Settings (Rendering -> " "Environment -> Default Environment) could not be loaded." msgstr "" -"Projektin asetuksissa määriteltyä oletusympäristöä (Renderöinti -> Näkymä -" -"> Oletusympäristö) ei voitu ladata." +"Projektin asetuksissa määriteltyä oletusympäristöä (Rendering -> " +"Environment -> Default Environment) ei voitu ladata." #: scene/main/viewport.cpp msgid "" @@ -8357,14 +8192,14 @@ msgid "" "obtain a size. Otherwise, make it a RenderTarget and assign its internal " "texture to some node for display." msgstr "" -"Tätä näyttöruutua ei ole asetettu renderöitäväksi. Jos haluat sen näyttävän " -"sisältöä suoraan näytölle, tee sitä Control:in lapsi, jotta se voi saada " -"koon. Muutoin tee siitä RenderTarget ja aseta sen sisäinen tekstuuri " -"johonkin Nodeen näkyväksi." +"Tätä näyttöikkunaa ei ole asetettu renderöitäväksi. Jos haluat sen näyttävän " +"sisältöä suoraan näytölle, tee sitä Control solmun alisolmu, jotta se voi " +"saada koon. Muutoin tee siitä RenderTarget ja aseta sen sisäinen tekstuuri " +"johonkin solmuun näkyväksi." #: scene/resources/dynamic_font.cpp msgid "Error initializing FreeType." -msgstr "Virhe FreetType:n alustamisessa." +msgstr "Virhe FreeType:n alustamisessa." #: scene/resources/dynamic_font.cpp msgid "Unknown font format." @@ -8378,6 +8213,13 @@ msgstr "Virhe fontin latauksessa." msgid "Invalid font size." msgstr "Virheellinen fonttikoko." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Edellinen välilehti" + +#~ msgid "Next" +#~ msgstr "Seuraava" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Virheellinen tapahtuma (muut käy, paitsi '/' tai ':')." @@ -8408,9 +8250,6 @@ msgstr "Virheellinen fonttikoko." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Ei voitu luoda godot.cfg -tiedostoa projektin polkuun." -#~ msgid "Next" -#~ msgstr "Seuraava" - #~ msgid "Not found!" #~ msgstr "Ei löytynyt!" @@ -8564,7 +8403,7 @@ msgstr "Virheellinen fonttikoko." #~ msgid "Info" #~ msgstr "Tietoja" -#~ msgid "Re-Import.." +#~ msgid "Re-Import..." #~ msgstr "Tuo uudelleen..." #~ msgid "Target path is empty." @@ -8816,13 +8655,13 @@ msgstr "Virheellinen fonttikoko." #~ msgid "Zoom (%):" #~ msgstr "Lähennä (%):" -#~ msgid "Skeleton.." +#~ msgid "Skeleton..." #~ msgstr "Luuranko..." #~ msgid "Zoom Reset" #~ msgstr "Palauta lähennys" -#~ msgid "Zoom Set.." +#~ msgid "Zoom Set..." #~ msgstr "Aseta Zoomaus..." #~ msgid "Set a Value" diff --git a/editor/translations/fr.po b/editor/translations/fr.po index 56969fe974..ee1d7b2cad 100644 --- a/editor/translations/fr.po +++ b/editor/translations/fr.po @@ -2,7 +2,6 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Antoine Carrier <ac.g392@gmail.com>, 2017-2018. # ARocherVj <a.rocher.vj@gmail.com>, 2017. # Arthur Templé <tuturtemple@gmail.com>, 2018. @@ -13,18 +12,23 @@ # finkiki <specialpopol@gmx.fr>, 2016. # Gilles Roudiere <gilles.roudiere@gmail.com>, 2017-2018. # Hugo Locurcio <hugo.l@openmailbox.org>, 2016-2018. +# Javier Ocampos <xavier.ocampos@gmail.com>, 2018. # John Bernier <john.bp@unknit.net>, 2018. -# Kanabenki <lucien.menassol@gmail.com>, 2017. +# Kanabenki <lucien.menassol@gmail.com>, 2017, 2018. # keltwookie <keltwookie@protonmail.com>, 2017-2018. # LL <lu.lecocq@free.fr>, 2018. # Luc Stepniewski <lior@gradstein.info>, 2017. # Marc <marc.gilleron@gmail.com>, 2016-2017. +# Marc-Andre Belisle <belisle.ma@gmail.com>, 2018. # Nathan Lovato <nathan.lovato.art@gmail.com>, 2017. +# Nathan Vallet <nathanvalletmarseille@gmail.com>, 2018. # Nicolas <flaithotw@gmail.com>, 2017. # Nicolas Lehuen <nicolas@lehuen.com>, 2016. # Nobelix <noe.le.cam@laposte.net>, 2017. -# Omicron <tritonic.dev@gmail.com>, 2016, 2018. +# Nocta Senestra <nocta@net-c.com>, 2018. +# Omicron <omicron666.dev@gmail.com>, 2016, 2018. # Onyx Steinheim <thevoxelmanonyx@gmail.com>, 2016. +# Philippe Gervaise <blah@malvese.org>, 2018. # Przemyslaw Gasinski <gasinski.przemek@protonmail.ch>, 2017. # rafeu <duchainer@gmail.com>, 2016-2017. # rawida <rawida@tempinbox.com>, 2018. @@ -36,13 +40,12 @@ # Tommy Melançon-Roy <tommel1234@hotmail.com>, 2017-2018. # Willow <theotimefd@aol.com>, 2018. # Xananax <xananax@yelostudio.com>, 2017-2018. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-05-02 21:48+0000\n" -"Last-Translator: Omicron <omicron666.dev@gmail.com>\n" +"PO-Revision-Date: 2018-06-12 16:38+0000\n" +"Last-Translator: Philippe Gervaise <blah@malvese.org>\n" "Language-Team: French <https://hosted.weblate.org/projects/godot-engine/" "godot/fr/>\n" "Language: fr\n" @@ -50,7 +53,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -532,7 +535,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Déconnecter « %s » de « %s »" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Connecter…" #: editor/connections_dialog.cpp @@ -858,19 +861,19 @@ msgstr "Ajouter effet" #: editor/editor_audio_buses.cpp msgid "Rename Audio Bus" -msgstr "Renommer bus audio" +msgstr "Renommer le bus audio" #: editor/editor_audio_buses.cpp msgid "Change Audio Bus Volume" -msgstr "Modifier le volume audio du bus" +msgstr "Modifier le volume du bus audio" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Solo" -msgstr "Activer/désactiver le mode solo pour le bus audio" +msgstr "Activer/désactiver le mode solo du bus audio" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Mute" -msgstr "Activer/désactiver le mode muet pour le bus audio" +msgstr "Activer/désactiver le mode muet du bus audio" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Bypass Effects" @@ -878,11 +881,11 @@ msgstr "Activer/désactiver le contournement du bus audio" #: editor/editor_audio_buses.cpp msgid "Select Audio Bus Send" -msgstr "Sélectionner l'envoi de tranport audio" +msgstr "Sélectionner l'envoi du bus audio" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus Effect" -msgstr "Ajouter effet de tranport audio" +msgstr "Ajouter un effet bus audio" #: editor/editor_audio_buses.cpp msgid "Move Bus Effect" @@ -894,7 +897,7 @@ msgstr "Supprimer l'effet de transport" #: editor/editor_audio_buses.cpp msgid "Audio Bus, Drag and Drop to rearrange." -msgstr "Transport audio, glisser-déposer pour réorganiser." +msgstr "Bus audio, glisser-déposer pour réorganiser." #: editor/editor_audio_buses.cpp msgid "Solo" @@ -931,19 +934,19 @@ msgstr "Audio" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus" -msgstr "Ajouter un transport audio" +msgstr "Ajouter un bus audio" #: editor/editor_audio_buses.cpp msgid "Master bus can't be deleted!" -msgstr "Le transport maître ne peut pas être supprimé !" +msgstr "Le bus maître ne peut pas être supprimé !" #: editor/editor_audio_buses.cpp msgid "Delete Audio Bus" -msgstr "Supprimer le transport audio" +msgstr "Supprimer le bus audio" #: editor/editor_audio_buses.cpp msgid "Duplicate Audio Bus" -msgstr "Dupliquer le transport audio" +msgstr "Dupliquer le bus audio" #: editor/editor_audio_buses.cpp msgid "Reset Bus Volume" @@ -951,19 +954,19 @@ msgstr "Réinitialiser le volume de bus" #: editor/editor_audio_buses.cpp msgid "Move Audio Bus" -msgstr "Déplacer le transport audio" +msgstr "Déplacer le bus audio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Enregistrer l'agencement du transport audio sous.." +msgid "Save Audio Bus Layout As..." +msgstr "Enregistrer la disposition des bus audio sous…" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Emplacement du nouvel agencement.." +msgid "Location for New Layout..." +msgstr "Emplacement du nouvel agencement..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" -msgstr "Ouvrir agencement de transport audio" +msgstr "Ouvrir une disposition de bus audio" #: editor/editor_audio_buses.cpp msgid "There is no 'res://default_bus_layout.tres' file." @@ -971,11 +974,11 @@ msgstr "Il n'existe aucun 'res://default_bus_layout.tres'." #: editor/editor_audio_buses.cpp msgid "Invalid file, not an audio bus layout." -msgstr "Fichier invalide, pas un agencement de transport audio." +msgstr "Fichier invalide, pas une disposition de bus audio." #: editor/editor_audio_buses.cpp msgid "Add Bus" -msgstr "Ajouter un transport" +msgstr "Ajouter un bus" #: editor/editor_audio_buses.cpp msgid "Create a new Bus Layout." @@ -1100,11 +1103,11 @@ msgid "Updating Scene" msgstr "Mise à jour de la scène" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Stockage des modifications locales…" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "Mise à jour de la scène…" #: editor/editor_data.cpp @@ -1173,8 +1176,8 @@ msgid "Show In File Manager" msgstr "Montrer dans le gestionnaire de fichiers" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nouveau dossier.." +msgid "New Folder..." +msgstr "Nouveau dossier..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1435,19 +1438,19 @@ msgstr "Effacer la sortie" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "L'export du projet a échoué avec le code erreur %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Erreur d'enregistrement de la ressource !" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Enregistrer la ressource sous…" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Je vois…" #: editor/editor_node.cpp @@ -1520,11 +1523,11 @@ msgstr "Erreur d'enregistrement de la MeshLibrary !" #: editor/editor_node.cpp msgid "Can't load TileSet for merging!" -msgstr "Impossible de charger la TileSet pour fusion !" +msgstr "Impossible de charger le TileSet pour fusion !" #: editor/editor_node.cpp msgid "Error saving TileSet!" -msgstr "Erreur d'enregistrement de la TileSet !" +msgstr "Erreur d'enregistrement du TileSet !" #: editor/editor_node.cpp msgid "Error trying to save layout!" @@ -1682,11 +1685,11 @@ msgid "Open Base Scene" msgstr "Ouvrir scène de base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "Ouvrir une scène rapidement…" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "Ouvrir un script rapidement…" #: editor/editor_node.cpp @@ -1698,7 +1701,7 @@ msgid "Save changes to '%s' before closing?" msgstr "Sauvegarder modifications de '%s' avant de quitter ?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "Enregistrer la scène sous…" #: editor/editor_node.cpp @@ -1724,11 +1727,11 @@ msgstr "Exporter une bibliothèque de maillages" #: editor/editor_node.cpp msgid "This operation can't be done without a root node." -msgstr "Cette opération ne peut être réalisée sans noeud parent." +msgstr "Cette opération ne peut être réalisée sans nÅ“ud racine." #: editor/editor_node.cpp msgid "Export Tile Set" -msgstr "Exporter un ensemble de tuiles" +msgstr "Exporter le TileSet" #: editor/editor_node.cpp msgid "This operation can't be done without a selected node." @@ -1751,7 +1754,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Cette action ne peut être annulée. Réinitialiser quand même ?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "Lancer une scène rapidement…" #: editor/editor_node.cpp @@ -1918,8 +1921,8 @@ msgid "Previous tab" msgstr "Onglet precedent" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrer Fichiers.." +msgid "Filter Files..." +msgstr "Filtrer Fichiers..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1930,11 +1933,11 @@ msgid "New Scene" msgstr "Nouvelle scène" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Nouvelle scène héritée…" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "Ouvrir une scène…" #: editor/editor_node.cpp @@ -1954,15 +1957,15 @@ msgid "Open Recent" msgstr "Fichiers récents" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "Convertir vers…" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "MeshLibrary…" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet…" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2025,7 +2028,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Small Deploy with Network FS" -msgstr "Petit déploiement avec le réseau" +msgstr "Déploiement minime avec système de fichier réseau" #: editor/editor_node.cpp msgid "" @@ -2228,7 +2231,7 @@ msgid "Save the currently edited resource." msgstr "Enregistrer la ressource actuellement modifiée." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Enregistrer sous…" #: editor/editor_node.cpp @@ -2337,7 +2340,7 @@ msgid "Creating Mesh Previews" msgstr "Création des prévisualisations des maillages" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "Aperçu…" #: editor/editor_plugin_settings.cpp @@ -2490,8 +2493,8 @@ msgid "(Current)" msgstr "(Actuel)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Récupération des miroirs, veuillez patienter.." +msgid "Retrieving mirrors, please wait..." +msgstr "Récupération des miroirs, veuillez patienter..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2568,8 +2571,8 @@ msgid "Error requesting url: " msgstr "Erreur lors de la requête de l’URL : " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Connexion au miroir" +msgid "Connecting to Mirror..." +msgstr "Connexion au Miroir..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2585,8 +2588,8 @@ msgstr "Impossible à résoudre" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Connexion en cours.." +msgid "Connecting..." +msgstr "Connexion en cours..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2598,8 +2601,8 @@ msgstr "Connecté" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Envoi d'une requête.." +msgid "Requesting..." +msgstr "Envoi d'une requête..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2736,11 +2739,11 @@ msgid "Collapse all" msgstr "Réduire tout" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Renommer.." +msgid "Rename..." +msgstr "Renommer..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Déplacer vers…" #: editor/filesystem_dock.cpp @@ -2752,15 +2755,15 @@ msgid "Instance" msgstr "Instance" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "Modifier les dépendances…" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Voir les propriétaires…" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Dupliquer…" #: editor/filesystem_dock.cpp @@ -2788,7 +2791,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Analyse des fichiers en cours,\n" "Veuillez patienter..." @@ -2856,7 +2859,7 @@ msgid "Import Scene" msgstr "Importer une scène" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "Importation de la scène…" #: editor/import/resource_importer_scene.cpp @@ -2865,10 +2868,10 @@ msgstr "Génération des lightmaps :" #: editor/import/resource_importer_scene.cpp msgid "Generating for Mesh: " -msgstr "Généreration pour le Mesh : " +msgstr "Génération pour le Mesh : " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "Lancement du script personnalisé…" #: editor/import/resource_importer_scene.cpp @@ -2885,7 +2888,7 @@ msgid "Error running post-import script:" msgstr "Erreur d'exécution du script de post-importation :" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Enregistrement…" #: editor/import_dock.cpp @@ -2905,7 +2908,7 @@ msgid "Import As:" msgstr "Importer comme :" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "Pré-réglage…" #: editor/import_dock.cpp @@ -3324,7 +3327,7 @@ msgid "Transition Node" msgstr "NÅ“ud Transition" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "Importer des animations…" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3332,7 +3335,7 @@ msgid "Edit Node Filters" msgstr "Modifier les filtres de nÅ“ud" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Filtres…" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3400,8 +3403,8 @@ msgid "Fetching:" msgstr "Récupération:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Résolution.." +msgid "Resolving..." +msgstr "Résolution..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3467,7 +3470,7 @@ msgid "Site:" msgstr "Site :" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Support…" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3666,6 +3669,7 @@ msgid "Use Rotation Snap" msgstr "Rotation alignée" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Configurer le magnétisme…" @@ -3759,17 +3763,15 @@ msgstr "Afficher les règles" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Guides" -msgstr "Montrer les guides" +msgstr "Afficher les guides" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "Afficher l'origine" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 vue" +msgstr "Afficher la Viewport" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4062,7 +4064,7 @@ msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Le type de maillage primitif n'est pas PRIMITIVE_TRIANGLES !" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4093,7 +4095,7 @@ msgid "Create Convex Collision Sibling" msgstr "Créer une collision convexe" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "Créer un maillage de contour…" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4263,7 +4265,7 @@ msgstr "Partitionnement..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating contours..." -msgstr "Création des coutours..." +msgstr "Création des contours..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Creating polymesh..." @@ -4304,8 +4306,8 @@ msgid "Error loading image:" msgstr "Erreur de chargement d'image :" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Pas de pixels avec transparence > 128 dans l'image.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Pas de pixels avec transparence > 128 dans l'image..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4556,7 +4558,7 @@ msgstr "Mettre à l'échelle le polygone" #: editor/project_settings_editor.cpp editor/property_editor.cpp #: modules/visual_script/visual_script_editor.cpp msgid "Edit" -msgstr "Modifier" +msgstr "Édition" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Polygon->UV" @@ -4665,7 +4667,7 @@ msgid "Import Theme" msgstr "Importer un thème" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Enregistrer le thème sous…" #: editor/plugins/script_editor_plugin.cpp @@ -4758,11 +4760,11 @@ msgstr "Lancer" #: editor/plugins/script_editor_plugin.cpp msgid "Toggle Scripts Panel" -msgstr "Afficher/Cacher la panneau des scripts" +msgstr "Afficher/Cacher le panneau des scripts" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "Trouver…" #: editor/plugins/script_editor_plugin.cpp @@ -4860,7 +4862,7 @@ msgstr "Prélever une couleur" #: editor/plugins/script_text_editor.cpp msgid "Convert Case" -msgstr "Cas de conversion" +msgstr "Modifier la casse" #: editor/plugins/script_text_editor.cpp msgid "Uppercase" @@ -4969,18 +4971,18 @@ msgstr "Convertir en minuscule" #: editor/plugins/script_text_editor.cpp msgid "Find Previous" -msgstr "trouver précédente" +msgstr "Trouver le précédent" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "Remplacer…" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "Aller à la fonction…" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "Aller à la ligne…" #: editor/plugins/script_text_editor.cpp @@ -5436,11 +5438,7 @@ msgid "Transform" msgstr "Transformation" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configurer la grille…" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "Dialogue de transformation…" #: editor/plugins/spatial_editor_plugin.cpp @@ -5693,7 +5691,7 @@ msgid "Remove All" msgstr "Supprimer tout" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Éditer le thème..." #: editor/plugins/theme_editor_plugin.cpp @@ -5741,14 +5739,12 @@ msgid "Checked Item" msgstr "Item coché" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Ajouter un item" +msgstr "Item radio" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Item coché" +msgstr "Item radio coché" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5763,8 +5759,8 @@ msgid "Options" msgstr "Options" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Ont,Plusieurs,Possibilités,D'options !" +msgid "Has,Many,Options" +msgstr "Possède,Plusieurs,Options" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5832,7 +5828,7 @@ msgstr "Supprimer la sélection" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Find tile" -msgstr "Chercher une case" +msgstr "Trouver une tuile" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Transpose" @@ -5888,7 +5884,7 @@ msgstr "Fusionner depuis la scène ?" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Tile Set" -msgstr "Ensemble de cases" +msgstr "Jeu de tuiles" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -5904,31 +5900,31 @@ msgstr "Erreur" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Autotiles" -msgstr "Coupe automatique" +msgstr "Autotiles" #: editor/plugins/tile_set_editor_plugin.cpp msgid "" "Select sub-tile to use as icon, this will be also used on invalid autotile " "bindings." msgstr "" -"Sélectionne une ressource à utiliser comme icône, celle-ci sera aussi " -"utilisée sur les liaisons autotile invalides." +"Sélectionner une sous-tuile à utiliser comme icône, celle-ci sera aussi " +"utilisée pour les liaisons de tuiles automatiques invalides." #: editor/plugins/tile_set_editor_plugin.cpp msgid "" "LMB: set bit on.\n" "RMB: set bit off." msgstr "" -"Clic gauche : Activer\n" -"Clic droit : Désactiver" +"Clic-gauche : Activer\n" +"Clic-droit : Désactiver" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Select current edited sub-tile." -msgstr "Enregistrer la ressource en cours de modification." +msgstr "Sélectionner la sous-tuile en cours d'édition." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Select sub-tile to change its priority." -msgstr "Sélectionner une sous-case pour changer sa priorité." +msgstr "Sélectionner une sous-tuile pour changer sa priorité." #: editor/progress_dialog.cpp scene/gui/dialogs.cpp msgid "Cancel" @@ -5955,7 +5951,7 @@ msgid "Presets" msgstr "Pré-réglages" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Ajouter…" #: editor/project_export.cpp @@ -6049,6 +6045,10 @@ msgid "Imported Project" msgstr "Projet importé" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Nom du Projet Invalide." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Impossible de créer le dossier." @@ -6252,9 +6252,11 @@ msgstr "Bouton de souris" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Nom d'action invalide. Il ne peux être vide ou contenir '/', ':', '=', '\\' " +"ou '\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6281,7 +6283,7 @@ msgid "Control+" msgstr "Contrôle+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "Appuyez sur une touche…" #: editor/project_settings_editor.cpp @@ -6414,7 +6416,7 @@ msgstr "Paramètres enregistrés avec succès." #: editor/project_settings_editor.cpp msgid "Override for Feature" -msgstr "Remplacement de fonctionnalité" +msgstr "Écrasement d'un paramètre, dédié à un tag de fonctionnalité" #: editor/project_settings_editor.cpp msgid "Add Translation" @@ -6465,8 +6467,8 @@ msgid "Property:" msgstr "Propriété :" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Remplacement pour.." +msgid "Override For..." +msgstr "Écraser pour…" #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6561,11 +6563,11 @@ msgid "Easing Out-In" msgstr "Ease out-in" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "Fichier…" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "Répertoire…" #: editor/property_editor.cpp @@ -6739,7 +6741,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Cette opération ne peut être réalisée sur des scènes instanciées." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Enregistrer la nouvelle scène sous…" #: editor/scene_tree_dock.cpp @@ -7170,11 +7172,11 @@ msgstr "Divers" #: editor/script_editor_debugger.cpp msgid "Clicked Control:" -msgstr "Control cliqué :" +msgstr "Contrôle cliqué :" #: editor/script_editor_debugger.cpp msgid "Clicked Control Type:" -msgstr "Type de Control cliqué :" +msgstr "Type de contrôle cliqué :" #: editor/script_editor_debugger.cpp msgid "Live Edit Root:" @@ -7461,7 +7463,7 @@ msgstr "Choisissez distance :" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Le nom de la classe ne peut pas être un mot clé réservé" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -7469,7 +7471,7 @@ msgstr "Génération de la solution en cours..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating C# project..." -msgstr "Création du projet C# ..." +msgstr "Création du projet C#..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Failed to create solution." @@ -8178,7 +8180,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "L'environnement mondial a besoin d'une ressource environnementale." #: scene/3d/scenario_fx.cpp msgid "" @@ -8192,6 +8194,9 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Cet WorldEnvironment est ignoré. Ajoutez une caméra (pour les scènes 3D) ou " +"définissez le mode Background Mode de cet environnement sur Canvas (pour les " +"scènes 2D)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8290,6 +8295,13 @@ msgstr "Erreur lors du chargement de la police." msgid "Invalid font size." msgstr "Taille de police invalide." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Onglet precedent" + +#~ msgid "Next" +#~ msgstr "Suivant" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Action invalide (tout passe, sauf « / » ou « : »)." @@ -8319,9 +8331,6 @@ msgstr "Taille de police invalide." #~ msgstr "" #~ "Impossible de trouver le fichier project.godot dans le chemin du projet." -#~ msgid "Next" -#~ msgstr "Suivant" - #~ msgid "Not found!" #~ msgstr "Non trouvé !" @@ -8469,7 +8478,7 @@ msgstr "Taille de police invalide." #~ msgid "Exporting for %s" #~ msgstr "Exportation pour %s" -#~ msgid "Setting Up.." +#~ msgid "Setting Up..." #~ msgstr "Configuration…" #~ msgid "Error loading scene." @@ -8532,7 +8541,7 @@ msgstr "Taille de police invalide." #~ msgid "Info" #~ msgstr "Information" -#~ msgid "Re-Import.." +#~ msgid "Re-Import..." #~ msgstr "Ré-importer…" #~ msgid "No bit masks to import!" @@ -8929,13 +8938,13 @@ msgstr "Taille de police invalide." #~ msgid "Zoom (%):" #~ msgstr "Zoom (%) :" -#~ msgid "Skeleton.." +#~ msgid "Skeleton..." #~ msgstr "Squelette…" #~ msgid "Zoom Reset" #~ msgstr "Réinitialiser le zoom" -#~ msgid "Zoom Set.." +#~ msgid "Zoom Set..." #~ msgstr "Définir le zoom…" #~ msgid "Set a Value" @@ -9413,7 +9422,7 @@ msgstr "Taille de police invalide." #~ msgid "Export Project PCK" #~ msgstr "Exporter le PCK du projet" -#~ msgid "Export.." +#~ msgid "Export..." #~ msgstr "Exporter…" #~ msgid "Project Export" @@ -9508,7 +9517,7 @@ msgstr "Taille de police invalide." #~ msgid "Method In Node:" #~ msgstr "Méthode dans le nÅ“ud :" -#~ msgid "Edit Connections.." +#~ msgid "Edit Connections..." #~ msgstr "Modifier les connexions..." #~ msgid "Set Params" @@ -9563,5 +9572,5 @@ msgstr "Taille de police invalide." #~ "modifiez les options d'exportation par la suite. Vous pouvez également " #~ "générer des atlas à l'exportation." -#~ msgid "Merging.." +#~ msgid "Merging..." #~ msgstr "Fusion..." diff --git a/editor/translations/he.po b/editor/translations/he.po index ee0a66cec1..0f1881211f 100644 --- a/editor/translations/he.po +++ b/editor/translations/he.po @@ -498,7 +498,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -908,11 +908,11 @@ msgid "Move Audio Bus" msgstr "הזזת ×פיק שמע" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "שמירת פריסת ×פיקי השמע בתור…" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "×ž×™×§×•× ×œ×¤×¨×™×¡×” החדשה…" #: editor/editor_audio_buses.cpp @@ -1048,11 +1048,11 @@ msgid "Updating Scene" msgstr "×”×¡×¦× ×” ×ž×ª×¢×“×›× ×ª" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "×”×©×™× ×•×™×™× ×”×ž×§×•×ž×™×™× ×ž××•×—×¡× ×™×…" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "×”×¡×¦× ×” ×ž×ª×¢×“×›× ×ªâ€¦" #: editor/editor_data.cpp @@ -1121,7 +1121,7 @@ msgid "Show In File Manager" msgstr "הצגה ×‘×ž× ×”×œ הקבצי×" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "תיקייה חדשה…" #: editor/editor_file_dialog.cpp @@ -1383,12 +1383,12 @@ msgid "Error saving resource!" msgstr "שגי××” בשמירת המש×ב!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "שמירת המש×ב בתור…" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "×× ×™ רו×ה…" #: editor/editor_node.cpp @@ -1597,11 +1597,11 @@ msgid "Open Base Scene" msgstr "פתיחת ×¡×¦× ×ª בסיס" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "פתיחת ×¡×¦× ×” מהירה…" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "פתיחת סקריפט מהירה…" #: editor/editor_node.cpp @@ -1613,7 +1613,7 @@ msgid "Save changes to '%s' before closing?" msgstr "לשמור ×ת ×”×©×™× ×•×™×™× ×œÖ¾â€š%s’ ×œ×¤× ×™ הסגירה?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "שמירת ×¡×¦× ×” בש×…" #: editor/editor_node.cpp @@ -1665,7 +1665,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "×œ× × ×™×ª×Ÿ לבטל פעולה זו. לשחזר בכל ×–×ת?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1812,7 +1812,7 @@ msgid "Previous tab" msgstr "×”×œ×©×•× ×™×ª הקודמת" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1824,11 +1824,11 @@ msgid "New Scene" msgstr "×¡×¦× ×” חדשה" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "×¡×¦× ×” חדשה בירושה…" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "פתיחת ×¡×¦× ×”â€¦" #: editor/editor_node.cpp @@ -1848,15 +1848,15 @@ msgid "Open Recent" msgstr "פתיחה מה××—×¨×•× ×™×" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "המרה ×ל…" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2103,7 +2103,7 @@ msgid "Save the currently edited resource." msgstr "שמירת המש×ב ×©× ×¢×¨×š כרגע." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "שמירה בש×…" #: editor/editor_node.cpp @@ -2212,7 +2212,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "×ª×ž×•× ×” ממוזערת…" #: editor/editor_plugin_settings.cpp @@ -2363,7 +2363,7 @@ msgid "(Current)" msgstr "(× ×•×›×—×™)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2439,7 +2439,7 @@ msgid "Error requesting url: " msgstr "שגי××” בבקשת כתובת: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2456,7 +2456,7 @@ msgstr "×œ× × ×™×ª×Ÿ לפתור" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "מתבצעת התחברות…" #: editor/export_template_manager.cpp @@ -2469,7 +2469,7 @@ msgstr "מחובר" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "מוגשת בקשה…" #: editor/export_template_manager.cpp @@ -2602,11 +2602,11 @@ msgid "Collapse all" msgstr "×œ×¦×ž×¦× ×”×›×•×œ" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "×©×™× ×•×™ ש×…" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "העברה ×ל…" #: editor/filesystem_dock.cpp @@ -2618,15 +2618,15 @@ msgid "Instance" msgstr "עותק" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "עריכת תלויות…" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "צפייה בבעלי×…" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "שכפול…" #: editor/filesystem_dock.cpp @@ -2652,7 +2652,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "×”×§×‘×¦×™× × ×¡×¨×§×™×,\n" "× × ×œ×”×ž×ª×™×Ÿâ€¦" @@ -2720,7 +2720,7 @@ msgid "Import Scene" msgstr "×™×™×‘×•× ×¡×¦× ×”" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "×¡×¦× ×” מיוב×ת…" #: editor/import/resource_importer_scene.cpp @@ -2732,7 +2732,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "מופעל סקריפט מות×× ×ישית…" #: editor/import/resource_importer_scene.cpp @@ -2748,7 +2748,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "שמירה…" #: editor/import_dock.cpp @@ -2768,7 +2768,7 @@ msgid "Import As:" msgstr "×™×™×‘×•× ×‘×ª×•×¨:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "ערכה מוגדרת…" #: editor/import_dock.cpp @@ -3182,7 +3182,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3190,7 +3190,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3258,7 +3258,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3325,7 +3325,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3512,8 +3512,9 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "" +msgstr "הגדרת הצמדה…" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3933,7 +3934,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4138,7 +4139,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4499,7 +4500,7 @@ msgid "Import Theme" msgstr "×™×™×‘×•× ×¢×¨×›×ª עיצוב" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "שמירת ערכת עיצוב בש×…" #: editor/plugins/script_editor_plugin.cpp @@ -4596,7 +4597,7 @@ msgstr "החלפת תצוגת ×—×œ×•× ×™×ª סקריפטי×" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "×יתור…" #: editor/plugins/script_editor_plugin.cpp @@ -4804,15 +4805,15 @@ msgid "Find Previous" msgstr "×יתור הקוד×" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "החלפה…" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "מעבר ×œ×¤×•× ×§×¦×™×”â€¦" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "מעבר לשורה…" #: editor/plugins/script_text_editor.cpp @@ -5266,11 +5267,7 @@ msgid "Transform" msgstr "התמרה" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "הגדרת הצמדה…" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5523,7 +5520,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5591,7 +5588,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5779,7 +5776,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5869,6 +5866,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "×©× ×©×’×•×™." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6057,8 +6059,8 @@ msgstr "כפתור עכבר" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6086,7 +6088,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "× × ×œ×œ×—×•×¥ על מקש…" #: editor/project_settings_editor.cpp @@ -6270,7 +6272,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6366,11 +6368,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6541,7 +6543,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -7975,12 +7977,16 @@ msgstr "שגי××” ×‘×˜×¢×™× ×ª הגופן." msgid "Invalid font size." msgstr "גודל הגופן שגוי." -#~ msgid "Can't write file." -#~ msgstr "×œ× × ×™×ª×Ÿ לכתוב קובץ." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "×”×œ×©×•× ×™×ª הקודמת" #~ msgid "Next" #~ msgstr "הב×" +#~ msgid "Can't write file." +#~ msgstr "×œ× × ×™×ª×Ÿ לכתוב קובץ." + #~ msgid "Not found!" #~ msgstr "×œ× × ×ž×¦×!" diff --git a/editor/translations/hi.po b/editor/translations/hi.po index e017935860..3340f13471 100644 --- a/editor/translations/hi.po +++ b/editor/translations/hi.po @@ -511,8 +511,8 @@ msgstr "जà¥à¤¡à¤¿à¤¯à¥‡ '%s' to '%s'" #: editor/connections_dialog.cpp #, fuzzy -msgid "Connect.." -msgstr "जà¥à¤¡à¤¿à¤¯à¥‡.." +msgid "Connect..." +msgstr "जà¥à¤¡à¤¿à¤¯à¥‡..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -946,11 +946,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1086,11 +1086,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1159,7 +1159,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1421,12 +1421,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1631,11 +1631,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1647,7 +1647,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1699,7 +1699,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1844,7 +1844,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1856,11 +1856,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1880,15 +1880,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2133,7 +2133,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2242,7 +2242,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2393,7 +2393,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2469,7 +2469,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2486,7 +2486,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2500,7 +2500,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2637,11 +2637,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2653,16 +2653,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "पà¥à¤°à¤¤à¤¿à¤²à¤¿à¤ªà¤¿" #: editor/filesystem_dock.cpp @@ -2688,7 +2688,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2754,7 +2754,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2766,7 +2766,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2782,7 +2782,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2802,7 +2802,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3217,7 +3217,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3225,7 +3225,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3293,7 +3293,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3360,7 +3360,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3547,6 +3547,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3968,7 +3969,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4173,7 +4174,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4535,7 +4536,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4632,7 +4633,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4838,15 +4839,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5297,11 +5298,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5554,7 +5551,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5622,7 +5619,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5810,7 +5807,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5900,6 +5897,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "गलत फॉणà¥à¤Ÿ का आकार |" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6088,8 +6090,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6117,7 +6119,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6301,7 +6303,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6397,11 +6399,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6572,7 +6574,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/hu.po b/editor/translations/hu.po index b6151574e9..b04dd073df 100644 --- a/editor/translations/hu.po +++ b/editor/translations/hu.po @@ -2,23 +2,22 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# +# Ãrpád Horváth <horvatha4@googlemail.com>, 2018. # Nagy Lajos <neutron9707@gmail.com>, 2017. # Sandor Domokos <sandor.domokos@gmail.com>, 2017-2018. # Varga Dániel <danikah.danikah@gmail.com>, 2016-2018. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-03 06:36+0000\n" -"Last-Translator: Sandor Domokos <sandor.domokos@gmail.com>\n" +"PO-Revision-Date: 2018-06-17 07:39+0000\n" +"Last-Translator: Ãrpád Horváth <horvatha4@googlemail.com>\n" "Language-Team: Hungarian <https://hosted.weblate.org/projects/godot-engine/" "godot/hu/>\n" "Language: hu\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.20-dev\n" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -34,7 +33,7 @@ msgstr "Animáció kulcsképkocka idÅ‘ változtatás" #: editor/animation_editor.cpp msgid "Anim Change Transition" -msgstr "Animáció átmenet változtatás" +msgstr "Animáció átmenet változtatása" #: editor/animation_editor.cpp msgid "Anim Change Transform" @@ -236,7 +235,7 @@ msgstr "Animáció kulcsok nyújtás" #: editor/animation_editor.cpp msgid "Anim Add Call Track" -msgstr "Animáció hÃvási nyomvonal hozzáadás" +msgstr "Animációhoz hÃvási nyomvonal hozzáadása" #: editor/animation_editor.cpp msgid "Animation zoom." @@ -500,7 +499,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "'%s' Lecsatlakoztatása '%s'-ról" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Kapcsolás..." #: editor/connections_dialog.cpp @@ -920,12 +919,12 @@ msgid "Move Audio Bus" msgstr "Hangbusz Ãthelyezése" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Hangbusz Elrendezés Mentése Másként.." +msgid "Save Audio Bus Layout As..." +msgstr "Hangbusz Elrendezés Mentése Másként..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Új Elrendezés Helye.." +msgid "Location for New Layout..." +msgstr "Új Elrendezés Helye..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1061,12 +1060,12 @@ msgid "Updating Scene" msgstr "Scene FrissÃtése" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Helyi módosÃtások eltárolása.." +msgid "Storing local changes..." +msgstr "Helyi módosÃtások eltárolása..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Scene frissÃtése.." +msgid "Updating scene..." +msgstr "Scene frissÃtése..." #: editor/editor_data.cpp msgid "[empty]" @@ -1134,8 +1133,8 @@ msgid "Show In File Manager" msgstr "Mutat FájlkezelÅ‘ben" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Új Mappa.." +msgid "New Folder..." +msgstr "Új Mappa..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1396,20 +1395,20 @@ msgstr "Kimenet Törlése" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Projekt export nem sikerült, hibakód %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Hiba történt az erÅ‘forrás mentésekor!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "ErÅ‘forrás Mentése Másként.." +msgid "Save Resource As..." +msgstr "ErÅ‘forrás Mentése Másként..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Értem.." +msgid "I see..." +msgstr "Értem..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1645,12 +1644,12 @@ msgid "Open Base Scene" msgstr "Alap Scene megnyitás" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Scene gyors megnyitás" +msgid "Quick Open Scene..." +msgstr "Jelenet gyors megnyitása..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Szkript gyors megnyitás" +msgid "Quick Open Script..." +msgstr "Szkript gyors megnyitás..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1661,8 +1660,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Bezárás elÅ‘tt menti a '%s'-n végzett módosÃtásokat?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Scene mentés másként" +msgid "Save Scene As..." +msgstr "Scene mentés másként..." #: editor/editor_node.cpp msgid "No" @@ -1713,8 +1712,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Ez a művelet nem vonható vissza. VisszaállÃtja mindenképp?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Scene gyors futtatás" +msgid "Quick Run Scene..." +msgstr "Scene gyors futtatás..." #: editor/editor_node.cpp msgid "Quit" @@ -1860,7 +1859,7 @@ msgstr "Hozzáad egy új jelenetet." #: editor/editor_node.cpp msgid "Scene" -msgstr "Scene" +msgstr "Jelenet" #: editor/editor_node.cpp msgid "Go to previously opened scene." @@ -1875,8 +1874,8 @@ msgid "Previous tab" msgstr "ElÅ‘zÅ‘ fül" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Fájlok Szűrése.." +msgid "Filter Files..." +msgstr "Fájlok Szűrése..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1887,12 +1886,12 @@ msgid "New Scene" msgstr "Új Scene" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Új örökölt Scene" +msgid "New Inherited Scene..." +msgstr "Új örökölt Jelenet..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Scene megnyitása" +msgid "Open Scene..." +msgstr "Jelenet megnyitása..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1911,16 +1910,16 @@ msgid "Open Recent" msgstr "Legutóbbi Megnyitása" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Ãtkonvertálás.." +msgid "Convert To..." +msgstr "Ãtkonvertálás..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary-ra.." +msgid "MeshLibrary..." +msgstr "MeshLibrary-ra..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet-re.." +msgid "TileSet..." +msgstr "TileSet-re..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1982,7 +1981,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Small Deploy with Network FS" -msgstr "Kis TelepÃtés Hálózati FR-rel" +msgstr "Kis TelepÃtés Hálózati FS-sel" #: editor/editor_node.cpp msgid "" @@ -2026,7 +2025,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Sync Scene Changes" -msgstr "Scene változtatások szinkronizálás" +msgstr "Jelenet változtatások szinkronizálása" #: editor/editor_node.cpp msgid "" @@ -2184,8 +2183,8 @@ msgid "Save the currently edited resource." msgstr "A jelenleg szerkesztett erÅ‘forrás elmentése." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Mentés Másként.." +msgid "Save As..." +msgstr "Mentés Másként..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2293,8 +2292,8 @@ msgid "Creating Mesh Previews" msgstr "Háló ElÅ‘nézetek Létrehozása" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Indexkép.." +msgid "Thumbnail..." +msgstr "Indexkép..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2446,8 +2445,8 @@ msgid "(Current)" msgstr "(Jelenlegi)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Tükrök letöltése, kérjük várjon.." +msgid "Retrieving mirrors, please wait..." +msgstr "Tükrök letöltése, kérjük várjon..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2524,8 +2523,8 @@ msgid "Error requesting url: " msgstr "Hiba történt az url lekérdezésekor: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Csatlakozás Tükörhöz.." +msgid "Connecting to Mirror..." +msgstr "Csatlakozás Tükörhöz..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2541,8 +2540,8 @@ msgstr "Nem Megoldható" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Csatlakozás.." +msgid "Connecting..." +msgstr "Csatlakozás..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2554,8 +2553,8 @@ msgstr "Csatlakozva" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Lekérdezés.." +msgid "Requesting..." +msgstr "Lekérdezés..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2690,12 +2689,12 @@ msgid "Collapse all" msgstr "Összes összecsukása" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Ãtnevezés.." +msgid "Rename..." +msgstr "Ãtnevezés..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Ãthelyezés.." +msgid "Move To..." +msgstr "Ãthelyezés..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2706,16 +2705,16 @@ msgid "Instance" msgstr "Példány" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "FüggÅ‘ségek Szerkesztése.." +msgid "Edit Dependencies..." +msgstr "FüggÅ‘ségek Szerkesztése..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Tulajdonosok Megtekintése.." +msgid "View Owners..." +msgstr "Tulajdonosok Megtekintése..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "MegkettÅ‘zés.." +msgid "Duplicate..." +msgstr "MegkettÅ‘zés..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2740,10 +2739,10 @@ msgstr "Kiválasztott Scene(k) példányosÃtása a kiválasztott Node gyermekek #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Fájlok Vizsgálata,\n" -"Kérem Várjon.." +"Kérem Várjon..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2808,8 +2807,8 @@ msgid "Import Scene" msgstr "Scene importálás" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Scene importálás" +msgid "Importing Scene..." +msgstr "Jelenet importálása..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2820,8 +2819,8 @@ msgid "Generating for Mesh: " msgstr "Létrehozás a KövetkezÅ‘ Hálóhoz: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "TetszÅ‘leges Szkript Futtatása.." +msgid "Running Custom Script..." +msgstr "TetszÅ‘leges Szkript Futtatása..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2837,8 +2836,8 @@ msgid "Error running post-import script:" msgstr "Hiba történt az importálás utána szkript futtatásakor:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Mentés.." +msgid "Saving..." +msgstr "Mentés..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2857,8 +2856,8 @@ msgid "Import As:" msgstr "Importálás Mint:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "BeépÃtett BeállÃtások.." +msgid "Preset..." +msgstr "BeépÃtett BeállÃtások..." #: editor/import_dock.cpp msgid "Reimport" @@ -3276,16 +3275,16 @@ msgid "Transition Node" msgstr "Ãtmenet Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Animációk Importálása.." +msgid "Import Animations..." +msgstr "Animációk Importálása..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Node szűrÅ‘k szerkesztés" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "SzűrÅ‘k.." +msgid "Filters..." +msgstr "SzűrÅ‘k..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3354,8 +3353,8 @@ msgid "Fetching:" msgstr "Lekérés:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Megoldás.." +msgid "Resolving..." +msgstr "Megoldás..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3421,8 +3420,8 @@ msgid "Site:" msgstr "Oldal:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Támogatás.." +msgid "Support..." +msgstr "Támogatás..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3430,7 +3429,7 @@ msgstr "Hivatalos" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Testing" -msgstr "Tesztelés Alatt" +msgstr "Tesztelés" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Assets ZIP File" @@ -3621,6 +3620,7 @@ msgid "Use Rotation Snap" msgstr "Forgatási Illesztés Használata" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Illesztés BeállÃtása..." @@ -3717,14 +3717,12 @@ msgid "Show Guides" msgstr "VezetÅ‘vonalak MegjelenÃtése" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Rács MegjelenÃtése" +msgstr "Origó MegjelenÃtése" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "SegÃtÅ‘k MegjelenÃtése" +msgstr "Nézet MegjelenÃtése" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4018,7 +4016,7 @@ msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "A Háló-primitÃv tÃpusa nem háromszög (PRIMITIVE_TRIANGLES)!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4049,8 +4047,8 @@ msgid "Create Convex Collision Sibling" msgstr "Konvex Ütközési Testvér Létrehozása" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Körvonalháló Létrehozása.." +msgid "Create Outline Mesh..." +msgstr "Körvonalháló Létrehozása..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4255,8 +4253,8 @@ msgid "Error loading image:" msgstr "Hiba a kép betöltésekor:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Nem létezik egyetlen pixel sem >128-as átlátszósággal a képben.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Nem létezik egyetlen pixel sem >128-as átlátszósággal a képben..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4616,8 +4614,8 @@ msgid "Import Theme" msgstr "Téma Importálása" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Téma Mentése Másként.." +msgid "Save Theme As..." +msgstr "Téma Mentése Másként..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4713,8 +4711,8 @@ msgstr "Szkript Panel MegjelenÃtése" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Keresés.." +msgid "Find..." +msgstr "Keresés..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4923,16 +4921,16 @@ msgid "Find Previous" msgstr "ElÅ‘zÅ‘ Keresése" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Csere.." +msgid "Replace..." +msgstr "Csere..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Ugrás Funkcióra.." +msgid "Goto Function..." +msgstr "Ugrás Funkcióra..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Ugrás Sorra.." +msgid "Goto Line..." +msgstr "Ugrás Sorra..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -4964,7 +4962,7 @@ msgstr "Vec kezelÅ‘ változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Scalar Operator" -msgstr "" +msgstr "Vektor skalár kezelÅ‘ változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change RGB Operator" @@ -4972,31 +4970,31 @@ msgstr "RGB kezelÅ‘ változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Toggle Rot Only" -msgstr "" +msgstr "Csak vörös kapcsolása" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Function" -msgstr "" +msgstr "Skalár-függvény változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Function" -msgstr "" +msgstr "Vektor-függvény változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Scalar Uniform" -msgstr "" +msgstr "Egységes-skalár változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Vec Uniform" -msgstr "" +msgstr "Egységes-vektor változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change RGB Uniform" -msgstr "" +msgstr "Egységes-RGB változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change Default Value" -msgstr "" +msgstr "Alapérték változtatás" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Change XForm Uniform" @@ -5092,7 +5090,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Plane Transform." -msgstr "" +msgstr "Megnéz a SÃklap transzformációját." #: editor/plugins/spatial_editor_plugin.cpp msgid "Scaling: " @@ -5244,7 +5242,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Doppler Enable" -msgstr "" +msgstr "Doppler engedélyezése" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Left" @@ -5382,11 +5380,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5639,7 +5633,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5687,9 +5681,8 @@ msgid "Checked Item" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Elem Hozzáadása" +msgstr "Rádió Elem" #: editor/plugins/theme_editor_plugin.cpp msgid "Checked Radio Item" @@ -5708,7 +5701,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5896,7 +5889,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5986,6 +5979,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Érvénytelen projektnév." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6172,8 +6169,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6201,7 +6198,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6378,14 +6375,14 @@ msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "" +msgstr "Ãltalános" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6481,11 +6478,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6656,7 +6653,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -7242,7 +7239,7 @@ msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Next Plane" -msgstr "KövetkezÅ‘ SÃk" +msgstr "KövetkezÅ‘ SÃklap" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Previous Plane" @@ -7722,7 +7719,7 @@ msgstr "" #: modules/visual_script/visual_script_func_nodes.cpp msgid "Path does not lead Node!" -msgstr "" +msgstr "Az út nem vezeti a csomópontot!" #: modules/visual_script/visual_script_func_nodes.cpp msgid "Invalid index property name '%s' in node %s." @@ -8100,6 +8097,13 @@ msgstr "Hiba a betűtÃpus betöltésekor." msgid "Invalid font size." msgstr "Érvénytelen betűtÃpus méret." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "ElÅ‘zÅ‘ fül" + +#~ msgid "Next" +#~ msgstr "KövetkezÅ‘" + #~ msgid "" #~ "Invalid version.txt format inside templates. Revision is not a valid " #~ "identifier." @@ -8110,9 +8114,6 @@ msgstr "Érvénytelen betűtÃpus méret." #~ msgid "Can't write file." #~ msgstr "Nem lehet fájlt Ãrni." -#~ msgid "Next" -#~ msgstr "KövetkezÅ‘" - #~ msgid "Not found!" #~ msgstr "Nincs Találat!" diff --git a/editor/translations/id.po b/editor/translations/id.po index 21d333009f..3956378ce7 100644 --- a/editor/translations/id.po +++ b/editor/translations/id.po @@ -2,31 +2,31 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Abdul Aziz Muslim Alqudsy <abdul.aziz.muslim.alqudsy@gmail.com>, 2016. # Andevid Dynmyn <doyan4forum@gmail.com>, 2016. # Andinawan Asa <asaandinawan@gmail.com>, 2016. # Damar Inderajati <damarind@gmail.com>, 2017. # Damar S. M <the.last.walla@gmail.com>, 2017. +# Fajar Ru <kzofajar@gmail.com>, 2018. # Khairul Hidayat <khairulcyber4rt@gmail.com>, 2016. +# Reza Hidayat Bayu Prabowo <rh.bayu.prabowo@gmail.com>, 2018. # Romi Kusuma Bakti <romikusumab@gmail.com>, 2017. # Sofyan Sugianto <sofyanartem@gmail.com>, 2017-2018. # Tito <ijavadroid@gmail.com>, 2018. # Tom My <tom.asadinawan@gmail.com>, 2017. # yursan9 <rizal.sagi@gmail.com>, 2016. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-18 16:38+0000\n" -"Last-Translator: Tito <ijavadroid@gmail.com>\n" +"PO-Revision-Date: 2018-06-22 08:30+0000\n" +"Last-Translator: Fajar Ru <kzofajar@gmail.com>\n" "Language-Team: Indonesian <https://hosted.weblate.org/projects/godot-engine/" "godot/id/>\n" "Language: id\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 3.0-dev\n" +"X-Generator: Weblate 3.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -506,8 +506,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Memutuskan '%s' dari '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Menyambungkan.." +msgid "Connect..." +msgstr "Menyambungkan..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -701,9 +701,8 @@ msgid "Change Dictionary Key" msgstr "Ubah Kunci Kamus" #: editor/dictionary_property_edit.cpp -#, fuzzy msgid "Change Dictionary Value" -msgstr "Ubah Nilai Dictionary" +msgstr "Ubah Nilai Kamus" #: editor/editor_about.cpp msgid "Thanks from the Godot community!" @@ -931,12 +930,12 @@ msgid "Move Audio Bus" msgstr "Pindahkan Audio Bus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Simpan Layout Suara Bus Ke.." +msgid "Save Audio Bus Layout As..." +msgstr "Simpan Layout Suara Bus Ke..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Lokasi untuk Layout Baru.." +msgid "Location for New Layout..." +msgstr "Lokasi untuk Layout Baru..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1074,12 +1073,12 @@ msgid "Updating Scene" msgstr "Memperbaharui Scene" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Menyimpan perubahan-perubahan lokal.." +msgid "Storing local changes..." +msgstr "Menyimpan perubahan-perubahan lokal..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Memperbaharui scene.." +msgid "Updating scene..." +msgstr "Memperbaharui scene..." #: editor/editor_data.cpp msgid "[empty]" @@ -1147,7 +1146,7 @@ msgid "Show In File Manager" msgstr "Tampilkan di Manajer Berkas" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "Buat Direktori..." #: editor/editor_file_dialog.cpp @@ -1411,21 +1410,21 @@ msgstr "Bersihkan Luaran" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Ekspor proyek gagal dengan kode kesalahan% d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Error menyimpan resource!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Simpan Resource Sebagai.." +msgid "Save Resource As..." +msgstr "Simpan Resource Sebagai..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp #, fuzzy -msgid "I see.." -msgstr "Aku tahu.." +msgid "I see..." +msgstr "Aku tahu..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1571,11 +1570,11 @@ msgstr "" #: editor/editor_node.cpp msgid "Expand all properties" -msgstr "" +msgstr "Perluas semua properti" #: editor/editor_node.cpp msgid "Collapse all properties" -msgstr "" +msgstr "Ciutkan semua properti" #: editor/editor_node.cpp msgid "Copy Params" @@ -1658,12 +1657,12 @@ msgid "Open Base Scene" msgstr "Buka Scene Dasar" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Buka Cepat Scene.." +msgid "Quick Open Scene..." +msgstr "Buka Cepat Scene..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Buka Cepat Script.." +msgid "Quick Open Script..." +msgstr "Buka Cepat Script..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1674,8 +1673,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Simpan perubahan '%s' sebelum tutup?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Simpan Scene Sebagai.." +msgid "Save Scene As..." +msgstr "Simpan Scene Sebagai..." #: editor/editor_node.cpp msgid "No" @@ -1726,8 +1725,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Tindakan ini tidak dapat dibatalkan. Pulihkan saja?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Jalankan Cepat Scene.." +msgid "Quick Run Scene..." +msgstr "Jalankan Cepat Scene..." #: editor/editor_node.cpp msgid "Quit" @@ -1802,9 +1801,8 @@ msgstr "" #: editor/editor_node.cpp editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp -#, fuzzy msgid "Ugh" -msgstr "Wadoo" +msgstr "Duh" #: editor/editor_node.cpp msgid "" @@ -1885,8 +1883,8 @@ msgid "Previous tab" msgstr "Tab sebelumnya" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Saring berkas.." +msgid "Filter Files..." +msgstr "Saring berkas..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1897,12 +1895,12 @@ msgid "New Scene" msgstr "Scene Baru" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Scene Turunan Baru.." +msgid "New Inherited Scene..." +msgstr "Scene Turunan Baru..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Buka Scene.." +msgid "Open Scene..." +msgstr "Buka Scene..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1921,16 +1919,16 @@ msgid "Open Recent" msgstr "Buka baru-baru ini" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Ubah ke.." +msgid "Convert To..." +msgstr "Ubah ke..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "" +msgid "MeshLibrary..." +msgstr "PerpustakaanMesh..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1976,7 +1974,7 @@ msgstr "Keluar ke daftar proyek" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Debug" -msgstr "" +msgstr "\"Debug\"" #: editor/editor_node.cpp msgid "Deploy with Remote Debug" @@ -2193,8 +2191,8 @@ msgid "Save the currently edited resource." msgstr "Simpan sumber yang sedang diatur." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Simpan Sebagai.." +msgid "Save As..." +msgstr "Simpan Sebagai..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2223,7 +2221,7 @@ msgstr "Impor" #: editor/editor_node.cpp msgid "Node" -msgstr "" +msgstr "Node" #: editor/editor_node.cpp msgid "FileSystem" @@ -2302,8 +2300,8 @@ msgid "Creating Mesh Previews" msgstr "Buat Pratinjau Mesh" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "" +msgid "Thumbnail..." +msgstr "Thumbnail..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2348,7 +2346,7 @@ msgstr "Waktu Rata-rata (sec)" #: editor/editor_profiler.cpp msgid "Frame %" -msgstr "" +msgstr "Bingkai %" #: editor/editor_profiler.cpp msgid "Physics Frame %" @@ -2455,8 +2453,8 @@ msgid "(Current)" msgstr "(Kondisi Saat Ini)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Mendapatkan informasi cermin, silakan tunggu.." +msgid "Retrieving mirrors, please wait..." +msgstr "Mendapatkan informasi cermin, silakan tunggu..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2516,8 +2514,9 @@ msgstr "Permintaan Gagal." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp +#, fuzzy msgid "Redirect Loop." -msgstr "" +msgstr "Mengarahkan Loop" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp @@ -2533,8 +2532,8 @@ msgid "Error requesting url: " msgstr "Kesalahan saat meminta url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Menyambungkan.." +msgid "Connecting to Mirror..." +msgstr "Menyambungkan..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2542,16 +2541,16 @@ msgstr "Terputus" #: editor/export_template_manager.cpp msgid "Resolving" -msgstr "" +msgstr "Menyelesaikan" #: editor/export_template_manager.cpp msgid "Can't Resolve" -msgstr "" +msgstr "Tidak Bisa Menyelesaikan" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Menyambungkan.." +msgid "Connecting..." +msgstr "Menyambungkan..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2563,8 +2562,8 @@ msgstr "Terhubung" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Melakukan permintaan.." +msgid "Requesting..." +msgstr "Melakukan permintaan..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2693,19 +2692,19 @@ msgstr "Menggandakan folder:" #: editor/filesystem_dock.cpp msgid "Expand all" -msgstr "" +msgstr "Perluas semua" #: editor/filesystem_dock.cpp msgid "Collapse all" -msgstr "" +msgstr "Ciutkan semua" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Ubah Nama.." +msgid "Rename..." +msgstr "Ubah Nama..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Pindahkan ke.." +msgid "Move To..." +msgstr "Pindahkan ke..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2716,16 +2715,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Sunting Dependensi.." +msgid "Edit Dependencies..." +msgstr "Sunting Dependensi..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Tampilkan Pemilik Berkas.." +msgid "View Owners..." +msgstr "Tampilkan Pemilik Berkas..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Gandakan.." +msgid "Duplicate..." +msgstr "Gandakan..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2750,10 +2749,10 @@ msgstr "Instance scene terpilih sebagai anak node saat ini." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Memindai Berkas,\n" -"Silakan Tunggu.." +"Silakan Tunggu..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2818,8 +2817,8 @@ msgid "Import Scene" msgstr "Impor Scene" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Mengimpor scene.." +msgid "Importing Scene..." +msgstr "Mengimpor scene..." #: editor/import/resource_importer_scene.cpp #, fuzzy @@ -2832,8 +2831,8 @@ msgstr "" #: editor/import/resource_importer_scene.cpp #, fuzzy -msgid "Running Custom Script.." -msgstr "Menjalankan Skrip Buatan.." +msgid "Running Custom Script..." +msgstr "Menjalankan Skrip Buatan..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2848,8 +2847,8 @@ msgid "Error running post-import script:" msgstr "Kesalahan saat menjalankan skrip post-import:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Menyimpan.." +msgid "Saving..." +msgstr "Menyimpan..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2868,7 +2867,7 @@ msgid "Import As:" msgstr "Impor sebagai:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -2877,7 +2876,7 @@ msgstr "Impor ulang" #: editor/multi_node_edit.cpp msgid "MultiNode Set" -msgstr "" +msgstr "Set MultiNode" #: editor/node_dock.cpp msgid "Groups" @@ -2987,7 +2986,7 @@ msgstr "" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Blend Time" -msgstr "" +msgstr "Ubah Waktu Blend" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load Animation" @@ -3093,8 +3092,9 @@ msgid "Copy Animation" msgstr "Salin Animasi" #: editor/plugins/animation_player_editor_plugin.cpp +#, fuzzy msgid "Onion Skinning" -msgstr "" +msgstr "Onion Skinning" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Enable Onion Skinning" @@ -3306,8 +3306,8 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Impor Animasi.." +msgid "Import Animations..." +msgstr "Impor Animasi..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3315,8 +3315,8 @@ msgid "Edit Node Filters" msgstr "Sunting Filter Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Penyaring.." +msgid "Filters..." +msgstr "Penyaring..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3388,7 +3388,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3455,8 +3455,8 @@ msgid "Site:" msgstr "Situs:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Dukungan.." +msgid "Support..." +msgstr "Dukungan..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3654,6 +3654,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -4086,7 +4087,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4238,7 +4239,7 @@ msgstr "" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Marking walkable triangles..." -msgstr "Menyimpan perubahan-perubahan lokal.." +msgstr "Menyimpan perubahan-perubahan lokal..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." @@ -4294,7 +4295,7 @@ msgid "Error loading image:" msgstr "Galat saat memuat gambar:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4663,7 +4664,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4764,8 +4765,8 @@ msgstr "Beralih Favorit" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Cari.." +msgid "Find..." +msgstr "Cari..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4976,15 +4977,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5175,7 +5176,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy msgid "Material Changes" -msgstr "Menyimpan perubahan-perubahan lokal.." +msgstr "Menyimpan perubahan-perubahan lokal..." #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy @@ -5448,11 +5449,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5709,8 +5706,8 @@ msgid "Remove All" msgstr "Hapus" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Sunting tema.." +msgid "Edit theme..." +msgstr "Sunting tema..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5779,7 +5776,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5908,7 +5905,7 @@ msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet.." +msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -5975,7 +5972,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -6070,6 +6067,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Nama Projek:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Tidak dapat membuat folder." @@ -6161,7 +6163,7 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy msgid "Can't open project" -msgstr "Menyambungkan.." +msgstr "Menyambungkan..." #: editor/project_manager.cpp #, fuzzy @@ -6242,7 +6244,7 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy msgid "Can't run project" -msgstr "Menyambungkan.." +msgstr "Menyambungkan..." #: editor/project_manager.cpp msgid "" @@ -6268,8 +6270,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6297,7 +6299,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6480,14 +6482,14 @@ msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "" +msgstr "Umum" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6584,11 +6586,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." -msgstr "Berkas.." +msgid "File..." +msgstr "Berkas..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6769,7 +6771,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -6939,7 +6941,7 @@ msgstr "" #: editor/scene_tree_editor.cpp #, fuzzy msgid "Open script" -msgstr "Buka Cepat Script.." +msgstr "Buka Cepat Script..." #: editor/scene_tree_editor.cpp msgid "" @@ -8284,9 +8286,8 @@ msgid "Please Confirm..." msgstr "Mohon konfirmasi..." #: scene/gui/file_dialog.cpp -#, fuzzy msgid "Select this Folder" -msgstr "Metode Publik:" +msgstr "Pilih Folder ini" #: scene/gui/popup.cpp msgid "" @@ -8307,7 +8308,7 @@ msgstr "" #: scene/gui/tree.cpp msgid "(Other)" -msgstr "" +msgstr "(Yang Lain)" #: scene/main/scene_tree.cpp #, fuzzy @@ -8319,7 +8320,6 @@ msgstr "" "> Lingkungan Baku) tidak dapat dimuat" #: scene/main/viewport.cpp -#, fuzzy msgid "" "This viewport is not set as render target. If you intend for it to display " "its contents directly to the screen, make it a child of a Control so it can " @@ -8328,9 +8328,9 @@ msgid "" msgstr "" "Viewport ini tidak diatur sebagai target render. Jika anda berniat untuk " "menampilkan konten-kontennya secara langsung ke layar, buatlah sebuah child " -"dari kontrol jadi hal tersebut bisa memperoleh ukuran. Jika tidak, buatlah " -"sebuah RenderTarget dan tetapkannya tekstur internal untuk beberapa node " -"untuk ditampilkan." +"dari Kontrol jadi hal tersebut bisa memperoleh ukuran. Jika tidak, buatlah " +"sebuah RenderTarget dan tetapkan tekstur internal untuk beberapa node untuk " +"ditampilkan." #: scene/resources/dynamic_font.cpp msgid "Error initializing FreeType." @@ -8349,6 +8349,13 @@ msgid "Invalid font size." msgstr "Ukuran font tidak sah." #, fuzzy +#~ msgid "Previous" +#~ msgstr "Tab sebelumnya" + +#~ msgid "Next" +#~ msgstr "Berikutnya" + +#, fuzzy #~ msgid "Can't contain '/' or ':'" #~ msgstr "Sambungkan Ke Node:" @@ -8363,9 +8370,6 @@ msgstr "Ukuran font tidak sah." #~ msgid "Can't write file." #~ msgstr "Tidak dapat membuat folder." -#~ msgid "Next" -#~ msgstr "Berikutnya" - #~ msgid "Not found!" #~ msgstr "Tidak ditemukan!" @@ -8410,7 +8414,7 @@ msgstr "Ukuran font tidak sah." #, fuzzy #~ msgid "Setting '" -#~ msgstr "Mengatur.." +#~ msgstr "Mengatur..." #, fuzzy #~ msgid "Selection -> Duplicate" @@ -8459,8 +8463,8 @@ msgstr "Ukuran font tidak sah." #~ msgid "Exporting for %s" #~ msgstr "Mengekspor untuk %s" -#~ msgid "Setting Up.." -#~ msgstr "Mengatur.." +#~ msgid "Setting Up..." +#~ msgstr "Mengatur..." #~ msgid "Error loading scene." #~ msgstr "Gagal memuat scene." @@ -8484,8 +8488,8 @@ msgstr "Ukuran font tidak sah." #~ msgid "No files selected!" #~ msgstr "Tidak ada berkas dipilih!" -#~ msgid "Re-Import.." -#~ msgstr "Impor Ulang.." +#~ msgid "Re-Import..." +#~ msgstr "Impor Ulang..." #, fuzzy #~ msgid "Root Node Name:" diff --git a/editor/translations/is.po b/editor/translations/is.po index eb4f29126c..98a376edca 100644 --- a/editor/translations/is.po +++ b/editor/translations/is.po @@ -4,138 +4,165 @@ # This file is distributed under the same license as the Godot source code. # # Jóhannes G. Þorsteinsson <johannesg@johannesg.com>, 2017. +# Kaan Gül <qaantum@hotmail.com>, 2018. # msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2017-12-05 21:47+0000\n" -"Last-Translator: Jóhannes G. Þorsteinsson <johannesg@johannesg.com>\n" +"PO-Revision-Date: 2018-06-05 05:39+0000\n" +"Last-Translator: Kaan Gül <qaantum@hotmail.com>\n" "Language-Team: Icelandic <https://hosted.weblate.org/projects/godot-engine/" "godot/is/>\n" "Language: is\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.18-dev\n" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp +#, fuzzy msgid "Disabled" msgstr "Óvirkt" #: editor/animation_editor.cpp msgid "All Selection" -msgstr "Allt Val" +msgstr "Allt úrvalið" #: editor/animation_editor.cpp #, fuzzy msgid "Anim Change Keyframe Time" -msgstr "Hreyfimynd Breyta Gildi" +msgstr "Anim breyta lyklagrind tÃmi" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Change Transition" -msgstr "Hreyfimynd Breyta Stöðuskiptum" +msgstr "Anim breyting umskipti" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Change Transform" -msgstr "Hreyfimynd Breyta Ummyndun" +msgstr "Breyta umbreytingu" #: editor/animation_editor.cpp #, fuzzy msgid "Anim Change Keyframe Value" -msgstr "Hreyfimynd Breyta Gildi" +msgstr "Anim breyta lyklagrind gildi" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Change Call" -msgstr "Hreyfimynd Breyta Kalli" +msgstr "Útkall breyting sÃmtal" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Add Track" -msgstr "Hreyfimynd Bæta Við Rás" +msgstr "Anim bæta við lag" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Duplicate Keys" -msgstr "Hreyfimynd Tvöfalda Lykla" +msgstr "TvÃteknir lyklar" #: editor/animation_editor.cpp +#, fuzzy msgid "Move Anim Track Up" -msgstr "Færa Hreyfimynda Rás Upp" +msgstr "Færa Anim track upp" #: editor/animation_editor.cpp +#, fuzzy msgid "Move Anim Track Down" -msgstr "Færa Hreyfimynda Rás Niður" +msgstr "Færa Anim track niður" #: editor/animation_editor.cpp +#, fuzzy msgid "Remove Anim Track" -msgstr "Fjarlægja Hreyfimynda Rás" +msgstr "Fjarlægja Anim track" #: editor/animation_editor.cpp +#, fuzzy msgid "Set Transitions to:" -msgstr "Sitja Stöðuskipti á:" +msgstr "Stillið breyting á:" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Track Rename" -msgstr "Endurnefna Hreyfimyndarás" +msgstr "Endurnefning Anim track" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Track Change Interpolation" -msgstr "Breyta Brúun á Hreyfimyndarás" +msgstr "Breytingar á Anim track" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Track Change Value Mode" -msgstr "" +msgstr "Breyta gildisstilling à Anim track" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Track Change Wrap Mode" -msgstr "" +msgstr "Anim track breyta hulum ham" #: editor/animation_editor.cpp +#, fuzzy msgid "Edit Node Curve" -msgstr "" +msgstr "Breyta hnútnum Ferill" #: editor/animation_editor.cpp +#, fuzzy msgid "Edit Selection Curve" -msgstr "" +msgstr "Breyta valferil" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Delete Keys" -msgstr "" +msgstr "Anim DELETE-lyklar" #: editor/animation_editor.cpp editor/plugins/tile_map_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp +#, fuzzy msgid "Duplicate Selection" -msgstr "" +msgstr "Afrita val" #: editor/animation_editor.cpp +#, fuzzy msgid "Duplicate Transposed" -msgstr "" +msgstr "TvÃskipt transposed" #: editor/animation_editor.cpp +#, fuzzy msgid "Remove Selection" -msgstr "" +msgstr "Fjarlægja val" #: editor/animation_editor.cpp +#, fuzzy msgid "Continuous" -msgstr "" +msgstr "Samfellt" #: editor/animation_editor.cpp +#, fuzzy msgid "Discrete" -msgstr "" +msgstr "Afmarkað" #: editor/animation_editor.cpp +#, fuzzy msgid "Trigger" -msgstr "" +msgstr "Kveikja:" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Add Key" -msgstr "" +msgstr "Anim bæta við lykli" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Move Keys" -msgstr "" +msgstr "Færa lykla af Anim" #: editor/animation_editor.cpp +#, fuzzy msgid "Scale Selection" -msgstr "" +msgstr "Val á kvarða" #: editor/animation_editor.cpp msgid "Scale From Cursor" @@ -496,7 +523,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -906,11 +933,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1046,11 +1073,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1119,7 +1146,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1381,12 +1408,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1591,11 +1618,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1607,7 +1634,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1659,7 +1686,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1804,7 +1831,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1816,11 +1843,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1840,15 +1867,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2093,7 +2120,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2202,7 +2229,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2353,7 +2380,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2429,7 +2456,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2446,7 +2473,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2459,7 +2486,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2591,11 +2618,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2607,16 +2634,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Hreyfimynd Tvöfalda Lykla" #: editor/filesystem_dock.cpp @@ -2642,7 +2669,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2708,7 +2735,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2720,7 +2747,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2736,7 +2763,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2756,7 +2783,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3170,7 +3197,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3178,7 +3205,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3246,7 +3273,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3313,7 +3340,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3500,6 +3527,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3921,7 +3949,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4126,7 +4154,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4487,7 +4515,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4584,7 +4612,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4790,15 +4818,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5249,11 +5277,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5506,7 +5530,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5574,7 +5598,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5762,7 +5786,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5852,6 +5876,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6038,8 +6066,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6067,7 +6095,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6251,7 +6279,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6347,11 +6375,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6522,7 +6550,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/it.po b/editor/translations/it.po index 85f4e665a1..2d566fe163 100644 --- a/editor/translations/it.po +++ b/editor/translations/it.po @@ -3,6 +3,7 @@ # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. # +# Alessio Corridori <alessiocorridori@hotmail.com>, 2018. # Dario Bonfanti <bonfi.96@hotmail.it>, 2016-2017. # Dario D'Ambra <legione0@gmail.com>, 2017. # dariocavada <cavada@ectrlsolutions.com>, 2017. @@ -19,8 +20,8 @@ msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-05-02 10:38+0000\n" -"Last-Translator: Samuele Zolfanelli <samdazel@gmail.com>\n" +"PO-Revision-Date: 2018-05-18 16:39+0000\n" +"Last-Translator: Alessio Corridori <alessiocorridori@hotmail.com>\n" "Language-Team: Italian <https://hosted.weblate.org/projects/godot-engine/" "godot/it/>\n" "Language: it\n" @@ -511,8 +512,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Disconnetti '%s' da '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Connetti.." +msgid "Connect..." +msgstr "Connetti..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -934,12 +935,12 @@ msgid "Move Audio Bus" msgstr "Sposta bus audio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Salva Layout Bus Audio Come..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Posizione per Nuovo Layout.." +msgid "Location for New Layout..." +msgstr "Posizione per Nuovo Layout..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1080,12 +1081,12 @@ msgid "Updating Scene" msgstr "Aggiornamento Scena" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Memorizzando i cambiamenti locali.." +msgid "Storing local changes..." +msgstr "Memorizzando i cambiamenti locali..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Aggiornando la scena.." +msgid "Updating scene..." +msgstr "Aggiornando la scena..." #: editor/editor_data.cpp msgid "[empty]" @@ -1154,8 +1155,8 @@ msgid "Show In File Manager" msgstr "Mostra nel File Manager" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nuova Cartella.." +msgid "New Folder..." +msgstr "Nuova Cartella..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1424,13 +1425,13 @@ msgid "Error saving resource!" msgstr "Errore salvando la Risorsa!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Salva Risorsa Come.." +msgid "Save Resource As..." +msgstr "Salva Risorsa Come..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Capisco.." +msgid "I see..." +msgstr "Capisco..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1666,12 +1667,12 @@ msgid "Open Base Scene" msgstr "Apri Scena Base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Apri scena rapido.." +msgid "Quick Open Scene..." +msgstr "Apri scena rapido..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Apri Script Rapido.." +msgid "Quick Open Script..." +msgstr "Apri Script Rapido..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1682,8 +1683,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Salvare le modifiche a '%s' prima di chiudere?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Salva Scena Come.." +msgid "Save Scene As..." +msgstr "Salva Scena Come..." #: editor/editor_node.cpp msgid "No" @@ -1734,8 +1735,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Questa azione non può essere annullata. Ripristinare comunque?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Esegui Scena Rapido.." +msgid "Quick Run Scene..." +msgstr "Esegui Scena Rapido..." #: editor/editor_node.cpp msgid "Quit" @@ -1895,8 +1896,8 @@ msgid "Previous tab" msgstr "Scheda precedente" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtra Files.." +msgid "Filter Files..." +msgstr "Filtra Files..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1907,12 +1908,12 @@ msgid "New Scene" msgstr "Nuova scena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Nuova Scena Ereditata.." +msgid "New Inherited Scene..." +msgstr "Nuova Scena Ereditata..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Apri Scena.." +msgid "Open Scene..." +msgstr "Apri Scena..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1931,16 +1932,16 @@ msgid "Open Recent" msgstr "Apri Recente" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Converti In.." +msgid "Convert To..." +msgstr "Converti In..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2203,8 +2204,8 @@ msgid "Save the currently edited resource." msgstr "Salva la risorsa in modifica." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Salva Come.." +msgid "Save As..." +msgstr "Salva Come..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2312,8 +2313,8 @@ msgid "Creating Mesh Previews" msgstr "Creazione Anteprime Mesh" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatura.." +msgid "Thumbnail..." +msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2468,7 +2469,7 @@ msgid "(Current)" msgstr "(Corrente)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Recupero dei mirror, attendi..." #: editor/export_template_manager.cpp @@ -2547,8 +2548,8 @@ msgid "Error requesting url: " msgstr "Errore di connessione all'URL: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Connessione al mirror in corso.." +msgid "Connecting to Mirror..." +msgstr "Connessione al mirror in corso..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2564,8 +2565,8 @@ msgstr "Impossibile risolvere" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Connettendo.." +msgid "Connecting..." +msgstr "Connettendo..." #: editor/export_template_manager.cpp #, fuzzy @@ -2578,8 +2579,8 @@ msgstr "Connesso" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Richiedendo.." +msgid "Requesting..." +msgstr "Richiedendo..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2718,12 +2719,12 @@ msgid "Collapse all" msgstr "Comprimi tutto" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Rinomina.." +msgid "Rename..." +msgstr "Rinomina..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Sposta in.." +msgid "Move To..." +msgstr "Sposta in..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2735,16 +2736,16 @@ msgid "Instance" msgstr "Istanza" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Modifica Dipendenze.." +msgid "Edit Dependencies..." +msgstr "Modifica Dipendenze..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Vedi Proprietari.." +msgid "View Owners..." +msgstr "Vedi Proprietari..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplica.." +msgid "Duplicate..." +msgstr "Duplica..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2769,10 +2770,10 @@ msgstr "Istanzia le scene selezionate come figlie del nodo selezionato." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Scansione File,\n" -"Si prega di attendere.." +"Si prega di attendere..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2837,8 +2838,8 @@ msgid "Import Scene" msgstr "Importa Scena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Importando Scena.." +msgid "Importing Scene..." +msgstr "Importando Scena..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2850,8 +2851,8 @@ msgid "Generating for Mesh: " msgstr "Generando per Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Eseguendo Script Personalizzato.." +msgid "Running Custom Script..." +msgstr "Eseguendo Script Personalizzato..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2866,8 +2867,8 @@ msgid "Error running post-import script:" msgstr "Errore di esecuzione dello script di post-import:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Salvataggio.." +msgid "Saving..." +msgstr "Salvataggio..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2886,8 +2887,8 @@ msgid "Import As:" msgstr "Importa Come:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Preset.." +msgid "Preset..." +msgstr "Preset..." #: editor/import_dock.cpp msgid "Reimport" @@ -3308,16 +3309,16 @@ msgid "Transition Node" msgstr "Nodo Transizione" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importa animazioni.." +msgid "Import Animations..." +msgstr "Importa animazioni..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Modifica Filtri Nodi" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filtri.." +msgid "Filters..." +msgstr "Filtri..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3385,8 +3386,8 @@ msgid "Fetching:" msgstr "Recupero:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Risolvendo.." +msgid "Resolving..." +msgstr "Risolvendo..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3452,8 +3453,8 @@ msgid "Site:" msgstr "Sito:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Supporta.." +msgid "Support..." +msgstr "Supporta..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3652,6 +3653,7 @@ msgid "Use Rotation Snap" msgstr "Usa lo Snap di Rotazione" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Configura Snap..." @@ -4100,8 +4102,8 @@ msgid "Create Convex Collision Sibling" msgstr "Crea Fratello di Collisione Convessa" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Crea Mesh di Outline.." +msgid "Create Outline Mesh..." +msgstr "Crea Mesh di Outline..." #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy @@ -4258,7 +4260,7 @@ msgstr "Creazione Octree Luci" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Marking walkable triangles..." -msgstr "Stringhe Traducibili.." +msgstr "Stringhe Traducibili..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." @@ -4280,7 +4282,7 @@ msgstr "Creazione Octree Texture" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Creating polymesh..." -msgstr "Crea Mesh di Outline.." +msgstr "Crea Mesh di Outline..." #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy @@ -4321,8 +4323,8 @@ msgid "Error loading image:" msgstr "Errore di caricamento immagine:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Nessun pixel con trasparenza >128 nell'immagine.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Nessun pixel con trasparenza >128 nell'immagine..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4688,8 +4690,8 @@ msgid "Import Theme" msgstr "Importa Tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Salva Tema Come.." +msgid "Save Theme As..." +msgstr "Salva Tema Come..." #: editor/plugins/script_editor_plugin.cpp #, fuzzy @@ -4791,8 +4793,8 @@ msgstr "Attiva Preferito" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Trova.." +msgid "Find..." +msgstr "Trova..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -5006,16 +5008,16 @@ msgid "Find Previous" msgstr "Trova Precedente" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Rimpiazza.." +msgid "Replace..." +msgstr "Rimpiazza..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Vai a Funzione.." +msgid "Goto Function..." +msgstr "Vai a Funzione..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Vai a Linea.." +msgid "Goto Line..." +msgstr "Vai a Linea..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5475,12 +5477,8 @@ msgid "Transform" msgstr "Transform" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configura Snap..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Finestra di Transform.." +msgid "Transform Dialog..." +msgstr "Finestra di Transform..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5739,7 +5737,7 @@ msgid "Remove All" msgstr "Rimuovi" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Modifica Tema…" #: editor/plugins/theme_editor_plugin.cpp @@ -5810,7 +5808,8 @@ msgid "Options" msgstr "Opzioni" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +#, fuzzy +msgid "Has,Many,Options" msgstr "Ha, Molte, Diverse, Opzioni!" #: editor/plugins/theme_editor_plugin.cpp @@ -5940,7 +5939,7 @@ msgstr "Unisci da scena?" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet.." +msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -6007,7 +6006,7 @@ msgid "Presets" msgstr "Presets" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Aggiungi..." #: editor/project_export.cpp @@ -6108,6 +6107,11 @@ msgstr "Progetto Importato" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Nome Progetto:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Impossibile creare cartella." @@ -6318,8 +6322,8 @@ msgstr "Pulsante Mouse" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6347,8 +6351,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Premi un tasto.." +msgid "Press a Key..." +msgstr "Premi un tasto..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6530,15 +6534,15 @@ msgstr "Impostazioni Progetto (project.godot)" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "Generali" +msgstr "Informazioni Generali" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "Proprietà :" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Sovrascrivi Per.." +msgid "Override For..." +msgstr "Sovrascrivi Per..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6637,12 +6641,12 @@ msgid "Easing Out-In" msgstr "Easing Out-In" #: editor/property_editor.cpp -msgid "File.." -msgstr "File.." +msgid "File..." +msgstr "File..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Dir.." +msgid "Dir..." +msgstr "Dir..." #: editor/property_editor.cpp msgid "Assign" @@ -6673,7 +6677,7 @@ msgstr "Mostra nel File System" #: editor/property_editor.cpp #, fuzzy msgid "Convert To %s" -msgstr "Converti In.." +msgstr "Converti In..." #: editor/property_editor.cpp msgid "Error loading file: Not a resource!" @@ -6820,8 +6824,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "Questa operazione no può essere eseguita su scene istanziate." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Salva Nuova Scena Come.." +msgid "Save New Scene As..." +msgstr "Salva Nuova Scena Come..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7353,12 +7357,12 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Platform" -msgstr "Copia A Piattaforma.." +msgstr "Copia A Piattaforma..." #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Dynamic Library" -msgstr "MeshLibrary.." +msgstr "MeshLibrary..." #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" @@ -7372,7 +7376,7 @@ msgstr "GDNative" #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy msgid "Library" -msgstr "MeshLibrary.." +msgstr "MeshLibrary..." #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy @@ -8445,6 +8449,13 @@ msgstr "Errore caricamento font." msgid "Invalid font size." msgstr "Dimensione font Invalida." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Scheda precedente" + +#~ msgid "Next" +#~ msgstr "Successivo" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Azione invalida (va bene tutto a parte '/' o ':')." @@ -8475,9 +8486,6 @@ msgstr "Dimensione font Invalida." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Impossibile creare project.godot nel percorso di progetto." -#~ msgid "Next" -#~ msgstr "Successivo" - #~ msgid "Not found!" #~ msgstr "Non trovato!" @@ -8623,8 +8631,8 @@ msgstr "Dimensione font Invalida." #~ msgid "Exporting for %s" #~ msgstr "Esportando per %s" -#~ msgid "Setting Up.." -#~ msgstr "Impostando.." +#~ msgid "Setting Up..." +#~ msgstr "Impostando..." #~ msgid "Error loading scene." #~ msgstr "Errore di caricamento della scena." @@ -8688,8 +8696,8 @@ msgstr "Dimensione font Invalida." #~ msgid "Info" #~ msgstr "Info" -#~ msgid "Re-Import.." -#~ msgstr "Re-Importa.." +#~ msgid "Re-Import..." +#~ msgstr "Re-Importa..." #~ msgid "No bit masks to import!" #~ msgstr "Nessuna bit mask da importare!" @@ -9084,14 +9092,14 @@ msgstr "Dimensione font Invalida." #~ msgid "Zoom (%):" #~ msgstr "Zoom(%):" -#~ msgid "Skeleton.." -#~ msgstr "Scheletro.." +#~ msgid "Skeleton..." +#~ msgstr "Scheletro..." #~ msgid "Zoom Reset" #~ msgstr "Zoom Reset" -#~ msgid "Zoom Set.." -#~ msgstr "Imposta Zoom.." +#~ msgid "Zoom Set..." +#~ msgstr "Imposta Zoom..." #~ msgid "Set a Value" #~ msgstr "Imposta un Valore" @@ -9566,8 +9574,8 @@ msgstr "Dimensione font Invalida." #~ msgid "Export Project PCK" #~ msgstr "Esporta Progetto PCK" -#~ msgid "Export.." -#~ msgstr "Esporta.." +#~ msgid "Export..." +#~ msgstr "Esporta..." #~ msgid "Project Export" #~ msgstr "Esportazione Progetto" @@ -9687,8 +9695,8 @@ msgstr "Dimensione font Invalida." #~ msgid "Reload Tool Script (Soft)" #~ msgstr "Ricarica Tool Script (Soft)" -#~ msgid "Edit Connections.." -#~ msgstr "Modifica Connessioni.." +#~ msgid "Edit Connections..." +#~ msgstr "Modifica Connessioni..." #~ msgid "Set Params" #~ msgstr "Imposta parametri" @@ -9742,5 +9750,5 @@ msgstr "Dimensione font Invalida." #~ msgid "Next Time:" #~ msgstr "Prossima Volta:" -#~ msgid "Merging.." -#~ msgstr "Unione.." +#~ msgid "Merging..." +#~ msgstr "Unione..." diff --git a/editor/translations/ja.po b/editor/translations/ja.po index e05530b258..5ce73d0442 100644 --- a/editor/translations/ja.po +++ b/editor/translations/ja.po @@ -2,30 +2,31 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # akirakido <achts.y@gmail.com>, 2016-2017. -# D_first <dntk.daisei@gmail.com>, 2017. -# Daisuke Saito <d.saito@coriginate.com>, 2017. +# D_first <dntk.daisei@gmail.com>, 2017, 2018. +# Daisuke Saito <d.saito@coriginate.com>, 2017, 2018. # h416 <shinichiro.hirama@gmail.com>, 2017. -# hopping tappy (ãŸã£ã´ã•ã‚“) <hopping.tappy@gmail.com>, 2016-2017. -# Jun Shiozawa <haresecret@gmail.com>, 2017. +# hopping tappy (ãŸã£ã´ã•ã‚“) <hopping.tappy@gmail.com>, 2016-2017, 2018. +# Jun Shiozawa <haresecret@gmail.com>, 2017, 2018. # Lexi Grafen <shfeedly@gmail.com>, 2017. # NoahDigital <taku_58@hotmail.com>, 2017. +# Shinsuke Masuda <shinsuke.masuda@gmail.com>, 2018. # Tetsuji Ochiai <ochiaixp@gmail.com>, 2017. # Tohru Ike (rokujyouhitoma) <rokujyouhitomajp@gmail.com>, 2017-2018. -# +# yu tang <0011solo@gmail.com>, 2018. +# zukkun <zukkun@gmail.com>, 2018. msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-02 08:38+0000\n" -"Last-Translator: Tohru Ike (rokujyouhitoma) <rokujyouhitomajp@gmail.com>\n" +"PO-Revision-Date: 2018-06-15 22:40+0000\n" +"Last-Translator: yu tang <0011solo@gmail.com>\n" "Language-Team: Japanese <https://hosted.weblate.org/projects/godot-engine/" "godot/ja/>\n" "Language: ja\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2.20-dev\n" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -41,14 +42,12 @@ msgid "Anim Change Keyframe Time" msgstr "Anim 値を変更" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Transition" -msgstr "Anim é·ç§»ï¼ˆãƒˆãƒ©ãƒ³ã‚¸ã‚·ãƒ§ãƒ³ï¼‰" +msgstr "アニメーション 変化ã¨ãã®ç§»ã‚Šå¤‰ã‚り(トランジション)" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Transform" -msgstr "Anim 変形(トランスフォーム)" +msgstr "アニメーションã®ãƒˆãƒ©ãƒ³ã‚¹ãƒ•ォーム(変形)" #: editor/animation_editor.cpp #, fuzzy @@ -66,9 +65,8 @@ msgid "Anim Add Track" msgstr "Anim ãƒˆãƒ©ãƒƒã‚¯ã‚’è¿½åŠ " #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Duplicate Keys" -msgstr "Anim ã‚ーを複製" +msgstr "アニメーションã®ã‚ーフレームを複製" #: editor/animation_editor.cpp msgid "Move Anim Track Up" @@ -84,7 +82,7 @@ msgstr "Anim トラックを削除" #: editor/animation_editor.cpp msgid "Set Transitions to:" -msgstr "ã“れã«ãƒˆãƒ©ãƒ³ã‚¸ã‚·ãƒ§ãƒ³ã‚’è¨å®š:" +msgstr "トランジションをè¨å®š:" #: editor/animation_editor.cpp msgid "Anim Track Rename" @@ -114,7 +112,7 @@ msgstr "é¸æŠžæ›²ç·šã‚’ç·¨é›†" #: editor/animation_editor.cpp msgid "Anim Delete Keys" -msgstr "Anim ã‚ー削除" +msgstr "アニメーションã®ã‚ーフレームを削除" #: editor/animation_editor.cpp editor/plugins/tile_map_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp @@ -536,8 +534,8 @@ msgstr "'%s' ã‚’ '%s' ã«æŽ¥ç¶š" #: editor/connections_dialog.cpp #, fuzzy -msgid "Connect.." -msgstr "接続.." +msgid "Connect..." +msgstr "接続..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -580,7 +578,6 @@ msgstr "最近ã®:" #: editor/plugins/asset_library_editor_plugin.cpp #: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp #: editor/quick_open.cpp -#, fuzzy msgid "Search:" msgstr "検索:" @@ -639,7 +636,6 @@ msgstr "リソース" #: editor/dependency_editor.cpp editor/editor_autoload_settings.cpp #: editor/project_manager.cpp editor/project_settings_editor.cpp #: editor/script_create_dialog.cpp -#, fuzzy msgid "Path" msgstr "パス" @@ -855,14 +851,12 @@ msgstr "" "ãƒãƒ¼ãƒãƒ³ãƒˆã®è‘—作権ãŠã‚ˆã³ãƒ©ã‚¤ã‚»ãƒ³ã‚¹æ¡é …ã®å®Œå…¨ãªãƒªã‚¹ãƒˆã§ã™ã€‚" #: editor/editor_about.cpp -#, fuzzy msgid "All Components" -msgstr "コンテンツ:" +msgstr "ã™ã¹ã¦ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆ(æ§‹æˆéƒ¨åˆ†)" #: editor/editor_about.cpp -#, fuzzy msgid "Components" -msgstr "コンテンツ:" +msgstr "コンãƒãƒ¼ãƒãƒ³ãƒˆ(æ§‹æˆéƒ¨åˆ†)" #: editor/editor_about.cpp #, fuzzy @@ -870,9 +864,8 @@ msgid "Licenses" msgstr "ライセンス" #: editor/editor_asset_installer.cpp editor/project_manager.cpp -#, fuzzy msgid "Error opening package file, not in zip format." -msgstr "zipå½¢å¼ã§ãªã„ãŸã‚パッケージをファイルを開ãéš›ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" +msgstr "パッケージファイルを開ã‘ã¾ã›ã‚“ã§ã—ãŸã€‚ zip å½¢å¼ã§ã¯ã‚りã¾ã›ã‚“。" #: editor/editor_asset_installer.cpp #, fuzzy @@ -896,9 +889,8 @@ msgid "Install" msgstr "インストール" #: editor/editor_asset_installer.cpp -#, fuzzy msgid "Package Installer" -msgstr "パッケージインストールæˆåŠŸ!" +msgstr "パッケージインストーラー" #: editor/editor_audio_buses.cpp msgid "Speakers" @@ -922,24 +914,23 @@ msgstr "オーディオãƒã‚¹ã‚’ソãƒã«åˆ‡ã‚Šæ›¿ãˆ" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Mute" -msgstr "オーディオãƒã‚¹ã‚’ミュートã«åˆ‡ã‚Šæ›¿ãˆ" +msgstr "オーディオãƒã‚¹ã‚’ミュート(無音)ã«åˆ‡ã‚Šæ›¿ãˆ" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Bypass Effects" -msgstr "オーディオãƒã‚¹ã®ãƒã‚¤ãƒ‘スエフェクト切り替ãˆ" +msgstr "オーディオãƒã‚¹ã®ãƒã‚¤ãƒ‘スエフェクトã®åˆ‡ã‚Šæ›¿ãˆ" #: editor/editor_audio_buses.cpp msgid "Select Audio Bus Send" -msgstr "オーディオãƒã‚¹ã®é€ä¿¡å…ˆã‚’é¸æŠž" +msgstr "オーディオãƒã‚¹ã®å‡ºåŠ›å…ˆã®é¸æŠž" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus Effect" msgstr "オーディオãƒã‚¹ã‚¨ãƒ•ã‚§ã‚¯ãƒˆã‚’è¿½åŠ " #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Move Bus Effect" -msgstr "ãƒã‚¹ã‚¨ãƒ•ェクトã®ç§»å‹•" +msgstr "ãƒã‚¹ã‚¨ãƒ•ェクトを移動" #: editor/editor_audio_buses.cpp msgid "Delete Bus Effect" @@ -951,11 +942,11 @@ msgstr "オーディオãƒã‚¹ã‚’ドラッグ・アンド・ドãƒãƒƒãƒ—ã§(å†)æ #: editor/editor_audio_buses.cpp msgid "Solo" -msgstr "ソãƒ" +msgstr "ソãƒ(独立)" #: editor/editor_audio_buses.cpp msgid "Mute" -msgstr "ミュート" +msgstr "ミュート(無音)" #: editor/editor_audio_buses.cpp msgid "Bypass" @@ -1009,12 +1000,12 @@ msgstr "オーディオãƒã‚¹ã‚’移動" #: editor/editor_audio_buses.cpp #, fuzzy -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "オーディオãƒã‚¹ã®ãƒ¬ã‚¤ã‚¢ã‚¦ãƒˆã‚’別åã§ä¿å˜" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "æ–°ã—ã„レイアウトã®å ´æ‰€.." +msgid "Location for New Layout..." +msgstr "æ–°ã—ã„レイアウトã®å ´æ‰€..." #: editor/editor_audio_buses.cpp #, fuzzy @@ -1023,7 +1014,8 @@ msgstr "オーディオãƒã‚¹ã®ãƒ¬ã‚¤ã‚¢ã‚¦ãƒˆã‚’é–‹ã" #: editor/editor_audio_buses.cpp msgid "There is no 'res://default_bus_layout.tres' file." -msgstr "'res://default_bus_layout.tres' ファイルãŒã‚りã¾ã›ã‚“." +msgstr "" +"リソースディレクトリã«ã€Œres://default_bus_layout.tresã€ãƒ•ァイルãŒã‚りã¾ã›ã‚“!" #: editor/editor_audio_buses.cpp msgid "Invalid file, not an audio bus layout." @@ -1149,7 +1141,7 @@ msgstr "自動èªã¿è¾¼ã¿ã‚’çµ„ã¿æ›¿ãˆã‚‹" #: editor/editor_autoload_settings.cpp editor/editor_file_dialog.cpp #: scene/gui/file_dialog.cpp msgid "Path:" -msgstr "Path:" +msgstr "パス:" #: editor/editor_autoload_settings.cpp #, fuzzy @@ -1158,7 +1150,6 @@ msgstr "ノードã®åå‰:" #: editor/editor_autoload_settings.cpp editor/editor_profiler.cpp #: editor/project_manager.cpp editor/settings_config_dialog.cpp -#, fuzzy msgid "Name" msgstr "åå‰" @@ -1173,12 +1164,12 @@ msgstr "シーンを更新" #: editor/editor_data.cpp #, fuzzy -msgid "Storing local changes.." -msgstr "ãƒãƒ¼ã‚«ãƒ«ç’°å¢ƒã®å¤‰æ›´ã‚’ä¿å˜ã™ã‚‹.." +msgid "Storing local changes..." +msgstr "ãƒãƒ¼ã‚«ãƒ«ç’°å¢ƒã®å¤‰æ›´ã‚’ä¿å˜ã™ã‚‹..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "シーンを更新ã—ã¦ã„ã¾ã™.." +msgid "Updating scene..." +msgstr "シーンを更新ã—ã¦ã„ã¾ã™..." #: editor/editor_data.cpp #, fuzzy @@ -1187,7 +1178,7 @@ msgstr "(空)" #: editor/editor_data.cpp msgid "[unsaved]" -msgstr "" +msgstr "(未ä¿å˜)" #: editor/editor_dir_dialog.cpp #, fuzzy @@ -1202,7 +1193,7 @@ msgstr "ディレクトリをé¸ã¶" #: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp #: editor/filesystem_dock.cpp scene/gui/file_dialog.cpp msgid "Create Folder" -msgstr "フォルダを作æˆã™ã‚‹" +msgstr "フォルダーを作æˆ" #: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp #: editor/editor_plugin_settings.cpp editor/filesystem_dock.cpp @@ -1241,9 +1232,8 @@ msgid "File Exists, Overwrite?" msgstr "ãƒ•ã‚¡ã‚¤ãƒ«ãŒæ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚上書ãã—ã¾ã™ã‹ï¼Ÿ" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp -#, fuzzy msgid "Select Current Folder" -msgstr "フォルダを作æˆã™ã‚‹" +msgstr "ç¾åœ¨ã®ãƒ•ã‚©ãƒ«ãƒ€ãƒ¼ã‚’é¸æŠž" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp msgid "Copy Path" @@ -1254,8 +1244,8 @@ msgid "Show In File Manager" msgstr "ファイルマãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã§è¡¨ç¤º" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "フォルダを作æˆã™ã‚‹.." +msgid "New Folder..." +msgstr "フォルダを作æˆã™ã‚‹..." #: editor/editor_file_dialog.cpp #, fuzzy @@ -1376,7 +1366,6 @@ msgstr "アセットを(å†ï¼‰ã‚¤ãƒ³ãƒãƒ¼ãƒˆ" #: editor/editor_help.cpp editor/editor_node.cpp #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Search Help" msgstr "ヘルプを検索" @@ -1555,7 +1544,7 @@ msgstr "出力" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "エラーコード %d ã«ã‚ˆã‚Šã€ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã«å¤±æ•—ã—ã¾ã—ãŸã€‚" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp #, fuzzy @@ -1564,14 +1553,14 @@ msgstr "リソースä¿å˜ã‚¨ãƒ©ãƒ¼!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp #, fuzzy -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "~ã¨ã„ã†åå‰ã§ãƒªã‚½ãƒ¼ã‚¹ã‚’ä¿å˜ã™ã‚‹" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp #, fuzzy -msgid "I see.." -msgstr "ã‚ã‹ã£ãŸ.." +msgid "I see..." +msgstr "ã‚ã‹ã£ãŸ..." #: editor/editor_node.cpp #, fuzzy @@ -1584,9 +1573,8 @@ msgid "Requested file format unknown:" msgstr "ãã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯æœªçŸ¥ã®ãƒ•ォーマットã§ã™:" #: editor/editor_node.cpp -#, fuzzy msgid "Error while saving." -msgstr "ä¿å˜ä¸ã«ã‚¨ãƒ©ãƒ¼ãŒèµ·ãã¾ã—ãŸ." +msgstr "ä¿å˜ä¸ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" #: editor/editor_node.cpp #, fuzzy @@ -1594,9 +1582,8 @@ msgid "Can't open '%s'." msgstr "'..'を処ç†ã§ãã¾ã›ã‚“" #: editor/editor_node.cpp -#, fuzzy msgid "Error while parsing '%s'." -msgstr "ä¿å˜ä¸ã«ã‚¨ãƒ©ãƒ¼ãŒèµ·ãã¾ã—ãŸ." +msgstr "「%sã€ã®è§£æžä¸ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" #: editor/editor_node.cpp msgid "Unexpected end of file '%s'." @@ -1608,9 +1595,8 @@ msgid "Missing '%s' or its dependencies." msgstr "シーン'%s' ã¯ä¾å˜é–¢ä¿‚ãŒå£Šã‚Œã¦ã„ã¾ã™:" #: editor/editor_node.cpp -#, fuzzy msgid "Error while loading '%s'." -msgstr "ä¿å˜ä¸ã«ã‚¨ãƒ©ãƒ¼ãŒèµ·ãã¾ã—ãŸ." +msgstr "「%sã€ã®èªè¾¼ä¸ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚" #: editor/editor_node.cpp msgid "Saving Scene" @@ -1835,13 +1821,13 @@ msgid "Open Base Scene" msgstr "基本シーンを開ã" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "シーンã®ã‚¯ã‚¤ãƒƒã‚¯ã‚ªãƒ¼ãƒ—ン.." +msgid "Quick Open Scene..." +msgstr "シーンã®ã‚¯ã‚¤ãƒƒã‚¯ã‚ªãƒ¼ãƒ—ン..." #: editor/editor_node.cpp #, fuzzy -msgid "Quick Open Script.." -msgstr "スクリプトã®ã‚¯ã‚¤ãƒƒã‚¯ã‚ªãƒ¼ãƒ—ン.." +msgid "Quick Open Script..." +msgstr "スクリプトã®ã‚¯ã‚¤ãƒƒã‚¯ã‚ªãƒ¼ãƒ—ン..." #: editor/editor_node.cpp #, fuzzy @@ -1854,7 +1840,7 @@ msgstr "終了ã™ã‚‹å‰ã«ã€'%s' ã¸ã®å¤‰æ›´ã‚’ä¿å˜ã—ã¾ã™ã‹ï¼Ÿ" #: editor/editor_node.cpp #, fuzzy -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "~ã®åå‰ã§ã‚·ãƒ¼ãƒ³ã‚’ä¿å˜ã™ã‚‹" #: editor/editor_node.cpp @@ -1917,7 +1903,7 @@ msgstr "ã“ã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã¯undoã§ãã¾ã›ã‚“. å…ƒã«æˆ»ã—ã¾ã™ã‹ï¼Ÿ" #: editor/editor_node.cpp #, fuzzy -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "シーンをクイックランã™ã‚‹" #: editor/editor_node.cpp @@ -1929,14 +1915,12 @@ msgid "Exit the editor?" msgstr "エディターを終了ã—ã¾ã™ã‹ï¼Ÿ" #: editor/editor_node.cpp -#, fuzzy msgid "Open Project Manager?" -msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼" +msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã‚’é–‹ãã¾ã™ã‹ï¼Ÿ" #: editor/editor_node.cpp -#, fuzzy msgid "Save & Quit" -msgstr "ファイルをä¿å˜" +msgstr "ファイルをä¿å˜ã—ã¦çµ‚了" #: editor/editor_node.cpp msgid "Save changes to the following scene(s) before quitting?" @@ -2096,8 +2080,8 @@ msgstr "以å‰ã®ã‚¿ãƒ–" #: editor/editor_node.cpp #, fuzzy -msgid "Filter Files.." -msgstr "ファイルを絞り込む.." +msgid "Filter Files..." +msgstr "ファイルを絞り込む..." #: editor/editor_node.cpp #, fuzzy @@ -2111,13 +2095,13 @@ msgstr "æ–°ã—ã„シーン" #: editor/editor_node.cpp #, fuzzy -msgid "New Inherited Scene.." -msgstr "æ–°ã—ã„継承ã—ãŸã‚·ãƒ¼ãƒ³.." +msgid "New Inherited Scene..." +msgstr "æ–°ã—ã„継承ã—ãŸã‚·ãƒ¼ãƒ³..." #: editor/editor_node.cpp #, fuzzy -msgid "Open Scene.." -msgstr "シーンを開ã.." +msgid "Open Scene..." +msgstr "シーンを開ã..." #: editor/editor_node.cpp #, fuzzy @@ -2139,18 +2123,18 @@ msgstr "最近使ã£ãŸãƒ•ァイルを開ã" #: editor/editor_node.cpp #, fuzzy -msgid "Convert To.." -msgstr "~ã«å¤‰æ›ã™ã‚‹.." +msgid "Convert To..." +msgstr "~ã«å¤‰æ›ã™ã‚‹..." #: editor/editor_node.cpp #, fuzzy -msgid "MeshLibrary.." -msgstr "メッシュライブラリ.." +msgid "MeshLibrary..." +msgstr "メッシュライブラリ..." #: editor/editor_node.cpp #, fuzzy -msgid "TileSet.." -msgstr "タイルセット.." +msgid "TileSet..." +msgstr "タイルセット..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2199,7 +2183,7 @@ msgstr "ツール" #: editor/editor_node.cpp msgid "Quit to Project List" -msgstr "終了ã—ã¦ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆãƒªã‚¹ãƒˆã‚’é–‹ã" +msgstr "終了ã—ã¦ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆä¸€è¦§ã‚’é–‹ã" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp #, fuzzy @@ -2339,7 +2323,6 @@ msgstr "クラス" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp #: editor/plugins/shader_editor_plugin.cpp editor/project_settings_editor.cpp -#, fuzzy msgid "Search" msgstr "検索" @@ -2358,7 +2341,6 @@ msgid "Issue Tracker" msgstr "課題(ãƒã‚°ï¼‰ç®¡ç†ã‚·ã‚¹ãƒ†ãƒ " #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Community" msgstr "コミュニティ" @@ -2456,8 +2438,8 @@ msgid "Save the currently edited resource." msgstr "ç¾åœ¨ç·¨é›†ä¸ã®ãƒªã‚½ãƒ¼ã‚¹ã‚’ä¿å˜ã™ã‚‹" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "åå‰ã‚’付ã‘ã¦ä¿å˜.." +msgid "Save As..." +msgstr "åå‰ã‚’付ã‘ã¦ä¿å˜..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2486,7 +2468,7 @@ msgstr "ベクトル定数を変更" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp #: editor/project_manager.cpp msgid "Import" -msgstr "インãƒãƒ¼ãƒˆï¼ˆå–り込ã¿ï¼‰" +msgstr "インãƒãƒ¼ãƒˆ" #: editor/editor_node.cpp #, fuzzy @@ -2535,7 +2517,7 @@ msgstr "é–‹ã„ã¦ã‚¹ã‚¯ãƒªãƒ—トを実行ã™ã‚‹" #: editor/editor_node.cpp #, fuzzy msgid "New Inherited" -msgstr "æ–°ã—ã„継承ã—ãŸã‚·ãƒ¼ãƒ³.." +msgstr "æ–°ã—ã„継承ã—ãŸã‚·ãƒ¼ãƒ³..." #: editor/editor_node.cpp msgid "Load Errors" @@ -2574,8 +2556,8 @@ msgid "Creating Mesh Previews" msgstr "メッシュライブラリを生æˆ" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "サムãƒã‚¤ãƒ«.." +msgid "Thumbnail..." +msgstr "サムãƒã‚¤ãƒ«..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2733,8 +2715,8 @@ msgid "(Current)" msgstr "(ç¾åœ¨ã®ï¼‰" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "ミラーサイトをå–å¾—ã—ã¦ã„ã¾ã™ã€‚ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„.." +msgid "Retrieving mirrors, please wait..." +msgstr "ミラーサイトをå–å¾—ã—ã¦ã„ã¾ã™ã€‚ã—ã°ã‚‰ããŠå¾…ã¡ãã ã•ã„..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2817,8 +2799,8 @@ msgid "Error requesting url: " msgstr "urlã®è¦æ±‚ã«å¤±æ•—ã—ã¾ã—ãŸ: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "ãƒŸãƒ©ãƒ¼ã‚µã‚¤ãƒˆã«æŽ¥ç¶šä¸.." +msgid "Connecting to Mirror..." +msgstr "ãƒŸãƒ©ãƒ¼ã‚µã‚¤ãƒˆã«æŽ¥ç¶šä¸..." #: editor/export_template_manager.cpp #, fuzzy @@ -2835,8 +2817,8 @@ msgstr "解決ã§ãã¾ã›ã‚“" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "接続ä¸.." +msgid "Connecting..." +msgstr "接続ä¸..." #: editor/export_template_manager.cpp #, fuzzy @@ -2849,8 +2831,8 @@ msgstr "接続ã—ã¾ã—ãŸ" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "リクエストä¸.." +msgid "Requesting..." +msgstr "リクエストä¸..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2914,7 +2896,7 @@ msgstr "サムãƒã‚¤ãƒ«è¡¨ç¤º" #: editor/filesystem_dock.cpp msgid "View items as a list" -msgstr "リスト表示" +msgstr "リストã§ã‚¢ã‚¤ãƒ†ãƒ を見る" #: editor/filesystem_dock.cpp #, fuzzy @@ -2996,12 +2978,12 @@ msgid "Collapse all" msgstr "ã™ã¹ã¦æŠ˜ã‚ŠãŸãŸã‚€" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "åå‰ã‚’変更ã™ã‚‹.." +msgid "Rename..." +msgstr "åå‰ã‚’変更ã™ã‚‹..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "~ã¸ç§»å‹•ã™ã‚‹.." +msgid "Move To..." +msgstr "~ã¸ç§»å‹•ã™ã‚‹..." #: editor/filesystem_dock.cpp #, fuzzy @@ -3013,17 +2995,17 @@ msgid "Instance" msgstr "インスタンス" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "ä¾å˜é–¢ä¿‚を編集.." +msgid "Edit Dependencies..." +msgstr "ä¾å˜é–¢ä¿‚を編集..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "View Owners.." -msgstr "オーナーを見る.." +msgid "View Owners..." +msgstr "オーナーを見る..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "複製" #: editor/filesystem_dock.cpp @@ -3051,7 +3033,7 @@ msgstr "é¸æŠžã—ãŸãƒŽãƒ¼ãƒ‰ã®åã¨ã—ã¦ã€é¸æŠžã—ãŸã‚·ãƒ¼ãƒ³ã‚’インス #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "ファイルをスã‚ャンã—ã¦ã„ã¾ã™\n" "ã—ã°ã‚‰ããŠå¾…ã¡ä¸‹ã•ã„..." @@ -3064,7 +3046,7 @@ msgstr "移動" #: editor/filesystem_dock.cpp editor/plugins/animation_tree_editor_plugin.cpp #: editor/project_manager.cpp msgid "Rename" -msgstr "åå‰ã‚’変更ã™ã‚‹" +msgstr "åå‰ã®å¤‰æ›´" #: editor/groups_editor.cpp #, fuzzy @@ -3079,12 +3061,12 @@ msgstr "グループã‹ã‚‰å–り除ã" #: editor/import/resource_importer_scene.cpp #, fuzzy msgid "Import as Single Scene" -msgstr "シーンをインãƒãƒ¼ãƒˆä¸.." +msgstr "シーンをインãƒãƒ¼ãƒˆä¸..." #: editor/import/resource_importer_scene.cpp #, fuzzy msgid "Import with Separate Animations" -msgstr "アニメーションをインãƒãƒ¼ãƒˆ.." +msgstr "アニメーションをインãƒãƒ¼ãƒˆ..." #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials" @@ -3127,8 +3109,8 @@ msgstr "シーンをインãƒãƒ¼ãƒˆ" #: editor/import/resource_importer_scene.cpp #, fuzzy -msgid "Importing Scene.." -msgstr "シーンをインãƒãƒ¼ãƒˆä¸.." +msgid "Importing Scene..." +msgstr "シーンをインãƒãƒ¼ãƒˆä¸..." #: editor/import/resource_importer_scene.cpp #, fuzzy @@ -3142,7 +3124,7 @@ msgstr "軸平行境界ボックス(AABB)を生æˆ" #: editor/import/resource_importer_scene.cpp #, fuzzy -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "カスタムスクリプトを実行ä¸" #: editor/import/resource_importer_scene.cpp @@ -3163,8 +3145,8 @@ msgstr "インãƒãƒ¼ãƒˆæ¸ˆã¿ã®ã‚¹ã‚¯ãƒªãƒ—ト実行エラー" #: editor/import/resource_importer_scene.cpp #, fuzzy -msgid "Saving.." -msgstr "ä¿å˜ä¸.." +msgid "Saving..." +msgstr "ä¿å˜ä¸..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -3185,8 +3167,8 @@ msgid "Import As:" msgstr "~ã¨ã—ã¦ã‚¤ãƒ³ãƒãƒ¼ãƒˆ:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "åˆæœŸè¨å®šå€¤.." +msgid "Preset..." +msgstr "åˆæœŸè¨å®šå€¤..." #: editor/import_dock.cpp #, fuzzy @@ -3670,8 +3652,8 @@ msgstr "トランジション(é·ç§»ï¼‰ãƒŽãƒ¼ãƒ‰" #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy -msgid "Import Animations.." -msgstr "アニメーションをインãƒãƒ¼ãƒˆ.." +msgid "Import Animations..." +msgstr "アニメーションをインãƒãƒ¼ãƒˆ..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3679,8 +3661,8 @@ msgid "Edit Node Filters" msgstr "ノードフィルターã®ç·¨é›†" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "フィルター.." +msgid "Filters..." +msgstr "フィルター..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3711,12 +3693,10 @@ msgid "Connection error, please try again." msgstr "接続失敗 å†è©¦è¡Œã‚’" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Can't connect to host:" msgstr "ãƒ›ã‚¹ãƒˆã«æŽ¥ç¶šã§ãã¾ã›ã‚“:" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "No response from host:" msgstr "ホストã‹ã‚‰å¿œç”ãŒã‚りã¾ã›ã‚“:" @@ -3762,8 +3742,8 @@ msgstr "å–å¾—ä¸:" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Resolving.." -msgstr "解決ä¸.." +msgid "Resolving..." +msgstr "解決ä¸..." #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy @@ -3831,8 +3811,8 @@ msgid "Site:" msgstr "サイト:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "サãƒãƒ¼ãƒˆ.." +msgid "Support..." +msgstr "サãƒãƒ¼ãƒˆ..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -4037,6 +4017,7 @@ msgid "Use Rotation Snap" msgstr "回転スナップ機能を使ã†" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp #, fuzzy msgid "Configure Snap..." msgstr "スナップ機能ã®è¨å®š" @@ -4516,8 +4497,8 @@ msgstr "凸型兄弟コリジョンを生æˆ" #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy -msgid "Create Outline Mesh.." -msgstr "アウトラインメッシュを生æˆ.." +msgid "Create Outline Mesh..." +msgstr "アウトラインメッシュを生æˆ..." #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy @@ -4717,7 +4698,7 @@ msgstr "八分木テクスãƒãƒ£ã‚’生æˆ" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Creating polymesh..." -msgstr "アウトラインメッシュを生æˆ.." +msgstr "アウトラインメッシュを生æˆ..." #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy @@ -4758,8 +4739,8 @@ msgstr "イメージèªã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼:" #: editor/plugins/particles_2d_editor_plugin.cpp #, fuzzy -msgid "No pixels with transparency > 128 in image.." -msgstr "イメージ内ã«é€æ˜Žåº¦>128ã®ãƒ”クセルãŒã‚りã¾ã›ã‚“.." +msgid "No pixels with transparency > 128 in image..." +msgstr "イメージ内ã«é€æ˜Žåº¦>128ã®ãƒ”クセルãŒã‚りã¾ã›ã‚“..." #: editor/plugins/particles_2d_editor_plugin.cpp #, fuzzy @@ -5179,8 +5160,8 @@ msgid "Import Theme" msgstr "テーマã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "テーマをåå‰ã‚’ã¤ã‘ã¦ä¿å˜.." +msgid "Save Theme As..." +msgstr "テーマをåå‰ã‚’ã¤ã‘ã¦ä¿å˜..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -5218,7 +5199,7 @@ msgstr "ファイル" #: editor/plugins/script_editor_plugin.cpp msgid "New" -msgstr "æ–°ã—ã„" +msgstr "æ–°è¦ä½œæˆ" #: editor/plugins/script_editor_plugin.cpp msgid "Save All" @@ -5286,8 +5267,8 @@ msgstr "ãŠæ°—ã«å…¥ã‚Šã‚’切り替ãˆã‚‹" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp #, fuzzy -msgid "Find.." -msgstr "検索.." +msgid "Find..." +msgstr "検索..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -5508,18 +5489,18 @@ msgid "Find Previous" msgstr "å‰ã‚’検索" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "ç½®ãæ›ãˆ.." +msgid "Replace..." +msgstr "ç½®ãæ›ãˆ..." #: editor/plugins/script_text_editor.cpp #, fuzzy -msgid "Goto Function.." -msgstr "関数~ã«ç§»å‹•.." +msgid "Goto Function..." +msgstr "関数~ã«ç§»å‹•..." #: editor/plugins/script_text_editor.cpp #, fuzzy -msgid "Goto Line.." -msgstr "~行ã«ç§»å‹•.." +msgid "Goto Line..." +msgstr "~行ã«ç§»å‹•..." #: editor/plugins/script_text_editor.cpp #, fuzzy @@ -6013,13 +5994,8 @@ msgstr "トランスフォーム" #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy -msgid "Configure Snap.." -msgstr "スナップ機能ã®è¨å®š" - -#: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy -msgid "Transform Dialog.." -msgstr "トランスフォームã®ãƒ€ã‚¤ã‚¢ãƒã‚°.." +msgid "Transform Dialog..." +msgstr "トランスフォームã®ãƒ€ã‚¤ã‚¢ãƒã‚°..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -6288,7 +6264,7 @@ msgid "Remove All" msgstr "削除" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "テーマを編集..." #: editor/plugins/theme_editor_plugin.cpp @@ -6361,8 +6337,9 @@ msgid "Options" msgstr "オプション" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "オプション" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -6496,7 +6473,7 @@ msgstr "シーンã‹ã‚‰ãƒžãƒ¼ã‚¸ã—ã¾ã™ã‹ï¼Ÿ" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "タイルセット.." +msgstr "タイルセット..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -6565,8 +6542,8 @@ msgid "Presets" msgstr "åˆæœŸè¨å®šå€¤" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "è¿½åŠ .." +msgid "Add..." +msgstr "è¿½åŠ ..." #: editor/project_export.cpp msgid "Resources" @@ -6664,9 +6641,8 @@ msgid "Please choose a 'project.godot' file." msgstr "'project.godot' ãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„." #: editor/project_manager.cpp -#, fuzzy msgid "Please choose an empty folder." -msgstr "'project.godot' ãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠžã—ã¦ãã ã•ã„." +msgstr "空ã®ãƒ•ã‚©ãƒ«ãƒ€ãƒ¼ã‚’é¸æŠžã—ã¦ãã ã•ã„。" #: editor/project_manager.cpp msgid "Imported Project" @@ -6674,12 +6650,17 @@ msgstr "インãƒãƒ¼ãƒˆã•れãŸãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆ" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆå:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "フォルダを作æˆã§ãã¾ã›ã‚“ã§ã—ãŸã€‚" #: editor/project_manager.cpp msgid "There is already a folder in this path with the specified name." -msgstr "" +msgstr "ã“ã®ãƒ‘スã«ã¯ã€æŒ‡å®šã•れãŸåå‰ã®ãƒ•ã‚©ãƒ«ãƒ€ãƒ¼ãŒæ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" #: editor/project_manager.cpp msgid "It would be a good idea to name your project." @@ -6713,9 +6694,8 @@ msgid "The following files failed extraction from package:" msgstr "以下ã®ãƒ•ァイルをパッケージã‹ã‚‰æŠ½å‡ºã§ãã¾ã›ã‚“ã§ã—ãŸ:" #: editor/project_manager.cpp -#, fuzzy msgid "Rename Project" -msgstr "åç„¡ã—ã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆ" +msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆåã®å¤‰æ›´" #: editor/project_manager.cpp msgid "New Game Project" @@ -6732,12 +6712,11 @@ msgstr "インãƒãƒ¼ãƒˆã—ã¦é–‹ã" #: editor/project_manager.cpp msgid "Create New Project" -msgstr "æ–°ã—ã„プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’作る" +msgstr "æ–°è¦ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚’作æˆ" #: editor/project_manager.cpp -#, fuzzy msgid "Create & Edit" -msgstr "発光物を生æˆ" +msgstr "作æˆã—ã¦ç·¨é›†" #: editor/project_manager.cpp msgid "Install Project:" @@ -6749,14 +6728,12 @@ msgid "Install & Edit" msgstr "インストール" #: editor/project_manager.cpp -#, fuzzy msgid "Project Name:" msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆå:" #: editor/project_manager.cpp -#, fuzzy msgid "Create folder" -msgstr "フォルダを作æˆã™ã‚‹" +msgstr "フォルダを作æˆ" #: editor/project_manager.cpp msgid "Project Path:" @@ -6764,16 +6741,15 @@ msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆãƒ‘ス:" #: editor/project_manager.cpp msgid "Browse" -msgstr "ブラウズ" +msgstr "å‚照…" #: editor/project_manager.cpp msgid "Unnamed Project" msgstr "åç„¡ã—ã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆ" #: editor/project_manager.cpp -#, fuzzy msgid "Can't open project" -msgstr "接続失敗." +msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’é–‹ã‘ã¾ã›ã‚“" #: editor/project_manager.cpp msgid "Are you sure to open more than one project?" @@ -6801,19 +6777,17 @@ msgid "Are you sure to run more than one project?" msgstr "複数ã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚’本当ã«å®Ÿè¡Œã—ã¾ã™ã‹ï¼Ÿ" #: editor/project_manager.cpp -#, fuzzy msgid "Remove project from the list? (Folder contents will not be modified)" msgstr "" -"リストã‹ã‚‰ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚’除去ã—ã¾ã™ã‹ï¼Ÿï¼ˆãƒ•ォルダーã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã¯å½±éŸ¿ã‚’å—ã‘ã¾" -"ã›ã‚“)" +"一覧ã‹ã‚‰ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚’削除ã—ã¾ã™ã‹ï¼Ÿï¼ˆãƒ•ォルダーã®å†…容ã¯å¤‰æ›´ã•れã¾ã›ã‚“)" #: editor/project_manager.cpp msgid "" "Language changed.\n" "The UI will update next time the editor or project manager starts." msgstr "" -"言語ãŒå¤‰æ›´ã•れã¾ã—ãŸ.\n" -"エディタã¾ãŸã¯ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆãƒžãƒã‚¸ãƒ£ãƒ¼å†é–‹æ™‚ã«UIãŒæ›´æ–°ã•れã¾ã™." +"言語ãŒå¤‰æ›´ã•れã¾ã—ãŸã€‚\n" +"エディターã¾ãŸã¯ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼å†èµ·å‹•後ã«UIãŒæ›´æ–°ã•れã¾ã™ã€‚" #: editor/project_manager.cpp msgid "" @@ -6827,7 +6801,7 @@ msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼" #: editor/project_manager.cpp msgid "Project List" -msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆã®ãƒªã‚¹ãƒˆ" +msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆä¸€è¦§" #: editor/project_manager.cpp msgid "Scan" @@ -6839,34 +6813,31 @@ msgstr "スã‚ャンã™ã‚‹ãƒ•ã‚©ãƒ«ãƒ€ãƒ¼ã‚’é¸æŠž" #: editor/project_manager.cpp msgid "New Project" -msgstr "æ–°ã—ã„プãƒã‚¸ã‚§ã‚¯ãƒˆ" +msgstr "æ–°è¦ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆ" #: editor/project_manager.cpp -#, fuzzy msgid "Templates" -msgstr "é¸æŠžã—ã¦ã„ã‚‹ã‚‚ã®ã‚’削除" +msgstr "テンプレート" #: editor/project_manager.cpp msgid "Exit" msgstr "終了" #: editor/project_manager.cpp -#, fuzzy msgid "Restart Now" -msgstr "アニメーションを最åˆã‹ã‚‰å†ç”Ÿã™ã‚‹ :" +msgstr "今ã™ãå†èµ·å‹•" #: editor/project_manager.cpp -#, fuzzy msgid "Can't run project" -msgstr "接続失敗." +msgstr "プãƒã‚¸ã‚§ã‚¯ãƒˆã‚’実行ã§ãã¾ã›ã‚“" #: editor/project_manager.cpp msgid "" "You don't currently have any projects.\n" "Would you like to explore the official example projects in the Asset Library?" msgstr "" -"ã‚ãªãŸã¯ç¾åœ¨ã©ã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚‚æŒã£ã¦ã„ã¾ã›ã‚“。\n" -"アセットライブラリã§å…¬å¼ã®ã‚µãƒ³ãƒ—ルプãƒã‚¸ã‚§ã‚¯ãƒˆã‚’探ã—ã¾ã—ょã†ã‹ï¼Ÿ" +"プãƒã‚¸ã‚§ã‚¯ãƒˆãŒä½•も登録ã•れã¦ã„ã¾ã›ã‚“。\n" +"アセットライブラリã§å…¬å¼ã®ã‚µãƒ³ãƒ—ルプãƒã‚¸ã‚§ã‚¯ãƒˆã‚’ãƒã‚§ãƒƒã‚¯ã—ã¾ã™ã‹ï¼Ÿ" #: editor/project_settings_editor.cpp #, fuzzy @@ -6889,8 +6860,8 @@ msgstr "マウスボタン" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6920,8 +6891,8 @@ msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp #, fuzzy -msgid "Press a Key.." -msgstr "ã‚ーを押ã—ã¦ãã ã•ã„.." +msgid "Press a Key..." +msgstr "ã‚ーを押ã—ã¦ãã ã•ã„..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -7123,7 +7094,7 @@ msgid "Property:" msgstr "プãƒãƒ‘ティ:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -7226,12 +7197,12 @@ msgid "Easing Out-In" msgstr "イージング(Out-In)" #: editor/property_editor.cpp -msgid "File.." -msgstr "ファイル.." +msgid "File..." +msgstr "ファイル..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "ディレクトリ.." +msgid "Dir..." +msgstr "ディレクトリ..." #: editor/property_editor.cpp msgid "Assign" @@ -7262,7 +7233,7 @@ msgstr "ファイルシステム上ã§è¡¨ç¤º" #: editor/property_editor.cpp #, fuzzy msgid "Convert To %s" -msgstr "~ã«å¤‰æ›ã™ã‚‹.." +msgstr "~ã«å¤‰æ›ã™ã‚‹..." #: editor/property_editor.cpp #, fuzzy @@ -7427,8 +7398,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "ã“ã®å‡¦ç†ã«ã¯ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹åŒ–ã•れãŸã‚·ãƒ¼ãƒ³ãŒå¿…è¦ã§ã™." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "æ–°è¦ã‚·ãƒ¼ãƒ³ã«åå‰ã‚’付ã‘ã¦ä¿å˜.." +msgid "Save New Scene As..." +msgstr "æ–°è¦ã‚·ãƒ¼ãƒ³ã«åå‰ã‚’付ã‘ã¦ä¿å˜..." #: editor/scene_tree_dock.cpp #, fuzzy @@ -8002,12 +7973,12 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Platform" -msgstr "プラットフォームã¸ã‚³ãƒ”ー.." +msgstr "プラットフォームã¸ã‚³ãƒ”ー..." #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Dynamic Library" -msgstr "メッシュライブラリ.." +msgstr "メッシュライブラリ..." #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" @@ -8016,12 +7987,12 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "GDNativeLibrary" -msgstr "メッシュライブラリ.." +msgstr "メッシュライブラリ..." #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy msgid "Library" -msgstr "メッシュライブラリ.." +msgstr "メッシュライブラリ..." #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy @@ -9054,7 +9025,7 @@ msgstr "è¦å‘Š!" #: scene/gui/dialogs.cpp msgid "Please Confirm..." -msgstr "確èªã—ã¦ãã ã•ã„。" +msgstr "確èª" #: scene/gui/file_dialog.cpp #, fuzzy @@ -9072,15 +9043,14 @@ msgstr "" "らã¯å®Ÿè¡Œæ™‚ã«éžè¡¨ç¤ºã«ãªã‚Šã¾ã™ã€‚" #: scene/gui/scroll_container.cpp -#, fuzzy msgid "" "ScrollContainer is intended to work with a single child control.\n" "Use a container as child (VBox,HBox,etc), or a Control and set the custom " "minimum size manually." msgstr "" -"スクãƒãƒ¼ãƒ«ã‚³ãƒ³ãƒ†ãƒŠã¯åコントãƒãƒ¼ãƒ«ãŒå‹•作ã«å¿…è¦ã§ã™. VBox,HBoxãªã©ã®ã‚³ãƒ³ãƒ†ãƒŠ" -"ã‚’åã¨ã™ã‚‹ã‹ã€ã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ã‚’カスタム最å°ã‚µã‚¤ã‚ºã«ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«ã§æŒ‡å®šã—ã¦ä½¿ç”¨ã—ã¦" -"ãã ã•ã„." +"ScrollContainerã¯å˜ä¸€ã®åコントãƒãƒ¼ãƒ«ã§å‹•作ã™ã‚‹ã‚ˆã†ã«æ„図ã•れã¦ã„ã¾ã™ã€‚コンテ" +"ナ(VBox, HBoxãªã©)ã‚’åã¨ã—ã¦ä½¿ç”¨ã™ã‚‹ã‹ã€ã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ã‚’使用ã—ã¦ã‚«ã‚¹ã‚¿ãƒ 最å°ã‚µ" +"イズを手動ã§è¨å®šã—ã¦ãã ã•ã„。" #: scene/gui/tree.cpp #, fuzzy @@ -9124,6 +9094,13 @@ msgstr "フォントèªã¿è¾¼ã¿ã‚¨ãƒ©ãƒ¼ã€‚" msgid "Invalid font size." msgstr "無効ãªãƒ•ォント サイズã§ã™ã€‚" +#, fuzzy +#~ msgid "Previous" +#~ msgstr "以å‰ã®ã‚¿ãƒ–" + +#~ msgid "Next" +#~ msgstr "次" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "䏿£ãªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ï¼ˆ '/' ã¨':'ã¯ä¸å¯ã§ã™ï¼‰." @@ -9152,9 +9129,6 @@ msgstr "無効ãªãƒ•ォント サイズã§ã™ã€‚" #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "project.godotをプãƒã‚¸ã‚§ã‚¯ãƒˆãƒ‘スã«ç”Ÿæˆã§ãã¾ã›ã‚“ã§ã—ãŸ" -#~ msgid "Next" -#~ msgstr "次" - #~ msgid "Not found!" #~ msgstr "見ã¤ã‹ã‚Šã¾ã›ã‚“!" @@ -9306,8 +9280,8 @@ msgstr "無効ãªãƒ•ォント サイズã§ã™ã€‚" #~ msgstr "%sã«ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆä¸" #, fuzzy -#~ msgid "Setting Up.." -#~ msgstr "セットアップä¸.." +#~ msgid "Setting Up..." +#~ msgstr "セットアップä¸..." #, fuzzy #~ msgid "Error loading scene." @@ -9375,8 +9349,8 @@ msgstr "無効ãªãƒ•ォント サイズã§ã™ã€‚" #~ msgstr "インフォーメーション" #, fuzzy -#~ msgid "Re-Import.." -#~ msgstr "å†ã‚¤ãƒ³ãƒãƒ¼ãƒˆ.." +#~ msgid "Re-Import..." +#~ msgstr "å†ã‚¤ãƒ³ãƒãƒ¼ãƒˆ..." #, fuzzy #~ msgid "No bit masks to import!" @@ -9871,15 +9845,15 @@ msgstr "無効ãªãƒ•ォント サイズã§ã™ã€‚" #~ msgstr "ズーム(%):" #, fuzzy -#~ msgid "Skeleton.." -#~ msgstr "スケルトン.." +#~ msgid "Skeleton..." +#~ msgstr "スケルトン..." #~ msgid "Zoom Reset" #~ msgstr "ズームをリセット" #, fuzzy -#~ msgid "Zoom Set.." -#~ msgstr "ズームをセットã™ã‚‹.." +#~ msgid "Zoom Set..." +#~ msgstr "ズームをセットã™ã‚‹..." #~ msgid "Set a Value" #~ msgstr "値をè¨å®šã™ã‚‹" diff --git a/editor/translations/ko.po b/editor/translations/ko.po index c3fcfbbb72..be6b540a9a 100644 --- a/editor/translations/ko.po +++ b/editor/translations/ko.po @@ -2,20 +2,20 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Ch <ccwpc@hanmail.net>, 2017. # paijai 송 (fivejobi) <xotjq237@gmail.com>, 2018. +# pgyage3263 <pgyage3263@naver.com>, 2018. # Sun Kim <perplexingsun@gmail.com>, 2018. # TheRedPlanet <junmo.moon8@gmail.com>, 2018. # Xavier Cho <mysticfallband@gmail.com>, 2018. # 박한얼 (volzhs) <volzhs@gmail.com>, 2016-2018. -# +# ì†¡íƒœì„ <xotjq237@gmail.com>, 2018. msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-04-18 15:39+0000\n" -"Last-Translator: Sun Kim <perplexingsun@gmail.com>\n" +"PO-Revision-Date: 2018-06-07 16:40+0000\n" +"Last-Translator: pgyage3263 <pgyage3263@naver.com>\n" "Language-Team: Korean <https://hosted.weblate.org/projects/godot-engine/" "godot/ko/>\n" "Language: ko\n" @@ -23,7 +23,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -39,7 +39,7 @@ msgstr "ì• ë‹ˆë©”ì´ì…˜ í‚¤í”„ë ˆìž„ 시간 변경" #: editor/animation_editor.cpp msgid "Anim Change Transition" -msgstr "ì• ë‹ˆë©”ì´ì…˜ 트랜지션 변경" +msgstr "ì• ë‹ˆë©”ì´ì…˜ ì „í™˜ 변경" #: editor/animation_editor.cpp msgid "Anim Change Transform" @@ -75,7 +75,7 @@ msgstr "ì• ë‹ˆë©”ì´ì…˜ 트랙 ì‚ì œ" #: editor/animation_editor.cpp msgid "Set Transitions to:" -msgstr "변화 ì„¤ì •:" +msgstr "ì „í™˜ ì„¤ì •:" #: editor/animation_editor.cpp msgid "Anim Track Rename" @@ -181,7 +181,7 @@ msgstr "ë°–-안" #: editor/animation_editor.cpp msgid "Transitions" -msgstr "변화" +msgstr "ì „í™˜" #: editor/animation_editor.cpp msgid "Optimize Animation" @@ -322,7 +322,7 @@ msgstr "키" #: editor/animation_editor.cpp msgid "Transition" -msgstr "변화" +msgstr "ì „í™˜" #: editor/animation_editor.cpp msgid "Scale Ratio:" @@ -504,8 +504,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "'%s'와 '%s'ì˜ ì—°ê²° í•´ì œ" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "연결하기.." +msgid "Connect..." +msgstr "연결하기..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -923,12 +923,12 @@ msgid "Move Audio Bus" msgstr "오디오 버스 ì´ë™" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "오디오 버스 ë ˆì´ì•„ì›ƒì„ ë‹¤ë¥¸ ì´ë¦„으로 ì €ìž¥.." +msgid "Save Audio Bus Layout As..." +msgstr "오디오 버스 ë ˆì´ì•„ì›ƒì„ ë‹¤ë¥¸ ì´ë¦„으로 ì €ìž¥..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "새 ë ˆì´ì•„ì›ƒì„ ì €ìž¥í• ìž¥ì†Œ.." +msgid "Location for New Layout..." +msgstr "새 ë ˆì´ì•„ì›ƒì„ ì €ìž¥í• ìž¥ì†Œ..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -966,7 +966,7 @@ msgstr "다른 ì´ë¦„으로 ì €ìž¥" #: editor/editor_audio_buses.cpp msgid "Save this Bus Layout to a file." -msgstr "ì´ ë²„ìŠ¤ ë ˆì´ì•„ì›ƒì„ íŒŒì¼ë¡œ ì €ìž¥í•©ë‹ˆë‹¤.." +msgstr "ì´ ë²„ìŠ¤ ë ˆì´ì•„ì›ƒì„ íŒŒì¼ë¡œ ì €ìž¥í•©ë‹ˆë‹¤..." #: editor/editor_audio_buses.cpp editor/import_dock.cpp msgid "Load Default" @@ -1065,12 +1065,12 @@ msgid "Updating Scene" msgstr "씬 ì—…ë°ì´íЏ 중" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "로컬 변경사í•ì„ ì €ìž¥ 중.." +msgid "Storing local changes..." +msgstr "로컬 변경사í•ì„ ì €ìž¥ 중..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "씬 ì—…ë°ì´íЏ 중.." +msgid "Updating scene..." +msgstr "씬 ì—…ë°ì´íЏ 중..." #: editor/editor_data.cpp msgid "[empty]" @@ -1138,8 +1138,8 @@ msgid "Show In File Manager" msgstr "íŒŒì¼ ë§¤ë‹ˆì €ì—서 보기" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "새 í´ë”.." +msgid "New Folder..." +msgstr "새 í´ë”..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1400,20 +1400,20 @@ msgstr "ì¶œë ¥ 지우기" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "프로ì 트 내보내기가 오류 코드 %d 로 실패했습니다." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "리소스 ì €ìž¥ 중 ì—러!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "리소스를 다른 ì´ë¦„으로 ì €ìž¥.." +msgid "Save Resource As..." +msgstr "리소스를 다른 ì´ë¦„으로 ì €ìž¥..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "ì•Œê² ìŠµë‹ˆë‹¤.." +msgid "I see..." +msgstr "ì•Œê² ìŠµë‹ˆë‹¤..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1437,7 +1437,7 @@ msgstr "'%s' 파싱 중 ì—러." #: editor/editor_node.cpp msgid "Unexpected end of file '%s'." -msgstr "예ìƒì¹˜ 못한 파ì¼ì˜ ë '%s' 입니다.." +msgstr "예ìƒì¹˜ 못한 파ì¼ì˜ ë '%s' 입니다..." #: editor/editor_node.cpp msgid "Missing '%s' or its dependencies." @@ -1639,12 +1639,12 @@ msgid "Open Base Scene" msgstr "기본 씬 열기" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "ë¹ ë¥¸ 씬 열기.." +msgid "Quick Open Scene..." +msgstr "ë¹ ë¥¸ 씬 열기..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "ë¹ ë¥¸ 스í¬ë¦½íЏ 열기.." +msgid "Quick Open Script..." +msgstr "ë¹ ë¥¸ 스í¬ë¦½íЏ 열기..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1655,8 +1655,8 @@ msgid "Save changes to '%s' before closing?" msgstr "닫기 ì „ì— '%s' ì— ë³€ê²½ì‚¬í•ì„ ì €ìž¥í•˜ì‹œê² ìŠµë‹ˆê¹Œ?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "ì”¬ì„ ë‹¤ë¥¸ ì´ë¦„으로 ì €ìž¥.." +msgid "Save Scene As..." +msgstr "ì”¬ì„ ë‹¤ë¥¸ ì´ë¦„으로 ì €ìž¥..." #: editor/editor_node.cpp msgid "No" @@ -1707,8 +1707,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "ì´ í–‰ë™ì€ 취소가 불가능합니다. ë¬´ì‹œí•˜ê³ ë˜ëŒë¦¬ì‹œê² 습니까?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "ë¹ ë¥¸ 씬 실행.." +msgid "Quick Run Scene..." +msgstr "ë¹ ë¥¸ 씬 실행..." #: editor/editor_node.cpp msgid "Quit" @@ -1862,8 +1862,8 @@ msgid "Previous tab" msgstr "ì´ì „ íƒ" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "íŒŒì¼ í•„í„°ë§.." +msgid "Filter Files..." +msgstr "íŒŒì¼ í•„í„°ë§..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1874,12 +1874,12 @@ msgid "New Scene" msgstr "새 씬" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "새 ìƒì† 씬.." +msgid "New Inherited Scene..." +msgstr "새 ìƒì† 씬..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "씬 열기.." +msgid "Open Scene..." +msgstr "씬 열기..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1898,16 +1898,16 @@ msgid "Open Recent" msgstr "최근 ì—´ì—ˆë˜ í•목" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "변환.." +msgid "Convert To..." +msgstr "변환..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "메시 ë¼ì´ë¸ŒëŸ¬ë¦¬.." +msgid "MeshLibrary..." +msgstr "메시 ë¼ì´ë¸ŒëŸ¬ë¦¬..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "íƒ€ì¼ ì…‹.." +msgid "TileSet..." +msgstr "íƒ€ì¼ ì…‹..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2171,8 +2171,8 @@ msgid "Save the currently edited resource." msgstr "현재 íŽ¸ì§‘ëœ ë¦¬ì†ŒìŠ¤ ì €ìž¥." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "다른 ì´ë¦„으로 ì €ìž¥.." +msgid "Save As..." +msgstr "다른 ì´ë¦„으로 ì €ìž¥..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2280,8 +2280,8 @@ msgid "Creating Mesh Previews" msgstr "메시 미리보기 ìƒì„± 중" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "ì¸ë„¤ì¼.." +msgid "Thumbnail..." +msgstr "ì¸ë„¤ì¼..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2433,8 +2433,8 @@ msgid "(Current)" msgstr "(현재)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "미러를 ê°€ì ¸ì˜¤ëŠ” 중입니다, ìž ì‹œë§Œ 기다리세요.." +msgid "Retrieving mirrors, please wait..." +msgstr "미러를 ê°€ì ¸ì˜¤ëŠ” 중입니다, ìž ì‹œë§Œ 기다리세요..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2511,8 +2511,8 @@ msgid "Error requesting url: " msgstr "url ìš”ì² ì—러: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "ë¯¸ëŸ¬ì— ì—°ê²°ì¤‘.." +msgid "Connecting to Mirror..." +msgstr "ë¯¸ëŸ¬ì— ì—°ê²°ì¤‘..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2528,8 +2528,8 @@ msgstr "í•´ê²°í• ìˆ˜ ì—†ìŒ" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "연결중.." +msgid "Connecting..." +msgstr "연결중..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2541,8 +2541,8 @@ msgstr "ì—°ê²°ë¨" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "ìš”ì²ì¤‘.." +msgid "Requesting..." +msgstr "ìš”ì²ì¤‘..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2674,12 +2674,12 @@ msgid "Collapse all" msgstr "ëª¨ë‘ ì ‘ê¸°" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "ì´ë¦„ 변경.." +msgid "Rename..." +msgstr "ì´ë¦„ 변경..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "ì´ë™.." +msgid "Move To..." +msgstr "ì´ë™..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2690,16 +2690,16 @@ msgid "Instance" msgstr "ì¸ìŠ¤í„´ìŠ¤" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "ì¢…ì† ê´€ê³„ 편집.." +msgid "Edit Dependencies..." +msgstr "ì¢…ì† ê´€ê³„ 편집..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "ì†Œìœ ìž ë³´ê¸°.." +msgid "View Owners..." +msgstr "ì†Œìœ ìž ë³´ê¸°..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "ë³µì œ.." +msgid "Duplicate..." +msgstr "ë³µì œ..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2724,10 +2724,10 @@ msgstr "ì„ íƒëœ ì”¬ì„ ì„ íƒëœ ë…¸ë“œì˜ ìžì‹ìœ¼ë¡œ ì¸ìŠ¤í„´ìŠ¤ 합니다 #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "íŒŒì¼ ìŠ¤ìº”ì¤‘,\n" -"ìž ì‹œë§Œ ê¸°ë‹¤ë ¤ì£¼ì„¸ìš”.." +"ìž ì‹œë§Œ ê¸°ë‹¤ë ¤ì£¼ì„¸ìš”..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2792,8 +2792,8 @@ msgid "Import Scene" msgstr "씬 ê°€ì ¸ì˜¤ê¸°" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "씬 ê°€ì ¸ì˜¤ëŠ” 중.." +msgid "Importing Scene..." +msgstr "씬 ê°€ì ¸ì˜¤ëŠ” 중..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2804,8 +2804,8 @@ msgid "Generating for Mesh: " msgstr "메시를 위해 ìƒì„± 중: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "ì‚¬ìš©ìž ì •ì˜ ìŠ¤í¬ë¦½íЏ 실행중.." +msgid "Running Custom Script..." +msgstr "ì‚¬ìš©ìž ì •ì˜ ìŠ¤í¬ë¦½íЏ 실행중..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2821,8 +2821,8 @@ msgid "Error running post-import script:" msgstr "ê°€ì ¸ì˜¤ê¸° 후 ì‹¤í–‰í• ìŠ¤í¬ë¦½íЏ 실행 중 ì—러:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "ì €ìž¥ 중.." +msgid "Saving..." +msgstr "ì €ìž¥ 중..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2841,8 +2841,8 @@ msgid "Import As:" msgstr "ë‹¤ìŒ í˜•ì‹ìœ¼ë¡œ ê°€ì ¸ì˜¤ê¸°:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "프리셋.." +msgid "Preset..." +msgstr "프리셋..." #: editor/import_dock.cpp msgid "Reimport" @@ -3256,19 +3256,19 @@ msgstr "시간 íƒìƒ‰ 노드" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Transition Node" -msgstr "변화 노드" +msgstr "ì „í™˜ 노드" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "ì• ë‹ˆë©”ì´ì…˜ ê°€ì ¸ì˜¤ê¸°.." +msgid "Import Animations..." +msgstr "ì• ë‹ˆë©”ì´ì…˜ ê°€ì ¸ì˜¤ê¸°..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "노드 í•„í„° 편집" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "í•„í„°.." +msgid "Filters..." +msgstr "í•„í„°..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3335,8 +3335,8 @@ msgid "Fetching:" msgstr "ê°€ì ¸ì˜¤ëŠ” 중:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "í•´ê²° 중.." +msgid "Resolving..." +msgstr "í•´ê²° 중..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3402,8 +3402,8 @@ msgid "Site:" msgstr "사ì´íЏ:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "ì§€ì›.." +msgid "Support..." +msgstr "ì§€ì›..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3596,6 +3596,7 @@ msgid "Use Rotation Snap" msgstr "íšŒì „ 스냅 사용" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "스냅 ì„¤ì •..." @@ -3692,14 +3693,12 @@ msgid "Show Guides" msgstr "ê°€ì´ë“œ 보기" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "ì›ì 보기" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1ê°œ ë·°í¬íЏ" +msgstr "ë·°í¬íЏ 보기" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3992,7 +3991,7 @@ msgstr "ë©”ì‹œì— ì™¸ê³½ì„ ì„ ë§Œë“¤ê¸° 위한 서피스가 없습니다!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "메시 기본 íƒ€ìž…ì´ PRIMITIVE_TRIANGLESì´ ì•„ë‹™ë‹ˆë‹¤!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4023,8 +4022,8 @@ msgid "Create Convex Collision Sibling" msgstr "Convex ì¶©ëŒ í˜•ì œ 만들기" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "ì™¸ê³½ì„ ë©”ì‹œ 만들기.." +msgid "Create Outline Mesh..." +msgstr "ì™¸ê³½ì„ ë©”ì‹œ 만들기..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4228,8 +4227,8 @@ msgid "Error loading image:" msgstr "ì´ë¯¸ì§€ 로드 ì—러:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "ì´ë¯¸ì§€ì— 투명ë„ê°€ 128보다 í° í”½ì…€ì´ ì—†ìŠµë‹ˆë‹¤.." +msgid "No pixels with transparency > 128 in image..." +msgstr "ì´ë¯¸ì§€ì— 투명ë„ê°€ 128보다 í° í”½ì…€ì´ ì—†ìŠµë‹ˆë‹¤..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4589,8 +4588,8 @@ msgid "Import Theme" msgstr "테마 ê°€ì ¸ì˜¤ê¸°" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "테마 다른 ì´ë¦„으로 ì €ìž¥.." +msgid "Save Theme As..." +msgstr "테마 다른 ì´ë¦„으로 ì €ìž¥..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4686,8 +4685,8 @@ msgstr "스í¬ë¦½íЏ íŒ¨ë„ í† ê¸€" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "찾기.." +msgid "Find..." +msgstr "찾기..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4894,16 +4893,16 @@ msgid "Find Previous" msgstr "ì´ì „ 찾기" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "변경.." +msgid "Replace..." +msgstr "변경..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "함수로 ì´ë™.." +msgid "Goto Function..." +msgstr "함수로 ì´ë™..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "ë¼ì¸ìœ¼ë¡œ ì´ë™.." +msgid "Goto Line..." +msgstr "ë¼ì¸ìœ¼ë¡œ ì´ë™..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5356,12 +5355,8 @@ msgid "Transform" msgstr "변형" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "스냅 ì„¤ì •.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "변형 다ì´ì–¼ë¡œê·¸.." +msgid "Transform Dialog..." +msgstr "변형 다ì´ì–¼ë¡œê·¸..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5613,8 +5608,8 @@ msgid "Remove All" msgstr "ëª¨ë‘ ì‚ì œ" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "테마 편집.." +msgid "Edit theme..." +msgstr "테마 편집..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5661,14 +5656,12 @@ msgid "Checked Item" msgstr "í•목 확ì¸ë¨" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "í•목 추가" +msgstr "ë¼ë””오 í•목" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "í•목 확ì¸ë¨" +msgstr "ë¼ë””오 í•목 확ì¸ë¨" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5683,8 +5676,8 @@ msgid "Options" msgstr "옵션" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "가진다,ë§Žì€,여러,옵션들!" +msgid "Has,Many,Options" +msgstr "가진다,ë§Žì€,옵션들" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5875,8 +5868,8 @@ msgid "Presets" msgstr "프리셋" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "추가.." +msgid "Add..." +msgstr "추가..." #: editor/project_export.cpp msgid "Resources" @@ -5965,6 +5958,10 @@ msgid "Imported Project" msgstr "ê°€ì ¸ì˜¨ 프로ì 트" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "ì¸ì‹í• 수 없는 프로ì 트 명입니다." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "í´ë”를 만들 수 없습니다." @@ -6163,9 +6160,11 @@ msgstr "마우스 버튼" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"ì¸ì‹í• 수 없는 ì•¡ì…˜ ì´ë¦„입니다. 공백ì´ê±°ë‚˜, '/' , ':', '=', '\\', '\"' ê°€ í¬í•¨" +"ë˜ë©´ 안 ë©ë‹ˆë‹¤." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6192,8 +6191,8 @@ msgid "Control+" msgstr "컨트롤+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "키를 눌러주세요.." +msgid "Press a Key..." +msgstr "키를 눌러주세요..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6376,8 +6375,8 @@ msgid "Property:" msgstr "ì†ì„±:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "ìž¬ì •ì˜.." +msgid "Override For..." +msgstr "ìž¬ì •ì˜..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6472,12 +6471,12 @@ msgid "Easing Out-In" msgstr "ê°€ì†-ê°ì†" #: editor/property_editor.cpp -msgid "File.." -msgstr "파ì¼.." +msgid "File..." +msgstr "파ì¼..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "ë””ë ‰í† ë¦¬.." +msgid "Dir..." +msgstr "ë””ë ‰í† ë¦¬..." #: editor/property_editor.cpp msgid "Assign" @@ -6647,8 +6646,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "ì´ ìž‘ì—…ì€ ì¸ìŠ¤í„´ìŠ¤ëœ ì”¬ì—서는 불가합니다." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "새 ì”¬ì„ ë‹¤ë¥¸ ì´ë¦„으로 ì €ìž¥.." +msgid "Save New Scene As..." +msgstr "새 ì”¬ì„ ë‹¤ë¥¸ ì´ë¦„으로 ì €ìž¥..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7363,7 +7362,7 @@ msgstr "거리 ì„ íƒ:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "í´ëž˜ìФ ì´ë¦„ì€ í‚¤ì›Œë“œê°€ ë 수 없습니다" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8064,7 +8063,7 @@ msgstr "Path ì†ì„±ì€ ìœ íš¨í•œ Spatial 노드를 가리켜야 합니다." #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment는 Environment 리소스가 필요합니다." #: scene/3d/scenario_fx.cpp msgid "" @@ -8076,6 +8075,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"ì´ WorldEnvironment는 무시ë©ë‹ˆë‹¤. (3D ì”¬ì„ ìœ„í•´) Camera를 추가하거나 아니면 " +"(2D ì”¬ì„ ìœ„í•´) ì´ í™˜ê²½ì˜ ë°°ê²½ 모드를 Canvas로 ì„¤ì •í•˜ì„¸ìš”." #: scene/3d/sprite_3d.cpp msgid "" @@ -8172,6 +8173,13 @@ msgstr "í°íЏ 로딩 ì—러." msgid "Invalid font size." msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "ì´ì „ íƒ" + +#~ msgid "Next" +#~ msgstr "다ìŒ" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ ì•¡ì…˜ ('/' ë˜ëŠ” ':' ë¬¸ìž ì‚¬ìš© 불가)." @@ -8197,9 +8205,6 @@ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "프로ì 트 ê²½ë¡œì— project.godot 파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다." -#~ msgid "Next" -#~ msgstr "다ìŒ" - #~ msgid "Not found!" #~ msgstr "ì°¾ì„ ìˆ˜ ì—†ìŒ!" @@ -8333,8 +8338,8 @@ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." #~ msgid "Exporting for %s" #~ msgstr "%s 내보내기" -#~ msgid "Setting Up.." -#~ msgstr "ì„¤ì • 중.." +#~ msgid "Setting Up..." +#~ msgstr "ì„¤ì • 중..." #~ msgid "Error loading scene." #~ msgstr "씬 로딩 중 ì—러." @@ -8388,8 +8393,8 @@ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." #~ msgid "Info" #~ msgstr "ì •ë³´" -#~ msgid "Re-Import.." -#~ msgstr "다시 ê°€ì ¸ì˜¤ê¸°.." +#~ msgid "Re-Import..." +#~ msgstr "다시 ê°€ì ¸ì˜¤ê¸°..." #~ msgid "No bit masks to import!" #~ msgstr "ê°€ì ¸ì˜¬ 비트 마스í¬ê°€ 없습니다!" @@ -8781,14 +8786,14 @@ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." #~ msgid "Zoom (%):" #~ msgstr "확대 (%):" -#~ msgid "Skeleton.." -#~ msgstr "ìŠ¤ì¼ˆë ˆí†¤.." +#~ msgid "Skeleton..." +#~ msgstr "ìŠ¤ì¼ˆë ˆí†¤..." #~ msgid "Zoom Reset" #~ msgstr "확대 초기화" -#~ msgid "Zoom Set.." -#~ msgstr "확대 ì„¤ì •.." +#~ msgid "Zoom Set..." +#~ msgstr "확대 ì„¤ì •..." #~ msgid "Set a Value" #~ msgstr "ê°’ ì„¤ì •" @@ -9219,8 +9224,8 @@ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." #~ msgid "Export Project PCK" #~ msgstr "프로ì 트 PCK 내보내기" -#~ msgid "Export.." -#~ msgstr "내보내기.." +#~ msgid "Export..." +#~ msgstr "내보내기..." #~ msgid "Project Export" #~ msgstr "프로ì 트 내보내기" @@ -9333,8 +9338,8 @@ msgstr "ìœ íš¨í•˜ì§€ ì•Šì€ í°íЏ í¬ê¸°." #~ msgid "Reload Tool Script (Soft)" #~ msgstr "툴 스í¬ë¦½íЏ 다시 로드 (소프트)" -#~ msgid "Edit Connections.." -#~ msgstr "ì—°ê²° 편집.." +#~ msgid "Edit Connections..." +#~ msgstr "ì—°ê²° 편집..." #~ msgid "Set Params" #~ msgstr "ì†ì„± ì ìš©" diff --git a/editor/translations/lt.po b/editor/translations/lt.po index 6504e570f7..bf4443627a 100644 --- a/editor/translations/lt.po +++ b/editor/translations/lt.po @@ -2,14 +2,12 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Ignas Kiela <ignaskiela@super.lt>, 2017. -# Kornelijus <kornelijus.github@gmail.com>, 2017. -# +# Kornelijus <kornelijus.github@gmail.com>, 2017, 2018. msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2017-11-27 00:48+0000\n" +"PO-Revision-Date: 2018-06-12 09:40+0000\n" "Last-Translator: Kornelijus <kornelijus.github@gmail.com>\n" "Language-Team: Lithuanian <https://hosted.weblate.org/projects/godot-engine/" "godot/lt/>\n" @@ -18,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=4; plural=n==1 ? 0 : n%10>=2 && (n%100<10 || n" "%100>=20) ? 1 : n%10==0 || (n%100>10 && n%100<20) ? 2 : 3;\n" -"X-Generator: Weblate 2.18-dev\n" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -503,7 +501,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Prijungti '%s' prie '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -914,11 +912,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1054,11 +1052,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1127,7 +1125,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1389,12 +1387,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1599,11 +1597,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1615,7 +1613,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1667,7 +1665,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1812,7 +1810,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1824,11 +1822,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1848,15 +1846,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2022,11 +2020,11 @@ msgstr "" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp msgid "Community" -msgstr "" +msgstr "BendruomenÄ—" #: editor/editor_node.cpp msgid "About" -msgstr "" +msgstr "Apie" #: editor/editor_node.cpp msgid "Play the project." @@ -2101,7 +2099,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2210,8 +2208,8 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "MiniatÅ«ra.." +msgid "Thumbnail..." +msgstr "MiniatÅ«ra..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2362,7 +2360,7 @@ msgid "(Current)" msgstr "(Esama)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2439,7 +2437,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2456,7 +2454,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2469,7 +2467,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2605,11 +2603,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2621,16 +2619,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Duplikuoti" #: editor/filesystem_dock.cpp @@ -2656,7 +2654,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2722,7 +2720,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2734,7 +2732,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2750,7 +2748,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2770,7 +2768,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3186,8 +3184,8 @@ msgid "Transition Node" msgstr "Transition Nodas" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importuoti Animacijas.." +msgid "Import Animations..." +msgstr "Importuoti Animacijas..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" @@ -3195,8 +3193,8 @@ msgstr "Redaguoti Nodų Filtrus" #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy -msgid "Filters.." -msgstr "Filtrai.." +msgid "Filters..." +msgstr "Filtrai..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3264,7 +3262,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3331,7 +3329,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3518,6 +3516,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3939,7 +3938,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4144,7 +4143,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4505,7 +4504,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4602,7 +4601,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4808,15 +4807,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5268,11 +5267,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5525,7 +5520,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5593,7 +5588,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5781,7 +5776,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5871,6 +5866,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "Netinkamas Å¡rifto dydis." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6060,8 +6060,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6089,7 +6089,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6273,7 +6273,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6369,11 +6369,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6544,7 +6544,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/ms.po b/editor/translations/ms.po new file mode 100644 index 0000000000..19d8b6b7d8 --- /dev/null +++ b/editor/translations/ms.po @@ -0,0 +1,7956 @@ +# Malay translation of the Godot Engine editor +# Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. +# Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) +# This file is distributed under the same license as the Godot source code. +# +# Sam Vanguard <syafz119@gmail.com>, 2018. +# Shaqir Rafiq <moshamoradev@gmail.com>, 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: Godot Engine editor\n" +"PO-Revision-Date: 2018-06-05 19:27+0000\n" +"Last-Translator: Shaqir Rafiq <moshamoradev@gmail.com>\n" +"Language-Team: Malay <https://hosted.weblate.org/projects/godot-engine/godot/" +"ms/>\n" +"Language: ms\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8-bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 3.0\n" + +#: editor/animation_editor.cpp +msgid "Disabled" +msgstr "Tidak Aktif" + +#: editor/animation_editor.cpp +msgid "All Selection" +msgstr "Semua Pilihan" + +#: editor/animation_editor.cpp +#, fuzzy +msgid "Anim Change Keyframe Time" +msgstr "Anim Ubah Masa Keyframe" + +#: editor/animation_editor.cpp +msgid "Anim Change Transition" +msgstr "Anim Ubah Peralihan" + +#: editor/animation_editor.cpp +msgid "Anim Change Transform" +msgstr "Anim Ubah Penukaran" + +#: editor/animation_editor.cpp +msgid "Anim Change Keyframe Value" +msgstr "Anim Ubah Nilai Keyframe" + +#: editor/animation_editor.cpp +msgid "Anim Change Call" +msgstr "Anim Ubah Panggilan" + +#: editor/animation_editor.cpp +msgid "Anim Add Track" +msgstr "Anim Tambah Trek" + +#: editor/animation_editor.cpp +msgid "Anim Duplicate Keys" +msgstr "Anim Menduakan Kunci" + +#: editor/animation_editor.cpp +msgid "Move Anim Track Up" +msgstr "Ubah Trek Anim Ke Atas" + +#: editor/animation_editor.cpp +msgid "Move Anim Track Down" +msgstr "Ubah Trek Anim Ke Bawah" + +#: editor/animation_editor.cpp +msgid "Remove Anim Track" +msgstr "Buang Trek Anim" + +#: editor/animation_editor.cpp +msgid "Set Transitions to:" +msgstr "Set Peralihan ke:" + +#: editor/animation_editor.cpp +msgid "Anim Track Rename" +msgstr "Ubah Nama Trek Anim" + +#: editor/animation_editor.cpp +msgid "Anim Track Change Interpolation" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Track Change Value Mode" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Track Change Wrap Mode" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Edit Node Curve" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Edit Selection Curve" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Delete Keys" +msgstr "" + +#: editor/animation_editor.cpp editor/plugins/tile_map_editor_plugin.cpp +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Duplicate Selection" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Duplicate Transposed" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Remove Selection" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Continuous" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Discrete" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Trigger" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Add Key" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Move Keys" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Scale Selection" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Scale From Cursor" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Goto Next Step" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Goto Prev Step" +msgstr "" + +#: editor/animation_editor.cpp editor/plugins/curve_editor_plugin.cpp +#: editor/property_editor.cpp +msgid "Linear" +msgstr "" + +#: editor/animation_editor.cpp editor/plugins/theme_editor_plugin.cpp +msgid "Constant" +msgstr "" + +#: editor/animation_editor.cpp +msgid "In" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Out" +msgstr "" + +#: editor/animation_editor.cpp +msgid "In-Out" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Out-In" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Transitions" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Optimize Animation" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Clean-Up Animation" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Create NEW track for %s and insert key?" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Create %d NEW tracks and insert keys?" +msgstr "" + +#: editor/animation_editor.cpp editor/create_dialog.cpp +#: editor/editor_audio_buses.cpp editor/plugins/abstract_polygon_2d_editor.cpp +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +#: editor/plugins/mesh_instance_editor_plugin.cpp +#: editor/plugins/particles_editor_plugin.cpp editor/script_create_dialog.cpp +msgid "Create" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Create & Insert" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Insert Track & Key" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Insert Key" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Change Anim Len" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Change Anim Loop" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Create Typed Value Key" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Insert" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Scale Keys" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim Add Call Track" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Animation zoom." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Length (s):" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Animation length (in seconds)." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Step (s):" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Cursor step snap (in seconds)." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Enable/Disable looping in animation." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Add new tracks." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Move current track up." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Move current track down." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Remove selected track." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Track tools" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Enable editing of individual keys by clicking them." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Anim. Optimizer" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Max. Linear Error:" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Max. Angular Error:" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Max Optimizable Angle:" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Optimize" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Select an AnimationPlayer from the Scene Tree to edit animations." +msgstr "" + +#: editor/animation_editor.cpp +msgid "Key" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Transition" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Scale Ratio:" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Call Functions in Which Node?" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Remove invalid keys" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Remove unresolved and empty tracks" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Clean-up all animations" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Clean-Up Animation(s) (NO UNDO!)" +msgstr "" + +#: editor/animation_editor.cpp +msgid "Clean-Up" +msgstr "" + +#: editor/array_property_edit.cpp +msgid "Resize Array" +msgstr "" + +#: editor/array_property_edit.cpp +msgid "Change Array Value Type" +msgstr "" + +#: editor/array_property_edit.cpp +msgid "Change Array Value" +msgstr "" + +#: editor/code_editor.cpp +msgid "Go to Line" +msgstr "" + +#: editor/code_editor.cpp +msgid "Line Number:" +msgstr "" + +#: editor/code_editor.cpp +msgid "No Matches" +msgstr "" + +#: editor/code_editor.cpp +msgid "Replaced %d occurrence(s)." +msgstr "" + +#: editor/code_editor.cpp +msgid "Match Case" +msgstr "" + +#: editor/code_editor.cpp +msgid "Whole Words" +msgstr "" + +#: editor/code_editor.cpp +msgid "Replace" +msgstr "" + +#: editor/code_editor.cpp +msgid "Replace All" +msgstr "" + +#: editor/code_editor.cpp +msgid "Selection Only" +msgstr "" + +#: editor/code_editor.cpp +msgid "Zoom In" +msgstr "" + +#: editor/code_editor.cpp +msgid "Zoom Out" +msgstr "" + +#: editor/code_editor.cpp +msgid "Reset Zoom" +msgstr "" + +#: editor/code_editor.cpp editor/script_editor_debugger.cpp +msgid "Line:" +msgstr "" + +#: editor/code_editor.cpp +msgid "Col:" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Method in target Node must be specified!" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "" +"Target method not found! Specify a valid method or attach a script to target " +"Node." +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Connect To Node:" +msgstr "" + +#: editor/connections_dialog.cpp editor/editor_autoload_settings.cpp +#: editor/groups_editor.cpp editor/plugins/item_list_editor_plugin.cpp +#: editor/plugins/theme_editor_plugin.cpp editor/project_settings_editor.cpp +msgid "Add" +msgstr "" + +#: editor/connections_dialog.cpp editor/dependency_editor.cpp +#: editor/plugins/animation_tree_editor_plugin.cpp +#: editor/plugins/theme_editor_plugin.cpp editor/project_manager.cpp +#: editor/project_settings_editor.cpp +msgid "Remove" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Add Extra Call Argument:" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Extra Call Arguments:" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Path to Node:" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Make Function" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Deferred" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Oneshot" +msgstr "" + +#: editor/connections_dialog.cpp editor/dependency_editor.cpp +#: editor/export_template_manager.cpp +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/plugins/script_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp editor/project_export.cpp +#: editor/project_settings_editor.cpp editor/property_editor.cpp +#: editor/run_settings_dialog.cpp editor/settings_config_dialog.cpp +#: modules/visual_script/visual_script_editor.cpp +msgid "Close" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Connect" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Connect '%s' to '%s'" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Connecting Signal:" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Disconnect '%s' from '%s'" +msgstr "" + +#: editor/connections_dialog.cpp +msgid "Connect..." +msgstr "" + +#: editor/connections_dialog.cpp +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Disconnect" +msgstr "" + +#: editor/connections_dialog.cpp editor/editor_help.cpp editor/node_dock.cpp +msgid "Signals" +msgstr "" + +#: editor/create_dialog.cpp +msgid "Change %s Type" +msgstr "" + +#: editor/create_dialog.cpp editor/project_settings_editor.cpp +#: modules/visual_script/visual_script_editor.cpp +msgid "Change" +msgstr "" + +#: editor/create_dialog.cpp +msgid "Create New %s" +msgstr "" + +#: editor/create_dialog.cpp editor/editor_file_dialog.cpp +#: editor/filesystem_dock.cpp +msgid "Favorites:" +msgstr "" + +#: editor/create_dialog.cpp editor/editor_file_dialog.cpp +msgid "Recent:" +msgstr "" + +#: editor/create_dialog.cpp editor/editor_node.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +#: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp +#: editor/quick_open.cpp +msgid "Search:" +msgstr "" + +#: editor/create_dialog.cpp editor/editor_help.cpp +#: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp +#: editor/quick_open.cpp +msgid "Matches:" +msgstr "" + +#: editor/create_dialog.cpp editor/editor_help.cpp +#: editor/plugins/asset_library_editor_plugin.cpp editor/property_selector.cpp +#: editor/script_editor_debugger.cpp +msgid "Description:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Search Replacement For:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Dependencies For:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "" +"Scene '%s' is currently being edited.\n" +"Changes will not take effect unless reloaded." +msgstr "" + +#: editor/dependency_editor.cpp +msgid "" +"Resource '%s' is in use.\n" +"Changes will take effect when reloaded." +msgstr "" + +#: editor/dependency_editor.cpp +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Dependencies" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Resource" +msgstr "" + +#: editor/dependency_editor.cpp editor/editor_autoload_settings.cpp +#: editor/project_manager.cpp editor/project_settings_editor.cpp +#: editor/script_create_dialog.cpp +msgid "Path" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Dependencies:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Fix Broken" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Dependency Editor" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Search Replacement Resource:" +msgstr "" + +#: editor/dependency_editor.cpp editor/editor_file_dialog.cpp +#: editor/editor_help.cpp editor/editor_node.cpp editor/filesystem_dock.cpp +#: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp +#: editor/quick_open.cpp scene/gui/file_dialog.cpp +msgid "Open" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Owners Of:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Remove selected files from the project? (no undo)" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "" +"The files being removed are required by other resources in order for them to " +"work.\n" +"Remove them anyway? (no undo)" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Cannot remove:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Error loading:" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Scene failed to load due to missing dependencies:" +msgstr "" + +#: editor/dependency_editor.cpp editor/editor_node.cpp +msgid "Open Anyway" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Which action should be taken?" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Fix Dependencies" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Errors loading!" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Permanently delete %d item(s)? (No undo!)" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Owns" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Resources Without Explicit Ownership:" +msgstr "" + +#: editor/dependency_editor.cpp editor/editor_node.cpp +msgid "Orphan Resource Explorer" +msgstr "" + +#: editor/dependency_editor.cpp +msgid "Delete selected files?" +msgstr "" + +#: editor/dependency_editor.cpp editor/editor_audio_buses.cpp +#: editor/editor_file_dialog.cpp editor/editor_node.cpp +#: editor/filesystem_dock.cpp editor/plugins/item_list_editor_plugin.cpp +#: editor/project_export.cpp editor/project_settings_editor.cpp +#: editor/scene_tree_dock.cpp +msgid "Delete" +msgstr "" + +#: editor/dictionary_property_edit.cpp +msgid "Change Dictionary Key" +msgstr "" + +#: editor/dictionary_property_edit.cpp +msgid "Change Dictionary Value" +msgstr "" + +#: editor/editor_about.cpp +msgid "Thanks from the Godot community!" +msgstr "" + +#: editor/editor_about.cpp +msgid "Thanks!" +msgstr "" + +#: editor/editor_about.cpp +msgid "Godot Engine contributors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Project Founders" +msgstr "" + +#: editor/editor_about.cpp +msgid "Lead Developer" +msgstr "" + +#: editor/editor_about.cpp +msgid "Project Manager " +msgstr "" + +#: editor/editor_about.cpp +msgid "Developers" +msgstr "" + +#: editor/editor_about.cpp +msgid "Authors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Platinum Sponsors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Gold Sponsors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Mini Sponsors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Gold Donors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Silver Donors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Bronze Donors" +msgstr "" + +#: editor/editor_about.cpp +msgid "Donors" +msgstr "" + +#: editor/editor_about.cpp +msgid "License" +msgstr "" + +#: editor/editor_about.cpp +msgid "Thirdparty License" +msgstr "" + +#: editor/editor_about.cpp +msgid "" +"Godot Engine relies on a number of thirdparty free and open source " +"libraries, all compatible with the terms of its MIT license. The following " +"is an exhaustive list of all such thirdparty components with their " +"respective copyright statements and license terms." +msgstr "" + +#: editor/editor_about.cpp +msgid "All Components" +msgstr "" + +#: editor/editor_about.cpp +msgid "Components" +msgstr "" + +#: editor/editor_about.cpp +msgid "Licenses" +msgstr "" + +#: editor/editor_asset_installer.cpp editor/project_manager.cpp +msgid "Error opening package file, not in zip format." +msgstr "" + +#: editor/editor_asset_installer.cpp +msgid "Uncompressing Assets" +msgstr "" + +#: editor/editor_asset_installer.cpp editor/project_manager.cpp +msgid "Package Installed Successfully!" +msgstr "" + +#: editor/editor_asset_installer.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Success!" +msgstr "" + +#: editor/editor_asset_installer.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Install" +msgstr "" + +#: editor/editor_asset_installer.cpp +msgid "Package Installer" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Speakers" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Add Effect" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Rename Audio Bus" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Change Audio Bus Volume" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Toggle Audio Bus Solo" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Toggle Audio Bus Mute" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Toggle Audio Bus Bypass Effects" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Select Audio Bus Send" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Add Audio Bus Effect" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Move Bus Effect" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Delete Bus Effect" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Audio Bus, Drag and Drop to rearrange." +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Solo" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Mute" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Bypass" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Bus options" +msgstr "" + +#: editor/editor_audio_buses.cpp editor/filesystem_dock.cpp +#: editor/plugins/tile_map_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "Duplicate" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Reset Volume" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Delete Effect" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Audio" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Add Audio Bus" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Master bus can't be deleted!" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Delete Audio Bus" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Duplicate Audio Bus" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Reset Bus Volume" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Move Audio Bus" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Save Audio Bus Layout As..." +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Location for New Layout..." +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Open Audio Bus Layout" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "There is no 'res://default_bus_layout.tres' file." +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Invalid file, not an audio bus layout." +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Add Bus" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Create a new Bus Layout." +msgstr "" + +#: editor/editor_audio_buses.cpp editor/property_editor.cpp +#: editor/script_create_dialog.cpp +msgid "Load" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Load an existing Bus Layout." +msgstr "" + +#: editor/editor_audio_buses.cpp +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Save As" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Save this Bus Layout to a file." +msgstr "" + +#: editor/editor_audio_buses.cpp editor/import_dock.cpp +msgid "Load Default" +msgstr "" + +#: editor/editor_audio_buses.cpp +msgid "Load the default Bus Layout." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Invalid name." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Valid characters:" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Invalid name. Must not collide with an existing engine class name." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Invalid name. Must not collide with an existing buit-in type name." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Invalid name. Must not collide with an existing global constant name." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Invalid Path." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "File does not exist." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Not in resource path." +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Add AutoLoad" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Autoload '%s' already exists!" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Rename Autoload" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Toggle AutoLoad Globals" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Move Autoload" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Remove Autoload" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Enable" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Rearrange Autoloads" +msgstr "" + +#: editor/editor_autoload_settings.cpp editor/editor_file_dialog.cpp +#: scene/gui/file_dialog.cpp +msgid "Path:" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Node Name:" +msgstr "" + +#: editor/editor_autoload_settings.cpp editor/editor_profiler.cpp +#: editor/project_manager.cpp editor/settings_config_dialog.cpp +msgid "Name" +msgstr "" + +#: editor/editor_autoload_settings.cpp +msgid "Singleton" +msgstr "" + +#: editor/editor_data.cpp +msgid "Updating Scene" +msgstr "" + +#: editor/editor_data.cpp +msgid "Storing local changes..." +msgstr "" + +#: editor/editor_data.cpp +msgid "Updating scene..." +msgstr "" + +#: editor/editor_data.cpp +msgid "[empty]" +msgstr "" + +#: editor/editor_data.cpp +msgid "[unsaved]" +msgstr "" + +#: editor/editor_dir_dialog.cpp +msgid "Please select a base directory first" +msgstr "" + +#: editor/editor_dir_dialog.cpp +msgid "Choose a Directory" +msgstr "" + +#: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp +#: editor/filesystem_dock.cpp scene/gui/file_dialog.cpp +msgid "Create Folder" +msgstr "" + +#: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp +#: editor/editor_plugin_settings.cpp editor/filesystem_dock.cpp +#: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp +#: scene/gui/file_dialog.cpp +msgid "Name:" +msgstr "" + +#: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp +#: editor/filesystem_dock.cpp scene/gui/file_dialog.cpp +msgid "Could not create folder." +msgstr "" + +#: editor/editor_dir_dialog.cpp +msgid "Choose" +msgstr "" + +#: editor/editor_export.cpp +msgid "Storing File:" +msgstr "" + +#: editor/editor_export.cpp +msgid "Packing" +msgstr "" + +#: editor/editor_export.cpp platform/javascript/export/export.cpp +msgid "Template file not found:" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "File Exists, Overwrite?" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Select Current Folder" +msgstr "" + +#: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp +msgid "Copy Path" +msgstr "" + +#: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp +msgid "Show In File Manager" +msgstr "" + +#: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp +msgid "New Folder..." +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Refresh" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "All Recognized" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "All Files (*)" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Open a File" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Open File(s)" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Open a Directory" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Open a File or Directory" +msgstr "" + +#: editor/editor_file_dialog.cpp editor/editor_node.cpp +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/script_editor_plugin.cpp scene/gui/file_dialog.cpp +msgid "Save" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Save a File" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Go Back" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Go Forward" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Go Up" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Toggle Hidden Files" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Toggle Favorite" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Toggle Mode" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Focus Path" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Move Favorite Up" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Move Favorite Down" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Go to parent folder" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Directories & Files:" +msgstr "" + +#: editor/editor_file_dialog.cpp +msgid "Preview:" +msgstr "" + +#: editor/editor_file_dialog.cpp editor/script_editor_debugger.cpp +#: scene/gui/file_dialog.cpp +msgid "File:" +msgstr "" + +#: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp +msgid "Must use a valid extension." +msgstr "" + +#: editor/editor_file_system.cpp +msgid "ScanSources" +msgstr "" + +#: editor/editor_file_system.cpp +msgid "(Re)Importing Assets" +msgstr "" + +#: editor/editor_help.cpp editor/editor_node.cpp +#: editor/plugins/script_editor_plugin.cpp +msgid "Search Help" +msgstr "" + +#: editor/editor_help.cpp +msgid "Class List:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Search Classes" +msgstr "" + +#: editor/editor_help.cpp editor/plugins/spatial_editor_plugin.cpp +msgid "Top" +msgstr "" + +#: editor/editor_help.cpp editor/property_editor.cpp +msgid "Class:" +msgstr "" + +#: editor/editor_help.cpp editor/scene_tree_editor.cpp +msgid "Inherits:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Inherited by:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Brief Description:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Members" +msgstr "" + +#: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp +msgid "Members:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Public Methods" +msgstr "" + +#: editor/editor_help.cpp +msgid "Public Methods:" +msgstr "" + +#: editor/editor_help.cpp +msgid "GUI Theme Items" +msgstr "" + +#: editor/editor_help.cpp +msgid "GUI Theme Items:" +msgstr "" + +#: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp +msgid "Signals:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Enumerations" +msgstr "" + +#: editor/editor_help.cpp +msgid "Enumerations:" +msgstr "" + +#: editor/editor_help.cpp +msgid "enum " +msgstr "" + +#: editor/editor_help.cpp +msgid "Constants" +msgstr "" + +#: editor/editor_help.cpp +msgid "Constants:" +msgstr "" + +#: editor/editor_help.cpp +msgid "Description" +msgstr "" + +#: editor/editor_help.cpp +msgid "Online Tutorials:" +msgstr "" + +#: editor/editor_help.cpp +msgid "" +"There are currently no tutorials for this class, you can [color=$color][url=" +"$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" +"url][/color]." +msgstr "" + +#: editor/editor_help.cpp +msgid "Properties" +msgstr "" + +#: editor/editor_help.cpp +msgid "Property Description:" +msgstr "" + +#: editor/editor_help.cpp +msgid "" +"There is currently no description for this property. Please help us by " +"[color=$color][url=$url]contributing one[/url][/color]!" +msgstr "" + +#: editor/editor_help.cpp +msgid "Methods" +msgstr "" + +#: editor/editor_help.cpp +msgid "Method Description:" +msgstr "" + +#: editor/editor_help.cpp +msgid "" +"There is currently no description for this method. Please help us by [color=" +"$color][url=$url]contributing one[/url][/color]!" +msgstr "" + +#: editor/editor_help.cpp +msgid "Search Text" +msgstr "" + +#: editor/editor_help.cpp +msgid "Find" +msgstr "" + +#: editor/editor_log.cpp +msgid "Output:" +msgstr "" + +#: editor/editor_log.cpp editor/plugins/animation_tree_editor_plugin.cpp +#: editor/property_editor.cpp editor/script_editor_debugger.cpp +#: modules/gdnative/gdnative_library_editor_plugin.cpp scene/gui/line_edit.cpp +#: scene/gui/text_edit.cpp +msgid "Clear" +msgstr "" + +#: editor/editor_log.cpp +msgid "Clear Output" +msgstr "" + +#: editor/editor_node.cpp +msgid "Project export failed with error code %d." +msgstr "" + +#: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp +msgid "Error saving resource!" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp +msgid "Save Resource As..." +msgstr "" + +#: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp +#: editor/scene_tree_dock.cpp +msgid "I see..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Can't open file for writing:" +msgstr "" + +#: editor/editor_node.cpp +msgid "Requested file format unknown:" +msgstr "" + +#: editor/editor_node.cpp +msgid "Error while saving." +msgstr "" + +#: editor/editor_node.cpp +msgid "Can't open '%s'." +msgstr "" + +#: editor/editor_node.cpp +msgid "Error while parsing '%s'." +msgstr "" + +#: editor/editor_node.cpp +msgid "Unexpected end of file '%s'." +msgstr "" + +#: editor/editor_node.cpp +msgid "Missing '%s' or its dependencies." +msgstr "" + +#: editor/editor_node.cpp +msgid "Error while loading '%s'." +msgstr "" + +#: editor/editor_node.cpp +msgid "Saving Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Analyzing" +msgstr "" + +#: editor/editor_node.cpp +msgid "Creating Thumbnail" +msgstr "" + +#: editor/editor_node.cpp +msgid "This operation can't be done without a tree root." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Couldn't save scene. Likely dependencies (instances or inheritance) couldn't " +"be satisfied." +msgstr "" + +#: editor/editor_node.cpp +msgid "Failed to load resource." +msgstr "" + +#: editor/editor_node.cpp +msgid "Can't load MeshLibrary for merging!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Error saving MeshLibrary!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Can't load TileSet for merging!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Error saving TileSet!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Error trying to save layout!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Default editor layout overridden." +msgstr "" + +#: editor/editor_node.cpp +msgid "Layout name not found!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Restored default layout to base settings." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"This resource belongs to a scene that was imported, so it's not editable.\n" +"Please read the documentation relevant to importing scenes to better " +"understand this workflow." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"This resource belongs to a scene that was instanced or inherited.\n" +"Changes to it will not be kept when saving the current scene." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"This resource was imported, so it's not editable. Change its settings in the " +"import panel and then re-import." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"This scene was imported, so changes to it will not be kept.\n" +"Instancing it or inheriting will allow making changes to it.\n" +"Please read the documentation relevant to importing scenes to better " +"understand this workflow." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"This is a remote object so changes to it will not be kept.\n" +"Please read the documentation relevant to debugging to better understand " +"this workflow." +msgstr "" + +#: editor/editor_node.cpp +msgid "Expand all properties" +msgstr "" + +#: editor/editor_node.cpp +msgid "Collapse all properties" +msgstr "" + +#: editor/editor_node.cpp +msgid "Copy Params" +msgstr "" + +#: editor/editor_node.cpp +msgid "Paste Params" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/resource_preloader_editor_plugin.cpp +msgid "Paste Resource" +msgstr "" + +#: editor/editor_node.cpp +msgid "Copy Resource" +msgstr "" + +#: editor/editor_node.cpp +msgid "Make Built-In" +msgstr "" + +#: editor/editor_node.cpp +msgid "Make Sub-Resources Unique" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open in Help" +msgstr "" + +#: editor/editor_node.cpp +msgid "There is no defined scene to run." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"No main scene has ever been defined, select one?\n" +"You can change it later in \"Project Settings\" under the 'application' " +"category." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Selected scene '%s' does not exist, select a valid one?\n" +"You can change it later in \"Project Settings\" under the 'application' " +"category." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Selected scene '%s' is not a scene file, select a valid one?\n" +"You can change it later in \"Project Settings\" under the 'application' " +"category." +msgstr "" + +#: editor/editor_node.cpp +msgid "Current scene was never saved, please save it prior to running." +msgstr "" + +#: editor/editor_node.cpp +msgid "Could not start subprocess!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open Base Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Quick Open Scene..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Quick Open Script..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Save & Close" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save changes to '%s' before closing?" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save Scene As..." +msgstr "" + +#: editor/editor_node.cpp +msgid "No" +msgstr "" + +#: editor/editor_node.cpp +msgid "Yes" +msgstr "" + +#: editor/editor_node.cpp +msgid "This scene has never been saved. Save before running?" +msgstr "" + +#: editor/editor_node.cpp editor/scene_tree_dock.cpp +msgid "This operation can't be done without a scene." +msgstr "" + +#: editor/editor_node.cpp +msgid "Export Mesh Library" +msgstr "" + +#: editor/editor_node.cpp +msgid "This operation can't be done without a root node." +msgstr "" + +#: editor/editor_node.cpp +msgid "Export Tile Set" +msgstr "" + +#: editor/editor_node.cpp +msgid "This operation can't be done without a selected node." +msgstr "" + +#: editor/editor_node.cpp +msgid "Current scene not saved. Open anyway?" +msgstr "" + +#: editor/editor_node.cpp +msgid "Can't reload a scene that was never saved." +msgstr "" + +#: editor/editor_node.cpp +msgid "Revert" +msgstr "" + +#: editor/editor_node.cpp +msgid "This action cannot be undone. Revert anyway?" +msgstr "" + +#: editor/editor_node.cpp +msgid "Quick Run Scene..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Quit" +msgstr "" + +#: editor/editor_node.cpp +msgid "Exit the editor?" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open Project Manager?" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save & Quit" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save changes to the following scene(s) before quitting?" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save changes the following scene(s) before opening Project Manager?" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"This option is deprecated. Situations where refresh must be forced are now " +"considered a bug. Please report." +msgstr "" + +#: editor/editor_node.cpp +msgid "Pick a Main Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Unable to enable addon plugin at: '%s' parsing of config failed." +msgstr "" + +#: editor/editor_node.cpp +msgid "Unable to find script field for addon plugin at: 'res://addons/%s'." +msgstr "" + +#: editor/editor_node.cpp +msgid "Unable to load addon script from path: '%s'." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Unable to load addon script from path: '%s' Base type is not EditorPlugin." +msgstr "" + +#: editor/editor_node.cpp +msgid "Unable to load addon script from path: '%s' Script is not in tool mode." +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Scene '%s' was automatically imported, so it can't be modified.\n" +"To make changes to it, a new inherited scene can be created." +msgstr "" + +#: editor/editor_node.cpp editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "Ugh" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Error loading scene, it must be inside the project path. Use 'Import' to " +"open the scene, then save it inside the project path." +msgstr "" + +#: editor/editor_node.cpp +msgid "Scene '%s' has broken dependencies:" +msgstr "" + +#: editor/editor_node.cpp +msgid "Clear Recent Scenes" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save Layout" +msgstr "" + +#: editor/editor_node.cpp +msgid "Delete Layout" +msgstr "" + +#: editor/editor_node.cpp editor/import_dock.cpp +#: editor/script_create_dialog.cpp +msgid "Default" +msgstr "" + +#: editor/editor_node.cpp +msgid "Switch Scene Tab" +msgstr "" + +#: editor/editor_node.cpp +msgid "%d more files or folders" +msgstr "" + +#: editor/editor_node.cpp +msgid "%d more folders" +msgstr "" + +#: editor/editor_node.cpp +msgid "%d more files" +msgstr "" + +#: editor/editor_node.cpp +msgid "Dock Position" +msgstr "" + +#: editor/editor_node.cpp +msgid "Distraction Free Mode" +msgstr "" + +#: editor/editor_node.cpp +msgid "Toggle distraction-free mode." +msgstr "" + +#: editor/editor_node.cpp +msgid "Add a new scene." +msgstr "" + +#: editor/editor_node.cpp +msgid "Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Go to previously opened scene." +msgstr "" + +#: editor/editor_node.cpp +msgid "Next tab" +msgstr "" + +#: editor/editor_node.cpp +msgid "Previous tab" +msgstr "" + +#: editor/editor_node.cpp +msgid "Filter Files..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Operations with scene files." +msgstr "" + +#: editor/editor_node.cpp +msgid "New Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "New Inherited Scene..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Open Scene..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Save Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Save all Scenes" +msgstr "" + +#: editor/editor_node.cpp +msgid "Close Scene" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp +msgid "Open Recent" +msgstr "" + +#: editor/editor_node.cpp +msgid "Convert To..." +msgstr "" + +#: editor/editor_node.cpp +msgid "MeshLibrary..." +msgstr "" + +#: editor/editor_node.cpp +msgid "TileSet..." +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_text_editor.cpp +#: scene/gui/line_edit.cpp scene/gui/text_edit.cpp +msgid "Undo" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_text_editor.cpp +#: scene/gui/line_edit.cpp +msgid "Redo" +msgstr "" + +#: editor/editor_node.cpp +msgid "Revert Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Miscellaneous project or scene-wide tools." +msgstr "" + +#: editor/editor_node.cpp +msgid "Project" +msgstr "" + +#: editor/editor_node.cpp +msgid "Project Settings" +msgstr "" + +#: editor/editor_node.cpp +msgid "Run Script" +msgstr "" + +#: editor/editor_node.cpp editor/project_export.cpp +msgid "Export" +msgstr "" + +#: editor/editor_node.cpp +msgid "Tools" +msgstr "" + +#: editor/editor_node.cpp +msgid "Quit to Project List" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp +msgid "Debug" +msgstr "" + +#: editor/editor_node.cpp +msgid "Deploy with Remote Debug" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"When exporting or deploying, the resulting executable will attempt to " +"connect to the IP of this computer in order to be debugged." +msgstr "" + +#: editor/editor_node.cpp +msgid "Small Deploy with Network FS" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"When this option is enabled, export or deploy will produce a minimal " +"executable.\n" +"The filesystem will be provided from the project by the editor over the " +"network.\n" +"On Android, deploy will use the USB cable for faster performance. This " +"option speeds up testing for games with a large footprint." +msgstr "" + +#: editor/editor_node.cpp +msgid "Visible Collision Shapes" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Collision shapes and raycast nodes (for 2D and 3D) will be visible on the " +"running game if this option is turned on." +msgstr "" + +#: editor/editor_node.cpp +msgid "Visible Navigation" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"Navigation meshes and polygons will be visible on the running game if this " +"option is turned on." +msgstr "" + +#: editor/editor_node.cpp +msgid "Sync Scene Changes" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"When this option is turned on, any changes made to the scene in the editor " +"will be replicated in the running game.\n" +"When used remotely on a device, this is more efficient with network " +"filesystem." +msgstr "" + +#: editor/editor_node.cpp +msgid "Sync Script Changes" +msgstr "" + +#: editor/editor_node.cpp +msgid "" +"When this option is turned on, any script that is saved will be reloaded on " +"the running game.\n" +"When used remotely on a device, this is more efficient with network " +"filesystem." +msgstr "" + +#: editor/editor_node.cpp +msgid "Editor" +msgstr "" + +#: editor/editor_node.cpp editor/settings_config_dialog.cpp +msgid "Editor Settings" +msgstr "" + +#: editor/editor_node.cpp +msgid "Editor Layout" +msgstr "" + +#: editor/editor_node.cpp +msgid "Toggle Fullscreen" +msgstr "" + +#: editor/editor_node.cpp editor/project_export.cpp +msgid "Manage Export Templates" +msgstr "" + +#: editor/editor_node.cpp +msgid "Help" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp +msgid "Classes" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp +#: editor/plugins/script_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp +#: editor/plugins/shader_editor_plugin.cpp editor/project_settings_editor.cpp +msgid "Search" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp +msgid "Online Docs" +msgstr "" + +#: editor/editor_node.cpp +msgid "Q&A" +msgstr "" + +#: editor/editor_node.cpp +msgid "Issue Tracker" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp +msgid "Community" +msgstr "" + +#: editor/editor_node.cpp +msgid "About" +msgstr "" + +#: editor/editor_node.cpp +msgid "Play the project." +msgstr "" + +#: editor/editor_node.cpp +msgid "Play" +msgstr "" + +#: editor/editor_node.cpp +msgid "Pause the scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Pause Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Stop the scene." +msgstr "" + +#: editor/editor_node.cpp +msgid "Stop" +msgstr "" + +#: editor/editor_node.cpp +msgid "Play the edited scene." +msgstr "" + +#: editor/editor_node.cpp +msgid "Play Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Play custom scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Play Custom Scene" +msgstr "" + +#: editor/editor_node.cpp +msgid "Spins when the editor window repaints!" +msgstr "" + +#: editor/editor_node.cpp +msgid "Update Always" +msgstr "" + +#: editor/editor_node.cpp +msgid "Update Changes" +msgstr "" + +#: editor/editor_node.cpp +msgid "Disable Update Spinner" +msgstr "" + +#: editor/editor_node.cpp +msgid "Inspector" +msgstr "" + +#: editor/editor_node.cpp +msgid "Create a new resource in memory and edit it." +msgstr "" + +#: editor/editor_node.cpp +msgid "Load an existing resource from disk and edit it." +msgstr "" + +#: editor/editor_node.cpp +msgid "Save the currently edited resource." +msgstr "" + +#: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp +msgid "Save As..." +msgstr "" + +#: editor/editor_node.cpp +msgid "Go to the previous edited object in history." +msgstr "" + +#: editor/editor_node.cpp +msgid "Go to the next edited object in history." +msgstr "" + +#: editor/editor_node.cpp +msgid "History of recently edited objects." +msgstr "" + +#: editor/editor_node.cpp +msgid "Object properties." +msgstr "" + +#: editor/editor_node.cpp +msgid "Changes may be lost!" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp +#: editor/project_manager.cpp +msgid "Import" +msgstr "" + +#: editor/editor_node.cpp +msgid "Node" +msgstr "" + +#: editor/editor_node.cpp +msgid "FileSystem" +msgstr "" + +#: editor/editor_node.cpp +msgid "Output" +msgstr "" + +#: editor/editor_node.cpp +msgid "Don't Save" +msgstr "" + +#: editor/editor_node.cpp +msgid "Import Templates From ZIP File" +msgstr "" + +#: editor/editor_node.cpp editor/project_export.cpp +msgid "Export Project" +msgstr "" + +#: editor/editor_node.cpp +msgid "Export Library" +msgstr "" + +#: editor/editor_node.cpp +msgid "Merge With Existing" +msgstr "" + +#: editor/editor_node.cpp +msgid "Password:" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open & Run a Script" +msgstr "" + +#: editor/editor_node.cpp +msgid "New Inherited" +msgstr "" + +#: editor/editor_node.cpp +msgid "Load Errors" +msgstr "" + +#: editor/editor_node.cpp editor/plugins/tile_map_editor_plugin.cpp +msgid "Select" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open 2D Editor" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open 3D Editor" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open Script Editor" +msgstr "" + +#: editor/editor_node.cpp editor/project_manager.cpp +msgid "Open Asset Library" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open the next Editor" +msgstr "" + +#: editor/editor_node.cpp +msgid "Open the previous Editor" +msgstr "" + +#: editor/editor_plugin.cpp +msgid "Creating Mesh Previews" +msgstr "" + +#: editor/editor_plugin.cpp +msgid "Thumbnail..." +msgstr "" + +#: editor/editor_plugin_settings.cpp +msgid "Installed Plugins:" +msgstr "" + +#: editor/editor_plugin_settings.cpp +msgid "Update" +msgstr "" + +#: editor/editor_plugin_settings.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Version:" +msgstr "" + +#: editor/editor_plugin_settings.cpp +msgid "Author:" +msgstr "" + +#: editor/editor_plugin_settings.cpp +msgid "Status:" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Stop Profiling" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Start Profiling" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Measure:" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Frame Time (sec)" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Average Time (sec)" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Frame %" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Physics Frame %" +msgstr "" + +#: editor/editor_profiler.cpp editor/script_editor_debugger.cpp +msgid "Time:" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Inclusive" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Self" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Frame #:" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Time" +msgstr "" + +#: editor/editor_profiler.cpp +msgid "Calls" +msgstr "" + +#: editor/editor_run_native.cpp +msgid "Select device from the list" +msgstr "" + +#: editor/editor_run_native.cpp +msgid "" +"No runnable export preset found for this platform.\n" +"Please add a runnable preset in the export menu." +msgstr "" + +#: editor/editor_run_script.cpp +msgid "Write your logic in the _run() method." +msgstr "" + +#: editor/editor_run_script.cpp +msgid "There is an edited scene already." +msgstr "" + +#: editor/editor_run_script.cpp +msgid "Couldn't instance script:" +msgstr "" + +#: editor/editor_run_script.cpp +msgid "Did you forget the 'tool' keyword?" +msgstr "" + +#: editor/editor_run_script.cpp +msgid "Couldn't run script:" +msgstr "" + +#: editor/editor_run_script.cpp +msgid "Did you forget the '_run' method?" +msgstr "" + +#: editor/editor_settings.cpp +msgid "Default (Same as Editor)" +msgstr "" + +#: editor/editor_sub_scene.cpp +msgid "Select Node(s) to Import" +msgstr "" + +#: editor/editor_sub_scene.cpp +msgid "Scene Path:" +msgstr "" + +#: editor/editor_sub_scene.cpp +msgid "Import From Node:" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Re-Download" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Uninstall" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "(Installed)" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Download" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "(Missing)" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "(Current)" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Retrieving mirrors, please wait..." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Remove template version '%s'?" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Can't open export templates zip." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Invalid version.txt format inside templates." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "No version.txt found inside templates." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Error creating path for templates:" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Extracting Export Templates" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Importing:" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "" +"No download links found for this version. Direct download is only available " +"for official releases." +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Can't resolve." +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Can't connect." +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "No response." +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Request Failed." +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Redirect Loop." +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Failed:" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Download Complete." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Error requesting url: " +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Connecting to Mirror..." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Disconnected" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Resolving" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Can't Resolve" +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Connecting..." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Can't Connect" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Connected" +msgstr "" + +#: editor/export_template_manager.cpp +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Requesting..." +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Downloading" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Connection Error" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "SSL Handshake Error" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Current Version:" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Installed Versions:" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Install From File" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Remove Template" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Select template file" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Export Template Manager" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Download Templates" +msgstr "" + +#: editor/export_template_manager.cpp +msgid "Select mirror from list: " +msgstr "" + +#: editor/file_type_cache.cpp +msgid "Can't open file_type_cache.cch for writing, not saving file type cache!" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Cannot navigate to '%s' as it has not been found in the file system!" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "View items as a grid of thumbnails" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "View items as a list" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Status: Import of file failed. Please fix file and reimport manually." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Cannot move/rename resources root." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Cannot move a folder into itself." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Error moving:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Error duplicating:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Unable to update dependencies:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "No name provided" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Provided name contains invalid characters" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "No name provided." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Name contains invalid characters." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "A file or folder with this name already exists." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Renaming file:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Renaming folder:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Duplicating file:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Duplicating folder:" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Expand all" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Collapse all" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Rename..." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Move To..." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Open Scene(s)" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Instance" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Edit Dependencies..." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "View Owners..." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Duplicate..." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Previous Directory" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Next Directory" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Re-Scan Filesystem" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Toggle folder status as Favorite" +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Instance the selected scene(s) as child of the selected node." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "" +"Scanning Files,\n" +"Please Wait..." +msgstr "" + +#: editor/filesystem_dock.cpp +msgid "Move" +msgstr "" + +#: editor/filesystem_dock.cpp editor/plugins/animation_tree_editor_plugin.cpp +#: editor/project_manager.cpp +msgid "Rename" +msgstr "" + +#: editor/groups_editor.cpp +msgid "Add to Group" +msgstr "" + +#: editor/groups_editor.cpp +msgid "Remove from Group" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import as Single Scene" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Animations" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Materials" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Objects" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Objects+Materials" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Objects+Animations" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Materials+Animations" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import with Separate Objects+Materials+Animations" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import as Multiple Scenes" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Import as Multiple Scenes+Materials" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +#: editor/plugins/cube_grid_theme_editor_plugin.cpp +msgid "Import Scene" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Importing Scene..." +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Generating Lightmaps" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Generating for Mesh: " +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Running Custom Script..." +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Couldn't load post-import script:" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Invalid/broken script for post-import (check console):" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Error running post-import script:" +msgstr "" + +#: editor/import/resource_importer_scene.cpp +msgid "Saving..." +msgstr "" + +#: editor/import_dock.cpp +msgid "Set as Default for '%s'" +msgstr "" + +#: editor/import_dock.cpp +msgid "Clear Default for '%s'" +msgstr "" + +#: editor/import_dock.cpp +msgid " Files" +msgstr "" + +#: editor/import_dock.cpp +msgid "Import As:" +msgstr "" + +#: editor/import_dock.cpp editor/property_editor.cpp +msgid "Preset..." +msgstr "" + +#: editor/import_dock.cpp +msgid "Reimport" +msgstr "" + +#: editor/multi_node_edit.cpp +msgid "MultiNode Set" +msgstr "" + +#: editor/node_dock.cpp +msgid "Groups" +msgstr "" + +#: editor/node_dock.cpp +msgid "Select a Node to edit Signals and Groups." +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Create Poly" +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +#: editor/plugins/collision_polygon_editor_plugin.cpp +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Edit Poly" +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +msgid "Insert Point" +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +#: editor/plugins/collision_polygon_editor_plugin.cpp +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Edit Poly (Remove Point)" +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +msgid "Remove Poly And Point" +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +msgid "Create a new polygon from scratch" +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +msgid "" +"Edit existing polygon:\n" +"LMB: Move Point.\n" +"Ctrl+LMB: Split Segment.\n" +"RMB: Erase Point." +msgstr "" + +#: editor/plugins/abstract_polygon_2d_editor.cpp +msgid "Delete points" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Toggle Autoplay" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "New Animation Name:" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "New Anim" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Change Animation Name:" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Delete Animation?" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Remove Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "ERROR: Invalid animation name!" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "ERROR: Animation name already exists!" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Rename Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Add Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Blend Next Changed" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Change Blend Time" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Load Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Duplicate Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "ERROR: No animation to copy!" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "ERROR: No animation resource on clipboard!" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Pasted Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Paste Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "ERROR: No animation to edit!" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Play selected animation backwards from current pos. (A)" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Play selected animation backwards from end. (Shift+A)" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Stop animation playback. (S)" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Play selected animation from start. (Shift+D)" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Play selected animation from current pos. (D)" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Animation position (in seconds)." +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Scale animation playback globally for the node." +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Create new animation in player." +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Load animation from disk." +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Load an animation from disk." +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Save the current animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Display list of animations in player." +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Autoplay on Load" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Edit Target Blend Times" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Animation Tools" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Copy Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Onion Skinning" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Enable Onion Skinning" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Directions" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Past" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Future" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Depth" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "1 step" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "2 steps" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "3 steps" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Differences Only" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Force White Modulate" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Include Gizmos (3D)" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Create New Animation" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Animation Name:" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp editor/property_editor.cpp +#: editor/script_create_dialog.cpp +msgid "Error!" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Blend Times:" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Next (Auto Queue):" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +msgid "Cross-Animation Blend Times" +msgstr "" + +#: editor/plugins/animation_player_editor_plugin.cpp +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Animation" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "New name:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Edit Filters" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Scale:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Fade In (s):" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Fade Out (s):" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Mix" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Auto Restart:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Restart (s):" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Random Restart (s):" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Start!" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Amount:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend 0:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend 1:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "X-Fade Time (s):" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Current:" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Add Input" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Clear Auto-Advance" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Set Auto-Advance" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Delete Input" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Animation tree is valid." +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Animation tree is invalid." +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Animation Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "OneShot Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Mix Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend2 Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend3 Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Blend4 Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "TimeScale Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "TimeSeek Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Transition Node" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Import Animations..." +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Edit Node Filters" +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "Filters..." +msgstr "" + +#: editor/plugins/animation_tree_editor_plugin.cpp +msgid "AnimationTree" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Free" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Contents:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "View Files" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Can't resolve hostname:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Connection error, please try again." +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Can't connect to host:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "No response from host:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Request failed, return code:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Request failed, too many redirects" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Bad download hash, assuming file has been tampered with." +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Expected:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Got:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Failed sha256 hash check" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Asset Download Error:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Fetching:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Resolving..." +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Error making request" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Idle" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Retry" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Download Error" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Download for this asset is already in progress!" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "first" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "prev" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "next" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "last" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "All" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +#: editor/project_settings_editor.cpp +msgid "Plugins" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Sort:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Reverse" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +#: editor/project_settings_editor.cpp +msgid "Category:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Site:" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Support..." +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Official" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Testing" +msgstr "" + +#: editor/plugins/asset_library_editor_plugin.cpp +msgid "Assets ZIP File" +msgstr "" + +#: editor/plugins/baked_lightmap_editor_plugin.cpp +msgid "" +"Can't determine a save path for lightmap images.\n" +"Save your scene (for images to be saved in the same dir), or pick a save " +"path from the BakedLightmap properties." +msgstr "" + +#: editor/plugins/baked_lightmap_editor_plugin.cpp +msgid "" +"No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake " +"Light' flag is on." +msgstr "" + +#: editor/plugins/baked_lightmap_editor_plugin.cpp +msgid "Failed creating lightmap images, make sure path is writable." +msgstr "" + +#: editor/plugins/baked_lightmap_editor_plugin.cpp +msgid "Bake Lightmaps" +msgstr "" + +#: editor/plugins/camera_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Preview" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Configure Snap" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Grid Offset:" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Grid Step:" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Rotation Offset:" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Rotation Step:" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Move Pivot" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Move Action" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Move vertical guide" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Create new vertical guide" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Remove vertical guide" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Move horizontal guide" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Create new horizontal guide" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Remove horizontal guide" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Create new horizontal and vertical guides" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Edit IK Chain" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Edit CanvasItem" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Anchors only" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Change Anchors and Margins" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Change Anchors" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Paste Pose" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Select Mode" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Drag: Rotate" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Alt+Drag: Move" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Press 'v' to Change Pivot, 'Shift+v' to Drag Pivot (while moving)." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Alt+RMB: Depth list selection" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Move Mode" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Rotate Mode" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "" +"Show a list of all objects at the position clicked\n" +"(same as Alt+RMB in select mode)." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Click to change object's rotation pivot." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Pan Mode" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Toggles snapping" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Use Snap" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snapping options" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap to grid" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Use Rotation Snap" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Configure Snap..." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap Relative" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Use Pixel Snap" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Smart snapping" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap to parent" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap to node anchor" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap to node sides" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap to other nodes" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Snap to guides" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Lock the selected object in place (can't be moved)." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Unlock the selected object (can be moved)." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Makes sure the object's children are not selectable." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Restores the object's children's ability to be selected." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Make Bones" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Clear Bones" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Show Bones" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Make IK Chain" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Clear IK Chain" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Show Grid" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Show Helpers" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Show Rulers" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Show Guides" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Show Origin" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Show Viewport" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Center Selection" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Frame Selection" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Layout" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Insert Keys" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Insert Key" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Insert Key (Existing Tracks)" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Copy Pose" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Clear Pose" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Drag pivot from mouse position" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Set pivot at mouse position" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Multiply grid step by 2" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Divide grid step by 2" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Add %s" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Adding %s..." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "Ok" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Cannot instantiate multiple nodes without root." +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "Create Node" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "Error instancing scene from %s" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "Change default type" +msgstr "" + +#: editor/plugins/canvas_item_editor_plugin.cpp +msgid "" +"Drag & drop + Shift : Add node as sibling\n" +"Drag & drop + Alt : Change node type" +msgstr "" + +#: editor/plugins/collision_polygon_editor_plugin.cpp +msgid "Create Poly3D" +msgstr "" + +#: editor/plugins/collision_shape_2d_editor_plugin.cpp +msgid "Set Handle" +msgstr "" + +#: editor/plugins/cube_grid_theme_editor_plugin.cpp +msgid "Remove item %d?" +msgstr "" + +#: editor/plugins/cube_grid_theme_editor_plugin.cpp +#: editor/plugins/theme_editor_plugin.cpp +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Add Item" +msgstr "" + +#: editor/plugins/cube_grid_theme_editor_plugin.cpp +msgid "Remove Selected Item" +msgstr "" + +#: editor/plugins/cube_grid_theme_editor_plugin.cpp +msgid "Import from Scene" +msgstr "" + +#: editor/plugins/cube_grid_theme_editor_plugin.cpp +msgid "Update from Scene" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Flat0" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Flat1" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Ease in" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Ease out" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Smoothstep" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Modify Curve Point" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Modify Curve Tangent" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Load Curve Preset" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Add point" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Remove point" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Left linear" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Right linear" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Load preset" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Remove Curve Point" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Toggle Curve Linear Tangent" +msgstr "" + +#: editor/plugins/curve_editor_plugin.cpp +msgid "Hold Shift to edit tangents individually" +msgstr "" + +#: editor/plugins/gi_probe_editor_plugin.cpp +msgid "Bake GI Probe" +msgstr "" + +#: editor/plugins/gradient_editor_plugin.cpp +msgid "Add/Remove Color Ramp Point" +msgstr "" + +#: editor/plugins/gradient_editor_plugin.cpp +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Modify Color Ramp" +msgstr "" + +#: editor/plugins/item_list_editor_plugin.cpp +msgid "Item %d" +msgstr "" + +#: editor/plugins/item_list_editor_plugin.cpp +msgid "Items" +msgstr "" + +#: editor/plugins/item_list_editor_plugin.cpp +msgid "Item List Editor" +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "" +"No OccluderPolygon2D resource on this node.\n" +"Create and assign one?" +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Create Occluder Polygon" +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Create a new polygon from scratch." +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Edit existing polygon:" +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "LMB: Move Point." +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "Ctrl+LMB: Split Segment." +msgstr "" + +#: editor/plugins/light_occluder_2d_editor_plugin.cpp +msgid "RMB: Erase Point." +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Mesh is empty!" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Static Trimesh Body" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Static Convex Body" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "This doesn't work on scene root!" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Trimesh Shape" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Convex Shape" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Navigation Mesh" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Contained Mesh is not of type ArrayMesh." +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "UV Unwrap failed, mesh may not be manifold?" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "No mesh to debug." +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Model has no UV in this layer" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "MeshInstance lacks a Mesh!" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Mesh has not surface to create outlines from!" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Could not create outline!" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Outline" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Mesh" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Trimesh Static Body" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Convex Static Body" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Trimesh Collision Sibling" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Convex Collision Sibling" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Outline Mesh..." +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "View UV1" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "View UV2" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Unwrap UV2 for Lightmap/AO" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Create Outline Mesh" +msgstr "" + +#: editor/plugins/mesh_instance_editor_plugin.cpp +msgid "Outline Size:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "No mesh source specified (and no MultiMesh set in node)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "No mesh source specified (and MultiMesh contains no Mesh)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Mesh source is invalid (invalid path)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Mesh source is invalid (not a MeshInstance)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Mesh source is invalid (contains no Mesh resource)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "No surface source specified." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Surface source is invalid (invalid path)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Surface source is invalid (no geometry)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Surface source is invalid (no faces)." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Parent has no solid faces to populate." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Couldn't map area." +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Select a Source Mesh:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Select a Target Surface:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Populate Surface" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Populate MultiMesh" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Target Surface:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Source Mesh:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "X-Axis" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Y-Axis" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Z-Axis" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Mesh Up Axis:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Random Rotation:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Random Tilt:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Random Scale:" +msgstr "" + +#: editor/plugins/multimesh_editor_plugin.cpp +msgid "Populate" +msgstr "" + +#: editor/plugins/navigation_mesh_editor_plugin.cpp +msgid "Bake!" +msgstr "" + +#: editor/plugins/navigation_mesh_editor_plugin.cpp +msgid "Bake the navigation mesh." +msgstr "" + +#: editor/plugins/navigation_mesh_editor_plugin.cpp +msgid "Clear the navigation mesh." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Setting up Configuration..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Calculating grid size..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Creating heightfield..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Marking walkable triangles..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Constructing compact heightfield..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Eroding walkable area..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Partitioning..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Creating contours..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Creating polymesh..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Converting to native navigation mesh..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Navigation Mesh Generator Setup:" +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Parsing Geometry..." +msgstr "" + +#: editor/plugins/navigation_mesh_generator.cpp +msgid "Done!" +msgstr "" + +#: editor/plugins/navigation_polygon_editor_plugin.cpp +msgid "Create Navigation Polygon" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +#: editor/plugins/particles_editor_plugin.cpp +msgid "Generating AABB" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Can only set point into a ParticlesMaterial process material" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Error loading image:" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "No pixels with transparency > 128 in image..." +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Generate Visibility Rect" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Load Emission Mask" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Clear Emission Mask" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +#: editor/plugins/particles_editor_plugin.cpp +msgid "Particles" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Generated Point Count:" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +#: editor/plugins/particles_editor_plugin.cpp +msgid "Generation Time (sec):" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Emission Mask" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Capture from Pixel" +msgstr "" + +#: editor/plugins/particles_2d_editor_plugin.cpp +msgid "Emission Colors" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Node does not contain geometry." +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Node does not contain geometry (faces)." +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "A processor material of type 'ParticlesMaterial' is required." +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Faces contain no area!" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "No faces!" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Generate AABB" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Create Emission Points From Mesh" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Create Emission Points From Node" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Create Emitter" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Emission Points:" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Surface Points" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Surface Points+Normal (Directed)" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Volume" +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Emission Source: " +msgstr "" + +#: editor/plugins/particles_editor_plugin.cpp +msgid "Generate Visibility AABB" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Remove Point from Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Remove Out-Control from Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Remove In-Control from Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Add Point to Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Move Point in Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Move In-Control in Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Move Out-Control in Curve" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Select Points" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Shift+Drag: Select Control Points" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Click: Add Point" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Right Click: Delete Point" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +msgid "Select Control Points (Shift+Drag)" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Add Point (in empty space)" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Split Segment (in curve)" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Delete Point" +msgstr "" + +#: editor/plugins/path_2d_editor_plugin.cpp +#: editor/plugins/path_editor_plugin.cpp +msgid "Close Curve" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Curve Point #" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Set Curve Point Position" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Set Curve In Position" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Set Curve Out Position" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Split Path" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Remove Path Point" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Remove Out-Control Point" +msgstr "" + +#: editor/plugins/path_editor_plugin.cpp +msgid "Remove In-Control Point" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Create UV Map" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Transform UV Map" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Polygon 2D UV Editor" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Move Point" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Ctrl: Rotate" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Shift: Move All" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Shift+Ctrl: Scale" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Move Polygon" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Rotate Polygon" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Scale Polygon" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp +#: editor/plugins/shader_editor_plugin.cpp editor/project_manager.cpp +#: editor/project_settings_editor.cpp editor/property_editor.cpp +#: modules/visual_script/visual_script_editor.cpp +msgid "Edit" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Polygon->UV" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "UV->Polygon" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Clear UV" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Snap" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Enable Snap" +msgstr "" + +#: editor/plugins/polygon_2d_editor_plugin.cpp +msgid "Grid" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +msgid "ERROR: Couldn't load resource!" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +msgid "Add Resource" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +msgid "Rename Resource" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Delete Resource" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +msgid "Resource clipboard is empty!" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/scene_tree_dock.cpp editor/scene_tree_editor.cpp +msgid "Open in Editor" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/scene_tree_editor.cpp +msgid "Instance:" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/plugins/theme_editor_plugin.cpp editor/project_settings_editor.cpp +#: editor/scene_tree_editor.cpp editor/script_editor_debugger.cpp +msgid "Type:" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Load Resource" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp editor/property_editor.cpp +#: scene/gui/line_edit.cpp scene/gui/text_edit.cpp +msgid "Paste" +msgstr "" + +#: editor/plugins/resource_preloader_editor_plugin.cpp +msgid "ResourcePreloader" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Clear Recent Files" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Close and save changes?" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Error while saving theme" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Error saving" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Error importing theme" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Error importing" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Import Theme" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Save Theme As..." +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid " Class Reference" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Sort" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp editor/scene_tree_dock.cpp +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Move Up" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp editor/scene_tree_dock.cpp +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Move Down" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Next script" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Previous script" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "File" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "New" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Save All" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Soft Reload Script" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Copy Script Path" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Show In File System" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "History Prev" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "History Next" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Reload Theme" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Save Theme" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Save Theme As" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Close Docs" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Close All" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Close Other Tabs" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp editor/project_manager.cpp +msgid "Run" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Toggle Scripts Panel" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp +msgid "Find..." +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +#: editor/plugins/script_text_editor.cpp +msgid "Find Next" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp +msgid "Step Over" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp +msgid "Step Into" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp +msgid "Break" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp editor/project_manager.cpp +#: editor/script_editor_debugger.cpp +msgid "Continue" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Keep Debugger Open" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Debug with external editor" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Open Godot online documentation" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Search the class hierarchy." +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Search the reference documentation." +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Go to previous edited document." +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Go to next edited document." +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Discard" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Create Script" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "" +"The following files are newer on disk.\n" +"What action should be taken?:" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Reload" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "Resave" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp editor/script_editor_debugger.cpp +msgid "Debugger" +msgstr "" + +#: editor/plugins/script_editor_plugin.cpp +msgid "" +"Built-in scripts can only be edited when the scene they belong to is loaded" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Only resources from filesystem can be dropped." +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Pick Color" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Convert Case" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Uppercase" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Lowercase" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Capitalize" +msgstr "" + +#: editor/plugins/script_text_editor.cpp scene/gui/line_edit.cpp +#: scene/gui/text_edit.cpp +msgid "Cut" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +#: editor/plugins/sprite_frames_editor_plugin.cpp editor/property_editor.cpp +#: scene/gui/line_edit.cpp scene/gui/text_edit.cpp +msgid "Copy" +msgstr "" + +#: editor/plugins/script_text_editor.cpp scene/gui/line_edit.cpp +#: scene/gui/text_edit.cpp +msgid "Select All" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Delete Line" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Indent Left" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Indent Right" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Toggle Comment" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Fold/Unfold Line" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Fold All Lines" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Unfold All Lines" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Clone Down" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Complete Symbol" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Trim Trailing Whitespace" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Convert Indent To Spaces" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Convert Indent To Tabs" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Auto Indent" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +#: modules/visual_script/visual_script_editor.cpp +msgid "Toggle Breakpoint" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Remove All Breakpoints" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Goto Next Breakpoint" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Goto Previous Breakpoint" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Convert To Uppercase" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Convert To Lowercase" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Find Previous" +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Replace..." +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Goto Function..." +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Goto Line..." +msgstr "" + +#: editor/plugins/script_text_editor.cpp +msgid "Contextual Help" +msgstr "" + +#: editor/plugins/shader_editor_plugin.cpp +msgid "Shader" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Scalar Constant" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Vec Constant" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change RGB Constant" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Scalar Operator" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Vec Operator" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Vec Scalar Operator" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change RGB Operator" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Toggle Rot Only" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Scalar Function" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Vec Function" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Scalar Uniform" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Vec Uniform" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change RGB Uniform" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Default Value" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change XForm Uniform" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Texture Uniform" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Cubemap Uniform" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Comment" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Add/Remove to Color Ramp" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Add/Remove to Curve Map" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Modify Curve Map" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Change Input Name" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Connect Graph Nodes" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Disconnect Graph Nodes" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Remove Shader Graph Node" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Move Shader Graph Node" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Duplicate Graph Node(s)" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Delete Shader Graph Node(s)" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Error: Cyclic Connection Link" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Error: Missing Input Connections" +msgstr "" + +#: editor/plugins/shader_graph_editor_plugin.cpp +msgid "Add Shader Graph Node" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Orthogonal" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Perspective" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Transform Aborted." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "X-Axis Transform." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Y-Axis Transform." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Z-Axis Transform." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Plane Transform." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Scaling: " +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Translating: " +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rotating %s degrees." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Keying is disabled (no key inserted)." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Animation Key Inserted." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Objects Drawn" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Material Changes" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Shader Changes" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Surface Changes" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Draw Calls" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Vertices" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "FPS" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Top View." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Bottom View." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Bottom" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Left View." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Left" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Right View." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Right" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Front View." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Front" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rear View." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rear" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Align with view" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "OK :(" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "No parent to instance a child at." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp +msgid "This operation requires a single selected node." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Display Normal" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Display Wireframe" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Display Overdraw" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Display Unshaded" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Environment" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Gizmos" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Information" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View FPS" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Half Resolution" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Audio Listener" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Doppler Enable" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Left" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Right" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Forward" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Backwards" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Up" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Down" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Freelook Speed Modifier" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "XForm Dialog" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Select Mode (Q)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "" +"Drag: Rotate\n" +"Alt+Drag: Move\n" +"Alt+RMB: Depth list selection" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Move Mode (W)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rotate Mode (E)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Scale Mode (R)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Local Coords" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Local Space Mode (%s)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Snap Mode (%s)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Bottom View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Top View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rear View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Front View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Left View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Right View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Switch Perspective/Orthogonal view" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Insert Animation Key" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Focus Origin" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Focus Selection" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Align Selection With View" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Tool Select" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Tool Move" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Tool Rotate" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Tool Scale" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Toggle Freelook" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Transform" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Transform Dialog..." +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "1 Viewport" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "2 Viewports" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "2 Viewports (Alt)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "3 Viewports" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "3 Viewports (Alt)" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "4 Viewports" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Origin" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Grid" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Settings" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Skeleton Gizmo visibility" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Snap Settings" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Translate Snap:" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rotate Snap (deg.):" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Scale Snap (%):" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Viewport Settings" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Perspective FOV (deg.):" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Z-Near:" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "View Z-Far:" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Transform Change" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Translate:" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Rotate (deg.):" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Scale (ratio):" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Transform Type" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Pre" +msgstr "" + +#: editor/plugins/spatial_editor_plugin.cpp +msgid "Post" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "ERROR: Couldn't load frame resource!" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Add Frame" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Resource clipboard is empty or not a texture!" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Paste Frame" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Add Empty" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Change Animation Loop" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Change Animation FPS" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "(empty)" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Animations" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Speed (FPS):" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Loop" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Animation Frames" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Insert Empty (Before)" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Insert Empty (After)" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Move (Before)" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "Move (After)" +msgstr "" + +#: editor/plugins/sprite_frames_editor_plugin.cpp +msgid "SpriteFrames" +msgstr "" + +#: editor/plugins/style_box_editor_plugin.cpp +msgid "StyleBox Preview:" +msgstr "" + +#: editor/plugins/style_box_editor_plugin.cpp +msgid "StyleBox" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Set Region Rect" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Snap Mode:" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "<None>" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Pixel Snap" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Grid Snap" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Auto Slice" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Offset:" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Step:" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Separation:" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Texture Region" +msgstr "" + +#: editor/plugins/texture_region_editor_plugin.cpp +msgid "Texture Region Editor" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Can't save theme to file:" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Add All Items" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Add All" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Remove Item" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Remove All Items" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Remove All" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Edit theme..." +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Theme editing menu." +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Add Class Items" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Remove Class Items" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Create Empty Template" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Create Empty Editor Template" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Create From Current Editor Theme" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "CheckBox Radio1" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "CheckBox Radio2" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Item" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Check Item" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Checked Item" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Radio Item" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Checked Radio Item" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Has" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Many" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp +msgid "Options" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Has,Many,Options" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Tab 1" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Tab 2" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Tab 3" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Data Type:" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Icon" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Style" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Font" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Color" +msgstr "" + +#: editor/plugins/theme_editor_plugin.cpp +msgid "Theme" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Erase Selection" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Paint TileMap" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Line Draw" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Rectangle Paint" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Bucket Fill" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Erase TileMap" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Erase selection" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Find tile" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Transpose" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Mirror X" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Mirror Y" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Paint Tile" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Pick Tile" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Rotate 0 degrees" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Rotate 90 degrees" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Rotate 180 degrees" +msgstr "" + +#: editor/plugins/tile_map_editor_plugin.cpp +msgid "Rotate 270 degrees" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Could not find tile:" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Item name or ID:" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Create from scene?" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Merge from scene?" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Tile Set" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Create from Scene" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Merge from Scene" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp editor/script_editor_debugger.cpp +msgid "Error" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Autotiles" +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "" +"Select sub-tile to use as icon, this will be also used on invalid autotile " +"bindings." +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "" +"LMB: set bit on.\n" +"RMB: set bit off." +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Select current edited sub-tile." +msgstr "" + +#: editor/plugins/tile_set_editor_plugin.cpp +msgid "Select sub-tile to change its priority." +msgstr "" + +#: editor/progress_dialog.cpp scene/gui/dialogs.cpp +msgid "Cancel" +msgstr "" + +#: editor/project_export.cpp +msgid "Runnable" +msgstr "" + +#: editor/project_export.cpp +msgid "Delete patch '%s' from list?" +msgstr "" + +#: editor/project_export.cpp +msgid "Delete preset '%s'?" +msgstr "" + +#: editor/project_export.cpp +msgid "Export templates for this platform are missing/corrupted: " +msgstr "" + +#: editor/project_export.cpp +msgid "Presets" +msgstr "" + +#: editor/project_export.cpp editor/project_settings_editor.cpp +msgid "Add..." +msgstr "" + +#: editor/project_export.cpp +msgid "Resources" +msgstr "" + +#: editor/project_export.cpp +msgid "Export all resources in the project" +msgstr "" + +#: editor/project_export.cpp +msgid "Export selected scenes (and dependencies)" +msgstr "" + +#: editor/project_export.cpp +msgid "Export selected resources (and dependencies)" +msgstr "" + +#: editor/project_export.cpp +msgid "Export Mode:" +msgstr "" + +#: editor/project_export.cpp +msgid "Resources to export:" +msgstr "" + +#: editor/project_export.cpp +msgid "" +"Filters to export non-resource files (comma separated, e.g: *.json, *.txt)" +msgstr "" + +#: editor/project_export.cpp +msgid "" +"Filters to exclude files from project (comma separated, e.g: *.json, *.txt)" +msgstr "" + +#: editor/project_export.cpp +msgid "Patches" +msgstr "" + +#: editor/project_export.cpp +msgid "Make Patch" +msgstr "" + +#: editor/project_export.cpp +msgid "Features" +msgstr "" + +#: editor/project_export.cpp +msgid "Custom (comma-separated):" +msgstr "" + +#: editor/project_export.cpp +msgid "Feature List:" +msgstr "" + +#: editor/project_export.cpp +msgid "Export PCK/Zip" +msgstr "" + +#: editor/project_export.cpp +msgid "Export templates for this platform are missing:" +msgstr "" + +#: editor/project_export.cpp +msgid "Export templates for this platform are missing/corrupted:" +msgstr "" + +#: editor/project_export.cpp +msgid "Export With Debug" +msgstr "" + +#: editor/project_manager.cpp +msgid "The path does not exist." +msgstr "" + +#: editor/project_manager.cpp +msgid "Please choose a 'project.godot' file." +msgstr "" + +#: editor/project_manager.cpp +msgid "Please choose an empty folder." +msgstr "" + +#: editor/project_manager.cpp +msgid "Imported Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp +msgid "Couldn't create folder." +msgstr "" + +#: editor/project_manager.cpp +msgid "There is already a folder in this path with the specified name." +msgstr "" + +#: editor/project_manager.cpp +msgid "It would be a good idea to name your project." +msgstr "" + +#: editor/project_manager.cpp +msgid "Invalid project path (changed anything?)." +msgstr "" + +#: editor/project_manager.cpp +msgid "" +"Couldn't load project.godot in project path (error %d). It may be missing or " +"corrupted." +msgstr "" + +#: editor/project_manager.cpp +msgid "Couldn't edit project.godot in project path." +msgstr "" + +#: editor/project_manager.cpp +msgid "Couldn't create project.godot in project path." +msgstr "" + +#: editor/project_manager.cpp +msgid "The following files failed extraction from package:" +msgstr "" + +#: editor/project_manager.cpp +msgid "Rename Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "New Game Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Import Existing Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Import & Edit" +msgstr "" + +#: editor/project_manager.cpp +msgid "Create New Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Create & Edit" +msgstr "" + +#: editor/project_manager.cpp +msgid "Install Project:" +msgstr "" + +#: editor/project_manager.cpp +msgid "Install & Edit" +msgstr "" + +#: editor/project_manager.cpp +msgid "Project Name:" +msgstr "" + +#: editor/project_manager.cpp +msgid "Create folder" +msgstr "" + +#: editor/project_manager.cpp +msgid "Project Path:" +msgstr "" + +#: editor/project_manager.cpp +msgid "Browse" +msgstr "" + +#: editor/project_manager.cpp +msgid "Unnamed Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Can't open project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Are you sure to open more than one project?" +msgstr "" + +#: editor/project_manager.cpp +msgid "" +"Can't run project: no main scene defined.\n" +"Please edit the project and set the main scene in \"Project Settings\" under " +"the \"Application\" category." +msgstr "" + +#: editor/project_manager.cpp +msgid "" +"Can't run project: Assets need to be imported.\n" +"Please edit the project to trigger the initial import." +msgstr "" + +#: editor/project_manager.cpp +msgid "Are you sure to run more than one project?" +msgstr "" + +#: editor/project_manager.cpp +msgid "Remove project from the list? (Folder contents will not be modified)" +msgstr "" + +#: editor/project_manager.cpp +msgid "" +"Language changed.\n" +"The UI will update next time the editor or project manager starts." +msgstr "" + +#: editor/project_manager.cpp +msgid "" +"You are about the scan %s folders for existing Godot projects. Do you " +"confirm?" +msgstr "" + +#: editor/project_manager.cpp +msgid "Project Manager" +msgstr "" + +#: editor/project_manager.cpp +msgid "Project List" +msgstr "" + +#: editor/project_manager.cpp +msgid "Scan" +msgstr "" + +#: editor/project_manager.cpp +msgid "Select a Folder to Scan" +msgstr "" + +#: editor/project_manager.cpp +msgid "New Project" +msgstr "" + +#: editor/project_manager.cpp +msgid "Templates" +msgstr "" + +#: editor/project_manager.cpp +msgid "Exit" +msgstr "" + +#: editor/project_manager.cpp +msgid "Restart Now" +msgstr "" + +#: editor/project_manager.cpp +msgid "Can't run project" +msgstr "" + +#: editor/project_manager.cpp +msgid "" +"You don't currently have any projects.\n" +"Would you like to explore the official example projects in the Asset Library?" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Key " +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Joy Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Joy Axis" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Mouse Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Action '%s' already exists!" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Rename Input Action Event" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Add Input Action Event" +msgstr "" + +#: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp +msgid "Shift+" +msgstr "" + +#: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp +msgid "Alt+" +msgstr "" + +#: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp +msgid "Control+" +msgstr "" + +#: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp +msgid "Press a Key..." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Mouse Button Index:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Left Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Right Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Middle Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Wheel Up Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Wheel Down Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Button 6" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Button 7" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Button 8" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Button 9" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Joypad Axis Index:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Axis" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Joypad Button Index:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Erase Input Action" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Erase Input Action Event" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Add Event" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Device" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Button" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Left Button." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Right Button." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Middle Button." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Wheel Up." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Wheel Down." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Add Global Property" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Select a setting item first!" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "No property '%s' exists." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Setting '%s' is internal, and it can't be deleted." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Delete Item" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Already existing" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Add Input Action" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Error saving settings." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Settings saved OK." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Override for Feature" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Add Translation" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Remove Translation" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Add Remapped Path" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Resource Remap Add Remap" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Change Resource Remap Language" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Remove Resource Remap" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Remove Resource Remap Option" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Changed Locale Filter" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Changed Locale Filter Mode" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Project Settings (project.godot)" +msgstr "" + +#: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp +msgid "General" +msgstr "" + +#: editor/project_settings_editor.cpp editor/property_editor.cpp +msgid "Property:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Override For..." +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Input Map" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Action:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Device:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Index:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Localization" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Translations" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Translations:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Remaps" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Resources:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Remaps by Locale:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Locale" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Locales Filter" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Show all locales" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Show only selected locales" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Filter mode:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "Locales:" +msgstr "" + +#: editor/project_settings_editor.cpp +msgid "AutoLoad" +msgstr "" + +#: editor/property_editor.cpp +msgid "Pick a Viewport" +msgstr "" + +#: editor/property_editor.cpp +msgid "Ease In" +msgstr "" + +#: editor/property_editor.cpp +msgid "Ease Out" +msgstr "" + +#: editor/property_editor.cpp +msgid "Zero" +msgstr "" + +#: editor/property_editor.cpp +msgid "Easing In-Out" +msgstr "" + +#: editor/property_editor.cpp +msgid "Easing Out-In" +msgstr "" + +#: editor/property_editor.cpp +msgid "File..." +msgstr "" + +#: editor/property_editor.cpp +msgid "Dir..." +msgstr "" + +#: editor/property_editor.cpp +msgid "Assign" +msgstr "" + +#: editor/property_editor.cpp +msgid "Select Node" +msgstr "" + +#: editor/property_editor.cpp +msgid "New Script" +msgstr "" + +#: editor/property_editor.cpp +msgid "New %s" +msgstr "" + +#: editor/property_editor.cpp +msgid "Make Unique" +msgstr "" + +#: editor/property_editor.cpp +msgid "Show in File System" +msgstr "" + +#: editor/property_editor.cpp +msgid "Convert To %s" +msgstr "" + +#: editor/property_editor.cpp +msgid "Error loading file: Not a resource!" +msgstr "" + +#: editor/property_editor.cpp +msgid "Selected node is not a Viewport!" +msgstr "" + +#: editor/property_editor.cpp +msgid "Pick a Node" +msgstr "" + +#: editor/property_editor.cpp +msgid "Bit %d, val %d." +msgstr "" + +#: editor/property_editor.cpp +msgid "On" +msgstr "" + +#: editor/property_editor.cpp +msgid "[Empty]" +msgstr "" + +#: editor/property_editor.cpp modules/visual_script/visual_script_editor.cpp +msgid "Set" +msgstr "" + +#: editor/property_editor.cpp +msgid "Properties:" +msgstr "" + +#: editor/property_selector.cpp +msgid "Select Property" +msgstr "" + +#: editor/property_selector.cpp +msgid "Select Virtual Method" +msgstr "" + +#: editor/property_selector.cpp +msgid "Select Method" +msgstr "" + +#: editor/pvrtc_compress.cpp +msgid "Could not execute PVRTC tool:" +msgstr "" + +#: editor/pvrtc_compress.cpp +msgid "Can't load back converted image using PVRTC tool:" +msgstr "" + +#: editor/reparent_dialog.cpp editor/scene_tree_dock.cpp +msgid "Reparent Node" +msgstr "" + +#: editor/reparent_dialog.cpp +msgid "Reparent Location (Select new Parent):" +msgstr "" + +#: editor/reparent_dialog.cpp +msgid "Keep Global Transform" +msgstr "" + +#: editor/reparent_dialog.cpp editor/scene_tree_dock.cpp +msgid "Reparent" +msgstr "" + +#: editor/run_settings_dialog.cpp +msgid "Run Mode:" +msgstr "" + +#: editor/run_settings_dialog.cpp +msgid "Current Scene" +msgstr "" + +#: editor/run_settings_dialog.cpp +msgid "Main Scene" +msgstr "" + +#: editor/run_settings_dialog.cpp +msgid "Main Scene Arguments:" +msgstr "" + +#: editor/run_settings_dialog.cpp +msgid "Scene Run Settings" +msgstr "" + +#: editor/scene_tree_dock.cpp editor/script_create_dialog.cpp +#: scene/gui/dialogs.cpp +msgid "OK" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "No parent to instance the scenes at." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Error loading scene from %s" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "" +"Cannot instance the scene '%s' because the current scene exists within one " +"of its nodes." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Instance Scene(s)" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "This operation can't be done on the tree root." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Move Node In Parent" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Move Nodes In Parent" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Duplicate Node(s)" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Delete Node(s)?" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Can not perform with the root node." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "This operation can't be done on instanced scenes." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Save New Scene As..." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Editable Children" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Load As Placeholder" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Discard Instancing" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Makes Sense!" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Can't operate on nodes from a foreign scene!" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Can't operate on nodes the current scene inherits from!" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Remove Node(s)" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "" +"Couldn't save new scene. Likely dependencies (instances) couldn't be " +"satisfied." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Error saving scene." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Error duplicating scene to save it." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Sub-Resources" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Clear Inheritance" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Delete Node(s)" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Add Child Node" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Instance Child Scene" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Change Type" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Attach Script" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Clear Script" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Merge From Scene" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Save Branch as Scene" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Copy Node Path" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Delete (No Confirm)" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Add/Create a New Node" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "" +"Instance a scene file as a Node. Creates an inherited scene if no root node " +"exists." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Filter nodes" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Attach a new or existing script for the selected node." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Clear a script for the selected node." +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Remote" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Local" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Clear Inheritance? (No Undo!)" +msgstr "" + +#: editor/scene_tree_dock.cpp +msgid "Clear!" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Toggle Spatial Visible" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Toggle CanvasItem Visible" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Node configuration warning:" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "" +"Node has connection(s) and group(s)\n" +"Click to show signals dock." +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "" +"Node has connections.\n" +"Click to show signals dock." +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "" +"Node is in group(s).\n" +"Click to show groups dock." +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Open script" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "" +"Node is locked.\n" +"Click to unlock" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "" +"Children are not selectable.\n" +"Click to make selectable" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Toggle Visibility" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Invalid node name, the following characters are not allowed:" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Rename Node" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Scene Tree (Nodes):" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Node Configuration Warning!" +msgstr "" + +#: editor/scene_tree_editor.cpp +msgid "Select a Node" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Error loading template '%s'" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Error - Could not create script in filesystem." +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Error loading script from %s" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "N/A" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Path is empty" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Path is not local" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Invalid base path" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Directory of the same name exists" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "File exists, will be reused" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Invalid extension" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Wrong extension chosen" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Invalid Path" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Invalid class name" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Invalid inherited parent name or path" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Script valid" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Allowed: a-z, A-Z, 0-9 and _" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Built-in script (into scene file)" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Create new script file" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Load existing script file" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Language" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Inherits" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Class Name" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Template" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Built-in Script" +msgstr "" + +#: editor/script_create_dialog.cpp +msgid "Attach Node Script" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Remote " +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Bytes:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Warning" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Error:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Source:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Function:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Pick one or more items from the list to display the graph." +msgstr "" + +#: editor/script_editor_debugger.cpp modules/mono/editor/mono_bottom_panel.cpp +msgid "Errors" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Child Process Connected" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Copy Error" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Inspect Previous Instance" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Inspect Next Instance" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Stack Frames" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Variable" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Errors:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Stack Trace (if applicable):" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Profiler" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Monitor" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Value" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Monitors" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "List of Video Memory Usage by Resource:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Total:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Video Mem" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Resource Path" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Type" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Format" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Usage" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Misc" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Clicked Control:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Clicked Control Type:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Live Edit Root:" +msgstr "" + +#: editor/script_editor_debugger.cpp +msgid "Set From Tree" +msgstr "" + +#: editor/settings_config_dialog.cpp +msgid "Shortcuts" +msgstr "" + +#: editor/settings_config_dialog.cpp +msgid "Binding" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Light Radius" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change AudioStreamPlayer3D Emission Angle" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Camera FOV" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Camera Size" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Sphere Shape Radius" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Box Shape Extents" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Capsule Shape Radius" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Capsule Shape Height" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Ray Shape Length" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Notifier Extents" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Particles AABB" +msgstr "" + +#: editor/spatial_editor_gizmos.cpp +msgid "Change Probe Extents" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Select the dynamic library for this entry" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Select dependencies of the library for this entry" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Remove current entry" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Double click to create a new entry" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Platform:" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Platform" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Dynamic Library" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "Add an architecture entry" +msgstr "" + +#: modules/gdnative/gdnative_library_editor_plugin.cpp +msgid "GDNativeLibrary" +msgstr "" + +#: modules/gdnative/gdnative_library_singleton_editor.cpp +msgid "Library" +msgstr "" + +#: modules/gdnative/gdnative_library_singleton_editor.cpp +msgid "Status" +msgstr "" + +#: modules/gdnative/gdnative_library_singleton_editor.cpp +msgid "Libraries: " +msgstr "" + +#: modules/gdnative/register_types.cpp +msgid "GDNative" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +#: modules/visual_script/visual_script_builtin_funcs.cpp +msgid "Invalid type argument to convert(), use TYPE_* constants." +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp modules/mono/glue/glue_header.h +#: modules/visual_script/visual_script_builtin_funcs.cpp +msgid "Not enough bytes for decoding bytes, or invalid format." +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "step argument is zero!" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Not a script with an instance" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Not based on a script" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Not based on a resource file" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Invalid instance dictionary format (missing @path)" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Invalid instance dictionary format (can't load script at @path)" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Invalid instance dictionary format (invalid script at @path)" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Invalid instance dictionary (invalid subclasses)" +msgstr "" + +#: modules/gdscript/gdscript_functions.cpp +msgid "Object can't provide a length." +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Next Plane" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Previous Plane" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Plane:" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Next Floor" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Previous Floor" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Floor:" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "GridMap Delete Selection" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "GridMap Duplicate Selection" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Grid Map" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Snap View" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Clip Disabled" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Clip Above" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Clip Below" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Edit X Axis" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Edit Y Axis" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Edit Z Axis" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Rotate X" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Rotate Y" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Rotate Z" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Back Rotate X" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Back Rotate Y" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Back Rotate Z" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Cursor Clear Rotation" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Create Area" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Create Exterior Connector" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Erase Area" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Clear Selection" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "GridMap Settings" +msgstr "" + +#: modules/gridmap/grid_map_editor_plugin.cpp +msgid "Pick Distance:" +msgstr "" + +#: modules/mono/csharp_script.cpp +msgid "Class name can't be a reserved keyword" +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Generating solution..." +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Generating C# project..." +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Failed to create solution." +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Failed to save solution." +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Done" +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Failed to create C# project." +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Mono" +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "About C# support" +msgstr "" + +#: modules/mono/editor/godotsharp_editor.cpp +msgid "Create C# solution" +msgstr "" + +#: modules/mono/editor/mono_bottom_panel.cpp +msgid "Builds" +msgstr "" + +#: modules/mono/editor/mono_bottom_panel.cpp +msgid "Build Project" +msgstr "" + +#: modules/mono/editor/mono_bottom_panel.cpp +msgid "Warnings" +msgstr "" + +#: modules/mono/mono_gd/gd_mono_utils.cpp +msgid "End of inner exception stack trace" +msgstr "" + +#: modules/visual_script/visual_script.cpp +msgid "" +"A node yielded without working memory, please read the docs on how to yield " +"properly!" +msgstr "" + +#: modules/visual_script/visual_script.cpp +msgid "" +"Node yielded, but did not return a function state in the first working " +"memory." +msgstr "" + +#: modules/visual_script/visual_script.cpp +msgid "" +"Return value must be assigned to first element of node working memory! Fix " +"your node please." +msgstr "" + +#: modules/visual_script/visual_script.cpp +msgid "Node returned an invalid sequence output: " +msgstr "" + +#: modules/visual_script/visual_script.cpp +msgid "Found sequence bit but not the node in the stack, report bug!" +msgstr "" + +#: modules/visual_script/visual_script.cpp +msgid "Stack overflow with stack depth: " +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Change Signal Arguments" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Change Argument Type" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Change Argument name" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Set Variable Default Value" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Set Variable Type" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Functions:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Variables:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Name is not a valid identifier:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Name already in use by another func/var/signal:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Rename Function" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Rename Variable" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Rename Signal" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Function" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Variable" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Signal" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Change Expression" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Node" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Remove VisualScript Nodes" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Duplicate VisualScript Nodes" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Hold %s to drop a Getter. Hold Shift to drop a generic signature." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Hold %s to drop a simple reference to the node." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Hold Ctrl to drop a simple reference to the node." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Hold %s to drop a Variable Setter." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Hold Ctrl to drop a Variable Setter." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Preload Node" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Node(s) From Tree" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Getter Property" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Add Setter Property" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Change Base Type" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Move Node(s)" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Remove VisualScript Node" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Connect Nodes" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Condition" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Sequence" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Switch" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Iterator" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "While" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Return" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Call" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Get" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Script already has function '%s'" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Change Input Value" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Can't copy the function node." +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Clipboard is empty!" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Paste VisualScript Nodes" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Remove Function" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Edit Variable" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Remove Variable" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Edit Signal" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Remove Signal" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Editing Variable:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Editing Signal:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Base Type:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Available Nodes:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Select or create a function to edit graph" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Edit Signal Arguments:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Edit Variable:" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Delete Selected" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Find Node Type" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Copy Nodes" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Cut Nodes" +msgstr "" + +#: modules/visual_script/visual_script_editor.cpp +msgid "Paste Nodes" +msgstr "" + +#: modules/visual_script/visual_script_flow_control.cpp +msgid "Input type not iterable: " +msgstr "" + +#: modules/visual_script/visual_script_flow_control.cpp +msgid "Iterator became invalid" +msgstr "" + +#: modules/visual_script/visual_script_flow_control.cpp +msgid "Iterator became invalid: " +msgstr "" + +#: modules/visual_script/visual_script_func_nodes.cpp +msgid "Invalid index property name." +msgstr "" + +#: modules/visual_script/visual_script_func_nodes.cpp +msgid "Base object is not a Node!" +msgstr "" + +#: modules/visual_script/visual_script_func_nodes.cpp +msgid "Path does not lead Node!" +msgstr "" + +#: modules/visual_script/visual_script_func_nodes.cpp +msgid "Invalid index property name '%s' in node %s." +msgstr "" + +#: modules/visual_script/visual_script_nodes.cpp +msgid ": Invalid argument of type: " +msgstr "" + +#: modules/visual_script/visual_script_nodes.cpp +msgid ": Invalid arguments: " +msgstr "" + +#: modules/visual_script/visual_script_nodes.cpp +msgid "VariableGet not found in script: " +msgstr "" + +#: modules/visual_script/visual_script_nodes.cpp +msgid "VariableSet not found in script: " +msgstr "" + +#: modules/visual_script/visual_script_nodes.cpp +msgid "Custom node has no _step() method, can't process graph." +msgstr "" + +#: modules/visual_script/visual_script_nodes.cpp +msgid "" +"Invalid return value from _step(), must be integer (seq out), or string " +"(error)." +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Run in Browser" +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Run exported HTML in the system's default browser." +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Could not write file:" +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Could not open template for export:" +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Invalid export template:" +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Could not read custom HTML shell:" +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Could not read boot splash image file:" +msgstr "" + +#: platform/javascript/export/export.cpp +msgid "Using default boot splash image." +msgstr "" + +#: scene/2d/animated_sprite.cpp +msgid "" +"A SpriteFrames resource must be created or set in the 'Frames' property in " +"order for AnimatedSprite to display frames." +msgstr "" + +#: scene/2d/canvas_modulate.cpp +msgid "" +"Only one visible CanvasModulate is allowed per scene (or set of instanced " +"scenes). The first created one will work, while the rest will be ignored." +msgstr "" + +#: scene/2d/collision_object_2d.cpp +msgid "" +"This node has no children shapes, so it can't interact with the space.\n" +"Consider adding CollisionShape2D or CollisionPolygon2D children nodes to " +"define its shape." +msgstr "" + +#: scene/2d/collision_polygon_2d.cpp +msgid "" +"CollisionPolygon2D only serves to provide a collision shape to a " +"CollisionObject2D derived node. Please only use it as a child of Area2D, " +"StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape." +msgstr "" + +#: scene/2d/collision_polygon_2d.cpp +msgid "An empty CollisionPolygon2D has no effect on collision." +msgstr "" + +#: scene/2d/collision_shape_2d.cpp +msgid "" +"CollisionShape2D only serves to provide a collision shape to a " +"CollisionObject2D derived node. Please only use it as a child of Area2D, " +"StaticBody2D, RigidBody2D, KinematicBody2D, etc. to give them a shape." +msgstr "" + +#: scene/2d/collision_shape_2d.cpp +msgid "" +"A shape must be provided for CollisionShape2D to function. Please create a " +"shape resource for it!" +msgstr "" + +#: scene/2d/light_2d.cpp +msgid "" +"A texture with the shape of the light must be supplied to the 'texture' " +"property." +msgstr "" + +#: scene/2d/light_occluder_2d.cpp +msgid "" +"An occluder polygon must be set (or drawn) for this occluder to take effect." +msgstr "" + +#: scene/2d/light_occluder_2d.cpp +msgid "The occluder polygon for this occluder is empty. Please draw a polygon!" +msgstr "" + +#: scene/2d/navigation_polygon.cpp +msgid "" +"A NavigationPolygon resource must be set or created for this node to work. " +"Please set a property or draw a polygon." +msgstr "" + +#: scene/2d/navigation_polygon.cpp +msgid "" +"NavigationPolygonInstance must be a child or grandchild to a Navigation2D " +"node. It only provides navigation data." +msgstr "" + +#: scene/2d/parallax_layer.cpp +msgid "" +"ParallaxLayer node only works when set as child of a ParallaxBackground node." +msgstr "" + +#: scene/2d/particles_2d.cpp scene/3d/particles.cpp +msgid "" +"A material to process the particles is not assigned, so no behavior is " +"imprinted." +msgstr "" + +#: scene/2d/path_2d.cpp +msgid "PathFollow2D only works when set as a child of a Path2D node." +msgstr "" + +#: scene/2d/physics_body_2d.cpp +msgid "" +"Size changes to RigidBody2D (in character or rigid modes) will be overridden " +"by the physics engine when running.\n" +"Change the size in children collision shapes instead." +msgstr "" + +#: scene/2d/remote_transform_2d.cpp +msgid "Path property must point to a valid Node2D node to work." +msgstr "" + +#: scene/2d/visibility_notifier_2d.cpp +msgid "" +"VisibilityEnable2D works best when used with the edited scene root directly " +"as parent." +msgstr "" + +#: scene/3d/arvr_nodes.cpp +msgid "ARVRCamera must have an ARVROrigin node as its parent" +msgstr "" + +#: scene/3d/arvr_nodes.cpp +msgid "ARVRController must have an ARVROrigin node as its parent" +msgstr "" + +#: scene/3d/arvr_nodes.cpp +msgid "" +"The controller id must not be 0 or this controller will not be bound to an " +"actual controller" +msgstr "" + +#: scene/3d/arvr_nodes.cpp +msgid "ARVRAnchor must have an ARVROrigin node as its parent" +msgstr "" + +#: scene/3d/arvr_nodes.cpp +msgid "" +"The anchor id must not be 0 or this anchor will not be bound to an actual " +"anchor" +msgstr "" + +#: scene/3d/arvr_nodes.cpp +msgid "ARVROrigin requires an ARVRCamera child node" +msgstr "" + +#: scene/3d/baked_lightmap.cpp +msgid "%d%%" +msgstr "" + +#: scene/3d/baked_lightmap.cpp +msgid "(Time Left: %d:%02d s)" +msgstr "" + +#: scene/3d/baked_lightmap.cpp +msgid "Plotting Meshes: " +msgstr "" + +#: scene/3d/baked_lightmap.cpp +msgid "Plotting Lights:" +msgstr "" + +#: scene/3d/baked_lightmap.cpp scene/3d/gi_probe.cpp +msgid "Finishing Plot" +msgstr "" + +#: scene/3d/baked_lightmap.cpp +msgid "Lighting Meshes: " +msgstr "" + +#: scene/3d/collision_object.cpp +msgid "" +"This node has no children shapes, so it can't interact with the space.\n" +"Consider adding CollisionShape or CollisionPolygon children nodes to define " +"its shape." +msgstr "" + +#: scene/3d/collision_polygon.cpp +msgid "" +"CollisionPolygon only serves to provide a collision shape to a " +"CollisionObject derived node. Please only use it as a child of Area, " +"StaticBody, RigidBody, KinematicBody, etc. to give them a shape." +msgstr "" + +#: scene/3d/collision_polygon.cpp +msgid "An empty CollisionPolygon has no effect on collision." +msgstr "" + +#: scene/3d/collision_shape.cpp +msgid "" +"CollisionShape only serves to provide a collision shape to a CollisionObject " +"derived node. Please only use it as a child of Area, StaticBody, RigidBody, " +"KinematicBody, etc. to give them a shape." +msgstr "" + +#: scene/3d/collision_shape.cpp +msgid "" +"A shape must be provided for CollisionShape to function. Please create a " +"shape resource for it!" +msgstr "" + +#: scene/3d/gi_probe.cpp +msgid "Plotting Meshes" +msgstr "" + +#: scene/3d/navigation_mesh.cpp +msgid "A NavigationMesh resource must be set or created for this node to work." +msgstr "" + +#: scene/3d/navigation_mesh.cpp +msgid "" +"NavigationMeshInstance must be a child or grandchild to a Navigation node. " +"It only provides navigation data." +msgstr "" + +#: scene/3d/particles.cpp +msgid "" +"Nothing is visible because meshes have not been assigned to draw passes." +msgstr "" + +#: scene/3d/physics_body.cpp +msgid "" +"Size changes to RigidBody (in character or rigid modes) will be overridden " +"by the physics engine when running.\n" +"Change the size in children collision shapes instead." +msgstr "" + +#: scene/3d/remote_transform.cpp +msgid "Path property must point to a valid Spatial node to work." +msgstr "" + +#: scene/3d/scenario_fx.cpp +msgid "WorldEnvironment needs an Environment resource." +msgstr "" + +#: scene/3d/scenario_fx.cpp +msgid "" +"Only one WorldEnvironment is allowed per scene (or set of instanced scenes)." +msgstr "" + +#: scene/3d/scenario_fx.cpp +msgid "" +"This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " +"this environment's Background Mode to Canvas (for 2D scenes)." +msgstr "" + +#: scene/3d/sprite_3d.cpp +msgid "" +"A SpriteFrames resource must be created or set in the 'Frames' property in " +"order for AnimatedSprite3D to display frames." +msgstr "" + +#: scene/3d/vehicle_body.cpp +msgid "" +"VehicleWheel serves to provide a wheel system to a VehicleBody. Please use " +"it as a child of a VehicleBody." +msgstr "" + +#: scene/gui/color_picker.cpp +msgid "Raw Mode" +msgstr "" + +#: scene/gui/color_picker.cpp +msgid "Add current color as a preset" +msgstr "" + +#: scene/gui/dialogs.cpp +msgid "Alert!" +msgstr "" + +#: scene/gui/dialogs.cpp +msgid "Please Confirm..." +msgstr "" + +#: scene/gui/file_dialog.cpp +msgid "Select this Folder" +msgstr "" + +#: scene/gui/popup.cpp +msgid "" +"Popups will hide by default unless you call popup() or any of the popup*() " +"functions. Making them visible for editing is fine though, but they will " +"hide upon running." +msgstr "" + +#: scene/gui/scroll_container.cpp +msgid "" +"ScrollContainer is intended to work with a single child control.\n" +"Use a container as child (VBox,HBox,etc), or a Control and set the custom " +"minimum size manually." +msgstr "" + +#: scene/gui/tree.cpp +msgid "(Other)" +msgstr "" + +#: scene/main/scene_tree.cpp +msgid "" +"Default Environment as specified in Project Settings (Rendering -> " +"Environment -> Default Environment) could not be loaded." +msgstr "" + +#: scene/main/viewport.cpp +msgid "" +"This viewport is not set as render target. If you intend for it to display " +"its contents directly to the screen, make it a child of a Control so it can " +"obtain a size. Otherwise, make it a RenderTarget and assign its internal " +"texture to some node for display." +msgstr "" + +#: scene/resources/dynamic_font.cpp +msgid "Error initializing FreeType." +msgstr "" + +#: scene/resources/dynamic_font.cpp +msgid "Unknown font format." +msgstr "" + +#: scene/resources/dynamic_font.cpp +msgid "Error loading font." +msgstr "" + +#: scene/resources/dynamic_font.cpp +msgid "Invalid font size." +msgstr "" diff --git a/editor/translations/nb.po b/editor/translations/nb.po index 17123dc8fc..e76053150c 100644 --- a/editor/translations/nb.po +++ b/editor/translations/nb.po @@ -2,27 +2,27 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Allan Nordhøy <epost@anotheragency.no>, 2017-2018. # Anonymous <GentleSaucepan@protonmail.com>, 2017. +# Elias <eliasnykrem@gmail.com>, 2018. # flesk <eivindkn@gmail.com>, 2017. +# Frank T. Rambol <frank@d-fect.com>, 2018. # Jørgen Aarmo Lund <jorgen.aarmo@gmail.com>, 2016. # NicolaiF <nico-fre@hotmail.com>, 2017-2018. # Norwegian Disaster <stian.furu.overbye@gmail.com>, 2017. # passeride <lukas@passeride.com>, 2017. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-03-22 03:38+0000\n" -"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n" +"PO-Revision-Date: 2018-06-22 08:31+0000\n" +"Last-Translator: Frank T. Rambol <frank@d-fect.com>\n" "Language-Team: Norwegian BokmÃ¥l <https://hosted.weblate.org/projects/godot-" "engine/godot/nb/>\n" "Language: nb\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.20-dev\n" +"X-Generator: Weblate 3.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -33,9 +33,8 @@ msgid "All Selection" msgstr "Alle valg" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Time" -msgstr "Anim Forandre Verdi" +msgstr "Anim Endre Nøkkelbildetid" #: editor/animation_editor.cpp msgid "Anim Change Transition" @@ -46,9 +45,8 @@ msgid "Anim Change Transform" msgstr "Anim Forandre Omforming" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Value" -msgstr "Anim Forandre Verdi" +msgstr "Anim Endre Nøkkelbildeverdi" #: editor/animation_editor.cpp msgid "Anim Change Call" @@ -99,9 +97,8 @@ msgid "Edit Node Curve" msgstr "Forandre Nodekurve" #: editor/animation_editor.cpp -#, fuzzy msgid "Edit Selection Curve" -msgstr "Forandre utvalgskurve" +msgstr "Rediger utvalgskurve" #: editor/animation_editor.cpp msgid "Anim Delete Keys" @@ -501,13 +498,12 @@ msgid "Connecting Signal:" msgstr "Kobler Til Signal:" #: editor/connections_dialog.cpp -#, fuzzy msgid "Disconnect '%s' from '%s'" -msgstr "Koble '%s' til '%s'" +msgstr "Koble '%s' fra '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Koble Til.." +msgid "Connect..." +msgstr "Koble Til..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -519,9 +515,8 @@ msgid "Signals" msgstr "Signaler" #: editor/create_dialog.cpp -#, fuzzy msgid "Change %s Type" -msgstr "Endre standard type" +msgstr "Endre %s type" #: editor/create_dialog.cpp editor/project_settings_editor.cpp #: modules/visual_script/visual_script_editor.cpp @@ -529,9 +524,8 @@ msgid "Change" msgstr "Forandre" #: editor/create_dialog.cpp -#, fuzzy msgid "Create New %s" -msgstr "Lag Ny" +msgstr "Lag ny %s" #: editor/create_dialog.cpp editor/editor_file_dialog.cpp #: editor/filesystem_dock.cpp @@ -598,7 +592,7 @@ msgstr "Ressurs" #: editor/project_manager.cpp editor/project_settings_editor.cpp #: editor/script_create_dialog.cpp msgid "Path" -msgstr "Sti" +msgstr "Søkesti" #: editor/dependency_editor.cpp msgid "Dependencies:" @@ -642,9 +636,8 @@ msgstr "" "Fjern dem likevel? (kan ikke angres)" #: editor/dependency_editor.cpp -#, fuzzy msgid "Cannot remove:" -msgstr "Kan ikke fjerne:\n" +msgstr "Kan ikke fjerne:" #: editor/dependency_editor.cpp msgid "Error loading:" @@ -729,9 +722,8 @@ msgid "Lead Developer" msgstr "Utviklingsleder" #: editor/editor_about.cpp -#, fuzzy msgid "Project Manager " -msgstr "Prosjektleder" +msgstr "Prosjektleder " #: editor/editor_about.cpp msgid "Developers" @@ -840,9 +832,8 @@ msgid "Rename Audio Bus" msgstr "Gi nytt navn til Audio Bus" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Change Audio Bus Volume" -msgstr "Veksle Audio Bus Solo" +msgstr "Endre Lydbuss Volum" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Solo" @@ -885,7 +876,6 @@ msgid "Mute" msgstr "Demp" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Bypass" msgstr "OmgÃ¥" @@ -935,12 +925,12 @@ msgid "Move Audio Bus" msgstr "Flytt Audio Bus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Lagre Audio Bus Oppsett som..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Plassering for nytt oppsett.." +msgid "Location for New Layout..." +msgstr "Plassering for nytt oppsett..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1078,12 +1068,12 @@ msgid "Updating Scene" msgstr "Oppdaterer Scene" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Lagrer lokale endringer.." +msgid "Storing local changes..." +msgstr "Lagrer lokale endringer..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Oppdaterer scene.." +msgid "Updating scene..." +msgstr "Oppdaterer scene..." #: editor/editor_data.cpp msgid "[empty]" @@ -1131,9 +1121,8 @@ msgid "Packing" msgstr "Pakking" #: editor/editor_export.cpp platform/javascript/export/export.cpp -#, fuzzy msgid "Template file not found:" -msgstr "Malfil ble ikke funnet:\n" +msgstr "Malfil ble ikke funnet:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "File Exists, Overwrite?" @@ -1152,8 +1141,8 @@ msgid "Show In File Manager" msgstr "Vis I Filutforsker" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Ny Mappe.." +msgid "New Folder..." +msgstr "Ny Mappe..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1344,19 +1333,18 @@ msgid "Description" msgstr "Beskrivelse" #: editor/editor_help.cpp -#, fuzzy msgid "Online Tutorials:" -msgstr "Online Dokumentasjon" +msgstr "Online dokumentasjon:" #: editor/editor_help.cpp -#, fuzzy msgid "" "There are currently no tutorials for this class, you can [color=$color][url=" "$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" "url][/color]." msgstr "" -"Det finnes i øyeblikket ingen beskrivelse av denne metoden. Hjelp til ved Ã¥ " -"[colour=$color][url=$url]bidra med en[/url][/color]!" +"Det finnes i øyeblikket ingen beskrivelse av denne metoden, men du kan " +"[colour=$color][url=$url]bidra med en[/url][/color] eller [color=$color][url=" +"$url2]be om en[/url][/color]." #: editor/editor_help.cpp msgid "Properties" @@ -1410,27 +1398,25 @@ msgid "Clear" msgstr "Tøm" #: editor/editor_log.cpp -#, fuzzy msgid "Clear Output" -msgstr "Output" +msgstr "Nullstill resultat" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Eksport av prosjektet mislyktes med feilkode %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Feil ved lagring av ressurs!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Lagre Ressurs Som.." +msgid "Save Resource As..." +msgstr "Lagre Ressurs Som..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -#, fuzzy -msgid "I see.." -msgstr "Jeg ser.." +msgid "I see..." +msgstr "Jeg forstÃ¥r..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1453,9 +1439,8 @@ msgid "Error while parsing '%s'." msgstr "Error ved parsing av '%s'." #: editor/editor_node.cpp -#, fuzzy msgid "Unexpected end of file '%s'." -msgstr "Uventet ende av fil '%s'." +msgstr "Uventet slutt pÃ¥ fil '%s'." #: editor/editor_node.cpp msgid "Missing '%s' or its dependencies." @@ -1482,13 +1467,12 @@ msgid "This operation can't be done without a tree root." msgstr "Denne operasjonen kan ikke gjennomføres uten en trerot." #: editor/editor_node.cpp -#, fuzzy msgid "" "Couldn't save scene. Likely dependencies (instances or inheritance) couldn't " "be satisfied." msgstr "" -"Kunne ikke lagre scene. Sannsynligvis avhengigheter (instanser) ikke kunne " -"oppfylles." +"Kunne ikke lagre scene. Sannsynligvis kunne ikke avhengigheter (instanser " +"eller arvinger) oppfylles." #: editor/editor_node.cpp msgid "Failed to load resource." @@ -1600,14 +1584,12 @@ msgid "Copy Resource" msgstr "Kopier Ressurs" #: editor/editor_node.cpp -#, fuzzy msgid "Make Built-In" -msgstr "Lag Innebygd" +msgstr "Lag innebygget" #: editor/editor_node.cpp -#, fuzzy msgid "Make Sub-Resources Unique" -msgstr "Gjør Underressurs Unik" +msgstr "Lag underressurser unike" #: editor/editor_node.cpp msgid "Open in Help" @@ -1628,14 +1610,13 @@ msgstr "" "'applikasjon'." #: editor/editor_node.cpp -#, fuzzy msgid "" "Selected scene '%s' does not exist, select a valid one?\n" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" -"Valgte scene '%s' finnes ikke, velg en gyldig en?\n" -"Du kan endre dette senere under \"Prosjekt Innstillinger\" under kategorien " +"Den valgte scenen '%s' finnes ikke. Vil du velge en gyldig scene?\n" +"Du kan endre dette senere i \"Prosjektinnstillinger\" under kategorien " "'applikasjon'." #: editor/editor_node.cpp @@ -1665,12 +1646,12 @@ msgid "Open Base Scene" msgstr "Ã…pne Base Scene" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "HurtigÃ¥pne Scene.." +msgid "Quick Open Scene..." +msgstr "HurtigÃ¥pne Scene..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "HurtigÃ¥pne Skript.." +msgid "Quick Open Script..." +msgstr "HurtigÃ¥pne Skript..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1681,8 +1662,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Lagre endringer til '%s' før lukking?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Lagre Scene Som.." +msgid "Save Scene As..." +msgstr "Lagre Scene Som..." #: editor/editor_node.cpp msgid "No" @@ -1733,8 +1714,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Denne handlingen kan ikke angres. GÃ¥ tilbake likevel?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Hurtigkjør Scene.." +msgid "Quick Run Scene..." +msgstr "Hurtigkjør Scene..." #: editor/editor_node.cpp msgid "Quit" @@ -1887,8 +1868,8 @@ msgid "Previous tab" msgstr "Forrige fane" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrer Filer.." +msgid "Filter Files..." +msgstr "Filtrer Filer..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1899,12 +1880,12 @@ msgid "New Scene" msgstr "Ny Scene" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Ny Arvet Scene.." +msgid "New Inherited Scene..." +msgstr "Ny Arvet Scene..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Ã…pne Scene.." +msgid "Open Scene..." +msgstr "Ã…pne Scene..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1923,17 +1904,17 @@ msgid "Open Recent" msgstr "Ã…pne Nylig" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Konverter Til.." +msgid "Convert To..." +msgstr "Konverter Til..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshBibliotek.." +msgid "MeshLibrary..." +msgstr "MeshBibliotek..." #: editor/editor_node.cpp #, fuzzy -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet…" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1983,9 +1964,8 @@ msgid "Debug" msgstr "Debug" #: editor/editor_node.cpp -#, fuzzy msgid "Deploy with Remote Debug" -msgstr "Deploy med Ekstern Debug" +msgstr "Distribuer med ekstern feilsøking" #: editor/editor_node.cpp #, fuzzy @@ -2145,7 +2125,7 @@ msgstr "Pause scenen" #: editor/editor_node.cpp msgid "Pause Scene" -msgstr "Pause Scene" +msgstr "Sett scenen pÃ¥ pause" #: editor/editor_node.cpp msgid "Stop the scene." @@ -2205,8 +2185,8 @@ msgid "Save the currently edited resource." msgstr "Lagre den nylige redigerte ressursen." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Lagre Som.." +msgid "Save As..." +msgstr "Lagre Som..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2315,8 +2295,8 @@ msgid "Creating Mesh Previews" msgstr "Lager ForhÃ¥ndsvisning av Mesh" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatyrbilde.." +msgid "Thumbnail..." +msgstr "Miniatyrbilde..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2473,8 +2453,8 @@ msgid "(Current)" msgstr "(Gjeldende)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Henter fillager, vennligst vent.." +msgid "Retrieving mirrors, please wait..." +msgstr "Henter fillager, vennligst vent..." #: editor/export_template_manager.cpp #, fuzzy @@ -2556,8 +2536,8 @@ msgid "Error requesting url: " msgstr "Error ved forespørsel av url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Kobler til Fillager.." +msgid "Connecting to Mirror..." +msgstr "Kobler til Fillager..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2573,8 +2553,8 @@ msgstr "Kan ikke Løses" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Kobler til.." +msgid "Connecting..." +msgstr "Kobler til..." #: editor/export_template_manager.cpp #, fuzzy @@ -2588,8 +2568,8 @@ msgstr "Tilkoblet" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Requesting.." -msgstr "Ber om.." +msgid "Requesting..." +msgstr "Ber om..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2734,12 +2714,12 @@ msgid "Collapse all" msgstr "Kollaps alle" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Endre Navn.." +msgid "Rename..." +msgstr "Endre Navn..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Flytt Til.." +msgid "Move To..." +msgstr "Flytt Til..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2751,16 +2731,16 @@ msgid "Instance" msgstr "Instans" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Endre Avhengigheter.." +msgid "Edit Dependencies..." +msgstr "Endre Avhengigheter..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Vis Eiere.." +msgid "View Owners..." +msgstr "Vis Eiere..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Duplisér" #: editor/filesystem_dock.cpp @@ -2787,10 +2767,10 @@ msgstr "Instanser den valgte scene(r) som barn av den valgte noden." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Skanner Filer,\n" -"Vennligst Vent.." +"Vennligst Vent..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2855,8 +2835,8 @@ msgid "Import Scene" msgstr "Importer Scene" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Importerer Scene.." +msgid "Importing Scene..." +msgstr "Importerer Scene..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2867,8 +2847,8 @@ msgid "Generating for Mesh: " msgstr "Genererer for Maske: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Kjører Tilpasser Skript.." +msgid "Running Custom Script..." +msgstr "Kjører Tilpasser Skript..." #: editor/import/resource_importer_scene.cpp #, fuzzy @@ -2884,8 +2864,8 @@ msgid "Error running post-import script:" msgstr "Error ved kjøring av post-import script:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Lagrer.." +msgid "Saving..." +msgstr "Lagrer..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2905,8 +2885,8 @@ msgstr "Importer Som:" #: editor/import_dock.cpp editor/property_editor.cpp #, fuzzy -msgid "Preset.." -msgstr "Preset.." +msgid "Preset..." +msgstr "Preset..." #: editor/import_dock.cpp msgid "Reimport" @@ -3336,16 +3316,16 @@ msgid "Transition Node" msgstr "Overgang Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importer Animasjoner.." +msgid "Import Animations..." +msgstr "Importer Animasjoner..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Rediger Node-Filtre" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filtre.." +msgid "Filters..." +msgstr "Filtre..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3414,8 +3394,8 @@ msgid "Fetching:" msgstr "Henter:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Løser.." +msgid "Resolving..." +msgstr "Løser..." #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy @@ -3484,8 +3464,8 @@ msgid "Site:" msgstr "Side:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Support.." +msgid "Support..." +msgstr "Support..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3675,6 +3655,7 @@ msgid "Use Rotation Snap" msgstr "Bruk Rotasjons-Snap" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Konfigurer Snap..." @@ -3837,7 +3818,7 @@ msgstr "Legg til %s" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Adding %s..." -msgstr "Legger til %s.." +msgstr "Legger til %s..." #: editor/plugins/canvas_item_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ok" @@ -4106,7 +4087,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4313,8 +4294,8 @@ msgid "Error loading image:" msgstr "Feil ved innlasting av bilde:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Ingen piksler med gjennomsiktighet > 128 i bilde.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Ingen piksler med gjennomsiktighet > 128 i bilde..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4682,8 +4663,8 @@ msgid "Import Theme" msgstr "Importer Tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Lagre Tema Som.." +msgid "Save Theme As..." +msgstr "Lagre Tema Som..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4779,8 +4760,8 @@ msgstr "Veksle skriptpanel" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Finn.." +msgid "Find..." +msgstr "Finn..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4987,15 +4968,15 @@ msgid "Find Previous" msgstr "Finn forrige" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Erstatt.." +msgid "Replace..." +msgstr "Erstatt..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5450,11 +5431,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5709,7 +5686,7 @@ msgid "Remove All" msgstr "Fjern Funksjon" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5778,8 +5755,9 @@ msgid "Options" msgstr "Innstillinger" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "Innstillinger" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5905,7 +5883,7 @@ msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet.." +msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -5969,7 +5947,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -6060,6 +6038,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Prosjektnavn:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Kunne ikke opprette mappe." @@ -6251,8 +6234,8 @@ msgstr "Museknapp" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6280,7 +6263,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6466,7 +6449,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6563,11 +6546,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6740,7 +6723,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8211,12 +8194,19 @@ msgstr "" #: scene/resources/dynamic_font.cpp msgid "Error loading font." -msgstr "" +msgstr "Feil ved innlasting av font." #: scene/resources/dynamic_font.cpp msgid "Invalid font size." msgstr "Ugyldig fontstørrelse." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Forrige fane" + +#~ msgid "Next" +#~ msgstr "Neste" + #~ msgid "" #~ "Invalid version.txt format inside templates. Revision is not a valid " #~ "identifier." @@ -8227,9 +8217,6 @@ msgstr "Ugyldig fontstørrelse." #~ msgid "Can't write file." #~ msgstr "Kan ikke skrive fil." -#~ msgid "Next" -#~ msgstr "Neste" - #~ msgid "Not found!" #~ msgstr "Ikke funnet!" diff --git a/editor/translations/nl.po b/editor/translations/nl.po index 9927fd8e8a..bfedf322b3 100644 --- a/editor/translations/nl.po +++ b/editor/translations/nl.po @@ -11,9 +11,12 @@ # Daeran Wereld <daeran@gmail.com>, 2017. # Dzejkop <jakubtrad@gmail.com>, 2017. # Ferdinand de Coninck <ferdinand.deconinck@gmail.com>, 2018. +# frank <frankvprive@gmail.com>, 2018. +# Johannes Smit <smitjohannes96@gmail.com>, 2018. # Jorn Theunissen <jorn-theunissen@hotmail.com>, 2018. # Maikel <maikel_martens_1@hotmail.com>, 2017. # millenniumproof <millenniumproof@gmail.com>, 2018. +# nee <lespam@protonmail.com>, 2018. # Pieter-Jan Briers <pieterjan.briers@gmail.com>, 2017-2018. # Robin Arys <robinarys@hotmail.com>, 2017. # Senno Kaasjager <senno.kaasjager@gmail.com>, 2017. @@ -25,15 +28,15 @@ msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-06 15:35+0000\n" -"Last-Translator: millenniumproof <millenniumproof@gmail.com>\n" +"PO-Revision-Date: 2018-05-21 18:36+0000\n" +"Last-Translator: Johannes Smit <smitjohannes96@gmail.com>\n" "Language-Team: Dutch <https://hosted.weblate.org/projects/godot-engine/godot/" "nl/>\n" "Language: nl\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.20\n" +"X-Generator: Weblate 3.0-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -514,8 +517,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Ontkoppel '%s' van '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Verbind.." +msgid "Connect..." +msgstr "Verbind..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -937,12 +940,12 @@ msgid "Move Audio Bus" msgstr "Verplaats audiobus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Sla Audio Bus Layout Op Als.." +msgid "Save Audio Bus Layout As..." +msgstr "Sla Audio Bus Layout Op Als..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Locatie voor Nieuwe Layout.." +msgid "Location for New Layout..." +msgstr "Locatie voor Nieuwe Layout..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1080,12 +1083,12 @@ msgid "Updating Scene" msgstr "Scene aan het Updaten" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Lokale wijziging aan het opslaan.." +msgid "Storing local changes..." +msgstr "Lokale wijziging aan het opslaan..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Scene aan het updaten.." +msgid "Updating scene..." +msgstr "Scene aan het updaten..." #: editor/editor_data.cpp msgid "[empty]" @@ -1153,8 +1156,8 @@ msgid "Show In File Manager" msgstr "Weergeven in Bestandsbeheer" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nieuwe Map.." +msgid "New Folder..." +msgstr "Nieuwe Map..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1415,20 +1418,20 @@ msgstr "Maak Uitvoer Leeg" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Project exporteren faalt door foutcode %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Error bij het opslaan van resource!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Resource Opslaan Als.." +msgid "Save Resource As..." +msgstr "Resource Opslaan Als..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Ik snap het.." +msgid "I see..." +msgstr "Ik snap het..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1660,11 +1663,11 @@ msgid "Open Base Scene" msgstr "Open Basisscene" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "Open Scene Snel..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "Open Script Snel..." #: editor/editor_node.cpp @@ -1676,7 +1679,7 @@ msgid "Save changes to '%s' before closing?" msgstr "Sla wijzigen aan '%s' op voor het afsluiten?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "Sla Scene Op Als..." #: editor/editor_node.cpp @@ -1729,7 +1732,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Deze actie kan niet ongedaan gemaakt worden. Toch herstellen?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "Snel Scene Uitvoeren..." #: editor/editor_node.cpp @@ -1889,7 +1892,7 @@ msgid "Previous tab" msgstr "Vorig tabblad" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Bestanden Filteren..." #: editor/editor_node.cpp @@ -1901,11 +1904,11 @@ msgid "New Scene" msgstr "Nieuwe Scene" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Nieuwe Geërfde Scene..." #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "Scene Openen..." #: editor/editor_node.cpp @@ -1925,15 +1928,15 @@ msgid "Open Recent" msgstr "Recente Scenes Openen" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "Converteer Naar..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2198,8 +2201,8 @@ msgid "Save the currently edited resource." msgstr "De bewerkte bron opslaan." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Opslaan Als.." +msgid "Save As..." +msgstr "Opslaan Als..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2307,8 +2310,8 @@ msgid "Creating Mesh Previews" msgstr "Creëren van Mesh Previews" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Voorbeeld.." +msgid "Thumbnail..." +msgstr "Voorbeeld..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2460,8 +2463,8 @@ msgid "(Current)" msgstr "(Huidig)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Mirrors ophalen, even wachten a.u.b.." +msgid "Retrieving mirrors, please wait..." +msgstr "Mirrors ophalen, even wachten a.u.b..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2538,8 +2541,8 @@ msgid "Error requesting url: " msgstr "Fout bij het opvragen van een URL: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Verbinden met Mirror.." +msgid "Connecting to Mirror..." +msgstr "Verbinden met Mirror..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2555,8 +2558,8 @@ msgstr "Kan niet oplossen" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Verbinden.." +msgid "Connecting..." +msgstr "Verbinden..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2568,7 +2571,7 @@ msgstr "Verbonden" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "Opvragen..." #: editor/export_template_manager.cpp @@ -2706,12 +2709,12 @@ msgid "Collapse all" msgstr "Klap alles in" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Hernoemen.." +msgid "Rename..." +msgstr "Hernoemen..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Verplaats Naar.." +msgid "Move To..." +msgstr "Verplaats Naar..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2722,16 +2725,16 @@ msgid "Instance" msgstr "Instantie" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Afhankelijkheden aanpassen.." +msgid "Edit Dependencies..." +msgstr "Afhankelijkheden aanpassen..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Bekijk eigenaren.." +msgid "View Owners..." +msgstr "Bekijk eigenaren..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Dupliceren.." +msgid "Duplicate..." +msgstr "Dupliceren..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2758,10 +2761,10 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Bestanden Scannen,\n" -"Wacht Alstublieft.." +"Wacht Alstublieft..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2826,8 +2829,8 @@ msgid "Import Scene" msgstr "Importeer Scene" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Scene Importeren.." +msgid "Importing Scene..." +msgstr "Scene Importeren..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2838,8 +2841,8 @@ msgid "Generating for Mesh: " msgstr "Bouw voor Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Aangepast script uitvoeren .." +msgid "Running Custom Script..." +msgstr "Aangepast script uitvoeren ..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2854,8 +2857,8 @@ msgid "Error running post-import script:" msgstr "Fout bij uitvoeren post-import script:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Opslaan.." +msgid "Saving..." +msgstr "Opslaan..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2874,8 +2877,8 @@ msgid "Import As:" msgstr "Importereen Als:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Voorinstelling.." +msgid "Preset..." +msgstr "Voorinstelling..." #: editor/import_dock.cpp msgid "Reimport" @@ -3294,15 +3297,15 @@ msgid "Transition Node" msgstr "Transition Node" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Importeer Animaties.." +msgid "Import Animations..." +msgstr "Importeer Animaties..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Wijzig Node Filters" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Filters..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3370,8 +3373,8 @@ msgid "Fetching:" msgstr "Ophalen:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "Oplossen .." +msgid "Resolving..." +msgstr "Oplossen ..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3437,8 +3440,8 @@ msgid "Site:" msgstr "Site:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Ondersteuning.." +msgid "Support..." +msgstr "Ondersteuning..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3635,8 +3638,9 @@ msgid "Use Rotation Snap" msgstr "Gebruik Rotatie Snap" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "Configureer Uitlijnen..." +msgstr "Configureer Snap..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3735,14 +3739,12 @@ msgid "Show Guides" msgstr "Toon hulplijnen" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Raster Weergeven" +msgstr "Toon Oorsprongspunt" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "Toon helpers" +msgstr "Toon Aanzicht Portaal" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3854,11 +3856,11 @@ msgstr "Verwijder Geselecteerde Item" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import from Scene" -msgstr "Importeer vanaf de Scene" +msgstr "Importeer Vanuit Scene" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Update from Scene" -msgstr "Werk bij vanaf de Scene" +msgstr "Update Vanuit Scene" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat0" @@ -3869,13 +3871,12 @@ msgid "Flat1" msgstr "Plat1" #: editor/plugins/curve_editor_plugin.cpp -#, fuzzy msgid "Ease in" -msgstr "Schaal Selectie" +msgstr "Rustig Aanzetten" #: editor/plugins/curve_editor_plugin.cpp msgid "Ease out" -msgstr "Neem af naar buiten" +msgstr "Rustig Afzetten" #: editor/plugins/curve_editor_plugin.cpp msgid "Smoothstep" @@ -4043,7 +4044,7 @@ msgstr "Mesh heeft geen oppervlakte om omlijning van te maken!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Mesh grondtype is niet PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4075,8 +4076,8 @@ msgstr "Creëer Convex Botsing Broer" #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy -msgid "Create Outline Mesh.." -msgstr "Creëer Omlijning Mesh.." +msgid "Create Outline Mesh..." +msgstr "Creëer Omlijning Mesh..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4227,7 +4228,7 @@ msgstr "Hoogteveld aan het creëeren..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Marking walkable triangles..." -msgstr "Markeer loopbare driehoeken.." +msgstr "Markeer loopbare driehoeken..." #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy @@ -4284,8 +4285,8 @@ msgid "Error loading image:" msgstr "Error bij het laden van afbeelding:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Geen pixels met transparantie > 128 in afbeelding.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Geen pixels met transparantie > 128 in afbeelding..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4645,8 +4646,8 @@ msgid "Import Theme" msgstr "Importeer Thema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Thema Opslaan Als.." +msgid "Save Theme As..." +msgstr "Thema Opslaan Als..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4742,8 +4743,8 @@ msgstr "Schakel Scripten Paneel" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Vind.." +msgid "Find..." +msgstr "Vind..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4952,16 +4953,16 @@ msgid "Find Previous" msgstr "Vind Vorige" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Vervang.." +msgid "Replace..." +msgstr "Vervang..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Ga Naar Functie.." +msgid "Goto Function..." +msgstr "Ga Naar Functie..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Ga Naar Regel.." +msgid "Goto Line..." +msgstr "Ga Naar Regel..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5305,12 +5306,11 @@ msgstr "Vrijekijk Snelheid Modificator" #: editor/plugins/spatial_editor_plugin.cpp msgid "XForm Dialog" -msgstr "" +msgstr "XForm Dialoog" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Select Mode (Q)" -msgstr "Selecteer Modus" +msgstr "Selectiestand (Q)" #: editor/plugins/spatial_editor_plugin.cpp msgid "" @@ -5318,22 +5318,25 @@ msgid "" "Alt+Drag: Move\n" "Alt+RMB: Depth list selection" msgstr "" +"Slepen: Roteren\n" +"Atl+Slepen: Verplaatsen\n" +"Alt+RMB: Diepte selectie" #: editor/plugins/spatial_editor_plugin.cpp msgid "Move Mode (W)" -msgstr "" +msgstr "Beweegstand (W)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotate Mode (E)" -msgstr "" +msgstr "Rotatiestand (E)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale Mode (R)" -msgstr "" +msgstr "Schaalstand (R)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Coords" -msgstr "" +msgstr "Lokale Coördinaten" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Space Mode (%s)" @@ -5346,64 +5349,63 @@ msgstr "Op hulplijnen uitlijnen" #: editor/plugins/spatial_editor_plugin.cpp msgid "Bottom View" -msgstr "" +msgstr "Onderaanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Top View" -msgstr "" +msgstr "Bovenaanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rear View" -msgstr "" +msgstr "Achteraanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Front View" -msgstr "" +msgstr "Vooraanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Left View" -msgstr "" +msgstr "Linker Zijaanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Right View" -msgstr "" +msgstr "Rechter Zijaanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Switch Perspective/Orthogonal view" -msgstr "" +msgstr "Schakel Perspectief/Orthogonaal aanzicht" #: editor/plugins/spatial_editor_plugin.cpp msgid "Insert Animation Key" -msgstr "" +msgstr "Voeg Animatiesleutel toe" #: editor/plugins/spatial_editor_plugin.cpp msgid "Focus Origin" -msgstr "" +msgstr "Focus op Oorsprongspunt" #: editor/plugins/spatial_editor_plugin.cpp msgid "Focus Selection" -msgstr "" +msgstr "Focus Selectie" #: editor/plugins/spatial_editor_plugin.cpp msgid "Align Selection With View" -msgstr "" +msgstr "Arrangeer Selectie naar Aanzicht" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Tool Select" -msgstr "Alle Selectie" +msgstr "Gereedschappen" #: editor/plugins/spatial_editor_plugin.cpp msgid "Tool Move" -msgstr "" +msgstr "Beweeg Gereedschap" #: editor/plugins/spatial_editor_plugin.cpp msgid "Tool Rotate" -msgstr "" +msgstr "Roteer Gereedschap" #: editor/plugins/spatial_editor_plugin.cpp msgid "Tool Scale" -msgstr "" +msgstr "Verschalen Gereedschap" #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy @@ -5412,52 +5414,48 @@ msgstr "Toggle Favoriet" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform" -msgstr "" +msgstr "Transformatie" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "" +msgid "Transform Dialog..." +msgstr "Transformatie Dialoog..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" -msgstr "" +msgstr "1 Aanzicht Portaal" #: editor/plugins/spatial_editor_plugin.cpp msgid "2 Viewports" -msgstr "" +msgstr "2 Aanzicht Portalen" #: editor/plugins/spatial_editor_plugin.cpp msgid "2 Viewports (Alt)" -msgstr "" +msgstr "2 Aanzicht Portalen (Alt)" #: editor/plugins/spatial_editor_plugin.cpp msgid "3 Viewports" -msgstr "" +msgstr "3 Aanzicht Portalen" #: editor/plugins/spatial_editor_plugin.cpp msgid "3 Viewports (Alt)" -msgstr "" +msgstr "3 Aanzicht Portalen (Alt)" #: editor/plugins/spatial_editor_plugin.cpp msgid "4 Viewports" -msgstr "" +msgstr "4 Aanzicht Portalen" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Origin" -msgstr "" +msgstr "Bekijk Oorsprongspunt" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Grid" -msgstr "" +msgstr "Bekijk Raster" #: editor/plugins/spatial_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Settings" -msgstr "" +msgstr "Instellingen" #: editor/plugins/spatial_editor_plugin.cpp msgid "Skeleton Gizmo visibility" @@ -5465,71 +5463,71 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Snap Settings" -msgstr "" +msgstr "Snap instellingen" #: editor/plugins/spatial_editor_plugin.cpp msgid "Translate Snap:" -msgstr "" +msgstr "Verplaats Snap:" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotate Snap (deg.):" -msgstr "" +msgstr "Draai Snap (grad.):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale Snap (%):" -msgstr "" +msgstr "Verander Grootte van Snap (%):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Viewport Settings" -msgstr "" +msgstr "Instellingen Aanzicht Portaal" #: editor/plugins/spatial_editor_plugin.cpp msgid "Perspective FOV (deg.):" -msgstr "" +msgstr "Perspectief FOV (grad.):" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Z-Near:" -msgstr "" +msgstr "Bekijk Z-Near:" #: editor/plugins/spatial_editor_plugin.cpp msgid "View Z-Far:" -msgstr "" +msgstr "Bekijk Z-Far:" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Change" -msgstr "" +msgstr "Transformatie Verandering" #: editor/plugins/spatial_editor_plugin.cpp msgid "Translate:" -msgstr "" +msgstr "Verplaats:" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotate (deg.):" -msgstr "" +msgstr "Rotatie (graden):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale (ratio):" -msgstr "" +msgstr "Verschalen (ratio):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Type" -msgstr "" +msgstr "Transformatie Type" #: editor/plugins/spatial_editor_plugin.cpp msgid "Pre" -msgstr "" +msgstr "Pre" #: editor/plugins/spatial_editor_plugin.cpp msgid "Post" -msgstr "" +msgstr "Post" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "ERROR: Couldn't load frame resource!" -msgstr "" +msgstr "FOUT: Kan frame benodigdheden niet laden!" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Frame" -msgstr "" +msgstr "Voeg Frame toe" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Resource clipboard is empty or not a texture!" @@ -5537,60 +5535,59 @@ msgstr "" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Paste Frame" -msgstr "" +msgstr "Frame Plakken" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Empty" -msgstr "" +msgstr "Lege Toevoegen" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Change Animation Loop" -msgstr "" +msgstr "Verander Animatie Lus" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Change Animation FPS" -msgstr "" +msgstr "Verander Animatie FPS" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "(empty)" -msgstr "" +msgstr "(leeg)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Animations" -msgstr "" +msgstr "Animaties" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Speed (FPS):" -msgstr "" +msgstr "Snelheid (FPS):" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Loop" -msgstr "" +msgstr "Lus" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Animation Frames" -msgstr "" +msgstr "Animatie Frames" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Insert Empty (Before)" -msgstr "" +msgstr "Lege Toevoegen (Hiervoor)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Insert Empty (After)" -msgstr "" +msgstr "Lege Toevoegen (Hierna)" #: editor/plugins/sprite_frames_editor_plugin.cpp -#, fuzzy msgid "Move (Before)" -msgstr "Kopiëer Nodes" +msgstr "Verplaats (Hiervoor)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Move (After)" -msgstr "" +msgstr "Verplaats (Hierna)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "SpriteFrames" -msgstr "" +msgstr "Sprite-Frames" #: editor/plugins/style_box_editor_plugin.cpp msgid "StyleBox Preview:" @@ -5610,7 +5607,7 @@ msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp msgid "<None>" -msgstr "" +msgstr "<Geen>" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Pixel Snap" @@ -5637,50 +5634,48 @@ msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Separation:" -msgstr "" +msgstr "Afzondering:" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Texture Region" -msgstr "" +msgstr "Textuur Regio" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Texture Region Editor" -msgstr "" +msgstr "Textuur Regio Editor" #: editor/plugins/theme_editor_plugin.cpp msgid "Can't save theme to file:" -msgstr "" +msgstr "Kan thema niet opslaan in bestand:" #: editor/plugins/theme_editor_plugin.cpp msgid "Add All Items" -msgstr "" +msgstr "Alle Items Toevoegen" #: editor/plugins/theme_editor_plugin.cpp msgid "Add All" -msgstr "" +msgstr "Allen Toevoegen" #: editor/plugins/theme_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Remove Item" -msgstr "" +msgstr "Verwijder Item" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Remove All Items" -msgstr "Verwijder Selectie" +msgstr "Verwijder Alle Items" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Remove All" -msgstr "Verwijderen" +msgstr "Verwijder Alles" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "" +msgid "Edit theme..." +msgstr "Bewerk Thema..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." -msgstr "" +msgstr "Thema Bewerkingsmenu." #: editor/plugins/theme_editor_plugin.cpp msgid "Add Class Items" @@ -5692,15 +5687,15 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp msgid "Create Empty Template" -msgstr "" +msgstr "Creëer Leeg Sjabloon" #: editor/plugins/theme_editor_plugin.cpp msgid "Create Empty Editor Template" -msgstr "" +msgstr "Creëer Lege Sjabloon Editor" #: editor/plugins/theme_editor_plugin.cpp msgid "Create From Current Editor Theme" -msgstr "" +msgstr "Creëer Derivatie Huidig Editor Thema" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio1" @@ -5712,7 +5707,7 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp msgid "Item" -msgstr "" +msgstr "Item" #: editor/plugins/theme_editor_plugin.cpp msgid "Check Item" @@ -5733,69 +5728,68 @@ msgstr "" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" -msgstr "" +msgstr "Had" #: editor/plugins/theme_editor_plugin.cpp msgid "Many" -msgstr "" +msgstr "Veel" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp msgid "Options" -msgstr "" +msgstr "Opties" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "Opties" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" -msgstr "" +msgstr "Tabblad 1" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 2" -msgstr "" +msgstr "Tabblad 2" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 3" -msgstr "" +msgstr "Tabblad 3" #: editor/plugins/theme_editor_plugin.cpp msgid "Data Type:" -msgstr "" +msgstr "Data Type:" #: editor/plugins/theme_editor_plugin.cpp msgid "Icon" -msgstr "" +msgstr "Icoon" #: editor/plugins/theme_editor_plugin.cpp msgid "Style" -msgstr "" +msgstr "Stijl" #: editor/plugins/theme_editor_plugin.cpp msgid "Font" -msgstr "" +msgstr "Lettertype" #: editor/plugins/theme_editor_plugin.cpp msgid "Color" -msgstr "" +msgstr "Kleur" #: editor/plugins/theme_editor_plugin.cpp msgid "Theme" -msgstr "" +msgstr "Thema" #: editor/plugins/tile_map_editor_plugin.cpp -#, fuzzy msgid "Erase Selection" -msgstr "Schaal Selectie" +msgstr "Selectie Verwijderen" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Paint TileMap" msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp -#, fuzzy msgid "Line Draw" -msgstr "Lineair" +msgstr "Teken Lijn" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rectangle Paint" @@ -5811,23 +5805,23 @@ msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Erase selection" -msgstr "" +msgstr "Verwijder Selectie" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Find tile" -msgstr "" +msgstr "Vind Tegel" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Transpose" -msgstr "" +msgstr "Transponeren" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Mirror X" -msgstr "" +msgstr "Spiegel X" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Mirror Y" -msgstr "" +msgstr "Spiegel Y" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Paint Tile" @@ -5835,39 +5829,39 @@ msgstr "" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Pick Tile" -msgstr "" +msgstr "Kies Tegel" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 0 degrees" -msgstr "" +msgstr "0 Graden Roteren" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 90 degrees" -msgstr "" +msgstr "90 Graden Roteren" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 180 degrees" -msgstr "" +msgstr "180 Graden Roteren" #: editor/plugins/tile_map_editor_plugin.cpp msgid "Rotate 270 degrees" -msgstr "" +msgstr "270 Graden Roteren" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Could not find tile:" -msgstr "" +msgstr "Niet gevonden titel:" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Item name or ID:" -msgstr "" +msgstr "Item naam of identificatiecode:" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from scene?" -msgstr "" +msgstr "Creëer vanuit scene?" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Merge from scene?" -msgstr "" +msgstr "Vervoegen vanuit scene?" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy @@ -5876,15 +5870,15 @@ msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" -msgstr "" +msgstr "Creëer vanuit Scene" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Merge from Scene" -msgstr "" +msgstr "Vervoeg vanuit Scene" #: editor/plugins/tile_set_editor_plugin.cpp editor/script_editor_debugger.cpp msgid "Error" -msgstr "" +msgstr "Fout" #: editor/plugins/tile_set_editor_plugin.cpp msgid "Autotiles" @@ -5903,44 +5897,40 @@ msgid "" msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp -#, fuzzy msgid "Select current edited sub-tile." -msgstr "De bewerkte bron opslaan." +msgstr "Selecteer zojuist bewerkte sub-tegel." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Select sub-tile to change its priority." -msgstr "" +msgstr "Selecteer een sub-tegel om zijn prioriteit te veranderen." #: editor/progress_dialog.cpp scene/gui/dialogs.cpp msgid "Cancel" -msgstr "Annuleren" +msgstr "Annuleer" #: editor/project_export.cpp -#, fuzzy msgid "Runnable" -msgstr "Inschakelen" +msgstr "Uitvoerbaar" #: editor/project_export.cpp -#, fuzzy msgid "Delete patch '%s' from list?" -msgstr "Verwijder" +msgstr "Verwijder patch '%s' van lijst?" #: editor/project_export.cpp -#, fuzzy msgid "Delete preset '%s'?" -msgstr "Verwijder geselecteerde bestanden?" +msgstr "Verwijder voorinstelling '%s'?" #: editor/project_export.cpp msgid "Export templates for this platform are missing/corrupted: " -msgstr "" +msgstr "Exportsjablonen voor dit platform zijn vermist/corrupt: " #: editor/project_export.cpp msgid "Presets" -msgstr "" +msgstr "Voorinstelling" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "" +msgid "Add..." +msgstr "Toevoegen..." #: editor/project_export.cpp msgid "Resources" @@ -5952,7 +5942,7 @@ msgstr "" #: editor/project_export.cpp msgid "Export selected scenes (and dependencies)" -msgstr "" +msgstr "Exporteer geselecteerde scenes (en afhankelijkheden)" #: editor/project_export.cpp msgid "Export selected resources (and dependencies)" @@ -5977,9 +5967,8 @@ msgid "" msgstr "" #: editor/project_export.cpp -#, fuzzy msgid "Patches" -msgstr "Matches:" +msgstr "Patches" #: editor/project_export.cpp msgid "Make Patch" @@ -5987,24 +5976,23 @@ msgstr "" #: editor/project_export.cpp msgid "Features" -msgstr "" +msgstr "Kenmerken" #: editor/project_export.cpp msgid "Custom (comma-separated):" msgstr "" #: editor/project_export.cpp -#, fuzzy msgid "Feature List:" -msgstr "Methode Lijst:" +msgstr "Kenmerkenlijst:" #: editor/project_export.cpp msgid "Export PCK/Zip" -msgstr "" +msgstr "Exporteer PCK/Zip" #: editor/project_export.cpp msgid "Export templates for this platform are missing:" -msgstr "" +msgstr "Vermiste Exportsjablonen voor dit platform:" #: editor/project_export.cpp msgid "Export templates for this platform are missing/corrupted:" @@ -6015,30 +6003,33 @@ msgid "Export With Debug" msgstr "" #: editor/project_manager.cpp -#, fuzzy msgid "The path does not exist." -msgstr "Bestand bestaat niet." +msgstr "Dit pad bestaat niet." #: editor/project_manager.cpp msgid "Please choose a 'project.godot' file." -msgstr "" +msgstr "Kies alstublieft een 'project.godot' bestand." #: editor/project_manager.cpp msgid "Please choose an empty folder." -msgstr "" +msgstr "Kies alstublieft een lege map." #: editor/project_manager.cpp msgid "Imported Project" -msgstr "" +msgstr "Geïmporteerd Project" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Ongeldige naam." + +#: editor/project_manager.cpp msgid "Couldn't create folder." -msgstr "Map kon niet gemaakt worden." +msgstr "Kon map niet creëren." #: editor/project_manager.cpp msgid "There is already a folder in this path with the specified name." -msgstr "" +msgstr "Er is al een map in dit pad met dezelfde naam." #: editor/project_manager.cpp msgid "It would be a good idea to name your project." @@ -6126,7 +6117,7 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy msgid "Can't open project" -msgstr "Verbind.." +msgstr "Verbind..." #: editor/project_manager.cpp msgid "Are you sure to open more than one project?" @@ -6201,7 +6192,7 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy msgid "Can't run project" -msgstr "Verbind.." +msgstr "Verbind..." #: editor/project_manager.cpp msgid "" @@ -6227,8 +6218,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6256,7 +6247,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6443,7 +6434,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6517,7 +6508,7 @@ msgstr "" #: editor/property_editor.cpp msgid "Pick a Viewport" -msgstr "" +msgstr "Kies een Aanzicht portaal" #: editor/property_editor.cpp msgid "Ease In" @@ -6540,11 +6531,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6582,8 +6573,9 @@ msgid "Error loading file: Not a resource!" msgstr "" #: editor/property_editor.cpp +#, fuzzy msgid "Selected node is not a Viewport!" -msgstr "" +msgstr "Geselecteerde ..... is geen Aanzicht Portaal!" #: editor/property_editor.cpp #, fuzzy @@ -6718,7 +6710,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8258,10 +8250,10 @@ msgid "" "obtain a size. Otherwise, make it a RenderTarget and assign its internal " "texture to some node for display." msgstr "" -"Deze viewport is niet ingesteld als render target. Maak het een kind van een " -"Control zodat het een grootte kan ontvangen, als je de bedoeling hebt zijn " -"inhoud direct op het scherm te weergeven. Anders, maak er een RenderTarget " -"van en wijs zijn interne texture toe aan een node om te tonen." +"Dit Aanzicht Portaal is niet ingesteld als render target. Maak het een kind " +"van een Control zodat het een grootte kan ontvangen, als je de bedoeling " +"hebt zijn inhoud direct op het scherm te weergeven. Anders, maak er een " +"RenderTarget van en wijs zijn interne texture toe aan een node om te tonen." #: scene/resources/dynamic_font.cpp msgid "Error initializing FreeType." @@ -8280,6 +8272,13 @@ msgid "Invalid font size." msgstr "Ongeldige lettertype grootte." #, fuzzy +#~ msgid "Previous" +#~ msgstr "Vorig tabblad" + +#~ msgid "Next" +#~ msgstr "Volgende" + +#, fuzzy #~ msgid "Can't contain '/' or ':'" #~ msgstr "Kan niet verbinden met host:" @@ -8293,9 +8292,6 @@ msgstr "Ongeldige lettertype grootte." #~ msgid "Can't write file." #~ msgstr "Kan niet naar bestand schrijven." -#~ msgid "Next" -#~ msgstr "Volgende" - #~ msgid "Not found!" #~ msgstr "Niet gevonden!" @@ -8339,7 +8335,7 @@ msgstr "Ongeldige lettertype grootte." #, fuzzy #~ msgid "Setting '" -#~ msgstr "Aan Het Opzetten.." +#~ msgstr "Aan Het Opzetten..." #, fuzzy #~ msgid "Selection -> Duplicate" @@ -8390,8 +8386,8 @@ msgstr "Ongeldige lettertype grootte." #~ msgid "Exporting for %s" #~ msgstr "Aan het exporteren voor %s" -#~ msgid "Setting Up.." -#~ msgstr "Aan Het Opzetten.." +#~ msgid "Setting Up..." +#~ msgstr "Aan Het Opzetten..." #~ msgid "Re-Importing" #~ msgstr "Aan Het Herimporteren" diff --git a/editor/translations/pl.po b/editor/translations/pl.po index 3c8ee72cec..5ca2760249 100644 --- a/editor/translations/pl.po +++ b/editor/translations/pl.po @@ -2,7 +2,6 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # 8-bit Pixel <dawdejw@gmail.com>, 2016. # Adam Wolanski <adam.wolanski94@gmail.com>, 2017. # Adrian WÄ™cÅ‚awski <weclawskiadrian@gmail.com>, 2016. @@ -11,6 +10,7 @@ # Dariusz Król <rexioweb@gmail.com>, 2018. # heya10 <igor.gielzak@gmail.com>, 2017. # holistyczny interlokutor <jakubowesmieci@gmail.com>, 2017. +# Igor <igor.gielzak@gmail.com>, 2018. # Kajetan KuszczyÅ„ski <kajetanek99@gmail.com>, 2016. # Kamil Lewan <lewan.kamil@gmail.com>, 2016. # Karol Walasek <coreconviction@gmail.com>, 2016. @@ -19,16 +19,16 @@ # NeverK <neverkoxu@gmail.com>, 2018. # Rafal Brozio <rafal.brozio@gmail.com>, 2016. # RafaÅ‚ Ziemniak <synaptykq@gmail.com>, 2017. +# RM <synaptykq@gmail.com>, 2018. # Sebastian Krzyszkowiak <dos@dosowisko.net>, 2017. # Sebastian Pasich <sebastian.pasich@gmail.com>, 2017. # siatek papieros <sbigneu@gmail.com>, 2016. # Zatherz <zatherz@linux.pl>, 2017. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-23 15:40+0000\n" -"Last-Translator: Dariusz Król <rexioweb@gmail.com>\n" +"PO-Revision-Date: 2018-06-22 08:31+0000\n" +"Last-Translator: RM <synaptykq@gmail.com>\n" "Language-Team: Polish <https://hosted.weblate.org/projects/godot-engine/" "godot/pl/>\n" "Language: pl\n" @@ -36,7 +36,7 @@ msgstr "" "Content-Transfer-Encoding: 8-bit\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.0-dev\n" +"X-Generator: Weblate 3.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -48,19 +48,19 @@ msgstr "Wszystkie zaznaczenia" #: editor/animation_editor.cpp msgid "Anim Change Keyframe Time" -msgstr "ZmieÅ„ czas klatki kluczowej" +msgstr "Zmiana czasu klatki kluczowej" #: editor/animation_editor.cpp msgid "Anim Change Transition" -msgstr "Zmiana przejÅ›cia animacji" +msgstr "Zmiana przejÅ›cia" #: editor/animation_editor.cpp msgid "Anim Change Transform" -msgstr "Animacja transformacji" +msgstr "Zmiana transformacji" #: editor/animation_editor.cpp msgid "Anim Change Keyframe Value" -msgstr "ZmieÅ„ wartość klatki kluczowej" +msgstr "Zmiana wartoÅ›ci klatki kluczowej" #: editor/animation_editor.cpp msgid "Anim Change Call" @@ -84,7 +84,7 @@ msgstr "PrzesuÅ„ Å›cieżkÄ™ animacji w dół" #: editor/animation_editor.cpp msgid "Remove Anim Track" -msgstr "UsuÅ„ animacjÄ™" +msgstr "UsuÅ„ Å›cieżkÄ™ animacji" #: editor/animation_editor.cpp msgid "Set Transitions to:" @@ -92,7 +92,7 @@ msgstr "Ustaw przejÅ›cia na:" #: editor/animation_editor.cpp msgid "Anim Track Rename" -msgstr "ZmieÅ„ nazwÄ™ animacji" +msgstr "ZmieÅ„ nazwÄ™ Å›ciezki animacji" #: editor/animation_editor.cpp msgid "Anim Track Change Interpolation" @@ -517,7 +517,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Rozłącz '%s' z '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Połącz..." #: editor/connections_dialog.cpp @@ -937,11 +937,11 @@ msgid "Move Audio Bus" msgstr "Przemieść magistralÄ™ audio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Zapisz ukÅ‚ad magistrali audio jako..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "Lokalizacja nowego ukÅ‚adu..." #: editor/editor_audio_buses.cpp @@ -1078,12 +1078,12 @@ msgid "Updating Scene" msgstr "Aktualizowanie Sceny" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Zachowywanie lokalnych zmian.." +msgid "Storing local changes..." +msgstr "Zachowywanie lokalnych zmian..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Aktualizacja sceny .." +msgid "Updating scene..." +msgstr "Aktualizacja sceny ..." #: editor/editor_data.cpp msgid "[empty]" @@ -1151,7 +1151,7 @@ msgid "Show In File Manager" msgstr "Pokaż w menadżerze plików" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "Utwórz katalog..." #: editor/editor_file_dialog.cpp @@ -1343,9 +1343,8 @@ msgid "Description" msgstr "Opis" #: editor/editor_help.cpp -#, fuzzy msgid "Online Tutorials:" -msgstr "Poradniki" +msgstr "Poradniki online:" #: editor/editor_help.cpp #, fuzzy @@ -1354,8 +1353,9 @@ msgid "" "$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" "url][/color]." msgstr "" -"Obecnie nie ma opisu dla tej metody. Pomóż nam, [color=$color][url=" -"$url]wysyÅ‚ajÄ…c jÄ…[/url][/color]!" +"Obecnie nie ma żadnych samouczków dla tej klasy, możesz [color=$color][url=" +"$url]dodać jeden[/url][/kolor] lub [color=$color] [url=$url2]poprosić o " +"jeden[/url][/barl]." #: editor/editor_help.cpp msgid "Properties" @@ -1414,20 +1414,20 @@ msgstr "Wyczyść dane wyjÅ›ciowe" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Eksport projektu nie powiódÅ‚ siÄ™, kod błędu to %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Błąd podczas zapisu zasobu!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Zapisz zasób jako..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "WidzÄ™.." +msgid "I see..." +msgstr "WidzÄ™..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1657,12 +1657,12 @@ msgid "Open Base Scene" msgstr "Otwórz scenÄ™ bazowÄ…" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Szybkie otwieranie sceny.." +msgid "Quick Open Scene..." +msgstr "Szybkie otwieranie sceny..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Szybkie otwieranie skryptu.." +msgid "Quick Open Script..." +msgstr "Szybkie otwieranie skryptu..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1673,8 +1673,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Zapisać zmiany w '%s' przed zamkniÄ™ciem?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Zapisz scenÄ™ jako.." +msgid "Save Scene As..." +msgstr "Zapisz scenÄ™ jako..." #: editor/editor_node.cpp msgid "No" @@ -1725,8 +1725,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Tego nie można cofnąć. Przywrócić mimo to?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Szybkie uruchomienie sceny.." +msgid "Quick Run Scene..." +msgstr "Szybkie uruchomienie sceny..." #: editor/editor_node.cpp msgid "Quit" @@ -1884,7 +1884,7 @@ msgid "Previous tab" msgstr "Poprzednia zakÅ‚adka" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Filtrowanie plików..." #: editor/editor_node.cpp @@ -1896,12 +1896,12 @@ msgid "New Scene" msgstr "Nowa scena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Nowa scena dziedziczÄ…ca..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Otwórz scenÄ™.." +msgid "Open Scene..." +msgstr "Otwórz scenÄ™..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1920,15 +1920,15 @@ msgid "Open Recent" msgstr "Ostatnio otwierane" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Konwertuje na.." +msgid "Convert To..." +msgstr "Konwertuje na..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2114,7 +2114,7 @@ msgstr "SpoÅ‚eczność" #: editor/editor_node.cpp msgid "About" -msgstr "O programie" +msgstr "O silniku" #: editor/editor_node.cpp msgid "Play the project." @@ -2189,7 +2189,7 @@ msgid "Save the currently edited resource." msgstr "Zapisz aktualnie edytowany zasób." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Zapisz jako..." #: editor/editor_node.cpp @@ -2298,8 +2298,8 @@ msgid "Creating Mesh Previews" msgstr "Tworzenie podglÄ…du Mesh" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatura.." +msgid "Thumbnail..." +msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2452,8 +2452,8 @@ msgid "(Current)" msgstr "(Bieżący)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Pobieranie informacji o serwerach lustrzanych, proszÄ™ czekać.." +msgid "Retrieving mirrors, please wait..." +msgstr "Pobieranie informacji o serwerach lustrzanych, proszÄ™ czekać..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2530,8 +2530,8 @@ msgid "Error requesting url: " msgstr "Błąd podczas żądania adresu url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "ÅÄ…czenie z serwerem lustrzanym.." +msgid "Connecting to Mirror..." +msgstr "ÅÄ…czenie z serwerem lustrzanym..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2547,8 +2547,8 @@ msgstr "Nie można rozwiÄ…zać" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "ÅÄ…czenie.." +msgid "Connecting..." +msgstr "ÅÄ…czenie..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2560,7 +2560,7 @@ msgstr "Podłączony" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "Żądanie danych..." #: editor/export_template_manager.cpp @@ -2696,11 +2696,11 @@ msgid "Collapse all" msgstr "ZwiÅ„ foldery" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "ZmieÅ„ nazwÄ™..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "PrzenieÅ› Do..." #: editor/filesystem_dock.cpp @@ -2712,16 +2712,16 @@ msgid "Instance" msgstr "Instancja" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "Edytuj ZależnoÅ›ci..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Pokaż wÅ‚aÅ›cicieli.." +msgid "View Owners..." +msgstr "Pokaż wÅ‚aÅ›cicieli..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Duplikuj.." +msgid "Duplicate..." +msgstr "Duplikuj..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2746,7 +2746,7 @@ msgstr "Utwórz instancje wybranej sceny/scen jako dziecko wybranego wÄ™zÅ‚a." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Skanowanie plików,\n" "ProszÄ™ czekać..." @@ -2814,8 +2814,8 @@ msgid "Import Scene" msgstr "Importuj ScenÄ™" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Importowanie Sceny.." +msgid "Importing Scene..." +msgstr "Importowanie Sceny..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2826,7 +2826,7 @@ msgid "Generating for Mesh: " msgstr "Generowanie dla siatki: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "Uruchamiam skrypt..." #: editor/import/resource_importer_scene.cpp @@ -2844,8 +2844,8 @@ msgid "Error running post-import script:" msgstr "Błąd podczas uruchamiania skryptu po imporcie:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Zapisywanie.." +msgid "Saving..." +msgstr "Zapisywanie..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2864,8 +2864,8 @@ msgid "Import As:" msgstr "Importuj jako:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Ustawienie predefiniowane.." +msgid "Preset..." +msgstr "Ustawienie predefiniowane..." #: editor/import_dock.cpp msgid "Reimport" @@ -3081,12 +3081,11 @@ msgstr "Tryb Å‚usek cebuli" #: editor/plugins/animation_player_editor_plugin.cpp #, fuzzy msgid "Enable Onion Skinning" -msgstr "Włącz tryb Å‚usek cebuli" +msgstr "Włącz tryb warstw cebuli" #: editor/plugins/animation_player_editor_plugin.cpp -#, fuzzy msgid "Directions" -msgstr "Kategorie:" +msgstr "Kierunki" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Past" @@ -3290,16 +3289,16 @@ msgid "Transition Node" msgstr "WÄ™zeÅ‚ PrzejÅ›cia" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Zaimportuj animacje.." +msgid "Import Animations..." +msgstr "Zaimportuj animacje..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Edytuj filtry wÄ™złów" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Filtry.." +msgid "Filters..." +msgstr "Filtry..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3342,9 +3341,10 @@ msgid "Request failed, too many redirects" msgstr "Żądanie nieudane, zbyt dużo przekierowaÅ„" #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Bad download hash, assuming file has been tampered with." -msgstr "ZÅ‚y hash pobranego pliku. ZakÅ‚adamy, że ktoś przy nim majstrowaÅ‚." +msgstr "" +"ZÅ‚y hash pobranego pliku. ZakÅ‚adamy, że ktoś przy nim majstrowaÅ‚, lub zostaÅ‚ " +"niepoprawnie pobrany." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Expected:" @@ -3367,13 +3367,12 @@ msgid "Fetching:" msgstr "Pobieranie:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "RozwiÄ…zywanie..." #: editor/plugins/asset_library_editor_plugin.cpp -#, fuzzy msgid "Error making request" -msgstr "WystÄ…piÅ‚ błąd podczas tworzenia żądania" +msgstr "WystÄ…piÅ‚ błąd podczas żądania" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Idle" @@ -3435,8 +3434,8 @@ msgid "Site:" msgstr "ŹródÅ‚o:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Wsparcie.." +msgid "Support..." +msgstr "Wsparcie..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3475,9 +3474,8 @@ msgstr "" "jedynie do odczytu." #: editor/plugins/baked_lightmap_editor_plugin.cpp -#, fuzzy msgid "Bake Lightmaps" -msgstr "Wypal Lightmaps" +msgstr "Stwórz Lightmaps" #: editor/plugins/camera_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp @@ -3585,9 +3583,8 @@ msgstr "" "poruszania)." #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Alt+RMB: Depth list selection" -msgstr "Alt+PPM: Lista wyboru głębi" +msgstr "Alt + RMB: Głębokość listy" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Mode" @@ -3635,8 +3632,9 @@ msgid "Use Rotation Snap" msgstr "Użyj kroków obrotu" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "Konfiguruj przyciÄ…ganie.." +msgstr "Konfiguruj przyciÄ…ganie..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3731,14 +3729,12 @@ msgid "Show Guides" msgstr "Pokaż prowadnice" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "Pokaż pozycjÄ™ poczÄ…tkowÄ…" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 widok" +msgstr "Pokaż widok" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3782,11 +3778,11 @@ msgstr "Ustaw pivot w pozycji myszy" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Multiply grid step by 2" -msgstr "" +msgstr "Podwój wielkość siatki" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Divide grid step by 2" -msgstr "" +msgstr "Zmniejsz wielkość siatki dwukrotnie" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Add %s" @@ -3802,7 +3798,7 @@ msgstr "Ok" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Cannot instantiate multiple nodes without root." -msgstr "" +msgstr "Nie można utworzyć wielu wezłów bez wÄ™zÅ‚a głównego." #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp @@ -3858,11 +3854,11 @@ msgstr "Aktualizuj ze sceny" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat0" -msgstr "" +msgstr "Flat0" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat1" -msgstr "" +msgstr "Flat1" #: editor/plugins/curve_editor_plugin.cpp msgid "Ease in" @@ -4042,7 +4038,6 @@ msgid "Could not create outline!" msgstr "Nie udaÅ‚o siÄ™ utworzyć zarysu!" #: editor/plugins/mesh_instance_editor_plugin.cpp -#, fuzzy msgid "Create Outline" msgstr "Utwórz zarys" @@ -4067,8 +4062,8 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Utwórz siatkÄ™ zarysu.." +msgid "Create Outline Mesh..." +msgstr "Utwórz siatkÄ™ zarysu..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4282,8 +4277,8 @@ msgid "Error loading image:" msgstr "Błąd wczytywania obrazu:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Brak pikseli z przeźroczystoÅ›ciÄ… > 128 w obrazie.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Brak pikseli z przeźroczystoÅ›ciÄ… > 128 w obrazie..." #: editor/plugins/particles_2d_editor_plugin.cpp #, fuzzy @@ -4653,8 +4648,8 @@ msgid "Import Theme" msgstr "Zaimportuj motyw" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Zapisz motyw jako.." +msgid "Save Theme As..." +msgstr "Zapisz motyw jako..." #: editor/plugins/script_editor_plugin.cpp #, fuzzy @@ -4752,8 +4747,8 @@ msgstr "Przełącz panel skryptów" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Znajdź.." +msgid "Find..." +msgstr "Znajdź..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4963,16 +4958,16 @@ msgid "Find Previous" msgstr "Znajdź poprzedni" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "ZamieÅ„.." +msgid "Replace..." +msgstr "ZamieÅ„..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Przejdź do funkcji.." +msgid "Goto Function..." +msgstr "Przejdź do funkcji..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Przejdź do linii.." +msgid "Goto Line..." +msgstr "Przejdź do linii..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5061,11 +5056,11 @@ msgstr "" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Add/Remove to Curve Map" -msgstr "" +msgstr "Dodaj/UsuÅ„ do mapy krzywej" #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Modify Curve Map" -msgstr "" +msgstr "Edytuj mape krzywej" #: editor/plugins/shader_graph_editor_plugin.cpp #, fuzzy @@ -5299,34 +5294,31 @@ msgstr "Efekt Dopplera" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Left" -msgstr "" +msgstr "\"Wolny widok\" w lewo" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Right" -msgstr "" +msgstr "\"Wolny widok\" w prawo" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Freelook Forward" -msgstr "Dalej" +msgstr "\"Wolny widok\" w przód" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Freelook Backwards" -msgstr "Wstecz" +msgstr "\"Wolny widok\" w tyÅ‚" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Up" -msgstr "" +msgstr "\"Wolny widok\" w góre" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Freelook Down" -msgstr "Kółko myszy w dół." +msgstr "\"Wolny widok\" w dół" #: editor/plugins/spatial_editor_plugin.cpp msgid "Freelook Speed Modifier" -msgstr "" +msgstr "Zmiennik prÄ™dkoÅ›ci \"Wolnego widoku\"" #: editor/plugins/spatial_editor_plugin.cpp msgid "XForm Dialog" @@ -5342,6 +5334,9 @@ msgid "" "Alt+Drag: Move\n" "Alt+RMB: Depth list selection" msgstr "" +"PociÄ…gniÄ™cie: Obrót\n" +"Alt+PociÄ…gniÄ™cie: Poruszenie\n" +"Alt+PPM: Lista wyboru głębi" #: editor/plugins/spatial_editor_plugin.cpp msgid "Move Mode (W)" @@ -5357,7 +5352,7 @@ msgstr "Tryb skalowania (R)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Coords" -msgstr "Koordynaty lokalne" +msgstr "Local Coords" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Space Mode (%s)" @@ -5440,12 +5435,8 @@ msgid "Transform" msgstr "PrzeksztaÅ‚canie" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Konfiguruj krokowanie.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Okno transformowania.." +msgid "Transform Dialog..." +msgstr "Okno transformowania..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5699,8 +5690,8 @@ msgid "Remove All" msgstr "UsuÅ„ wszystkie" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Edytuj motyw interfejsu.." +msgid "Edit theme..." +msgstr "Edytuj motyw interfejsu..." #: editor/plugins/theme_editor_plugin.cpp #, fuzzy @@ -5772,7 +5763,7 @@ msgstr "Opcje" #: editor/plugins/theme_editor_plugin.cpp #, fuzzy -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "Ma,Wiele,Różnych,Opcji!" #: editor/plugins/theme_editor_plugin.cpp @@ -5965,8 +5956,8 @@ msgid "Presets" msgstr "Profile eksportu" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Dodaj.." +msgid "Add..." +msgstr "Dodaj..." #: editor/project_export.cpp msgid "Resources" @@ -6060,12 +6051,17 @@ msgid "Imported Project" msgstr "Zaimportowano projekt" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "Nazwa projektu:" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Nie można utworzyć katalogu." #: editor/project_manager.cpp msgid "There is already a folder in this path with the specified name." -msgstr "" +msgstr "Folder o podanej nazwie istnieje już w tej lokalizacji." #: editor/project_manager.cpp msgid "It would be a good idea to name your project." @@ -6256,10 +6252,13 @@ msgid "Mouse Button" msgstr "Przycisk myszy" #: editor/project_settings_editor.cpp +#, fuzzy msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Niepoprawna nazwa akcji. Nazwa nie może być pusta ani zawierać znaki takie " +"jak: '/', ':', '=', '\\' lub '\"'" #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6286,8 +6285,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "NaciÅ›nij klawisz.." +msgid "Press a Key..." +msgstr "NaciÅ›nij klawisz..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6359,7 +6358,7 @@ msgstr "UrzÄ…dzenie" #: editor/project_settings_editor.cpp msgid "Button" -msgstr "Przycisk" +msgstr "Button" #: editor/project_settings_editor.cpp msgid "Left Button." @@ -6463,15 +6462,15 @@ msgstr "Ustawienia projektu (project.godot)" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "Ogólny" +msgstr "Ogólne" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "WÅ‚aÅ›ciwość:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Nadpisz dla.." +msgid "Override For..." +msgstr "Nadpisz dla..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6566,12 +6565,12 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." -msgstr "Plik.." +msgid "File..." +msgstr "Plik..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Katalog.." +msgid "Dir..." +msgstr "Katalog..." #: editor/property_editor.cpp msgid "Assign" @@ -6682,7 +6681,7 @@ msgstr "Aktualna scena" #: editor/run_settings_dialog.cpp msgid "Main Scene" -msgstr "Główna scena" +msgstr "Scena główna" #: editor/run_settings_dialog.cpp msgid "Main Scene Arguments:" @@ -6710,6 +6709,7 @@ msgid "" "Cannot instance the scene '%s' because the current scene exists within one " "of its nodes." msgstr "" +"Nie można utworzyć sceny '%s' ponieważ obecna scena jest jednym z jej wezłów." #: editor/scene_tree_dock.cpp #, fuzzy @@ -6747,7 +6747,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Tej operacji nie można wykonać na dziedziczÄ…cej scenie." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Zapisz nowÄ… scenÄ™ jako ..." #: editor/scene_tree_dock.cpp @@ -7197,7 +7197,7 @@ msgstr "Skróty" #: editor/settings_config_dialog.cpp msgid "Binding" -msgstr "" +msgstr "WiÄ…zanie" #: editor/spatial_editor_gizmos.cpp msgid "Change Light Radius" @@ -7475,7 +7475,7 @@ msgstr "Wybierz odlegÅ‚ość:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Nazwa klasy nie może być sÅ‚owem zastrzeżonym" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -7483,7 +7483,7 @@ msgstr "Generowanie solucji..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating C# project..." -msgstr "" +msgstr "Generowanie projektu C#..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Failed to create solution." @@ -7507,7 +7507,7 @@ msgstr "Mono" #: modules/mono/editor/godotsharp_editor.cpp msgid "About C# support" -msgstr "" +msgstr "O wsparciu jÄ™zyka C#" #: modules/mono/editor/godotsharp_editor.cpp msgid "Create C# solution" @@ -7708,7 +7708,7 @@ msgstr "Przełącznik" #: modules/visual_script/visual_script_editor.cpp msgid "Iterator" -msgstr "" +msgstr "Iterator" #: modules/visual_script/visual_script_editor.cpp msgid "While" @@ -7930,6 +7930,10 @@ msgid "" "Consider adding CollisionShape2D or CollisionPolygon2D children nodes to " "define its shape." msgstr "" +"Ten wÄ™zeÅ‚ nie posiada podwezÅ‚a, który definiowaÅ‚ by jego ksztaÅ‚t, wiÄ™c nie " +"może wchodzić w interakcje.\n" +"PowinieneÅ› dodać wÄ™zeÅ‚ \"CollisionShape2D\" lub \"CollisionPolygon2D\" jako " +"podwÄ™zeÅ‚ aby zdefiniować ksztaÅ‚t." #: scene/2d/collision_polygon_2d.cpp msgid "" @@ -8009,6 +8013,8 @@ msgid "" "A material to process the particles is not assigned, so no behavior is " "imprinted." msgstr "" +"Nie przypisano materiaÅ‚u do przetwarzania czÄ…steczek, wiÄ™c zmiany nie bÄ™dÄ… " +"widoczne." #: scene/2d/path_2d.cpp msgid "PathFollow2D only works when set as a child of a Path2D node." @@ -8051,6 +8057,8 @@ msgid "" "The controller id must not be 0 or this controller will not be bound to an " "actual controller" msgstr "" +"Id kontrolera nie może być '0' w innym przypadku kontroler nie zostanie " +"przypisany do żadnego rzeczywistego kontrolera" #: scene/3d/arvr_nodes.cpp #, fuzzy @@ -8073,7 +8081,7 @@ msgstr "" #: scene/3d/baked_lightmap.cpp msgid "(Time Left: %d:%02d s)" -msgstr "" +msgstr "(PozostaÅ‚y czas: %d:%02d s)" #: scene/3d/baked_lightmap.cpp msgid "Plotting Meshes: " @@ -8284,6 +8292,13 @@ msgstr "Błąd Å‚adowania fonta." msgid "Invalid font size." msgstr "Niepoprawny rozmiar fonta." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Poprzednia zakÅ‚adka" + +#~ msgid "Next" +#~ msgstr "NastÄ™pny" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "NieprawidÅ‚owa akcja (wszystko oprócz '/' lub ':')." @@ -8309,9 +8324,6 @@ msgstr "Niepoprawny rozmiar fonta." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Nie znaleziono project.godot w Å›cieżce projektu." -#~ msgid "Next" -#~ msgstr "NastÄ™pny" - #~ msgid "Not found!" #~ msgstr "Nie znaleziono!" @@ -8450,8 +8462,8 @@ msgstr "Niepoprawny rozmiar fonta." #~ msgid "Exporting for %s" #~ msgstr "Exportowanie do %s" -#~ msgid "Setting Up.." -#~ msgstr "Konfigurowanie .." +#~ msgid "Setting Up..." +#~ msgstr "Konfigurowanie ..." #~ msgid "Error loading scene." #~ msgstr "Błąd Å‚adowania sceny." @@ -8509,8 +8521,8 @@ msgstr "Niepoprawny rozmiar fonta." #~ msgid "Info" #~ msgstr "Informacje" -#~ msgid "Re-Import.." -#~ msgstr "Importuj ponownie.." +#~ msgid "Re-Import..." +#~ msgstr "Importuj ponownie..." #~ msgid "No bit masks to import!" #~ msgstr "Brak mask bitowych do zaimportowania!" @@ -8883,13 +8895,13 @@ msgstr "Niepoprawny rozmiar fonta." #~ msgid "Zoom (%):" #~ msgstr "PowiÄ™kszenie (%):" -#~ msgid "Skeleton.." -#~ msgstr "Szkielet.." +#~ msgid "Skeleton..." +#~ msgstr "Szkielet..." #~ msgid "Zoom Reset" #~ msgstr "Wyzeruj przybliżenie" -#~ msgid "Zoom Set.." +#~ msgid "Zoom Set..." #~ msgstr "Ustaw przybliżenie..." #~ msgid "Set a Value" @@ -9238,8 +9250,8 @@ msgstr "Niepoprawny rozmiar fonta." #~ msgid "Export Project PCK" #~ msgstr "Eksport projektu PCK" -#~ msgid "Export.." -#~ msgstr "Eksport.." +#~ msgid "Export..." +#~ msgstr "Eksport..." #~ msgid "Project Export" #~ msgstr "Eksport projektu" diff --git a/editor/translations/pr.po b/editor/translations/pr.po index 94856aa875..0c085024e0 100644 --- a/editor/translations/pr.po +++ b/editor/translations/pr.po @@ -500,7 +500,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -915,11 +915,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1055,11 +1055,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1129,7 +1129,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1394,12 +1394,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1606,11 +1606,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1622,7 +1622,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1674,7 +1674,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1819,7 +1819,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1831,11 +1831,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1855,15 +1855,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2109,7 +2109,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2218,7 +2218,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2370,7 +2370,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2447,7 +2447,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2464,7 +2464,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2479,7 +2479,7 @@ msgstr "Slit th' Node" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2618,11 +2618,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2634,15 +2634,15 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "" #: editor/filesystem_dock.cpp @@ -2668,7 +2668,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2734,7 +2734,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2746,7 +2746,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2762,7 +2762,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2782,7 +2782,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3199,7 +3199,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3207,7 +3207,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3275,7 +3275,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3342,7 +3342,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3532,6 +3532,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3957,7 +3958,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4162,7 +4163,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4527,7 +4528,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4625,7 +4626,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4833,15 +4834,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5296,11 +5297,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5556,7 +5553,7 @@ msgid "Remove All" msgstr "Discharge ye' Signal" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5624,7 +5621,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5813,7 +5810,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5903,6 +5900,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "Yer index property name be thrown overboard!" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6091,8 +6093,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6120,7 +6122,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6306,7 +6308,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6403,11 +6405,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6580,7 +6582,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/pt_BR.po b/editor/translations/pt_BR.po index 3ad4798ca0..6d26cbc500 100644 --- a/editor/translations/pt_BR.po +++ b/editor/translations/pt_BR.po @@ -2,14 +2,14 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Allyson Souza <allyson_as@outlook.com>, 2017. # Anderson Araujo <anderson.araujoprog@gmail.com>, 2018. # António Sarmento <antonio.luis.sarmento@gmail.com>, 2016. # AWK <arthurwk1800@gmail.com>, 2017. +# Breno Caldeira <breno.caldeira@gmail.com>, 2018. # Francesco Perrotti-Garcia <fpg1503@gmail.com>, 2017. # George Marques <george@gmarqu.es>, 2016. -# Guilherme Felipe C G Silva <guilhermefelipecgs@gmail.com>, 2017. +# Guilherme Felipe C G Silva <guilhermefelipecgs@gmail.com>, 2017, 2018. # João Victor Lima <victordevtb@outlook.com>, 2018. # João Vitor de Oliveira Carlos <lopogax@gmail.com>, 2018. # Joaquim Ferreira <joaquimferreira1996@bol.com.br>, 2016. @@ -23,12 +23,11 @@ # Renato Rotenberg <renato.rotenberg@gmail.com>, 2017. # Rodolfo R Gomes <rodolforg@gmail.com>, 2017-2018. # Tiago Almeida <thyagoeap@gmail.com>, 2017. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: 2016-05-30\n" -"PO-Revision-Date: 2018-04-30 13:39+0000\n" +"PO-Revision-Date: 2018-06-16 18:43+0000\n" "Last-Translator: Rodolfo R Gomes <rodolforg@gmail.com>\n" "Language-Team: Portuguese (Brazil) <https://hosted.weblate.org/projects/" "godot-engine/godot/pt_BR/>\n" @@ -37,7 +36,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -517,13 +516,13 @@ msgid "Disconnect '%s' from '%s'" msgstr "Desconectar '%s' do '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Conectar..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Disconnect" -msgstr "Disconectar" +msgstr "Desconectar" #: editor/connections_dialog.cpp editor/editor_help.cpp editor/node_dock.cpp msgid "Signals" @@ -938,12 +937,12 @@ msgid "Move Audio Bus" msgstr "Mover Canal de Ãudio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Salvar Layout de Canais de Ãudio Como..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Localização para o Novo Layout.." +msgid "Location for New Layout..." +msgstr "Localização para o Novo Layout..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1081,11 +1080,11 @@ msgid "Updating Scene" msgstr "Atualizando Cena" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Armazenando alterações locais..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "Atualizando Cena..." #: editor/editor_data.cpp @@ -1154,7 +1153,7 @@ msgid "Show In File Manager" msgstr "Mostrar no Gerenciador de Arquivos" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "Nova Pasta..." #: editor/editor_file_dialog.cpp @@ -1416,19 +1415,19 @@ msgstr "Limpar SaÃda" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Falha na exportação do projeto com código de erro %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Erro ao salvar Recurso!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Salvar Recuso como..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Entendo..." #: editor/editor_node.cpp @@ -1658,11 +1657,11 @@ msgid "Open Base Scene" msgstr "Abrir Cena Base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "Abrir Cena Rapidamente..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "Abrir Script Rapidamente..." #: editor/editor_node.cpp @@ -1674,7 +1673,7 @@ msgid "Save changes to '%s' before closing?" msgstr "Salvar alterações em '%s' antes de fechar?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "Salvar Cena Como..." #: editor/editor_node.cpp @@ -1726,7 +1725,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Esta ação não pode ser desfeita. Reverter mesmo assim?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "Rodar Cena Ãgil..." #: editor/editor_node.cpp @@ -1887,8 +1886,8 @@ msgid "Previous tab" msgstr "Guia anterior" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrar Arquivos.." +msgid "Filter Files..." +msgstr "Filtrar Arquivos..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1899,11 +1898,11 @@ msgid "New Scene" msgstr "Nova Cena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Nova Cena Herdada..." #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "Abrir Cena..." #: editor/editor_node.cpp @@ -1923,15 +1922,15 @@ msgid "Open Recent" msgstr "Abrir Recente" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "Converter Para..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -1994,7 +1993,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Small Deploy with Network FS" -msgstr "Pequeno teste com o sistema de arquivos em rede" +msgstr "Pequena DIstribuição com Sistema de Arquivos de Rede" #: editor/editor_node.cpp msgid "" @@ -2195,7 +2194,7 @@ msgid "Save the currently edited resource." msgstr "Salva o recurso editado atualmente." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Salvar Como..." #: editor/editor_node.cpp @@ -2304,7 +2303,7 @@ msgid "Creating Mesh Previews" msgstr "Criando Previsualizações das Malhas" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp @@ -2458,7 +2457,7 @@ msgid "(Current)" msgstr "(Atual)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Reconectando, por favor aguarde." #: editor/export_template_manager.cpp @@ -2536,7 +2535,7 @@ msgid "Error requesting url: " msgstr "Erro ao solicitar url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "Conectando..." #: editor/export_template_manager.cpp @@ -2553,8 +2552,8 @@ msgstr "Não foi possÃvel resolver" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Conectando.." +msgid "Connecting..." +msgstr "Conectando..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2566,8 +2565,8 @@ msgstr "Conectado" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Solicitando.." +msgid "Requesting..." +msgstr "Solicitando..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2702,11 +2701,11 @@ msgid "Collapse all" msgstr "Recolher tudo" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "Renomear..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Mover Para..." #: editor/filesystem_dock.cpp @@ -2718,15 +2717,15 @@ msgid "Instance" msgstr "Instância" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Editar Dependências.." +msgid "Edit Dependencies..." +msgstr "Editar Dependências..." #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Visualizar Proprietários..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Duplicar..." #: editor/filesystem_dock.cpp @@ -2752,7 +2751,7 @@ msgstr "Instanciar a(s) cena(s) selecionada como filho do nó selecionado." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Escaneando arquivos,\n" "Por favor aguarde..." @@ -2820,7 +2819,7 @@ msgid "Import Scene" msgstr "Importar cena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "Importando Cena..." #: editor/import/resource_importer_scene.cpp @@ -2832,7 +2831,7 @@ msgid "Generating for Mesh: " msgstr "Generando para a Malha: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "Rodando Script Personalizado..." #: editor/import/resource_importer_scene.cpp @@ -2848,7 +2847,7 @@ msgid "Error running post-import script:" msgstr "Erro ao rodar script pós-importação:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Salvando..." #: editor/import_dock.cpp @@ -2868,7 +2867,7 @@ msgid "Import As:" msgstr "Importar como:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "Predefinição..." #: editor/import_dock.cpp @@ -3289,7 +3288,7 @@ msgid "Transition Node" msgstr "Nó Transition" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "Importar Animações..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3297,7 +3296,7 @@ msgid "Edit Node Filters" msgstr "Editar Filtros de Nó" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Filtros..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3306,7 +3305,7 @@ msgstr "AnimationTree" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Free" -msgstr "Livrar" +msgstr "Livre" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Contents:" @@ -3365,7 +3364,7 @@ msgid "Fetching:" msgstr "Procurando:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "Resolvendo..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3432,8 +3431,8 @@ msgid "Site:" msgstr "Site:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Suporte.." +msgid "Support..." +msgstr "Suporte..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3630,8 +3629,9 @@ msgid "Use Rotation Snap" msgstr "Usar Snap de Rotação" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "Configurar Encaixe..." +msgstr "Configurar Snap..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3726,14 +3726,12 @@ msgid "Show Guides" msgstr "Mostrar guias" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Ver Origem" +msgstr "Mostrar Origem" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Viewport" +msgstr "Mostrar Viewport" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4026,7 +4024,7 @@ msgstr "Malha não tem superfÃcie para criar contornos dela!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Tipo primitivo da Mesh não é PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4057,8 +4055,8 @@ msgid "Create Convex Collision Sibling" msgstr "Criar Colisão Convexa Irmã" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Criar Malha de Contorno.." +msgid "Create Outline Mesh..." +msgstr "Criar Malha de Contorno..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4265,7 +4263,7 @@ msgid "Error loading image:" msgstr "Erro ao carregar imagem:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "Nenhum pixel com transparência > 128 na imagem." #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4626,7 +4624,7 @@ msgid "Import Theme" msgstr "Importar Tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Salvar Tema Como..." #: editor/plugins/script_editor_plugin.cpp @@ -4723,7 +4721,7 @@ msgstr "Alternar Painel de Scripts" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "Localizar..." #: editor/plugins/script_editor_plugin.cpp @@ -4933,15 +4931,15 @@ msgid "Find Previous" msgstr "Encontrar Anterior" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "Substituir..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "Ir para Função..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "Ir para linha..." #: editor/plugins/script_text_editor.cpp @@ -5395,11 +5393,7 @@ msgid "Transform" msgstr "Transformação" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configurar Snap..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "Diálogo Transformação..." #: editor/plugins/spatial_editor_plugin.cpp @@ -5652,8 +5646,8 @@ msgid "Remove All" msgstr "Remover Tudo" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Editar tema.." +msgid "Edit theme..." +msgstr "Editar tema..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5681,11 +5675,11 @@ msgstr "Criar a Partir do Tema Atual do Editor" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio1" -msgstr "Rádio Checkbox 1" +msgstr "CheckBox Rádio1" #: editor/plugins/theme_editor_plugin.cpp msgid "CheckBox Radio2" -msgstr "Caixa de Seleção 2" +msgstr "CheckBox Rádio2" #: editor/plugins/theme_editor_plugin.cpp msgid "Item" @@ -5693,21 +5687,19 @@ msgstr "Item" #: editor/plugins/theme_editor_plugin.cpp msgid "Check Item" -msgstr "Checar Item" +msgstr "Item Marcável" #: editor/plugins/theme_editor_plugin.cpp msgid "Checked Item" msgstr "Item Checado" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Adicionar Item" +msgstr "Item Rádio" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Item Checado" +msgstr "Item Rádio Marcado" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5715,15 +5707,15 @@ msgstr "Tem" #: editor/plugins/theme_editor_plugin.cpp msgid "Many" -msgstr "Muitos" +msgstr "Muitas" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp msgid "Options" msgstr "Opções" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Ter,Muitas,Várias,Opções!" +msgid "Has,Many,Options" +msgstr "Tem,Muitas,Opções" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5755,7 +5747,7 @@ msgstr "Fonte" #: editor/plugins/theme_editor_plugin.cpp msgid "Color" -msgstr "Color" +msgstr "Cor" #: editor/plugins/theme_editor_plugin.cpp msgid "Theme" @@ -5916,7 +5908,7 @@ msgid "Presets" msgstr "Predefiniçoes" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Adicionar..." #: editor/project_export.cpp @@ -6012,6 +6004,10 @@ msgid "Imported Project" msgstr "Projeto Importado" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Nome do Projeto Inválido." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "ImpossÃvel criar a pasta." @@ -6212,9 +6208,11 @@ msgstr "Botão do Mous" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Nome de ação inválido. Ele não pode estar vazio ou conter '/', ':', '=', " +"'\\' ou '\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6241,7 +6239,7 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "Pressione uma Tecla..." #: editor/project_settings_editor.cpp @@ -6425,7 +6423,7 @@ msgid "Property:" msgstr "Propriedade:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "Sobrescrever Para..." #: editor/project_settings_editor.cpp @@ -6521,11 +6519,11 @@ msgid "Easing Out-In" msgstr "Easing Out-In" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "Arquivo..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "Dir..." #: editor/property_editor.cpp @@ -6698,7 +6696,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Essa operação não pode ser realizada em cenas instanciadas." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Salvar Nova Cena Como..." #: editor/scene_tree_dock.cpp @@ -7415,7 +7413,7 @@ msgstr "Escolha uma Distância:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Nome da classe não pode ser uma palavra reservada" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8129,7 +8127,7 @@ msgstr "A propriedade Caminho deve apontar para um nó Spatial para funcionar." #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment precisa de um recurso Environment." #: scene/3d/scenario_fx.cpp msgid "" @@ -8143,6 +8141,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Este WorldEnvironment está sendo ignorado. Adicione uma Camera (para cenas " +"3D) ou defina o Background Mode deste ambiente para Canvas (para cenas 2D)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8240,6 +8240,13 @@ msgstr "Erro ao carregar fonte." msgid "Invalid font size." msgstr "Tamanho de fonte inválido." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Guia anterior" + +#~ msgid "Next" +#~ msgstr "Próximo" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Ação Inválida (qualquer coisa serve, exceto '/' ou ':')." @@ -8266,9 +8273,6 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Não foi possÃvel encontrar project.godot no caminho do projeto." -#~ msgid "Next" -#~ msgstr "Próximo" - #~ msgid "Not found!" #~ msgstr "Não encontrado!" @@ -8414,7 +8418,7 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Exporting for %s" #~ msgstr "Exportando para %s" -#~ msgid "Setting Up.." +#~ msgid "Setting Up..." #~ msgstr "Ajustando..." #~ msgid "Error loading scene." @@ -8476,7 +8480,7 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Info" #~ msgstr "Informação" -#~ msgid "Re-Import.." +#~ msgid "Re-Import..." #~ msgstr "Re-importar..." #~ msgid "No bit masks to import!" @@ -8871,13 +8875,13 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Zoom (%):" #~ msgstr "Ampliação (%):" -#~ msgid "Skeleton.." +#~ msgid "Skeleton..." #~ msgstr "Esqueleto..." #~ msgid "Zoom Reset" #~ msgstr "Restaurar Ampliação" -#~ msgid "Zoom Set.." +#~ msgid "Zoom Set..." #~ msgstr "Definir Ampliação..." #~ msgid "Set a Value" @@ -9297,7 +9301,7 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Export Project PCK" #~ msgstr "Exportar PCK do Projeto" -#~ msgid "Export.." +#~ msgid "Export..." #~ msgstr "Exportar..." #~ msgid "Project Export" @@ -9404,7 +9408,7 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Reload Tool Script (Soft)" #~ msgstr "Recarregar Tool Script (suave)" -#~ msgid "Edit Connections.." +#~ msgid "Edit Connections..." #~ msgstr "Editar Conexões..." #~ msgid "Set Params" @@ -9461,5 +9465,5 @@ msgstr "Tamanho de fonte inválido." #~ msgid "Source Texture:" #~ msgstr "Textura de Origem:" -#~ msgid "Merging.." +#~ msgid "Merging..." #~ msgstr "Fundindo..." diff --git a/editor/translations/pt_PT.po b/editor/translations/pt_PT.po index 84e80718da..71275cd19a 100644 --- a/editor/translations/pt_PT.po +++ b/editor/translations/pt_PT.po @@ -2,9 +2,9 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # António Sarmento <antonio.luis.sarmento@gmail.com>, 2016. # Carlos Vieira <carlos.vieira@gmail.com>, 2017. +# João <joao@nogordio.com>, 2018. # João Graça <jgraca95@gmail.com>, 2017. # João Lopes <linux-man@hotmail.com>, 2017-2018. # Miguel Gomes <miggas09@gmail.com>, 2017. @@ -13,11 +13,10 @@ # Rueben Stevens <supercell03@gmail.com>, 2017. # SARDON <fabio3_Santos@hotmail.com>, 2017. # Vinicius Gonçalves <viniciusgoncalves21@gmail.com>, 2017. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-25 09:40+0000\n" +"PO-Revision-Date: 2018-06-10 01:02+0000\n" "Last-Translator: João Lopes <linux-man@hotmail.com>\n" "Language-Team: Portuguese (Portugal) <https://hosted.weblate.org/projects/" "godot-engine/godot/pt_PT/>\n" @@ -25,7 +24,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.0-dev\n" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -505,7 +504,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Desligar '%s' de '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Ligar..." #: editor/connections_dialog.cpp @@ -926,12 +925,12 @@ msgid "Move Audio Bus" msgstr "Mover barramento de áudio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Guardar Modelo do barramento de áudio como.." +msgid "Save Audio Bus Layout As..." +msgstr "Guardar Modelo do barramento de áudio como..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Localização para o Novo Modelo.." +msgid "Location for New Layout..." +msgstr "Localização para o Novo Modelo..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1072,12 +1071,12 @@ msgid "Updating Scene" msgstr "Atualizando a Cena" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Armazenando alterações locais.." +msgid "Storing local changes..." +msgstr "Armazenando alterações locais..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Atualizando a Cena.." +msgid "Updating scene..." +msgstr "Atualizando a Cena..." #: editor/editor_data.cpp msgid "[empty]" @@ -1145,8 +1144,8 @@ msgid "Show In File Manager" msgstr "Mostrar no Gestor de Ficheiros" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Nova Diretoria.." +msgid "New Folder..." +msgstr "Nova Diretoria..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1407,20 +1406,20 @@ msgstr "Limpar SaÃda" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Exportação do projeto falhou com código de erro %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Erro ao guardar recurso!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Guardar Recurso Como.." +msgid "Save Resource As..." +msgstr "Guardar Recurso Como..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Eu vejo.." +msgid "I see..." +msgstr "Eu vejo..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1653,12 +1652,12 @@ msgid "Open Base Scene" msgstr "Abrir Cena Base" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Abrir Cena de forma rápida.." +msgid "Quick Open Scene..." +msgstr "Abrir Cena de forma rápida..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Abrir Script de forma rápida.." +msgid "Quick Open Script..." +msgstr "Abrir Script de forma rápida..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1669,8 +1668,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Guardar alterações a '%s' antes de fechar?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Guardar Cena como.." +msgid "Save Scene As..." +msgstr "Guardar Cena como..." #: editor/editor_node.cpp msgid "No" @@ -1721,8 +1720,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Esta acção não pode ser desfeita. Reverter na mesma?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Executar Cena de forma rápida.." +msgid "Quick Run Scene..." +msgstr "Executar Cena de forma rápida..." #: editor/editor_node.cpp msgid "Quit" @@ -1878,8 +1877,8 @@ msgid "Previous tab" msgstr "Guia anterior" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrar Ficheiro.." +msgid "Filter Files..." +msgstr "Filtrar Ficheiro..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1890,12 +1889,12 @@ msgid "New Scene" msgstr "Nova Cena" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Nova Cena Herdada.." +msgid "New Inherited Scene..." +msgstr "Nova Cena Herdada..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Abrir Cena.." +msgid "Open Scene..." +msgstr "Abrir Cena..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1914,16 +1913,16 @@ msgid "Open Recent" msgstr "Abrir Recente" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Converter Para.." +msgid "Convert To..." +msgstr "Converter Para..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2110,7 +2109,7 @@ msgstr "Comunidade" #: editor/editor_node.cpp msgid "About" -msgstr "Sobre" +msgstr "Sobre Nós" #: editor/editor_node.cpp msgid "Play the project." @@ -2185,8 +2184,8 @@ msgid "Save the currently edited resource." msgstr "Guarde o recurso editado." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Guardar Como.." +msgid "Save As..." +msgstr "Guardar Como..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2294,8 +2293,8 @@ msgid "Creating Mesh Previews" msgstr "A criar pré-visualizações de Mesh" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Miniatura.." +msgid "Thumbnail..." +msgstr "Miniatura..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2447,7 +2446,7 @@ msgid "(Current)" msgstr "(Atual)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "A readquirir servidores, espere por favor..." #: editor/export_template_manager.cpp @@ -2525,7 +2524,7 @@ msgid "Error requesting url: " msgstr "Erro ao solicitar url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "A ligar ao servidor..." #: editor/export_template_manager.cpp @@ -2542,8 +2541,8 @@ msgstr "ImpossÃvel resolver" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "A ligar.." +msgid "Connecting..." +msgstr "A ligar..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2555,7 +2554,7 @@ msgstr "Ligado" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "A solicitar..." #: editor/export_template_manager.cpp @@ -2691,12 +2690,12 @@ msgid "Collapse all" msgstr "Colapsar tudo" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Renomear.." +msgid "Rename..." +msgstr "Renomear..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Mover para.." +msgid "Move To..." +msgstr "Mover para..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2707,15 +2706,15 @@ msgid "Instance" msgstr "Instância" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Editar Dependências.." +msgid "Edit Dependencies..." +msgstr "Editar Dependências..." #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Ver proprietários..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Duplicar..." #: editor/filesystem_dock.cpp @@ -2741,7 +2740,7 @@ msgstr "Instancie a(s) Cena(s) selecionada(s) como filha(s) do Nó selecionado." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "A analisar Ficheiros,\n" "Espere, por favor..." @@ -2809,8 +2808,8 @@ msgid "Import Scene" msgstr "Importar Cena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "A importar Cena.." +msgid "Importing Scene..." +msgstr "A importar Cena..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2821,8 +2820,8 @@ msgid "Generating for Mesh: " msgstr "A gerar para Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "A executar Script Customizado.." +msgid "Running Custom Script..." +msgstr "A executar Script Customizado..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2837,8 +2836,8 @@ msgid "Error running post-import script:" msgstr "Erro na execução do Script de pós-importação:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "A guardar.." +msgid "Saving..." +msgstr "A guardar..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2857,8 +2856,8 @@ msgid "Import As:" msgstr "Importar Como:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Predefinido.." +msgid "Preset..." +msgstr "Predefinido..." #: editor/import_dock.cpp msgid "Reimport" @@ -3276,7 +3275,7 @@ msgid "Transition Node" msgstr "Nó Transition" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "Importar Animações..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3284,7 +3283,7 @@ msgid "Edit Node Filters" msgstr "Editar filtros de Nó" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Filtros..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3352,7 +3351,7 @@ msgid "Fetching:" msgstr "Em busca:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "A resolver..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3419,7 +3418,7 @@ msgid "Site:" msgstr "Site:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Suporte..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3614,6 +3613,7 @@ msgid "Use Rotation Snap" msgstr "Usar Ajuste na rotação" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Configurar Ajuste..." @@ -3710,14 +3710,12 @@ msgid "Show Guides" msgstr "Mostrar guias" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Ver origem" +msgstr "Mostrar Origem" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Vista" +msgstr "Mostrar Vista" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4010,7 +4008,7 @@ msgstr "A Mesh não tem superfÃcie para criar contornos!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Tipo primitivo de Mesh não é PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4041,7 +4039,7 @@ msgid "Create Convex Collision Sibling" msgstr "Criar irmão de colisão convexa" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "Criar Mesh contorno..." #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4246,7 +4244,7 @@ msgid "Error loading image:" msgstr "Erro ao carregar imagem:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "Sem pixeis com transparência > 128 na imagem..." #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4607,7 +4605,7 @@ msgid "Import Theme" msgstr "Importar tema" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Guardar tema como..." #: editor/plugins/script_editor_plugin.cpp @@ -4704,7 +4702,7 @@ msgstr "Alternar painel de Scripts" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "Encontrar..." #: editor/plugins/script_editor_plugin.cpp @@ -4914,15 +4912,15 @@ msgid "Find Previous" msgstr "Encontrar anterior" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "Substituir..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "Ir para Função..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "Ir para linha..." #: editor/plugins/script_text_editor.cpp @@ -5376,11 +5374,7 @@ msgid "Transform" msgstr "Transformar" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Configurar Ajuste..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "Diálogo de transformação..." #: editor/plugins/spatial_editor_plugin.cpp @@ -5442,7 +5436,7 @@ msgstr "Ajuste de escala (%):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Viewport Settings" -msgstr "Configuração de vista" +msgstr "Configuração de Vista" #: editor/plugins/spatial_editor_plugin.cpp msgid "Perspective FOV (deg.):" @@ -5633,7 +5627,7 @@ msgid "Remove All" msgstr "Remover tudo" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Editar tema..." #: editor/plugins/theme_editor_plugin.cpp @@ -5681,14 +5675,12 @@ msgid "Checked Item" msgstr "Item verificado" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Adicionar item" +msgstr "Item Rádio" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Item verificado" +msgstr "Item Rádio marcado" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5703,8 +5695,8 @@ msgid "Options" msgstr "Opções" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Tem,Muitos,Vários,Opções!" +msgid "Has,Many,Options" +msgstr "Tem,Muitas,Opções" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5896,7 +5888,7 @@ msgid "Presets" msgstr "Predefinições" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Adicionar..." #: editor/project_export.cpp @@ -5991,6 +5983,10 @@ msgid "Imported Project" msgstr "Projeto importado" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Nome do Projeto Inválido." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "ImpossÃvel criar pasta." @@ -6190,9 +6186,11 @@ msgstr "Botão do rato" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Nome de ação inválido. Não pode ser vazio nem conter '/', ':', '=', '\\' ou " +"'\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6219,7 +6217,7 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "Pressione uma tecla..." #: editor/project_settings_editor.cpp @@ -6403,7 +6401,7 @@ msgid "Property:" msgstr "Propriedade:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "Sobrepor por..." #: editor/project_settings_editor.cpp @@ -6476,7 +6474,7 @@ msgstr "Carregamento automático" #: editor/property_editor.cpp msgid "Pick a Viewport" -msgstr "Escolha uma vista" +msgstr "Escolha uma Vista" #: editor/property_editor.cpp msgid "Ease In" @@ -6499,11 +6497,11 @@ msgid "Easing Out-In" msgstr "Easing Out-In" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "Ficheiro..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "Diretoria..." #: editor/property_editor.cpp @@ -6540,7 +6538,7 @@ msgstr "Erro ao carregar Ficheiro: Não é um recurso!" #: editor/property_editor.cpp msgid "Selected node is not a Viewport!" -msgstr "Nó selecionado não é uma vista!" +msgstr "Nó selecionado não é uma Vista!" #: editor/property_editor.cpp msgid "Pick a Node" @@ -6676,7 +6674,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Esta operação não pode ser feita numa Cena instanciada." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Guardar nova Cena como..." #: editor/scene_tree_dock.cpp @@ -7394,7 +7392,7 @@ msgstr "Distância de escolha:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Nome de classe não pode ser uma palavra-chave reservada" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8108,7 +8106,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment precisa de um recurso Environment." #: scene/3d/scenario_fx.cpp msgid "" @@ -8122,6 +8120,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Este WorldEnvironment Ä— ignorado. Pode adicionar uma Camera (para cenas 3D) " +"ou definir o Modo Background deste ambiente como Canvas (para cenas 2D)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8219,6 +8219,13 @@ msgstr "Erro ao carregar letra." msgid "Invalid font size." msgstr "Tamanho de letra inválido." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Guia anterior" + +#~ msgid "Next" +#~ msgstr "Proximo" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Ação inválida (tudo menos '/' ou ':')." @@ -8244,9 +8251,6 @@ msgstr "Tamanho de letra inválido." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "ImpossÃvel encontrar project.godot no Caminho do Projeto." -#~ msgid "Next" -#~ msgstr "Proximo" - #~ msgid "Not found!" #~ msgstr "Não encontrado!" diff --git a/editor/translations/ro.po b/editor/translations/ro.po index e5b3fcbad7..eaf931092a 100644 --- a/editor/translations/ro.po +++ b/editor/translations/ro.po @@ -2,15 +2,15 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# +# Calin Sopterean <csopterean@gmail.com>, 2018. # Filip <filipanton@tutanota.com>, 2018. +# Nitroretro <nitroretro@protonmail.com>, 2018. # TigerxWood <TigerxWood@gmail.com>, 2018. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-05-02 18:03+0000\n" -"Last-Translator: Filip <filipanton@tutanota.com>\n" +"PO-Revision-Date: 2018-06-20 20:43+0000\n" +"Last-Translator: Calin Sopterean <csopterean@gmail.com>\n" "Language-Team: Romanian <https://hosted.weblate.org/projects/godot-engine/" "godot/ro/>\n" "Language: ro\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" -"X-Generator: Weblate 3.0-dev\n" +"X-Generator: Weblate 3.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -256,7 +256,7 @@ msgstr "Pas (s):" #: editor/animation_editor.cpp msgid "Cursor step snap (in seconds)." -msgstr "Pas Bruscare Cursor (în secunde)." +msgstr "Pas de Cursor Snap (în secunde)." #: editor/animation_editor.cpp msgid "Enable/Disable looping in animation." @@ -498,8 +498,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "DeconectaÈ›i '%s' de la '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "ConectaÈ›i.." +msgid "Connect..." +msgstr "ConectaÈ›i..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -549,7 +549,7 @@ msgstr "Potriviri:" #: editor/plugins/asset_library_editor_plugin.cpp editor/property_selector.cpp #: editor/script_editor_debugger.cpp msgid "Description:" -msgstr "DescripÈ›ie:" +msgstr "Descriere:" #: editor/dependency_editor.cpp msgid "Search Replacement For:" @@ -793,7 +793,7 @@ msgstr "Eroare la deschiderea fiÅŸierului pachet, nu este în format zip." #: editor/editor_asset_installer.cpp msgid "Uncompressing Assets" -msgstr "Decompresez Active" +msgstr "Decomprimare Asset-uri" #: editor/editor_asset_installer.cpp editor/project_manager.cpp msgid "Package Installed Successfully!" @@ -892,7 +892,7 @@ msgstr "ȘtergeÈ›i Efectul" #: editor/editor_audio_buses.cpp msgid "Audio" -msgstr "Audio" +msgstr "Sunet" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus" @@ -919,16 +919,16 @@ msgid "Move Audio Bus" msgstr "MutaÈ›i Pista Audio" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "SalvaÈ›i Pista Audio Șablon Ca.." +msgid "Save Audio Bus Layout As..." +msgstr "SalvaÈ›i Schema Pistei Audio Ca..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "LocaÈ›ie pentru Noul Șablon..." +msgid "Location for New Layout..." +msgstr "LocaÈ›ie pentru Noua Schemă..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" -msgstr "DeschideÈ›i Șablon Pistă Audio" +msgstr "Deschide Schema Pistei Audio" #: editor/editor_audio_buses.cpp msgid "There is no 'res://default_bus_layout.tres' file." @@ -936,7 +936,7 @@ msgstr "Nu există nici un fiÅŸier 'res://default_bus_layout.tres'." #: editor/editor_audio_buses.cpp msgid "Invalid file, not an audio bus layout." -msgstr "FiÅŸier nevalid, nu un È™ablon de pistă audio." +msgstr "FiÅŸier nevalid, nu este o schemă de pistă audio." #: editor/editor_audio_buses.cpp msgid "Add Bus" @@ -944,7 +944,7 @@ msgstr "AdaugaÈ›i Pistă Audio" #: editor/editor_audio_buses.cpp msgid "Create a new Bus Layout." -msgstr "CreaÅ£i un Șablon nou Pistă Audio." +msgstr "CreaÅ£i o Schemă nouă de Pistă Audio." #: editor/editor_audio_buses.cpp editor/property_editor.cpp #: editor/script_create_dialog.cpp @@ -953,7 +953,7 @@ msgstr "ÃŽncărcaÈ›i" #: editor/editor_audio_buses.cpp msgid "Load an existing Bus Layout." -msgstr "ÃŽncărcaÅ£i un Șablon de Pistă Audio existent." +msgstr "ÃŽncărcaÅ£i o Schemă de Pistă Audio existentă." #: editor/editor_audio_buses.cpp #: editor/plugins/animation_player_editor_plugin.cpp @@ -962,7 +962,7 @@ msgstr "SalvaÈ›i Ca" #: editor/editor_audio_buses.cpp msgid "Save this Bus Layout to a file." -msgstr "SalvaÅ£i acest Șablon Pistă Audio într-un fiÅŸier." +msgstr "SalvaÅ£i acestă Schemă de Pistă Audio într-un fiÅŸier." #: editor/editor_audio_buses.cpp editor/import_dock.cpp msgid "Load Default" @@ -970,7 +970,7 @@ msgstr "ÃŽncărcaÈ›i Implicit" #: editor/editor_audio_buses.cpp msgid "Load the default Bus Layout." -msgstr "ÃŽncărcat Șablonul Pistă Audio implicit." +msgstr "ÃŽncarcă Schema de Pistă Audio implicită." #: editor/editor_autoload_settings.cpp msgid "Invalid name." @@ -1064,12 +1064,12 @@ msgid "Updating Scene" msgstr "Scena se Actualizează" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Modificările locale se stochează..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Scena se Actualizează.." +msgid "Updating scene..." +msgstr "Scena se Actualizează..." #: editor/editor_data.cpp msgid "[empty]" @@ -1114,223 +1114,223 @@ msgstr "FiÅŸierul se Stochează:" #: editor/editor_export.cpp msgid "Packing" -msgstr "" +msgstr "Ambalare" #: editor/editor_export.cpp platform/javascript/export/export.cpp msgid "Template file not found:" -msgstr "" +msgstr "FiÈ™ierul È™ablon nu a fost găsit:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "File Exists, Overwrite?" -msgstr "" +msgstr "FiÈ™ierul există, suprascrieÅ£i?" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Select Current Folder" -msgstr "" +msgstr "SelectaÅ£i directorul curent" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp msgid "Copy Path" -msgstr "" +msgstr "CopiaÅ£i Calea" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp msgid "Show In File Manager" -msgstr "" +msgstr "ArătaÈ›i în Administratorul de FiÈ™iere" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "" +msgid "New Folder..." +msgstr "Director Nou..." #: editor/editor_file_dialog.cpp msgid "Refresh" -msgstr "" +msgstr "ReîmprospătaÈ›i" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Recognized" -msgstr "" +msgstr "Toate Recunoscute" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Files (*)" -msgstr "" +msgstr "Toate FiÅŸierele (*)" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a File" -msgstr "" +msgstr "DeschideÈ›i un FiÈ™ier" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open File(s)" -msgstr "" +msgstr "DeschideÈ›i FiÈ™ier(e)" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a Directory" -msgstr "" +msgstr "DeschideÅ£i un Director" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a File or Directory" -msgstr "" +msgstr "DeschideÈ›i un FiÅŸier sau Director" #: editor/editor_file_dialog.cpp editor/editor_node.cpp #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/script_editor_plugin.cpp scene/gui/file_dialog.cpp msgid "Save" -msgstr "" +msgstr "SalvaÈ›i" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Save a File" -msgstr "" +msgstr "SalvaÈ›i un FiÈ™ier" #: editor/editor_file_dialog.cpp msgid "Go Back" -msgstr "" +msgstr "ÃŽnapoi" #: editor/editor_file_dialog.cpp msgid "Go Forward" -msgstr "" +msgstr "ÃŽnainte" #: editor/editor_file_dialog.cpp msgid "Go Up" -msgstr "" +msgstr "Sus" #: editor/editor_file_dialog.cpp msgid "Toggle Hidden Files" -msgstr "" +msgstr "ComutaÈ›i FiÈ™iere Ascunse" #: editor/editor_file_dialog.cpp msgid "Toggle Favorite" -msgstr "" +msgstr "ComutaÈ›i Favorite" #: editor/editor_file_dialog.cpp msgid "Toggle Mode" -msgstr "" +msgstr "Modul de Comutare" #: editor/editor_file_dialog.cpp msgid "Focus Path" -msgstr "" +msgstr "Calea Focală" #: editor/editor_file_dialog.cpp msgid "Move Favorite Up" -msgstr "" +msgstr "DeplasaÈ›i Favorit Sus" #: editor/editor_file_dialog.cpp msgid "Move Favorite Down" -msgstr "" +msgstr "DeplasaÈ›i Favorit Jos" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Go to parent folder" -msgstr "" +msgstr "AccesaÈ›i Directorul Părinte" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Directories & Files:" -msgstr "" +msgstr "Directoare È™i FiÅŸiere:" #: editor/editor_file_dialog.cpp msgid "Preview:" -msgstr "" +msgstr "PrevizualizaÈ›i:" #: editor/editor_file_dialog.cpp editor/script_editor_debugger.cpp #: scene/gui/file_dialog.cpp msgid "File:" -msgstr "" +msgstr "FiÈ™ier:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Must use a valid extension." -msgstr "" +msgstr "Trebuie să utilizaÅ£i o extensie valida." #: editor/editor_file_system.cpp msgid "ScanSources" -msgstr "" +msgstr "SurseScan" #: editor/editor_file_system.cpp msgid "(Re)Importing Assets" -msgstr "" +msgstr "(Re)Importând Asset-uri" #: editor/editor_help.cpp editor/editor_node.cpp #: editor/plugins/script_editor_plugin.cpp msgid "Search Help" -msgstr "" +msgstr "CăutaÈ›i în Ajutor" #: editor/editor_help.cpp msgid "Class List:" -msgstr "" +msgstr "Listă de Clase:" #: editor/editor_help.cpp msgid "Search Classes" -msgstr "" +msgstr "Căutare Clase" #: editor/editor_help.cpp editor/plugins/spatial_editor_plugin.cpp msgid "Top" -msgstr "" +msgstr "Sus" #: editor/editor_help.cpp editor/property_editor.cpp msgid "Class:" -msgstr "" +msgstr "Clasă:" #: editor/editor_help.cpp editor/scene_tree_editor.cpp msgid "Inherits:" -msgstr "" +msgstr "MoÈ™teneÈ™te:" #: editor/editor_help.cpp msgid "Inherited by:" -msgstr "" +msgstr "MoÅŸtenit de:" #: editor/editor_help.cpp msgid "Brief Description:" -msgstr "" +msgstr "Descriere Scurtă:" #: editor/editor_help.cpp msgid "Members" -msgstr "" +msgstr "Membri" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Members:" -msgstr "" +msgstr "Membri:" #: editor/editor_help.cpp msgid "Public Methods" -msgstr "" +msgstr "Metode Publice" #: editor/editor_help.cpp msgid "Public Methods:" -msgstr "" +msgstr "Metode Publice:" #: editor/editor_help.cpp msgid "GUI Theme Items" -msgstr "" +msgstr "Obiecte Tema InterfaÈ›a Grafică" #: editor/editor_help.cpp msgid "GUI Theme Items:" -msgstr "" +msgstr "Obiecte Tema InterfaÈ›a Grafică:" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Signals:" -msgstr "" +msgstr "Semnale:" #: editor/editor_help.cpp msgid "Enumerations" -msgstr "" +msgstr "Enumerări" #: editor/editor_help.cpp msgid "Enumerations:" -msgstr "" +msgstr "Enumerări:" #: editor/editor_help.cpp msgid "enum " -msgstr "" +msgstr "enum " #: editor/editor_help.cpp msgid "Constants" -msgstr "" +msgstr "Constante" #: editor/editor_help.cpp msgid "Constants:" -msgstr "" +msgstr "Constante:" #: editor/editor_help.cpp msgid "Description" -msgstr "" +msgstr "Descriere" #: editor/editor_help.cpp msgid "Online Tutorials:" -msgstr "" +msgstr "Tutoriale Internet:" #: editor/editor_help.cpp msgid "" @@ -1338,164 +1338,174 @@ msgid "" "$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" "url][/color]." msgstr "" +"Nu există în prezent nici un tutorial pentru această clasă, puteÅ£i [culoare " +"= $color] [url = $url] contribui unul [/ URL] [/ color] sau [culoare = " +"$color] [url = $url2] cerere unul[/ URL] [/ color]." #: editor/editor_help.cpp msgid "Properties" -msgstr "" +msgstr "Proprietăți" #: editor/editor_help.cpp msgid "Property Description:" -msgstr "" +msgstr "Descriere Proprietate:" #: editor/editor_help.cpp msgid "" "There is currently no description for this property. Please help us by " "[color=$color][url=$url]contributing one[/url][/color]!" msgstr "" +"Nu există în prezent nici o descriere pentru această proprietate. Te rog " +"ajută-ne prin a [color = $color] [url = $url] contribui cu una [/ URL] [/ " +"color]!" #: editor/editor_help.cpp msgid "Methods" -msgstr "" +msgstr "Metode" #: editor/editor_help.cpp msgid "Method Description:" -msgstr "" +msgstr "Descrierea metodei:" #: editor/editor_help.cpp msgid "" "There is currently no description for this method. Please help us by [color=" "$color][url=$url]contributing one[/url][/color]!" msgstr "" +"Nu există în prezent nici o descriere pentru această metodă. Te rog ajută-ne " +"de prin a [color = $color] [url = $url] contribui cu una [/ URL] [/ color]!" #: editor/editor_help.cpp msgid "Search Text" -msgstr "" +msgstr "CăutaÈ›i Text" #: editor/editor_help.cpp msgid "Find" -msgstr "" +msgstr "GăsiÈ›i" #: editor/editor_log.cpp msgid "Output:" -msgstr "" +msgstr "AfiÈ™are:" #: editor/editor_log.cpp editor/plugins/animation_tree_editor_plugin.cpp #: editor/property_editor.cpp editor/script_editor_debugger.cpp #: modules/gdnative/gdnative_library_editor_plugin.cpp scene/gui/line_edit.cpp #: scene/gui/text_edit.cpp msgid "Clear" -msgstr "" +msgstr "Curăță" #: editor/editor_log.cpp msgid "Clear Output" -msgstr "" +msgstr "Curăță AfiÈ™area" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Exportul de proiect nu a reuÅŸit cu un cod de eroare %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" -msgstr "" +msgstr "Eroare la salvarea resursei!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "" +msgid "Save Resource As..." +msgstr "SalvaÈ›i Resursa Ca..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "" +msgid "I see..." +msgstr "Am înÈ›eles..." #: editor/editor_node.cpp msgid "Can't open file for writing:" -msgstr "" +msgstr "Nu pot deschide fiÅŸierul pentru scris:" #: editor/editor_node.cpp msgid "Requested file format unknown:" -msgstr "" +msgstr "Formatul fiÅŸierului solicitat este necunoscut:" #: editor/editor_node.cpp msgid "Error while saving." -msgstr "" +msgstr "Eroare la salvare." #: editor/editor_node.cpp msgid "Can't open '%s'." -msgstr "" +msgstr "Imposibil de deschis '%s'." #: editor/editor_node.cpp msgid "Error while parsing '%s'." -msgstr "" +msgstr "Eroare analizând '%s'." #: editor/editor_node.cpp msgid "Unexpected end of file '%s'." -msgstr "" +msgstr "SfârÈ™it de fiÈ™ier neaÅŸteptat '%s'." #: editor/editor_node.cpp msgid "Missing '%s' or its dependencies." -msgstr "" +msgstr "LipseÈ™te '%s' sau dependenÅ£ele sale." #: editor/editor_node.cpp msgid "Error while loading '%s'." -msgstr "" +msgstr "Eroare în timpul încărcării '%s'." #: editor/editor_node.cpp msgid "Saving Scene" -msgstr "" +msgstr "Salvând Scena" #: editor/editor_node.cpp msgid "Analyzing" -msgstr "" +msgstr "Analizând" #: editor/editor_node.cpp msgid "Creating Thumbnail" -msgstr "" +msgstr "Creând Thumbnail" #: editor/editor_node.cpp msgid "This operation can't be done without a tree root." -msgstr "" +msgstr "Aceasta operaÈ›iune nu se poate face fără o rădăcină de copac." #: editor/editor_node.cpp msgid "" "Couldn't save scene. Likely dependencies (instances or inheritance) couldn't " "be satisfied." msgstr "" +"Nu am putut salva scena. Probabil dependenÅ£e (instanÅ£e sau moÅŸteniri) nu au " +"putut fi satisfăcute." #: editor/editor_node.cpp msgid "Failed to load resource." -msgstr "" +msgstr "ÃŽncărcarea resursei a eÈ™uat." #: editor/editor_node.cpp msgid "Can't load MeshLibrary for merging!" -msgstr "" +msgstr "Imposibil de încărcat MeshLibrary pentru unire!" #: editor/editor_node.cpp msgid "Error saving MeshLibrary!" -msgstr "" +msgstr "Eroare la salvarea MeshLibrary!" #: editor/editor_node.cpp msgid "Can't load TileSet for merging!" -msgstr "" +msgstr "Imposibil de încărcat TileSet pentru unire!" #: editor/editor_node.cpp msgid "Error saving TileSet!" -msgstr "" +msgstr "Eroare la salvarea TileSet!" #: editor/editor_node.cpp msgid "Error trying to save layout!" -msgstr "" +msgstr "Eroare la încercarea de a salva schema!" #: editor/editor_node.cpp msgid "Default editor layout overridden." -msgstr "" +msgstr "Schemă implicită de editor suprascrisă." #: editor/editor_node.cpp msgid "Layout name not found!" -msgstr "" +msgstr "Numele schemei nu a fost găsit!" #: editor/editor_node.cpp msgid "Restored default layout to base settings." -msgstr "" +msgstr "S-a restaurat schema implictă la setările de bază." #: editor/editor_node.cpp msgid "" @@ -1503,18 +1513,26 @@ msgid "" "Please read the documentation relevant to importing scenes to better " "understand this workflow." msgstr "" +"Această resursă aparÅ£ine de o scena care a fost importată, astfel încât nu " +"este editabilă.\n" +"Vă rugăm să citiÅ£i documentaÅ£ia relevantă pentru importul scene pentru a " +"înÅ£elege mai bine cum sa lucraÈ›i cu acestea." #: editor/editor_node.cpp msgid "" "This resource belongs to a scene that was instanced or inherited.\n" "Changes to it will not be kept when saving the current scene." msgstr "" +"Această resursă este o scena care a fost instanÈ›ată sau moÅŸtenită.\n" +"Modificările la acesta nu vor fi păstrate la salvarea scenei curente." #: editor/editor_node.cpp msgid "" "This resource was imported, so it's not editable. Change its settings in the " "import panel and then re-import." msgstr "" +"Această resursă a fost importată, astfel încât nu este editabilă. ModificaÅ£i " +"setările din panoul de import ÅŸi apoi reimportaÈ›i." #: editor/editor_node.cpp msgid "" @@ -1523,6 +1541,11 @@ msgid "" "Please read the documentation relevant to importing scenes to better " "understand this workflow." msgstr "" +"Această scenă a fost importată, astfel încât modificările la acesta nu vor " +"fi păstrate.\n" +"InstanÈ›area sau moÅŸtenirea vă permite efectuarea de modificări la acesta.\n" +"Vă rugăm să citiÅ£i documentaÅ£ia relevantă pentru importul scene pentru a " +"înÅ£elege mai bine acest mod de lucru." #: editor/editor_node.cpp msgid "" @@ -1530,46 +1553,50 @@ msgid "" "Please read the documentation relevant to debugging to better understand " "this workflow." msgstr "" +"Acesta este un obiect îndepărtat, astfel încât modificările la acesta nu vor " +"fi păstrate.\n" +"Vă rugăm să citiÅ£i documentaÅ£ia relevantă pentru depanare pentru a înÅ£elege " +"mai bine acest mod de lucru." #: editor/editor_node.cpp msgid "Expand all properties" -msgstr "" +msgstr "Extinde toate proprietăţile" #: editor/editor_node.cpp msgid "Collapse all properties" -msgstr "" +msgstr "Restrânge toate proprietăţile" #: editor/editor_node.cpp msgid "Copy Params" -msgstr "" +msgstr "Copie Parametrii" #: editor/editor_node.cpp msgid "Paste Params" -msgstr "" +msgstr "LipiÅ£i Parametrii" #: editor/editor_node.cpp editor/plugins/resource_preloader_editor_plugin.cpp msgid "Paste Resource" -msgstr "" +msgstr "LipiÈ›i Resursa" #: editor/editor_node.cpp msgid "Copy Resource" -msgstr "" +msgstr "CopiaÈ›i Resursa" #: editor/editor_node.cpp msgid "Make Built-In" -msgstr "" +msgstr "FaceÈ›i ÃŽncorporat" #: editor/editor_node.cpp msgid "Make Sub-Resources Unique" -msgstr "" +msgstr "FaceÈ›i Sub-Resursa Unică" #: editor/editor_node.cpp msgid "Open in Help" -msgstr "" +msgstr "DeschideÈ›i în Ajutor" #: editor/editor_node.cpp msgid "There is no defined scene to run." -msgstr "" +msgstr "Nu există nici o scenă definită pentru a execuÈ›ie." #: editor/editor_node.cpp msgid "" @@ -1577,6 +1604,8 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" +"Nici o scena principala a fost definită, selectaÈ›i una?\n" +"PuteÈ›i schimba mai târziu, în \"Setări Proiect\" în categoria 'AplicaÈ›ie'." #: editor/editor_node.cpp msgid "" @@ -1584,6 +1613,8 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" +"Scena selectată ’%s’ nu există, selectaÈ›i una?\n" +"PuteÈ›i schimba mai târziu în „Setări Proiect†în categoria „AplicaÈ›ieâ€." #: editor/editor_node.cpp msgid "" @@ -1591,343 +1622,364 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" +"Scena selectată ’%s’ nu este un fiÈ™ier scenă, selectaÈ›i una validă?\n" +"PuteÈ›i schimba mai târziu în „Setări Proiect†în categoria „AplicaÈ›ieâ€." #: editor/editor_node.cpp msgid "Current scene was never saved, please save it prior to running." msgstr "" +"Scena curentă nu a fost salvată niciodată, salvaÈ›i-o înainte de rulare." #: editor/editor_node.cpp msgid "Could not start subprocess!" -msgstr "" +msgstr "Nu s-a putut porni subprocesul!" #: editor/editor_node.cpp msgid "Open Scene" -msgstr "" +msgstr "Deschide o scenă" #: editor/editor_node.cpp msgid "Open Base Scene" -msgstr "" +msgstr "Deschide o scenă de bază" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "" +msgid "Quick Open Scene..." +msgstr "Deschide o scenă rapid..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "" +msgid "Quick Open Script..." +msgstr "Deschide un script rapid..." #: editor/editor_node.cpp msgid "Save & Close" -msgstr "" +msgstr "Salvează È™i închide" #: editor/editor_node.cpp msgid "Save changes to '%s' before closing?" -msgstr "" +msgstr "Salvează schimbările la ’%s’ înainte de ieÈ™ire?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "" +msgid "Save Scene As..." +msgstr "Salvează scena ca..." #: editor/editor_node.cpp msgid "No" -msgstr "" +msgstr "Nu" #: editor/editor_node.cpp msgid "Yes" -msgstr "" +msgstr "Da" #: editor/editor_node.cpp msgid "This scene has never been saved. Save before running?" -msgstr "" +msgstr "Această scenă nu a fost salvată niciodata. SalvaÈ›i înainte de rulare?" #: editor/editor_node.cpp editor/scene_tree_dock.cpp msgid "This operation can't be done without a scene." -msgstr "" +msgstr "Această operaÈ›ie nu se poate face fără o scenă." #: editor/editor_node.cpp msgid "Export Mesh Library" -msgstr "" +msgstr "Exportă Librăria de Mesh-uri" #: editor/editor_node.cpp msgid "This operation can't be done without a root node." -msgstr "" +msgstr "Această operaÈ›iune nu poate fi făcută fără un nod de bază." #: editor/editor_node.cpp msgid "Export Tile Set" -msgstr "" +msgstr "Exportă Setul de Plăci" #: editor/editor_node.cpp msgid "This operation can't be done without a selected node." -msgstr "" +msgstr "Această operaÈ›iune nu poate fi făcută fără un nod selectat." #: editor/editor_node.cpp msgid "Current scene not saved. Open anyway?" -msgstr "" +msgstr "Scena curentă nu este salvată. Deschizi oricum?" #: editor/editor_node.cpp msgid "Can't reload a scene that was never saved." -msgstr "" +msgstr "Nu se poate reîncărca o scenă care nu a fost salvată niciodată." #: editor/editor_node.cpp msgid "Revert" -msgstr "" +msgstr "ÃŽntoarcere" #: editor/editor_node.cpp msgid "This action cannot be undone. Revert anyway?" -msgstr "" +msgstr "Această acÈ›iune nu poate fi recuperată. Te reîntorci oricum?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "" +msgid "Quick Run Scene..." +msgstr "Execută Rapid Scena..." #: editor/editor_node.cpp msgid "Quit" -msgstr "" +msgstr "ÃŽnchidere" #: editor/editor_node.cpp msgid "Exit the editor?" -msgstr "" +msgstr "IeÈ™i din editor?" #: editor/editor_node.cpp msgid "Open Project Manager?" -msgstr "" +msgstr "Deschizi Managerul de Proiect?" #: editor/editor_node.cpp msgid "Save & Quit" -msgstr "" +msgstr "Salvează È™i ÃŽnchide" #: editor/editor_node.cpp msgid "Save changes to the following scene(s) before quitting?" msgstr "" +"Salvezi modificările făcute în urmatoarea(le) scenă(e) înainte să închizi?" #: editor/editor_node.cpp msgid "Save changes the following scene(s) before opening Project Manager?" msgstr "" +"Salvezi modificările făcute în urmatoarea(le) scenă(e) înainte să deschizi " +"Managerul de Proiect?" #: editor/editor_node.cpp msgid "" "This option is deprecated. Situations where refresh must be forced are now " "considered a bug. Please report." msgstr "" +"Această opÈ›iune este depreciată. SituaÈ›iile în care reînprospătarea trebuie " +"forÈ›ată sunt acum considerate buguri. Te rugăm să raportezi." #: editor/editor_node.cpp msgid "Pick a Main Scene" -msgstr "" +msgstr "Alege o Scenă Principală" #: editor/editor_node.cpp msgid "Unable to enable addon plugin at: '%s' parsing of config failed." msgstr "" +"Nu se poate iniÈ›ializa plugin-ul la: '%s' analizarea configuraÈ›iei a eÈ™uat." #: editor/editor_node.cpp msgid "Unable to find script field for addon plugin at: 'res://addons/%s'." msgstr "" +"Nu a putut fi găsit câmpul scriptului pentru plugin la: 'res://addons/%s'." #: editor/editor_node.cpp msgid "Unable to load addon script from path: '%s'." -msgstr "" +msgstr "Nu a putut fi încărcat scriptul add-on din calea: '%s'." #: editor/editor_node.cpp msgid "" "Unable to load addon script from path: '%s' Base type is not EditorPlugin." msgstr "" +"Nu a putut fi încărcat scriptul add-on din calea: '%s' tipul de Bază nu este " +"EditorPlugin." #: editor/editor_node.cpp msgid "Unable to load addon script from path: '%s' Script is not in tool mode." msgstr "" +"Nu a putut fi încărcat scriptul add-on din calea: '%s' Scriptul nu este în " +"modul unealtă." #: editor/editor_node.cpp msgid "" "Scene '%s' was automatically imported, so it can't be modified.\n" "To make changes to it, a new inherited scene can be created." msgstr "" +"Scena '%s' nu a fost importată automat, deci ea nu poate fi modificată.\n" +"Ca să poÈ›i face modificări, o nouă scenă derivată poate fi creată." #: editor/editor_node.cpp editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ugh" -msgstr "" +msgstr "Uh" #: editor/editor_node.cpp msgid "" "Error loading scene, it must be inside the project path. Use 'Import' to " "open the scene, then save it inside the project path." msgstr "" +"Eroare la încărcarea scenei, aceasta trebuie să fie în calea spre proiect. " +"FoloseÈ™te 'Importă' ca să deschizi scena, apoi salveaz-o în calea spre " +"proiect." #: editor/editor_node.cpp msgid "Scene '%s' has broken dependencies:" -msgstr "" +msgstr "Scena '%s' are dependinÈ›e nefuncÈ›ionale:" #: editor/editor_node.cpp msgid "Clear Recent Scenes" -msgstr "" +msgstr "Curăță Scenele Recente" #: editor/editor_node.cpp msgid "Save Layout" -msgstr "" +msgstr "Salvează Schema" #: editor/editor_node.cpp msgid "Delete Layout" -msgstr "" +msgstr "Șterge Schema" #: editor/editor_node.cpp editor/import_dock.cpp #: editor/script_create_dialog.cpp msgid "Default" -msgstr "" +msgstr "Implicit" #: editor/editor_node.cpp msgid "Switch Scene Tab" -msgstr "" +msgstr "ComutaÈ›i între Scene" #: editor/editor_node.cpp msgid "%d more files or folders" -msgstr "" +msgstr "%d mai multe fiÈ™iere sau foldere" #: editor/editor_node.cpp msgid "%d more folders" -msgstr "" +msgstr "%d mai multe foldere" #: editor/editor_node.cpp msgid "%d more files" -msgstr "" +msgstr "%d mai multe fiÈ™iere" #: editor/editor_node.cpp msgid "Dock Position" -msgstr "" +msgstr "PoziÈ›ia Dock-ului" #: editor/editor_node.cpp msgid "Distraction Free Mode" -msgstr "" +msgstr "Modul Fără Distrageri" #: editor/editor_node.cpp msgid "Toggle distraction-free mode." -msgstr "" +msgstr "Comutează modul fără distrageri." #: editor/editor_node.cpp msgid "Add a new scene." -msgstr "" +msgstr "Adaugă o nouă scenă." #: editor/editor_node.cpp msgid "Scene" -msgstr "" +msgstr "Scenă" #: editor/editor_node.cpp msgid "Go to previously opened scene." -msgstr "" +msgstr "Mergi la o scenă deschisă anterior." #: editor/editor_node.cpp msgid "Next tab" -msgstr "" +msgstr "Fila următoare" #: editor/editor_node.cpp msgid "Previous tab" -msgstr "" +msgstr "Fila anterioară" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "" +msgid "Filter Files..." +msgstr "Filtrează fiÈ™ierele..." #: editor/editor_node.cpp msgid "Operations with scene files." -msgstr "" +msgstr "OperaÈ›iuni cu fiÈ™iere tip scenă." #: editor/editor_node.cpp msgid "New Scene" -msgstr "" +msgstr "Scenă Nouă" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "" +msgid "New Inherited Scene..." +msgstr "Scenă Derivată Nouă..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "" +msgid "Open Scene..." +msgstr "Deschide Scena..." #: editor/editor_node.cpp msgid "Save Scene" -msgstr "" +msgstr "Salvează Scena" #: editor/editor_node.cpp msgid "Save all Scenes" -msgstr "" +msgstr "Salvează toate Scenele" #: editor/editor_node.cpp msgid "Close Scene" -msgstr "" +msgstr "ÃŽnchide Scena" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Open Recent" -msgstr "" +msgstr "Deschide Recente" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "" +msgid "Convert To..." +msgstr "ConverteÈ™te ÃŽn..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "" +msgid "MeshLibrary..." +msgstr "Librărie_de_Structuri..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "" +msgid "TileSet..." +msgstr "Set_de_Plăci..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp msgid "Undo" -msgstr "" +msgstr "Revenire" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp msgid "Redo" -msgstr "" +msgstr "Reîntoarcere" #: editor/editor_node.cpp msgid "Revert Scene" -msgstr "" +msgstr "RestabileÈ™te Scena" #: editor/editor_node.cpp msgid "Miscellaneous project or scene-wide tools." -msgstr "" +msgstr "Proiect Divers sau unelte pentru scenă." #: editor/editor_node.cpp msgid "Project" -msgstr "" +msgstr "Proiect" #: editor/editor_node.cpp msgid "Project Settings" -msgstr "" +msgstr "Setări ale Proiectului" #: editor/editor_node.cpp msgid "Run Script" -msgstr "" +msgstr "Execută Scriptul" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export" -msgstr "" +msgstr "Exportare" #: editor/editor_node.cpp msgid "Tools" -msgstr "" +msgstr "Unelte" #: editor/editor_node.cpp msgid "Quit to Project List" -msgstr "" +msgstr "ÃŽnchide spre Lista Proiectului" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Debug" -msgstr "" +msgstr "Depanare" #: editor/editor_node.cpp msgid "Deploy with Remote Debug" -msgstr "" +msgstr "Lansează cu Depanare la Distanță" #: editor/editor_node.cpp msgid "" "When exporting or deploying, the resulting executable will attempt to " "connect to the IP of this computer in order to be debugged." msgstr "" +"Când exporÈ›i sau lansezi, executabilul rezultat va încerca să se conecteze " +"la IP-ul acestui computer pentru a putea fi depanat." #: editor/editor_node.cpp msgid "Small Deploy with Network FS" -msgstr "" +msgstr "Mini Lansare cu ReÈ›ea FS" #: editor/editor_node.cpp msgid "" @@ -1938,30 +1990,39 @@ msgid "" "On Android, deploy will use the USB cable for faster performance. This " "option speeds up testing for games with a large footprint." msgstr "" +"Când această opÈ›iune este activată, exportarea sau lansarea va produce un " +"executabil minimal.\n" +"Sistemul de fiÈ™iere va fi furnizat de la proiect la editor prin reÈ›ea.\n" +"Pe Android, lansarea va folosi cablul USB pentru performanță mai rapidă. " +"Această opÈ›iune accelerează testarea jocurilor cu o marime substanÈ›ială." #: editor/editor_node.cpp msgid "Visible Collision Shapes" -msgstr "" +msgstr "Forme de Coliziune Vizibile" #: editor/editor_node.cpp msgid "" "Collision shapes and raycast nodes (for 2D and 3D) will be visible on the " "running game if this option is turned on." msgstr "" +"Formele de coliziune si nodurile raycast (pentru 2D È™i 3D) vor fi vizibile " +"când jocul rulează dacă această opÈ›iune este activată." #: editor/editor_node.cpp msgid "Visible Navigation" -msgstr "" +msgstr "Navigare Vizibilă" #: editor/editor_node.cpp msgid "" "Navigation meshes and polygons will be visible on the running game if this " "option is turned on." msgstr "" +"Structurile de navigare È™i poligoanele vor fi vizibile când jocul rulează " +"dacă această opÈ›iune este activată." #: editor/editor_node.cpp msgid "Sync Scene Changes" -msgstr "" +msgstr "Sincronizează Modificările Scenei" #: editor/editor_node.cpp msgid "" @@ -1970,10 +2031,14 @@ msgid "" "When used remotely on a device, this is more efficient with network " "filesystem." msgstr "" +"Când această opÈ›iune este activată, orice modificare facută în scenă din " +"editor va fi replicată în jocul care rulează.\n" +"Când această opÈ›iune este folosită de la distanță pe un dispozitiv, este " +"mult mai eficient dacă este folosit un sistem de fiÈ™iere în reÈ›ea." #: editor/editor_node.cpp msgid "Sync Script Changes" -msgstr "" +msgstr "Sincronizează Modificările Scriptului" #: editor/editor_node.cpp msgid "" @@ -1982,844 +2047,861 @@ msgid "" "When used remotely on a device, this is more efficient with network " "filesystem." msgstr "" +"Când această opÈ›iune este activată, orice script salvat ulterior va fi " +"reîncărcat în jocul care rulează.\n" +"Când această opÈ›iune este folosită de la distanță pe un dispozitiv, este " +"mult mai eficient dacă este folosit un sistem de fiÈ™iere în reÈ›ea." #: editor/editor_node.cpp msgid "Editor" -msgstr "" +msgstr "Editor" #: editor/editor_node.cpp editor/settings_config_dialog.cpp msgid "Editor Settings" -msgstr "" +msgstr "Setări ale Editorului" #: editor/editor_node.cpp msgid "Editor Layout" -msgstr "" +msgstr "Schema Editorului" #: editor/editor_node.cpp msgid "Toggle Fullscreen" -msgstr "" +msgstr "Comută în Ecran Complet" #: editor/editor_node.cpp editor/project_export.cpp msgid "Manage Export Templates" -msgstr "" +msgstr "Administrează Șabloanele de Export" #: editor/editor_node.cpp msgid "Help" -msgstr "" +msgstr "Ajutor" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Classes" -msgstr "" +msgstr "Clase" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp #: editor/plugins/shader_editor_plugin.cpp editor/project_settings_editor.cpp msgid "Search" -msgstr "" +msgstr "Căutare" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Online Docs" -msgstr "" +msgstr "DocumentaÈ›ie Online" #: editor/editor_node.cpp msgid "Q&A" -msgstr "" +msgstr "ÃŽntrebări È™i Răspunsuri" #: editor/editor_node.cpp msgid "Issue Tracker" -msgstr "" +msgstr "Agent de Monitorizare al Problemelor" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp msgid "Community" -msgstr "" +msgstr "Comunitate" #: editor/editor_node.cpp msgid "About" -msgstr "" +msgstr "Despre" #: editor/editor_node.cpp msgid "Play the project." -msgstr "" +msgstr "Rulează proiectul." #: editor/editor_node.cpp msgid "Play" -msgstr "" +msgstr "Rulează" #: editor/editor_node.cpp msgid "Pause the scene" -msgstr "" +msgstr "ÃŽntrerupe scena" #: editor/editor_node.cpp msgid "Pause Scene" -msgstr "" +msgstr "ÃŽntrerupere Scenă" #: editor/editor_node.cpp msgid "Stop the scene." -msgstr "" +msgstr "OpreÈ™te scena." #: editor/editor_node.cpp msgid "Stop" -msgstr "" +msgstr "OpreÈ™te" #: editor/editor_node.cpp msgid "Play the edited scene." -msgstr "" +msgstr "Rulează scena editată." #: editor/editor_node.cpp msgid "Play Scene" -msgstr "" +msgstr "Rulează Scena" #: editor/editor_node.cpp msgid "Play custom scene" -msgstr "" +msgstr "Rulează scena personalizată" #: editor/editor_node.cpp msgid "Play Custom Scene" -msgstr "" +msgstr "Rulează Scena Personalizată" #: editor/editor_node.cpp msgid "Spins when the editor window repaints!" -msgstr "" +msgstr "Se roteÈ™te când ferestra editorului se recolorează!" #: editor/editor_node.cpp msgid "Update Always" -msgstr "" +msgstr "Actualizează ÃŽntotdeauna" #: editor/editor_node.cpp msgid "Update Changes" -msgstr "" +msgstr "Modificări ale Actualizării" #: editor/editor_node.cpp msgid "Disable Update Spinner" -msgstr "" +msgstr "Dezactivează Cercul de Actualizare" #: editor/editor_node.cpp msgid "Inspector" -msgstr "" +msgstr "Inspector" #: editor/editor_node.cpp msgid "Create a new resource in memory and edit it." -msgstr "" +msgstr "Creează o nouă resursă în memorie È™i editeaz-o." #: editor/editor_node.cpp msgid "Load an existing resource from disk and edit it." -msgstr "" +msgstr "ÃŽncarcă o resursă existentă de pe disc si editeaz-o." #: editor/editor_node.cpp msgid "Save the currently edited resource." -msgstr "" +msgstr "Salvează resursa editată curentă." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "" +msgid "Save As..." +msgstr "Salvează Ca..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." -msgstr "" +msgstr "Mergi la un obiect din istoric editat anterior." #: editor/editor_node.cpp msgid "Go to the next edited object in history." -msgstr "" +msgstr "Mergi la următorul obiect editat din istoric." #: editor/editor_node.cpp msgid "History of recently edited objects." -msgstr "" +msgstr "Istoricul obiectelor editate recent." #: editor/editor_node.cpp msgid "Object properties." -msgstr "" +msgstr "Proprietățile obiectului." #: editor/editor_node.cpp msgid "Changes may be lost!" -msgstr "" +msgstr "Modificările pot fi pierdute!" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp #: editor/project_manager.cpp msgid "Import" -msgstr "" +msgstr "Importă" #: editor/editor_node.cpp msgid "Node" -msgstr "" +msgstr "Nod" #: editor/editor_node.cpp msgid "FileSystem" -msgstr "" +msgstr "Sistemul De FiÈ™iere" #: editor/editor_node.cpp msgid "Output" -msgstr "" +msgstr "IeÈ™ire" #: editor/editor_node.cpp msgid "Don't Save" -msgstr "" +msgstr "Nu Salva" #: editor/editor_node.cpp msgid "Import Templates From ZIP File" -msgstr "" +msgstr "Importă Șabloane Dintr-o Arhivă ZIP" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export Project" -msgstr "" +msgstr "Exportă Proiectul" #: editor/editor_node.cpp msgid "Export Library" -msgstr "" +msgstr "Exportă Librăria" #: editor/editor_node.cpp msgid "Merge With Existing" -msgstr "" +msgstr "ContopeÈ™te Cu Existentul" #: editor/editor_node.cpp msgid "Password:" -msgstr "" +msgstr "Parola:" #: editor/editor_node.cpp msgid "Open & Run a Script" -msgstr "" +msgstr "Deschide È™i Execută un Script" #: editor/editor_node.cpp msgid "New Inherited" -msgstr "" +msgstr "Derivare Nouă" #: editor/editor_node.cpp msgid "Load Errors" -msgstr "" +msgstr "ÃŽncarcă Erorile" #: editor/editor_node.cpp editor/plugins/tile_map_editor_plugin.cpp msgid "Select" -msgstr "" +msgstr "Selectează" #: editor/editor_node.cpp msgid "Open 2D Editor" -msgstr "" +msgstr "Deschide Editorul 2D" #: editor/editor_node.cpp msgid "Open 3D Editor" -msgstr "" +msgstr "Deschide Editorul 3D" #: editor/editor_node.cpp msgid "Open Script Editor" -msgstr "" +msgstr "Deschide Editorul de Scripturi" #: editor/editor_node.cpp editor/project_manager.cpp msgid "Open Asset Library" -msgstr "" +msgstr "Deschide Librăria de Asseturi" #: editor/editor_node.cpp msgid "Open the next Editor" -msgstr "" +msgstr "Deschide Editorul următor" #: editor/editor_node.cpp msgid "Open the previous Editor" -msgstr "" +msgstr "Deschide Editorul anterior" #: editor/editor_plugin.cpp msgid "Creating Mesh Previews" -msgstr "" +msgstr "Se creează Previzualizările Mesh-ului" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "" +msgid "Thumbnail..." +msgstr "Miniatură..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" -msgstr "" +msgstr "Pluginuri instalate:" #: editor/editor_plugin_settings.cpp msgid "Update" -msgstr "" +msgstr "Actualizare" #: editor/editor_plugin_settings.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Version:" -msgstr "" +msgstr "Versiune:" #: editor/editor_plugin_settings.cpp msgid "Author:" -msgstr "" +msgstr "Autor:" #: editor/editor_plugin_settings.cpp msgid "Status:" -msgstr "" +msgstr "Stare:" #: editor/editor_profiler.cpp msgid "Stop Profiling" -msgstr "" +msgstr "OpreÈ™te Profilarea" #: editor/editor_profiler.cpp msgid "Start Profiling" -msgstr "" +msgstr "PorneÈ™te Profilarea" #: editor/editor_profiler.cpp msgid "Measure:" -msgstr "" +msgstr "Măsura:" #: editor/editor_profiler.cpp msgid "Frame Time (sec)" -msgstr "" +msgstr "Timpul Cadrului (sec)" #: editor/editor_profiler.cpp msgid "Average Time (sec)" -msgstr "" +msgstr "Media Timpului (sec)" #: editor/editor_profiler.cpp msgid "Frame %" -msgstr "" +msgstr "Cadru %" #: editor/editor_profiler.cpp msgid "Physics Frame %" -msgstr "" +msgstr "Cadru Fizic %" #: editor/editor_profiler.cpp editor/script_editor_debugger.cpp msgid "Time:" -msgstr "" +msgstr "Timp:" #: editor/editor_profiler.cpp msgid "Inclusive" -msgstr "" +msgstr "Inclusiv" #: editor/editor_profiler.cpp msgid "Self" -msgstr "" +msgstr "Propriu" #: editor/editor_profiler.cpp msgid "Frame #:" -msgstr "" +msgstr "Cadru #:" #: editor/editor_profiler.cpp msgid "Time" -msgstr "" +msgstr "Timp" #: editor/editor_profiler.cpp msgid "Calls" -msgstr "" +msgstr "Apeluri" #: editor/editor_run_native.cpp msgid "Select device from the list" -msgstr "" +msgstr "Selectează un dispozitiv din listă" #: editor/editor_run_native.cpp msgid "" "No runnable export preset found for this platform.\n" "Please add a runnable preset in the export menu." msgstr "" +"Nu a fost găsită nicio presetare de export care să poată rula pentru această " +"platformă.\n" +"Te rog adaugă o presetare de rulare în meniul pentru export." #: editor/editor_run_script.cpp msgid "Write your logic in the _run() method." -msgstr "" +msgstr "Scrie logica programului în metoda _run()." #: editor/editor_run_script.cpp msgid "There is an edited scene already." -msgstr "" +msgstr "Acolo este o scenă deja editată." #: editor/editor_run_script.cpp msgid "Couldn't instance script:" -msgstr "" +msgstr "Nu s-a putut iniÈ›ializa scriptul:" #: editor/editor_run_script.cpp msgid "Did you forget the 'tool' keyword?" -msgstr "" +msgstr "Ai uitat cumva cuvântul 'unealtă'?" #: editor/editor_run_script.cpp msgid "Couldn't run script:" -msgstr "" +msgstr "Nu a putut fi executat scriptul:" #: editor/editor_run_script.cpp msgid "Did you forget the '_run' method?" -msgstr "" +msgstr "Ai uitat cumva metoda '_run' ?" #: editor/editor_settings.cpp msgid "Default (Same as Editor)" -msgstr "" +msgstr "Implicit (Asemănător ca Editor)" #: editor/editor_sub_scene.cpp msgid "Select Node(s) to Import" -msgstr "" +msgstr "Selectează Nodul(rile) pentru Importare" #: editor/editor_sub_scene.cpp msgid "Scene Path:" -msgstr "" +msgstr "Calea Scenei:" #: editor/editor_sub_scene.cpp msgid "Import From Node:" -msgstr "" +msgstr "Importă Din Nod:" #: editor/export_template_manager.cpp msgid "Re-Download" -msgstr "" +msgstr "Descarcă din nou" #: editor/export_template_manager.cpp msgid "Uninstall" -msgstr "" +msgstr "Dezinstalează" #: editor/export_template_manager.cpp msgid "(Installed)" -msgstr "" +msgstr "(Instalat)" #: editor/export_template_manager.cpp msgid "Download" -msgstr "" +msgstr "Descarcă" #: editor/export_template_manager.cpp msgid "(Missing)" -msgstr "" +msgstr "(LipseÈ™te)" #: editor/export_template_manager.cpp msgid "(Current)" -msgstr "" +msgstr "(Curent)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "" +msgid "Retrieving mirrors, please wait..." +msgstr "Se recuperează oglinzile, te rog aÈ™teaptă..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" -msgstr "" +msgstr "Elimini È™ablonul versiunea '%s'?" #: editor/export_template_manager.cpp msgid "Can't open export templates zip." -msgstr "" +msgstr "Nu se pot deschide È™abloanele de export zip." #: editor/export_template_manager.cpp msgid "Invalid version.txt format inside templates." -msgstr "" +msgstr "Format nevalid versiune.txt în È™abloane." #: editor/export_template_manager.cpp msgid "No version.txt found inside templates." -msgstr "" +msgstr "Nu s-a găsit versiune.txt în È™abloane." #: editor/export_template_manager.cpp msgid "Error creating path for templates:" -msgstr "" +msgstr "Eroare la crearea căii pentru È™abloane:" #: editor/export_template_manager.cpp msgid "Extracting Export Templates" -msgstr "" +msgstr "Se extrag Șabloanele de Export" #: editor/export_template_manager.cpp msgid "Importing:" -msgstr "" +msgstr "Se importă:" #: editor/export_template_manager.cpp msgid "" "No download links found for this version. Direct download is only available " "for official releases." msgstr "" +"Niciun link pentru descărcare nu a fost găsit pentru această versiune. " +"Descărcarea directă este disponibilă numai pentru lansări oficiale." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't resolve." -msgstr "" +msgstr "Nu se poate rezolva." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't connect." -msgstr "" +msgstr "Nu se poate face conexiunea." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "No response." -msgstr "" +msgstr "Niciun răspuns." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request Failed." -msgstr "" +msgstr "Cerere EÈ™uată." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Redirect Loop." -msgstr "" +msgstr "Buclă de RedirecÈ›ionare." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Failed:" -msgstr "" +msgstr "A EÈ™uat:" #: editor/export_template_manager.cpp msgid "Download Complete." -msgstr "" +msgstr "Descărcare Completă." #: editor/export_template_manager.cpp msgid "Error requesting url: " -msgstr "" +msgstr "Eroare la solicitarea URL: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "" +msgid "Connecting to Mirror..." +msgstr "Se conectează la Oglinda..." #: editor/export_template_manager.cpp msgid "Disconnected" -msgstr "" +msgstr "Deconectat" #: editor/export_template_manager.cpp msgid "Resolving" -msgstr "" +msgstr "Se SoluÈ›ionează" #: editor/export_template_manager.cpp msgid "Can't Resolve" -msgstr "" +msgstr "Nu se poate SoluÈ›iona" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "" +msgid "Connecting..." +msgstr "Conectare..." #: editor/export_template_manager.cpp msgid "Can't Connect" -msgstr "" +msgstr "Nu se poate Conecta" #: editor/export_template_manager.cpp msgid "Connected" -msgstr "" +msgstr "Conectat" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "" +msgid "Requesting..." +msgstr "Se Solicită..." #: editor/export_template_manager.cpp msgid "Downloading" -msgstr "" +msgstr "Se Descarcă" #: editor/export_template_manager.cpp msgid "Connection Error" -msgstr "" +msgstr "Eroare de Conexiune" #: editor/export_template_manager.cpp msgid "SSL Handshake Error" -msgstr "" +msgstr "Eroare SSL Handshake" #: editor/export_template_manager.cpp msgid "Current Version:" -msgstr "" +msgstr "Versiune Curentă:" #: editor/export_template_manager.cpp msgid "Installed Versions:" -msgstr "" +msgstr "Versiuni Instalate:" #: editor/export_template_manager.cpp msgid "Install From File" -msgstr "" +msgstr "Instalează Din FiÈ™ier" #: editor/export_template_manager.cpp msgid "Remove Template" -msgstr "" +msgstr "Elimină Șablon" #: editor/export_template_manager.cpp msgid "Select template file" -msgstr "" +msgstr "Selectează fiÈ™ierul È™ablon" #: editor/export_template_manager.cpp msgid "Export Template Manager" -msgstr "" +msgstr "Exportă Managerul de Șabloane" #: editor/export_template_manager.cpp msgid "Download Templates" -msgstr "" +msgstr "Descarcă Șabloane" #: editor/export_template_manager.cpp msgid "Select mirror from list: " -msgstr "" +msgstr "Selectează oglinda din listă: " #: editor/file_type_cache.cpp msgid "Can't open file_type_cache.cch for writing, not saving file type cache!" msgstr "" +"Nu se poate deschide file_type_cache.cch pentru scriere, nu se salvează " +"fiÈ™ierul tip cache!" #: editor/filesystem_dock.cpp msgid "Cannot navigate to '%s' as it has not been found in the file system!" msgstr "" +"Nu se poate naviga către '%s' pentru că nu a fost găsit în sistemul de " +"fiÈ™iere!" #: editor/filesystem_dock.cpp msgid "View items as a grid of thumbnails" -msgstr "" +msgstr "Vizualizează articolele ca È™i o grilă de miniaturi" #: editor/filesystem_dock.cpp msgid "View items as a list" -msgstr "" +msgstr "Vizualizează articolele ca È™i o listă" #: editor/filesystem_dock.cpp msgid "Status: Import of file failed. Please fix file and reimport manually." msgstr "" +"Stare: Importarea fiÈ™ierului eÈ™uată. Te rog repară fiÈ™ierul È™i reimportă " +"manual." #: editor/filesystem_dock.cpp msgid "Cannot move/rename resources root." -msgstr "" +msgstr "Nu se poate muta/redenumi rădăcina resurselor." #: editor/filesystem_dock.cpp msgid "Cannot move a folder into itself." -msgstr "" +msgstr "Nu se poate muta un director în el însuÈ™i." #: editor/filesystem_dock.cpp msgid "Error moving:" -msgstr "" +msgstr "Eroare mutând:" #: editor/filesystem_dock.cpp msgid "Error duplicating:" -msgstr "" +msgstr "Eroare duplicând:" #: editor/filesystem_dock.cpp msgid "Unable to update dependencies:" -msgstr "" +msgstr "Imposibil de actualizat dependinÈ›ele:" #: editor/filesystem_dock.cpp msgid "No name provided" -msgstr "" +msgstr "Niciun nume furnizat" #: editor/filesystem_dock.cpp msgid "Provided name contains invalid characters" -msgstr "" +msgstr "Numele furnizat conÈ›ine caractere nevalide" #: editor/filesystem_dock.cpp msgid "No name provided." -msgstr "" +msgstr "Niciun nume furnizat." #: editor/filesystem_dock.cpp msgid "Name contains invalid characters." -msgstr "" +msgstr "Numele furnizat conÈ›ine caractere nevalide." #: editor/filesystem_dock.cpp msgid "A file or folder with this name already exists." -msgstr "" +msgstr "Un fiÈ™ier sau un director cu acest nume există deja." #: editor/filesystem_dock.cpp msgid "Renaming file:" -msgstr "" +msgstr "Redenumind fiÈ™ierul:" #: editor/filesystem_dock.cpp msgid "Renaming folder:" -msgstr "" +msgstr "Redenumind directorul:" #: editor/filesystem_dock.cpp msgid "Duplicating file:" -msgstr "" +msgstr "Duplicând fiÈ™ierul:" #: editor/filesystem_dock.cpp msgid "Duplicating folder:" -msgstr "" +msgstr "Duplicând directorul:" #: editor/filesystem_dock.cpp msgid "Expand all" -msgstr "" +msgstr "Extinde toate" #: editor/filesystem_dock.cpp msgid "Collapse all" -msgstr "" +msgstr "Restrânge toate" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "" +msgid "Rename..." +msgstr "RedenumeÈ™te..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "" +msgid "Move To..." +msgstr "Mută ÃŽn..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" -msgstr "" +msgstr "Deschide Scena(ele)" #: editor/filesystem_dock.cpp msgid "Instance" -msgstr "" +msgstr "Instanță" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "" +msgid "Edit Dependencies..." +msgstr "Editează DependinÈ›ele..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "" +msgid "View Owners..." +msgstr "Vizualizează Proprietarii..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "" +msgid "Duplicate..." +msgstr "DuplicaÈ›i..." #: editor/filesystem_dock.cpp msgid "Previous Directory" -msgstr "" +msgstr "Directorul Anterior" #: editor/filesystem_dock.cpp msgid "Next Directory" -msgstr "" +msgstr "Directorul Urmator" #: editor/filesystem_dock.cpp msgid "Re-Scan Filesystem" -msgstr "" +msgstr "Rescanează Sistemul de FiÈ™iere" #: editor/filesystem_dock.cpp msgid "Toggle folder status as Favorite" -msgstr "" +msgstr "Marchează statutul directorului ca Favorit" #: editor/filesystem_dock.cpp msgid "Instance the selected scene(s) as child of the selected node." -msgstr "" +msgstr "InstanÈ›iază scena(ele) selectată ca un copil al nodului selectat." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" +"Se Scanează FiÈ™ierele,\n" +"Te Rog AÈ™teaptă..." #: editor/filesystem_dock.cpp msgid "Move" -msgstr "" +msgstr "Mută" #: editor/filesystem_dock.cpp editor/plugins/animation_tree_editor_plugin.cpp #: editor/project_manager.cpp msgid "Rename" -msgstr "" +msgstr "RedenumeÈ™te" #: editor/groups_editor.cpp msgid "Add to Group" -msgstr "" +msgstr "Adaugă în Grup" #: editor/groups_editor.cpp msgid "Remove from Group" -msgstr "" +msgstr "Elimină din Grup" #: editor/import/resource_importer_scene.cpp msgid "Import as Single Scene" -msgstr "" +msgstr "Importă ca Scenă Simplă" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Animations" -msgstr "" +msgstr "Importă cu AnimaÈ›ii Separate" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials" -msgstr "" +msgstr "Importă cu Materiale Separate" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects" -msgstr "" +msgstr "Importă cu Obiecte Separate" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Materials" -msgstr "" +msgstr "Importă cu Obiecte+Materiale Separate" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Animations" -msgstr "" +msgstr "Importă cu Obiecte+AnimaÈ›ii Separate" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials+Animations" -msgstr "" +msgstr "Importă cu Materiale+AnimaÈ›ii Separate" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Materials+Animations" -msgstr "" +msgstr "Importă cu Obiecte+Materiale+AnimaÈ›ii Separate" #: editor/import/resource_importer_scene.cpp msgid "Import as Multiple Scenes" -msgstr "" +msgstr "Importă ca Scene Multiple" #: editor/import/resource_importer_scene.cpp msgid "Import as Multiple Scenes+Materials" -msgstr "" +msgstr "Importă ca Scene+Materiale Multiple" #: editor/import/resource_importer_scene.cpp #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import Scene" -msgstr "" +msgstr "Importă Scena" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "" +msgid "Importing Scene..." +msgstr "Se Importa Scena..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" -msgstr "" +msgstr "Se Genereaza Lightmaps" #: editor/import/resource_importer_scene.cpp msgid "Generating for Mesh: " -msgstr "" +msgstr "Se Generează pentru Mesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "" +msgid "Running Custom Script..." +msgstr "Se Execută un Script Personalizat..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" -msgstr "" +msgstr "Nu s-a putut încărca scriptul post-importare:" #: editor/import/resource_importer_scene.cpp msgid "Invalid/broken script for post-import (check console):" -msgstr "" +msgstr "Script nevalid/nefuncÈ›ional pentru post-importare (vezi consola):" #: editor/import/resource_importer_scene.cpp msgid "Error running post-import script:" -msgstr "" +msgstr "Eroare la executarea scripyului post-importare:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "" +msgid "Saving..." +msgstr "Se Salvează..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" -msgstr "" +msgstr "Setează ca Implicit pentru '%s'" #: editor/import_dock.cpp msgid "Clear Default for '%s'" -msgstr "" +msgstr "Curăță setarea Implicită pentru '%s'" #: editor/import_dock.cpp msgid " Files" -msgstr "" +msgstr " FiÈ™iere" #: editor/import_dock.cpp msgid "Import As:" -msgstr "" +msgstr "Importă Ca:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "" +msgid "Preset..." +msgstr "Presetare..." #: editor/import_dock.cpp msgid "Reimport" -msgstr "" +msgstr "Reimportă" #: editor/multi_node_edit.cpp msgid "MultiNode Set" -msgstr "" +msgstr "Set MultiNod" #: editor/node_dock.cpp msgid "Groups" -msgstr "" +msgstr "Grupuri" #: editor/node_dock.cpp msgid "Select a Node to edit Signals and Groups." -msgstr "" +msgstr "Selectează un Nod pentru a edita Semnalele È™i Grupurile." #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create Poly" -msgstr "" +msgstr "Crează Poligon" #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/collision_polygon_editor_plugin.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit Poly" -msgstr "" +msgstr "Editează Poligon" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Insert Point" -msgstr "" +msgstr "Inserează Punct" #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/collision_polygon_editor_plugin.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit Poly (Remove Point)" -msgstr "" +msgstr "Editează Poligon (Elimină Punct)" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Remove Poly And Point" -msgstr "" +msgstr "Elimină Poligon Și Punct" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Create a new polygon from scratch" -msgstr "" +msgstr "Crează un nou poligon de la zero" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "" @@ -2828,522 +2910,526 @@ msgid "" "Ctrl+LMB: Split Segment.\n" "RMB: Erase Point." msgstr "" +"Editează poligon existent:\n" +"LMB: Mută Punct.\n" +"Ctrl+LMB: Despică Segment.\n" +"RMB: Șterge Punct." #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Delete points" -msgstr "" +msgstr "Șterge puncte" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Toggle Autoplay" -msgstr "" +msgstr "Comutează Auto-ExecuÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "New Animation Name:" -msgstr "" +msgstr "Nume Nou AnimaÈ›ie:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "New Anim" -msgstr "" +msgstr "Anim Nouă" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Animation Name:" -msgstr "" +msgstr "Schimbă Numele AnimaÈ›iei:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Delete Animation?" -msgstr "" +msgstr "Ștergi AnimaÈ›ia?" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Remove Animation" -msgstr "" +msgstr "Elimină AnimaÈ›ia" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Invalid animation name!" -msgstr "" +msgstr "EROARE: Nume animaÈ›ie nevalid!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Animation name already exists!" -msgstr "" +msgstr "EROARE: Numele animaÈ›iei există deja!" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Rename Animation" -msgstr "" +msgstr "RedenumeÈ™te AnimaÈ›ia" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Animation" -msgstr "" +msgstr "Adaugă AnimaÈ›ia" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Blend Next Changed" -msgstr "" +msgstr "Amestecă Următoarea Schimbare" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Blend Time" -msgstr "" +msgstr "Schimbă Timpul Amestecului" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load Animation" -msgstr "" +msgstr "ÃŽncarcă AnimaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Duplicate Animation" -msgstr "" +msgstr "Duplicare AnimaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to copy!" -msgstr "" +msgstr "EROARE: Nicio copie a animaÈ›iei!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation resource on clipboard!" -msgstr "" +msgstr "EROARE: Nicio resursă de animaÈ›ie în clipboard!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Pasted Animation" -msgstr "" +msgstr "AnimaÈ›ie Lipită" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Paste Animation" -msgstr "" +msgstr "LipeÈ™te AnimaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to edit!" -msgstr "" +msgstr "EROARE: Nicio animaÈ›ie pentru editare!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation backwards from current pos. (A)" -msgstr "" +msgstr "Rulează animaÈ›ia selectată în sens invers de la poziÈ›ia curentă. (A)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation backwards from end. (Shift+A)" -msgstr "" +msgstr "Rulează animaÈ›ia selectată în sens invers de la sfârÈ™it. (Shift+A)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Stop animation playback. (S)" -msgstr "" +msgstr "OpreÈ™te rularea animaÈ›iei. (S)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation from start. (Shift+D)" -msgstr "" +msgstr "Rulează animaÈ›ia selectată de la început. (Shift+D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation from current pos. (D)" -msgstr "" +msgstr "Rulează animaÈ›ia selectată de la poziÈ›ia curentă. (D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation position (in seconds)." -msgstr "" +msgstr "PoziÈ›ia animaÈ›iei (în secunde)." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Scale animation playback globally for the node." -msgstr "" +msgstr "Redimensionează rularea animaÈ›iei pentru nod." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create new animation in player." -msgstr "" +msgstr "Creează o nouă animaÈ›ie în player." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load animation from disk." -msgstr "" +msgstr "ÃŽncarcă animaÈ›ie de pe disc." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load an animation from disk." -msgstr "" +msgstr "ÃŽncarcă o animaÈ›ie de pe disc." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Save the current animation" -msgstr "" +msgstr "Salvează actuala animaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Display list of animations in player." -msgstr "" +msgstr "AfiÈ™ează o listă a animaÈ›iilor în player." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Autoplay on Load" -msgstr "" +msgstr "Auto-Execută la ÃŽncărcare" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Edit Target Blend Times" -msgstr "" +msgstr "Editează Timpul de Amestecare al Èšintei" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation Tools" -msgstr "" +msgstr "Unelte AnimaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Copy Animation" -msgstr "" +msgstr "Copiză AnimaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Onion Skinning" -msgstr "" +msgstr "Onion Skinning" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Enable Onion Skinning" -msgstr "" +msgstr "Activează Onion Skinning" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Directions" -msgstr "" +msgstr "DirecÈ›ii" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Past" -msgstr "" +msgstr "Trecut" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Future" -msgstr "" +msgstr "Viitor" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Depth" -msgstr "" +msgstr "Adâncime" #: editor/plugins/animation_player_editor_plugin.cpp msgid "1 step" -msgstr "" +msgstr "1 pas" #: editor/plugins/animation_player_editor_plugin.cpp msgid "2 steps" -msgstr "" +msgstr "2 paÈ™i" #: editor/plugins/animation_player_editor_plugin.cpp msgid "3 steps" -msgstr "" +msgstr "3 paÈ™i" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Differences Only" -msgstr "" +msgstr "Doar DiferenÈ›e" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Force White Modulate" -msgstr "" +msgstr "ForÈ›ează Modulare Albă" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Include Gizmos (3D)" -msgstr "" +msgstr "Include Gizmos (3D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create New Animation" -msgstr "" +msgstr "Creează AnimaÈ›ie Nouă" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation Name:" -msgstr "" +msgstr "Nume AnimaÈ›ie:" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp editor/property_editor.cpp #: editor/script_create_dialog.cpp msgid "Error!" -msgstr "" +msgstr "Eroare!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Blend Times:" -msgstr "" +msgstr "Timpi de Amestecare:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Next (Auto Queue):" -msgstr "" +msgstr "Următorul (Rând Automat):" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Cross-Animation Blend Times" -msgstr "" +msgstr "Timpi de Amestecare Cross-AnimaÈ›ie" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Animation" -msgstr "" +msgstr "AnimaÈ›ie" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "New name:" -msgstr "" +msgstr "Nume nou:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Filters" -msgstr "" +msgstr "Editează Filtrele" #: editor/plugins/animation_tree_editor_plugin.cpp #: editor/plugins/multimesh_editor_plugin.cpp msgid "Scale:" -msgstr "" +msgstr "Dimensiune:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Fade In (s):" -msgstr "" +msgstr "Estompează (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Fade Out (s):" -msgstr "" +msgstr "Reliefează (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend" -msgstr "" +msgstr "Amestec" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix" -msgstr "" +msgstr "Mix" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Auto Restart:" -msgstr "" +msgstr "Restartare Automată:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Restart (s):" -msgstr "" +msgstr "Restartare (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Random Restart (s):" -msgstr "" +msgstr "Restartare Aleatorie (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Start!" -msgstr "" +msgstr "Start!" #: editor/plugins/animation_tree_editor_plugin.cpp #: editor/plugins/multimesh_editor_plugin.cpp msgid "Amount:" -msgstr "" +msgstr "Cantitate:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend:" -msgstr "" +msgstr "Amestec:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend 0:" -msgstr "" +msgstr "Amestec 0:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend 1:" -msgstr "" +msgstr "Amestec 1:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "X-Fade Time (s):" -msgstr "" +msgstr "Timp X-Decolorare (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Current:" -msgstr "" +msgstr "Curent:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Add Input" -msgstr "" +msgstr "Adaugă Intrare(Input)" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Clear Auto-Advance" -msgstr "" +msgstr "Curăță Auto-Avansarea" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Set Auto-Advance" -msgstr "" +msgstr "Setează Auto-Avansare" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Delete Input" -msgstr "" +msgstr "Șterge Intrare(Input)" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation tree is valid." -msgstr "" +msgstr "Arborele AnimaÈ›iei este valid." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation tree is invalid." -msgstr "" +msgstr "Arborele AnimaÈ›iei este nevalid." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation Node" -msgstr "" +msgstr "Nod de AnimaÈ›ie" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "OneShot Node" -msgstr "" +msgstr "Nod OneShot" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix Node" -msgstr "" +msgstr "Nod de Amestecare" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend2 Node" -msgstr "" +msgstr "Nod Amestec2" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend3 Node" -msgstr "" +msgstr "Nod Amestec3" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend4 Node" -msgstr "" +msgstr "Nod Amestec4" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeScale Node" -msgstr "" +msgstr "Nod DimensiuneTimp" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeSeek Node" -msgstr "" +msgstr "Nod CăutareTimp" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Transition Node" -msgstr "" +msgstr "Nod TranziÈ›ie" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "" +msgid "Import Animations..." +msgstr "Importă AnimaÈ›ii..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" -msgstr "" +msgstr "Editează Filtrele Nodurilor" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "" +msgid "Filters..." +msgstr "Filtre..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" -msgstr "" +msgstr "ArboreAnimaÈ›ie" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Free" -msgstr "" +msgstr "Gratuit" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Contents:" -msgstr "" +msgstr "ConÈ›inut:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "View Files" -msgstr "" +msgstr "Vizualizează FiÈ™ierele" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't resolve hostname:" -msgstr "" +msgstr "Nu se poate rezolva numele gazdei:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Connection error, please try again." -msgstr "" +msgstr "Eroare la conectare, te rog încearcă din nou." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't connect to host:" -msgstr "" +msgstr "Nu se poate conecta la gazda:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "No response from host:" -msgstr "" +msgstr "Nciun răspuns de la gazda:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request failed, return code:" -msgstr "" +msgstr "Cerere eÈ™uată, cod returnat:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request failed, too many redirects" -msgstr "" +msgstr "Cerere eÈ™uată, prea multe redirecÈ›ionări" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Bad download hash, assuming file has been tampered with." -msgstr "" +msgstr "Hash eronat de descărcare, se presupune că fiÈ™ierul este falsificat." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Expected:" -msgstr "" +msgstr "AÈ™teptat:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Got:" -msgstr "" +msgstr "Primit:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Failed sha256 hash check" -msgstr "" +msgstr "Verificare hash sha256 eÈ™uată" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Asset Download Error:" -msgstr "" +msgstr "Eroare la Descărcarea Asset-ului:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Fetching:" -msgstr "" +msgstr "Se Preia(u):" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "" +msgid "Resolving..." +msgstr "Se Rezolvă..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" -msgstr "" +msgstr "Eroare la solicitare" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Idle" -msgstr "" +msgstr "Inactiv" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Retry" -msgstr "" +msgstr "Reîncearcă" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Download Error" -msgstr "" +msgstr "Eroare Descărcare" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Download for this asset is already in progress!" -msgstr "" +msgstr "Descărcarea acestui asset rulează deja!" #: editor/plugins/asset_library_editor_plugin.cpp msgid "first" -msgstr "" +msgstr "primul" #: editor/plugins/asset_library_editor_plugin.cpp msgid "prev" -msgstr "" +msgstr "anterior" #: editor/plugins/asset_library_editor_plugin.cpp msgid "next" -msgstr "" +msgstr "următorul" #: editor/plugins/asset_library_editor_plugin.cpp msgid "last" -msgstr "" +msgstr "ultimul" #: editor/plugins/asset_library_editor_plugin.cpp #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "All" -msgstr "" +msgstr "Toate" #: editor/plugins/asset_library_editor_plugin.cpp #: editor/project_settings_editor.cpp msgid "Plugins" -msgstr "" +msgstr "Plugin-uri" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Sort:" -msgstr "" +msgstr "Sorare:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Reverse" -msgstr "" +msgstr "Revers" #: editor/plugins/asset_library_editor_plugin.cpp #: editor/project_settings_editor.cpp msgid "Category:" -msgstr "" +msgstr "Categorie:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Site:" -msgstr "" +msgstr "Site:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "" +msgid "Support..." +msgstr "Suport..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" -msgstr "" +msgstr "Oficial" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Testing" -msgstr "" +msgstr "Se Testează" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Assets ZIP File" -msgstr "" +msgstr "FiÈ™ier ZIP cu Asset-uri" #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "" @@ -3351,135 +3437,144 @@ msgid "" "Save your scene (for images to be saved in the same dir), or pick a save " "path from the BakedLightmap properties." msgstr "" +"Nu se poate determina p cale de salvare pentru imaginile lightmap.\n" +"Salvează scena (imaginile vor fi salvate în acelasi director), sau alege o " +"cale de salvare din proprietățile BakedLightmap." #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "" "No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake " "Light' flag is on." msgstr "" +"Nicio structură pentru procesare. Asigură-te că acestea conÈ›in un canal UV2 " +"È™i că opÈ›iunea 'Procesează Lumina' este pornită." #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "Failed creating lightmap images, make sure path is writable." msgstr "" +"Crearea imaginilor lightmap eÈ™uată, asigură-te că poate fi scrisă calea spre " +"ele." #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "Bake Lightmaps" -msgstr "" +msgstr "Procesează Lightmaps" #: editor/plugins/camera_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp msgid "Preview" -msgstr "" +msgstr "Previzualizare" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Configure Snap" -msgstr "" +msgstr "Configurare Snap" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid Offset:" -msgstr "" +msgstr "Compensare Grilă:" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid Step:" -msgstr "" +msgstr "Pas Grilă:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotation Offset:" -msgstr "" +msgstr "Compensare RotaÈ›ie:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotation Step:" -msgstr "" +msgstr "Pas RotaÈ›ie:" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Pivot" -msgstr "" +msgstr "Mută Pivot" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Action" -msgstr "" +msgstr "AcÈ›iune de Mutare" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move vertical guide" -msgstr "" +msgstr "Mută ghidul vertical" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Create new vertical guide" -msgstr "" +msgstr "Creează un nou ghid vertical" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Remove vertical guide" -msgstr "" +msgstr "Elimină ghidul vertical" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move horizontal guide" -msgstr "" +msgstr "Mută ghidul orizontal" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Create new horizontal guide" -msgstr "" +msgstr "Creează un nou ghid orizontal" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Remove horizontal guide" -msgstr "" +msgstr "Elimină ghidul orizontal" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Create new horizontal and vertical guides" -msgstr "" +msgstr "Creează ghizi noi orizontal È™i vertical" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Edit IK Chain" -msgstr "" +msgstr "Editează LanÈ› IK" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Edit CanvasItem" -msgstr "" +msgstr "Editează ObiectulPânză" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Anchors only" -msgstr "" +msgstr "Doar ancore" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Change Anchors and Margins" -msgstr "" +msgstr "Modifică Ancorele È™i Limitele" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Change Anchors" -msgstr "" +msgstr "Modifică Ancorele" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Paste Pose" -msgstr "" +msgstr "LipeÈ™te Postura" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Select Mode" -msgstr "" +msgstr "Mod Selectare" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Drag: Rotate" -msgstr "" +msgstr "Trage: Rotire" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Alt+Drag: Move" -msgstr "" +msgstr "Alt+Trage: Mutare" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Press 'v' to Change Pivot, 'Shift+v' to Drag Pivot (while moving)." msgstr "" +"Apasă 'v' pentru a Schimba Pivotul, 'Shift+v' pentru a Trage Pivotul (în " +"timpul miÈ™cării)." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Alt+RMB: Depth list selection" -msgstr "" +msgstr "Alt+RMB: SelecÈ›ie adâncime listă" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move Mode" -msgstr "" +msgstr "Mod Mutare" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Rotate Mode" -msgstr "" +msgstr "Mod RotaÈ›ie" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp @@ -3487,559 +3582,566 @@ msgid "" "Show a list of all objects at the position clicked\n" "(same as Alt+RMB in select mode)." msgstr "" +"Arată o listă a tuturor obiectelor la poziÈ›ia clickului\n" +"(similar cu Alt+RMB în modul selectare)." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Click to change object's rotation pivot." -msgstr "" +msgstr "Click pentru a modifica pivotul de rotaÈ›ie al obiectului." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Pan Mode" -msgstr "" +msgstr "Mod ÃŽn Jur" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Toggles snapping" -msgstr "" +msgstr "Comutare snapping" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Use Snap" -msgstr "" +msgstr "Utilizează Snap" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snapping options" -msgstr "" +msgstr "OpÈ›iuni Snapping" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to grid" -msgstr "" +msgstr "Snap pe grilă" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Use Rotation Snap" -msgstr "" +msgstr "FoloseÈ™te RotaÈ›ia Snap" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "" +msgstr "Configurare Snap..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" -msgstr "" +msgstr "Snap Relativ" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Use Pixel Snap" -msgstr "" +msgstr "Utilizează Pixel Snap" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Smart snapping" -msgstr "" +msgstr "Snapping inteligent" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to parent" -msgstr "" +msgstr "Snap către părinte" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to node anchor" -msgstr "" +msgstr "Snap către ancora nodului" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to node sides" -msgstr "" +msgstr "Snap pe feÈ›ele nodului" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to other nodes" -msgstr "" +msgstr "Snap către alte noduri" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap to guides" -msgstr "" +msgstr "Snap pe ghizi" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp msgid "Lock the selected object in place (can't be moved)." -msgstr "" +msgstr "Imobilizează obiectul selectat (nu poate fi miÈ™cat)." #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp msgid "Unlock the selected object (can be moved)." -msgstr "" +msgstr "Remobilizează obiectul selectat (poate fi miÈ™cat)." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Makes sure the object's children are not selectable." -msgstr "" +msgstr "Asigură-te că nu pot fi selectaÈ›i copiii obiectului." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Restores the object's children's ability to be selected." -msgstr "" +msgstr "Restaurează abilitatea copiilor obiectului de a fi selectaÈ›i." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Make Bones" -msgstr "" +msgstr "Creează Oase" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Clear Bones" -msgstr "" +msgstr "Curăță Oasele" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Bones" -msgstr "" +msgstr "Arată Oasele" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Make IK Chain" -msgstr "" +msgstr "Creează LanÈ› IK" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Clear IK Chain" -msgstr "" +msgstr "Curăță LanÈ›ul IK" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp msgid "View" -msgstr "" +msgstr "Perspectivă" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Show Grid" -msgstr "" +msgstr "Arată Grila" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Helpers" -msgstr "" +msgstr "Arată AsistenÈ›ii" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Rulers" -msgstr "" +msgstr "Arată Riglele" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Guides" -msgstr "" +msgstr "Arată Ghizii" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Origin" -msgstr "" +msgstr "Arată Originea" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Show Viewport" -msgstr "" +msgstr "Arată Fereastra de Lucru" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" -msgstr "" +msgstr "Centrează SelecÈ›ia" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Frame Selection" -msgstr "" +msgstr "ÃŽncadrează în Ecran SelecÈ›ia" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Layout" -msgstr "" +msgstr "Schemă" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Keys" -msgstr "" +msgstr "Inserează Note" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Key" -msgstr "" +msgstr "Inserează Notă" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Insert Key (Existing Tracks)" -msgstr "" +msgstr "Inserează Notă (Melodii existente)" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Copy Pose" -msgstr "" +msgstr "Copiază Postura" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Clear Pose" -msgstr "" +msgstr "Curăță Postura" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Drag pivot from mouse position" -msgstr "" +msgstr "Trage pivotul de la poziÈ›ia mouse-ului" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Set pivot at mouse position" -msgstr "" +msgstr "Setează pivotul la poziÈ›ia mouse-ului" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Multiply grid step by 2" -msgstr "" +msgstr "Multiplică pasul pe grilă cu 2" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Divide grid step by 2" -msgstr "" +msgstr "ÃŽmparte pasul pe grilă cu 2" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Add %s" -msgstr "" +msgstr "Adaugă %s" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Adding %s..." -msgstr "" +msgstr "Se adaugă %s..." #: editor/plugins/canvas_item_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ok" -msgstr "" +msgstr "Bine" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Cannot instantiate multiple nodes without root." -msgstr "" +msgstr "Nu se pot instanÈ›ia noduri multiple fără o rădacină." #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Create Node" -msgstr "" +msgstr "Creează Nod" #: editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Error instancing scene from %s" -msgstr "" +msgstr "Eroare la instanÈ›ierea scenei din %s" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Change default type" -msgstr "" +msgstr "Schimbă tipul implicit" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "" "Drag & drop + Shift : Add node as sibling\n" "Drag & drop + Alt : Change node type" msgstr "" +"Trage & lasă + Shift: Adaugă nod ca È™i frate\n" +"Trage & lasă + Shift: Schimbă tipul nodului" #: editor/plugins/collision_polygon_editor_plugin.cpp msgid "Create Poly3D" -msgstr "" +msgstr "Creează Poligon3D" #: editor/plugins/collision_shape_2d_editor_plugin.cpp msgid "Set Handle" -msgstr "" +msgstr "Setează Mâner" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Remove item %d?" -msgstr "" +msgstr "Elimini obiectul %d?" #: editor/plugins/cube_grid_theme_editor_plugin.cpp #: editor/plugins/theme_editor_plugin.cpp #: editor/plugins/tile_set_editor_plugin.cpp msgid "Add Item" -msgstr "" +msgstr "Adaugă Obiect" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Remove Selected Item" -msgstr "" +msgstr "Elimină Obiectul Selectat" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import from Scene" -msgstr "" +msgstr "Importă din Scenă" #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Update from Scene" -msgstr "" +msgstr "Actualizează din Scenă" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat0" -msgstr "" +msgstr "Plat0" #: editor/plugins/curve_editor_plugin.cpp msgid "Flat1" -msgstr "" +msgstr "Plat1" #: editor/plugins/curve_editor_plugin.cpp msgid "Ease in" -msgstr "" +msgstr "Facilitare în" #: editor/plugins/curve_editor_plugin.cpp msgid "Ease out" -msgstr "" +msgstr "Facilitare din" #: editor/plugins/curve_editor_plugin.cpp msgid "Smoothstep" -msgstr "" +msgstr "PasOmogen" #: editor/plugins/curve_editor_plugin.cpp msgid "Modify Curve Point" -msgstr "" +msgstr "Modifică Punctul Curbei" #: editor/plugins/curve_editor_plugin.cpp msgid "Modify Curve Tangent" -msgstr "" +msgstr "Modifică Tangenta Curbei" #: editor/plugins/curve_editor_plugin.cpp msgid "Load Curve Preset" -msgstr "" +msgstr "ÃŽncarcă Presetare a Curbei" #: editor/plugins/curve_editor_plugin.cpp msgid "Add point" -msgstr "" +msgstr "Adaugă punct" #: editor/plugins/curve_editor_plugin.cpp msgid "Remove point" -msgstr "" +msgstr "Elimină punct" #: editor/plugins/curve_editor_plugin.cpp msgid "Left linear" -msgstr "" +msgstr "Stânga liniară" #: editor/plugins/curve_editor_plugin.cpp msgid "Right linear" -msgstr "" +msgstr "Dreapta liniară" #: editor/plugins/curve_editor_plugin.cpp msgid "Load preset" -msgstr "" +msgstr "ÃŽncarcă presetare" #: editor/plugins/curve_editor_plugin.cpp msgid "Remove Curve Point" -msgstr "" +msgstr "Elimină Punctul Curbei" #: editor/plugins/curve_editor_plugin.cpp msgid "Toggle Curve Linear Tangent" -msgstr "" +msgstr "Comută Tangenta Liniară a Curbei" #: editor/plugins/curve_editor_plugin.cpp msgid "Hold Shift to edit tangents individually" -msgstr "" +msgstr "Èšine apăsat Shift pentru a edita individual tangentele" #: editor/plugins/gi_probe_editor_plugin.cpp msgid "Bake GI Probe" -msgstr "" +msgstr "Procesează Sonda GI" #: editor/plugins/gradient_editor_plugin.cpp msgid "Add/Remove Color Ramp Point" -msgstr "" +msgstr "Adaugă/Elimină Punctul Rampei de Culori" #: editor/plugins/gradient_editor_plugin.cpp #: editor/plugins/shader_graph_editor_plugin.cpp msgid "Modify Color Ramp" -msgstr "" +msgstr "Modifică Rampa de Culori" #: editor/plugins/item_list_editor_plugin.cpp msgid "Item %d" -msgstr "" +msgstr "Obiect %d" #: editor/plugins/item_list_editor_plugin.cpp msgid "Items" -msgstr "" +msgstr "Obiecte" #: editor/plugins/item_list_editor_plugin.cpp msgid "Item List Editor" -msgstr "" +msgstr "Editor Lista de Obiect" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "" "No OccluderPolygon2D resource on this node.\n" "Create and assign one?" msgstr "" +"Nicio resursă OccluderPolygon2D în acest nod.\n" +"Vrei să creezi È™i să atribui una?" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create Occluder Polygon" -msgstr "" +msgstr "Creează Poligon de Ocluziune" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create a new polygon from scratch." -msgstr "" +msgstr "Creează un nou poligon de la zero." #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit existing polygon:" -msgstr "" +msgstr "Editează poligonul existent:" #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "LMB: Move Point." -msgstr "" +msgstr "LMB: MiÈ™că Punctul." #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Ctrl+LMB: Split Segment." -msgstr "" +msgstr "Ctrl+LMB: Despică Segmentul." #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "RMB: Erase Point." -msgstr "" +msgstr "RMB: Șterge Punctul." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh is empty!" -msgstr "" +msgstr "Mesh-ul este gol!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Static Trimesh Body" -msgstr "" +msgstr "Creează un Corp Static Trimesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Static Convex Body" -msgstr "" +msgstr "Creează un Corp Static Convex" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "This doesn't work on scene root!" -msgstr "" +msgstr "Asta nu funcÈ›ionează în rădăcina scenei!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Shape" -msgstr "" +msgstr "Creează o Formă Trimesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Shape" -msgstr "" +msgstr "Creează o Formă Convexă" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Navigation Mesh" -msgstr "" +msgstr "Creează un Mesh de Navigare" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Contained Mesh is not of type ArrayMesh." -msgstr "" +msgstr "Mesh-ul conÈ›inut nu este de tipul ArrayMesh." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "UV Unwrap failed, mesh may not be manifold?" -msgstr "" +msgstr "Despachetarea UV a eÈ™uat, se poate ca mesh-ul să nu fie multiplu?" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "No mesh to debug." -msgstr "" +msgstr "Niciun mesh de depanat." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Model has no UV in this layer" -msgstr "" +msgstr "Modelul nu are UV în acest strat" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "MeshInstance lacks a Mesh!" -msgstr "" +msgstr "MeshInstance nu are un Mesh!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh has not surface to create outlines from!" -msgstr "" +msgstr "Mesh-ul nu are o suprafață din care să se poată creea contururi!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Mesh-ul primitiv nu este de tipul PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" -msgstr "" +msgstr "Nu s-a putut creea un contur!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Outline" -msgstr "" +msgstr "Creează Contur" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh" -msgstr "" +msgstr "Mesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Static Body" -msgstr "" +msgstr "Creează un Corp Static Trimesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Static Body" -msgstr "" +msgstr "Creează un Corp Static Convex" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Trimesh Collision Sibling" -msgstr "" +msgstr "Creează un Frate de Coliziune Trimesh" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Convex Collision Sibling" -msgstr "" +msgstr "Creează un Frate de Coliziune Convex" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "" +msgid "Create Outline Mesh..." +msgstr "Se Creează un Mesh de Contur..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" -msgstr "" +msgstr "Vizionare UV1" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV2" -msgstr "" +msgstr "Vizionare UV2" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Unwrap UV2 for Lightmap/AO" -msgstr "" +msgstr "Despachetează UV2 pentru Lightmap/AO" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Outline Mesh" -msgstr "" +msgstr "Creează Mesh de Contur" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Outline Size:" -msgstr "" +msgstr "Dimensiunea Conturului:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "No mesh source specified (and no MultiMesh set in node)." -msgstr "" +msgstr "Niciun mesh sursă specificată (È™i niciun MultiMesh setat în nod)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "No mesh source specified (and MultiMesh contains no Mesh)." -msgstr "" +msgstr "Niciun mesh sursă specificată (È™i MultiMesh nu conÈ›ine un Mesh)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh source is invalid (invalid path)." -msgstr "" +msgstr "Sursa mesh-ului nevalidă (cale nevalidă)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh source is invalid (not a MeshInstance)." -msgstr "" +msgstr "Sursa mesh-ului nevalidă (nu este un MeshInstance)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh source is invalid (contains no Mesh resource)." -msgstr "" +msgstr "Sursa mesh-ului nevalidă (nu conÈ›ine nicio resursă Mesh)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "No surface source specified." -msgstr "" +msgstr "Nicio sursă de suprafață specificată." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Surface source is invalid (invalid path)." -msgstr "" +msgstr "Sursa suprafeÈ›ei nevalidă (cale nevalidă)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Surface source is invalid (no geometry)." -msgstr "" +msgstr "Sursa suprafeÈ›ei nevalidă (nu există geometrie)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Surface source is invalid (no faces)." -msgstr "" +msgstr "Sursa suprafeÈ›ei nevalidă (nu există feÈ›e)." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Parent has no solid faces to populate." -msgstr "" +msgstr "Părintele nu are feÈ›e solide pentru a fi populate." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Couldn't map area." -msgstr "" +msgstr "Nu s-a putut mapa zona." #: editor/plugins/multimesh_editor_plugin.cpp msgid "Select a Source Mesh:" -msgstr "" +msgstr "Selectează un Mesh Sursă:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Select a Target Surface:" -msgstr "" +msgstr "Selectează o Suprafață Èšintă:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate Surface" -msgstr "" +msgstr "Populează SuprafaÈ›a" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate MultiMesh" -msgstr "" +msgstr "Populează MultiMesh" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Target Surface:" -msgstr "" +msgstr "Suprafață Èšintă:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Source Mesh:" -msgstr "" +msgstr "Mesh Sursă:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "X-Axis" -msgstr "" +msgstr "Axa-X" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Y-Axis" -msgstr "" +msgstr "Axa-Y" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Z-Axis" -msgstr "" +msgstr "Axa-Z" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Mesh Up Axis:" @@ -4055,7 +4157,7 @@ msgstr "" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Random Scale:" -msgstr "" +msgstr "Dimensiune Aleatorie:" #: editor/plugins/multimesh_editor_plugin.cpp msgid "Populate" @@ -4067,11 +4169,11 @@ msgstr "" #: editor/plugins/navigation_mesh_editor_plugin.cpp msgid "Bake the navigation mesh." -msgstr "" +msgstr "Procesează mesh-ul de navigare." #: editor/plugins/navigation_mesh_editor_plugin.cpp msgid "Clear the navigation mesh." -msgstr "" +msgstr "Curăță mesh-ul de navigare." #: editor/plugins/navigation_mesh_generator.cpp msgid "Setting up Configuration..." @@ -4111,11 +4213,11 @@ msgstr "" #: editor/plugins/navigation_mesh_generator.cpp msgid "Converting to native navigation mesh..." -msgstr "" +msgstr "Se converteÈ™te în mesh nativ de navigare..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Navigation Mesh Generator Setup:" -msgstr "" +msgstr "Setup Generare Mesh de Navigare:" #: editor/plugins/navigation_mesh_generator.cpp msgid "Parsing Geometry..." @@ -4143,7 +4245,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4156,7 +4258,7 @@ msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Clear Emission Mask" -msgstr "" +msgstr "Curăță Masca de Emisie" #: editor/plugins/particles_2d_editor_plugin.cpp #: editor/plugins/particles_editor_plugin.cpp @@ -4210,7 +4312,7 @@ msgstr "" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Mesh" -msgstr "" +msgstr "Creează Puncte de Emisie Din Mesh" #: editor/plugins/particles_editor_plugin.cpp msgid "Create Emission Points From Node" @@ -4375,7 +4477,7 @@ msgstr "" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Shift+Ctrl: Scale" -msgstr "" +msgstr "Shift+Ctrl: Dimensiune" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Move Polygon" @@ -4387,7 +4489,7 @@ msgstr "" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Scale Polygon" -msgstr "" +msgstr "Redimensionează Poligon" #: editor/plugins/polygon_2d_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4407,16 +4509,16 @@ msgstr "" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Clear UV" -msgstr "" +msgstr "Curăță UV" #: editor/plugins/polygon_2d_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp msgid "Snap" -msgstr "" +msgstr "Snap" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Enable Snap" -msgstr "" +msgstr "Activează Snap" #: editor/plugins/polygon_2d_editor_plugin.cpp msgid "Grid" @@ -4477,7 +4579,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp msgid "Clear Recent Files" -msgstr "" +msgstr "Curăță FiÈ™ierele Recente" #: editor/plugins/script_editor_plugin.cpp msgid "Close and save changes?" @@ -4504,7 +4606,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4593,7 +4695,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp editor/project_manager.cpp msgid "Run" -msgstr "" +msgstr "Execută" #: editor/plugins/script_editor_plugin.cpp msgid "Toggle Scripts Panel" @@ -4601,7 +4703,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4807,15 +4909,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5183,7 +5285,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale Mode (R)" -msgstr "" +msgstr "Mod Redimensionare (R)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Local Coords" @@ -5195,7 +5297,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Snap Mode (%s)" -msgstr "" +msgstr "Mod Snap (%s)" #: editor/plugins/spatial_editor_plugin.cpp msgid "Bottom View" @@ -5255,7 +5357,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Tool Scale" -msgstr "" +msgstr "Unealtă Dimensiune" #: editor/plugins/spatial_editor_plugin.cpp msgid "Toggle Freelook" @@ -5266,11 +5368,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5316,19 +5414,19 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Snap Settings" -msgstr "" +msgstr "Setări Snap" #: editor/plugins/spatial_editor_plugin.cpp msgid "Translate Snap:" -msgstr "" +msgstr "Tradu Snap:" #: editor/plugins/spatial_editor_plugin.cpp msgid "Rotate Snap (deg.):" -msgstr "" +msgstr "RotaÈ›ie Snap (grade):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale Snap (%):" -msgstr "" +msgstr "Dimensionare Snap (%):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Viewport Settings" @@ -5360,7 +5458,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp msgid "Scale (ratio):" -msgstr "" +msgstr "Dimensiune (raport):" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform Type" @@ -5456,7 +5554,7 @@ msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Snap Mode:" -msgstr "" +msgstr "Mod Snap:" #: editor/plugins/texture_region_editor_plugin.cpp msgid "<None>" @@ -5464,11 +5562,11 @@ msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Pixel Snap" -msgstr "" +msgstr "Pixel Snap" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Grid Snap" -msgstr "" +msgstr "Snap Grilă" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Auto Slice" @@ -5523,7 +5621,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5591,7 +5689,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5779,7 +5877,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5869,6 +5967,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Nume de Proiect Nevalid." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -5968,16 +6070,21 @@ msgid "" "Please edit the project and set the main scene in \"Project Settings\" under " "the \"Application\" category." msgstr "" +"Proiectul nu poate fi executat: nicio scenă principală nu a fost definită.\n" +"Te rog editează proiectul È™i setează o scenă principală în \"Setările " +"Proiectului\" din categoria \"AplicaÈ›ii\"." #: editor/project_manager.cpp msgid "" "Can't run project: Assets need to be imported.\n" "Please edit the project to trigger the initial import." msgstr "" +"Nu se poate executa priectul: există Asset-uri care trebuie importate.\n" +"Te rog editează proiectul pentru a declanÈ™a importul iniÈ›ial." #: editor/project_manager.cpp msgid "Are you sure to run more than one project?" -msgstr "" +msgstr "EÈ™ti sigur că vrei să execuÈ›i acel proiect?" #: editor/project_manager.cpp msgid "Remove project from the list? (Folder contents will not be modified)" @@ -6029,13 +6136,16 @@ msgstr "" #: editor/project_manager.cpp msgid "Can't run project" -msgstr "" +msgstr "Proiectul nu poate fi executat" #: editor/project_manager.cpp msgid "" "You don't currently have any projects.\n" "Would you like to explore the official example projects in the Asset Library?" msgstr "" +"Deocamdată nu ai niciun proiect.\n" +"DoreÈ™ti să explorezi exemplele de proiecte oficiale din Librăria de Asset-" +"uri?" #: editor/project_settings_editor.cpp msgid "Key " @@ -6055,8 +6165,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6084,7 +6194,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6261,14 +6371,14 @@ msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "" +msgstr "General" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6364,11 +6474,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6469,7 +6579,7 @@ msgstr "" #: editor/run_settings_dialog.cpp msgid "Run Mode:" -msgstr "" +msgstr "Modul de ExecuÈ›ie:" #: editor/run_settings_dialog.cpp msgid "Current Scene" @@ -6485,7 +6595,7 @@ msgstr "" #: editor/run_settings_dialog.cpp msgid "Scene Run Settings" -msgstr "" +msgstr "Setările de ExecuÈ›ie ale Scenei" #: editor/scene_tree_dock.cpp editor/script_create_dialog.cpp #: scene/gui/dialogs.cpp @@ -6539,7 +6649,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -6590,7 +6700,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Clear Inheritance" -msgstr "" +msgstr "Curăță Derivarea" #: editor/scene_tree_dock.cpp msgid "Delete Node(s)" @@ -6614,7 +6724,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Clear Script" -msgstr "" +msgstr "Curăță Scriptul" #: editor/scene_tree_dock.cpp msgid "Merge From Scene" @@ -6652,7 +6762,7 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Clear a script for the selected node." -msgstr "" +msgstr "Curăță un script pentru nodul selectat." #: editor/scene_tree_dock.cpp msgid "Remote" @@ -6664,11 +6774,11 @@ msgstr "" #: editor/scene_tree_dock.cpp msgid "Clear Inheritance? (No Undo!)" -msgstr "" +msgstr "Curăță Derivarea? (Fără ÃŽntoarcere)" #: editor/scene_tree_dock.cpp msgid "Clear!" -msgstr "" +msgstr "Curăță!" #: editor/scene_tree_editor.cpp msgid "Toggle Spatial Visible" @@ -7160,7 +7270,7 @@ msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Snap View" -msgstr "" +msgstr "Perspectivă Snap" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Clip Disabled" @@ -7212,7 +7322,7 @@ msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Cursor Clear Rotation" -msgstr "" +msgstr "Curăță RotaÈ›ia Cursorului" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Create Area" @@ -7228,7 +7338,7 @@ msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Clear Selection" -msgstr "" +msgstr "Curăță SelecÈ›ia" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "GridMap Settings" @@ -7632,11 +7742,11 @@ msgstr "" #: platform/javascript/export/export.cpp msgid "Run in Browser" -msgstr "" +msgstr "Execută în Browser" #: platform/javascript/export/export.cpp msgid "Run exported HTML in the system's default browser." -msgstr "" +msgstr "Execută HTML-ul exportat în browserul prestabilit al sistemului." #: platform/javascript/export/export.cpp msgid "Could not write file:" @@ -7971,3 +8081,11 @@ msgstr "" #: scene/resources/dynamic_font.cpp msgid "Invalid font size." msgstr "" + +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Fila anterioară" + +#, fuzzy +#~ msgid "Next" +#~ msgstr "Fila următoare" diff --git a/editor/translations/ru.po b/editor/translations/ru.po index 9ddbc965e5..97c7284404 100644 --- a/editor/translations/ru.po +++ b/editor/translations/ru.po @@ -2,7 +2,7 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# +# Ðркадий ÐÐ²Ð°Ñ <savvot@gmail.com>, 2018. # Artem Varaksa <aymfst@gmail.com>, 2018. # B10nicMachine <shumik1337@gmail.com>, 2017. # Chaosus89 <chaosus89@gmail.com>, 2018. @@ -14,15 +14,15 @@ # Maxim toby3d Lebedev <mail@toby3d.ru>, 2016. # outbools <drag4e@yandex.ru>, 2017. # pitchblack <pitchblack@mail.ru>, 2017. +# Sergey <maligin.serega2010@yandex.ru>, 2018. # Sergey Agarkov <zorgsoft@gmail.com>, 2017. -# Ðркадий ÐÐ²Ð°Ñ <savvot@gmail.com>, 2018. -# +# teriva <spirin.cos@yandex.ru>, 2018. msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" "POT-Creation-Date: \n" -"PO-Revision-Date: 2018-04-27 16:39+0000\n" -"Last-Translator: Chaosus89 <chaosus89@gmail.com>\n" +"PO-Revision-Date: 2018-06-18 19:42+0000\n" +"Last-Translator: ijet <my-ijet@mail.ru>\n" "Language-Team: Russian <https://hosted.weblate.org/projects/godot-engine/" "godot/ru/>\n" "Language: ru\n" @@ -31,7 +31,7 @@ msgstr "" "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.0-dev\n" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -511,8 +511,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Отключить '%s' от '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "ПриÑоединить.." +msgid "Connect..." +msgstr "ПриÑоединить..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -931,12 +931,12 @@ msgid "Move Audio Bus" msgstr "ПеремеÑтить аудио шину" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Сохранить раÑкладку звуковой шины как.." +msgid "Save Audio Bus Layout As..." +msgstr "Сохранить раÑкладку звуковой шины как..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "МеÑтоположение новой раÑкладки.." +msgid "Location for New Layout..." +msgstr "МеÑтоположение новой раÑкладки..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1077,12 +1077,12 @@ msgid "Updating Scene" msgstr "Обновление Ñцены" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Сохранение локальных изменений.." +msgid "Storing local changes..." +msgstr "Сохранение локальных изменений..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Обновление Ñцены.." +msgid "Updating scene..." +msgstr "Обновление Ñцены..." #: editor/editor_data.cpp msgid "[empty]" @@ -1150,8 +1150,8 @@ msgid "Show In File Manager" msgstr "ПроÑмотреть в проводнике" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "ÐÐ¾Ð²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°.." +msgid "New Folder..." +msgstr "ÐÐ¾Ð²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1412,20 +1412,20 @@ msgstr "ОчиÑтить вывод" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "ÐкÑпорт проекта не удалÑÑ, код %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Ошибка при Ñохранении реÑурÑа!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Сохранить реÑÑƒÑ€Ñ ÐºÐ°Ðº.." +msgid "Save Resource As..." +msgstr "Сохранить реÑÑƒÑ€Ñ ÐºÐ°Ðº..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "ЯÑно.." +msgid "I see..." +msgstr "ЯÑно..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1655,12 +1655,12 @@ msgid "Open Base Scene" msgstr "Открыть оÑновную Ñцену" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "БыÑтро открыть Ñцену.." +msgid "Quick Open Scene..." +msgstr "БыÑтро открыть Ñцену..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "БыÑтро открыть Ñкрипт.." +msgid "Quick Open Script..." +msgstr "БыÑтро открыть Ñкрипт..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1671,8 +1671,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Сохранить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² «%s» перед закрытием?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Сохранить Ñцену как.." +msgid "Save Scene As..." +msgstr "Сохранить Ñцену как..." #: editor/editor_node.cpp msgid "No" @@ -1723,8 +1723,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Ðто дейÑтвие Ð½ÐµÐ»ÑŒÐ·Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ. ВоÑÑтановить в любом Ñлучае?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "БыÑтро запуÑтить Ñцену.." +msgid "Quick Run Scene..." +msgstr "БыÑтро запуÑтить Ñцену..." #: editor/editor_node.cpp msgid "Quit" @@ -1880,8 +1880,8 @@ msgid "Previous tab" msgstr "ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ°" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "ОтÑортировать файлы.." +msgid "Filter Files..." +msgstr "ОтÑортировать файлы..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1892,12 +1892,12 @@ msgid "New Scene" msgstr "ÐÐ¾Ð²Ð°Ñ Ñцена" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "ÐÐ¾Ð²Ð°Ñ ÑƒÐ½Ð°ÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¡Ñ†ÐµÐ½Ð°.." +msgid "New Inherited Scene..." +msgstr "ÐÐ¾Ð²Ð°Ñ ÑƒÐ½Ð°ÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¡Ñ†ÐµÐ½Ð°..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Открыть Ñцену.." +msgid "Open Scene..." +msgstr "Открыть Ñцену..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1916,16 +1916,16 @@ msgid "Open Recent" msgstr "Открыть поÑледнее" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Конвертировать в.." +msgid "Convert To..." +msgstr "Конвертировать в..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "Библиотека полиÑеток.." +msgid "MeshLibrary..." +msgstr "Библиотека полиÑеток..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "Ðабор тайлов.." +msgid "TileSet..." +msgstr "Ðабор тайлов..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2109,7 +2109,7 @@ msgstr "СиÑтема отÑÐ»ÐµÐ¶Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp msgid "Community" -msgstr "ОбщеÑтвенные" +msgstr "СообщеÑтво" #: editor/editor_node.cpp msgid "About" @@ -2188,8 +2188,8 @@ msgid "Save the currently edited resource." msgstr "Сохранить текущий редактируемый реÑурÑ." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Сохранить как.." +msgid "Save As..." +msgstr "Сохранить как..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2297,8 +2297,8 @@ msgid "Creating Mesh Previews" msgstr "Создание предпроÑмотра" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Миниатюра.." +msgid "Thumbnail..." +msgstr "Миниатюра..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2359,7 +2359,7 @@ msgstr "Включительно" #: editor/editor_profiler.cpp msgid "Self" -msgstr "Self" +msgstr "" #: editor/editor_profiler.cpp msgid "Frame #:" @@ -2450,7 +2450,7 @@ msgid "(Current)" msgstr "(Текущий)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Получение зеркал, пожалуйÑта подождите." #: editor/export_template_manager.cpp @@ -2528,8 +2528,8 @@ msgid "Error requesting url: " msgstr "Ошибка запроÑа адреÑа ÑÑылки: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Подключение к зеркалам.." +msgid "Connecting to Mirror..." +msgstr "Подключение к зеркалам..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2545,8 +2545,8 @@ msgstr "Ðе удаётÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐ¸Ñ‚ÑŒ" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "Подключение.." +msgid "Connecting..." +msgstr "Подключение..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2558,8 +2558,8 @@ msgstr "Подключен" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "Запрашиваю.." +msgid "Requesting..." +msgstr "Запрашиваю..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2695,12 +2695,12 @@ msgid "Collapse all" msgstr "Свернуть вÑе" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Переименовать.." +msgid "Rename..." +msgstr "Переименовать..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "ПеремеÑтить в.." +msgid "Move To..." +msgstr "ПеремеÑтить в..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2711,16 +2711,16 @@ msgid "Instance" msgstr "Добавить ÑкземплÑÑ€" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Редактировать завиÑимоÑти.." +msgid "Edit Dependencies..." +msgstr "Редактировать завиÑимоÑти..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "ПроÑмотреть владельцев.." +msgid "View Owners..." +msgstr "ПроÑмотреть владельцев..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Дублировать.." +msgid "Duplicate..." +msgstr "Дублировать..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2745,7 +2745,7 @@ msgstr "Добавить выбранную Ñцену(Ñ‹), в качеÑтве #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Сканирование файлов,\n" "пожалуйÑта, подождите..." @@ -2813,8 +2813,8 @@ msgid "Import Scene" msgstr "Импортировать Ñцену" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Импортирование Ñцены.." +msgid "Importing Scene..." +msgstr "Импортирование Ñцены..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2825,8 +2825,8 @@ msgid "Generating for Mesh: " msgstr "Создание Ð´Ð»Ñ Ð¿Ð¾Ð»Ð¸Ñетки: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "ЗапуÑк пользовательÑкого Ñкрипта.." +msgid "Running Custom Script..." +msgstr "ЗапуÑк пользовательÑкого Ñкрипта..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2841,8 +2841,8 @@ msgid "Error running post-import script:" msgstr "Ошибка запуÑка поÑÑ‚-импорт Ñкрипта:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Сохранение.." +msgid "Saving..." +msgstr "Сохранение..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2861,8 +2861,8 @@ msgid "Import As:" msgstr "Импортировать как:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "ПредуÑтановка.." +msgid "Preset..." +msgstr "ПредуÑтановка..." #: editor/import_dock.cpp msgid "Reimport" @@ -3282,16 +3282,16 @@ msgid "Transition Node" msgstr "Transition узел" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Импортировать анимации.." +msgid "Import Animations..." +msgstr "Импортировать анимации..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Редактировать фильтры узла" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "Фильтры.." +msgid "Filters..." +msgstr "Фильтры..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3358,7 +3358,7 @@ msgid "Fetching:" msgstr "Извлечение:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "ИнициализациÑ..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3425,8 +3425,8 @@ msgid "Site:" msgstr "Сайт:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Поддержка.." +msgid "Support..." +msgstr "Поддержка..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3622,6 +3622,7 @@ msgid "Use Rotation Snap" msgstr "ИÑпользовать привÑзку вращениÑ" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "ÐаÑтроить привÑзку..." @@ -3718,18 +3719,16 @@ msgid "Show Guides" msgstr "Показывать направлÑющие" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "Отображать начало координат" +msgstr "Отображать центр" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Окно" +msgstr "Показать окно проÑмотра" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" -msgstr "Центрировать на выбранном" +msgstr "Центрировать выбранное" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Frame Selection" @@ -4018,7 +4017,7 @@ msgstr "Полиcетка не имеет поверхноÑти Ð´Ð»Ñ Ñозд #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Тип полиÑетки не PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4049,8 +4048,8 @@ msgid "Create Convex Collision Sibling" msgstr "Создать выпуклую облаÑть ÑтолкновениÑ" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Создать полиÑетку обводки.." +msgid "Create Outline Mesh..." +msgstr "Создать полиÑетку обводки..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4254,8 +4253,8 @@ msgid "Error loading image:" msgstr "Ошибка при загрузке изображениÑ:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Ðикаких пикÑелей Ñ Ð¿Ñ€Ð¾Ð·Ñ€Ð°Ñ‡Ð½Ð¾Ñтью > 128 в изображении.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Ðикаких пикÑелей Ñ Ð¿Ñ€Ð¾Ð·Ñ€Ð°Ñ‡Ð½Ð¾Ñтью > 128 в изображении..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4615,8 +4614,8 @@ msgid "Import Theme" msgstr "Импортировать тему" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Сохранить тему как.." +msgid "Save Theme As..." +msgstr "Сохранить тему как..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4712,8 +4711,8 @@ msgstr "Переключить панель Ñкриптов" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Ðайти.." +msgid "Find..." +msgstr "Ðайти..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4922,16 +4921,16 @@ msgid "Find Previous" msgstr "Ðайти предыдущее" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Заменить.." +msgid "Replace..." +msgstr "Заменить..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Перейти к функции.." +msgid "Goto Function..." +msgstr "Перейти к функции..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Перейти к Ñтроке.." +msgid "Goto Line..." +msgstr "Перейти к Ñтроке..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5384,12 +5383,8 @@ msgid "Transform" msgstr "Преобразование" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "ÐаÑтроить привÑзку.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Окно преобразованиÑ.." +msgid "Transform Dialog..." +msgstr "Окно преобразованиÑ..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5558,7 +5553,7 @@ msgstr "ПеремеÑтить (поÑле)" #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "SpriteFrames" -msgstr "SpriteFrames" +msgstr "Спрайт кадры" #: editor/plugins/style_box_editor_plugin.cpp msgid "StyleBox Preview:" @@ -5566,7 +5561,7 @@ msgstr "ПредпроÑмотр StyleBox:" #: editor/plugins/style_box_editor_plugin.cpp msgid "StyleBox" -msgstr "StyleBox" +msgstr "" #: editor/plugins/texture_region_editor_plugin.cpp msgid "Set Region Rect" @@ -5641,8 +5636,8 @@ msgid "Remove All" msgstr "Удалить вÑе" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Редактировать тему.." +msgid "Edit theme..." +msgstr "Редактировать тему..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5711,8 +5706,8 @@ msgid "Options" msgstr "Параметры" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Имеет,Много,Разных,Опций!" +msgid "Has,Many,Options" +msgstr "ЕÑть,Много,Вариантов" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5903,8 +5898,8 @@ msgid "Presets" msgstr "ПредуÑтановки" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Добавить.." +msgid "Add..." +msgstr "Добавить..." #: editor/project_export.cpp msgid "Resources" @@ -5995,6 +5990,10 @@ msgid "Imported Project" msgstr "Импортированный проект" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "ÐедопуÑтимое Ð¸Ð¼Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð°." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Ðе удалоÑÑŒ Ñоздать папку." @@ -6194,9 +6193,11 @@ msgstr "Кнопка мыши" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"ÐедопуÑтимое Ð¸Ð¼Ñ Ð´ÐµÐ¹ÑтвиÑ. Оно не может быть пуÑтым или Ñодержать '/', ':', " +"'=', '\\' или '\"'." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6223,7 +6224,7 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "Ðажмите любую клавишу..." #: editor/project_settings_editor.cpp @@ -6407,7 +6408,7 @@ msgid "Property:" msgstr "Параметр:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "Переопределить длÑ..." #: editor/project_settings_editor.cpp @@ -6503,12 +6504,12 @@ msgid "Easing Out-In" msgstr "Переход ИЗ-Ð’" #: editor/property_editor.cpp -msgid "File.." -msgstr "Файл.." +msgid "File..." +msgstr "Файл..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Папка.." +msgid "Dir..." +msgstr "Папка..." #: editor/property_editor.cpp msgid "Assign" @@ -6682,8 +6683,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "Ðта Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ðµ может быть Ñделана на редактируемой Ñцене." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Сохранить новую Сцену как.." +msgid "Save New Scene As..." +msgstr "Сохранить новую Сцену как..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7232,7 +7233,7 @@ msgstr "Библиотеки: " #: modules/gdnative/register_types.cpp msgid "GDNative" -msgstr "GDNative" +msgstr "" #: modules/gdscript/gdscript_functions.cpp #: modules/visual_script/visual_script_builtin_funcs.cpp @@ -7399,7 +7400,7 @@ msgstr "РаÑÑтоÑние выбора:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Ð˜Ð¼Ñ ÐºÐ»Ð°ÑÑа не может быть зарезервированным ключевым Ñловом" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8111,7 +8112,7 @@ msgstr "СвойÑтво Path должно указывать на дейÑтвР#: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment необходим Environment реÑурÑ." #: scene/3d/scenario_fx.cpp msgid "" @@ -8125,6 +8126,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Ðтот WorldEnvironment игнорируетÑÑ. Либо добавьте Camera (Ð´Ð»Ñ 3D-Ñцен), либо " +"уÑтановите в Environment реÑурÑе Background режим в Canvas (Ð´Ð»Ñ 2D Ñцен)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8225,6 +8228,13 @@ msgstr "Ошибка загрузки шрифта." msgid "Invalid font size." msgstr "ÐедопуÑтимый размер шрифта." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ°" + +#~ msgid "Next" +#~ msgstr "Следующий" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "ÐедопуÑтимое название дейÑÑ‚Ð²Ð¸Ñ (подойдёт вÑÑ‘ кроме '/' или ':')." @@ -8251,9 +8261,6 @@ msgstr "ÐедопуÑтимый размер шрифта." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "ОтÑутÑтвует project.godot в папке проекта." -#~ msgid "Next" -#~ msgstr "Следующий" - #~ msgid "Not found!" #~ msgstr "Ðе найдено!" @@ -8398,8 +8405,8 @@ msgstr "ÐедопуÑтимый размер шрифта." #~ msgid "Exporting for %s" #~ msgstr "ÐкÑпортирование Ð´Ð»Ñ %s" -#~ msgid "Setting Up.." -#~ msgstr "ÐаÑтройка.." +#~ msgid "Setting Up..." +#~ msgstr "ÐаÑтройка..." #~ msgid "Error loading scene." #~ msgstr "Ошибка загрузки Ñцены." @@ -8459,8 +8466,8 @@ msgstr "ÐедопуÑтимый размер шрифта." #~ msgid "Info" #~ msgstr "ИнформациÑ" -#~ msgid "Re-Import.." -#~ msgstr "Переимпортировать.." +#~ msgid "Re-Import..." +#~ msgstr "Переимпортировать..." #~ msgid "No bit masks to import!" #~ msgstr "Ðет битовой маÑки Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð°!" @@ -8856,14 +8863,14 @@ msgstr "ÐедопуÑтимый размер шрифта." #~ msgid "Zoom (%):" #~ msgstr "МаÑштаб (%):" -#~ msgid "Skeleton.." -#~ msgstr "Скелет.." +#~ msgid "Skeleton..." +#~ msgstr "Скелет..." #~ msgid "Zoom Reset" #~ msgstr "СброÑить маÑштаб" -#~ msgid "Zoom Set.." -#~ msgstr "УÑтановить маÑштаб.." +#~ msgid "Zoom Set..." +#~ msgstr "УÑтановить маÑштаб..." #~ msgid "Set a Value" #~ msgstr "УÑтановить значение" @@ -9336,8 +9343,8 @@ msgstr "ÐедопуÑтимый размер шрифта." #~ msgid "Export Project PCK" #~ msgstr "ÐкÑпортировать PCK проекта" -#~ msgid "Export.." -#~ msgstr "ÐкÑпортировать.." +#~ msgid "Export..." +#~ msgstr "ÐкÑпортировать..." #~ msgid "Project Export" #~ msgstr "ÐкÑпортирование проекта" @@ -9457,8 +9464,8 @@ msgstr "ÐедопуÑтимый размер шрифта." #~ msgid "Reload Tool Script (Soft)" #~ msgstr "Перезагрузить инÑтрум. Ñкрипт (мÑгко)" -#~ msgid "Edit Connections.." -#~ msgstr "Изменить ÑвÑзи.." +#~ msgid "Edit Connections..." +#~ msgstr "Изменить ÑвÑзи..." #~ msgid "Set Params" #~ msgstr "Ðазначить параметры" diff --git a/editor/translations/sk.po b/editor/translations/sk.po index 16f675df37..9716dee696 100644 --- a/editor/translations/sk.po +++ b/editor/translations/sk.po @@ -2,25 +2,24 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # J08nY <johnenter@gmail.com>, 2016. -# +# MineGame 159 <minegame459@gmail.com>, 2018. msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2016-06-25 14:16+0000\n" -"Last-Translator: J08nY <johnenter@gmail.com>\n" +"PO-Revision-Date: 2018-06-18 08:43+0000\n" +"Last-Translator: MineGame 159 <minegame459@gmail.com>\n" "Language-Team: Slovak <https://hosted.weblate.org/projects/godot-engine/" "godot/sk/>\n" "Language: sk\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -"X-Generator: Weblate 2.7-dev\n" +"X-Generator: Weblate 3.0.1\n" #: editor/animation_editor.cpp msgid "Disabled" -msgstr "" +msgstr "Vypnuté" #: editor/animation_editor.cpp msgid "All Selection" @@ -28,11 +27,11 @@ msgstr "VÅ¡etky vybrané" #: editor/animation_editor.cpp msgid "Anim Change Keyframe Time" -msgstr "" +msgstr "Animácia ZmeniÅ¥ Keyframe ÄŒas" #: editor/animation_editor.cpp msgid "Anim Change Transition" -msgstr "" +msgstr "Animácia zmeniÅ¥ prechod" #: editor/animation_editor.cpp msgid "Anim Change Transform" @@ -40,11 +39,12 @@ msgstr "" #: editor/animation_editor.cpp msgid "Anim Change Keyframe Value" -msgstr "" +msgstr "Animácia ZmeniÅ¥ Keyframe Hodnotu" #: editor/animation_editor.cpp +#, fuzzy msgid "Anim Change Call" -msgstr "" +msgstr "Animácia ZmeniÅ¥ Hovor" #: editor/animation_editor.cpp msgid "Anim Add Track" @@ -68,7 +68,7 @@ msgstr "" #: editor/animation_editor.cpp msgid "Set Transitions to:" -msgstr "" +msgstr "NastaviÅ¥ prechody na:" #: editor/animation_editor.cpp msgid "Anim Track Rename" @@ -92,7 +92,7 @@ msgstr "" #: editor/animation_editor.cpp msgid "Edit Selection Curve" -msgstr "" +msgstr "UpraviÅ¥ výber krivky" #: editor/animation_editor.cpp msgid "Anim Delete Keys" @@ -101,20 +101,19 @@ msgstr "" #: editor/animation_editor.cpp editor/plugins/tile_map_editor_plugin.cpp #: modules/gridmap/grid_map_editor_plugin.cpp msgid "Duplicate Selection" -msgstr "" +msgstr "DuplikovaÅ¥ výber" #: editor/animation_editor.cpp msgid "Duplicate Transposed" msgstr "" #: editor/animation_editor.cpp -#, fuzzy msgid "Remove Selection" -msgstr "VÅ¡etky vybrané" +msgstr "OdstrániÅ¥ výber" #: editor/animation_editor.cpp msgid "Continuous" -msgstr "" +msgstr "Priebežný" #: editor/animation_editor.cpp msgid "Discrete" @@ -134,19 +133,19 @@ msgstr "" #: editor/animation_editor.cpp msgid "Scale Selection" -msgstr "" +msgstr "ZmeniÅ¥ veľkosÅ¥ výberu" #: editor/animation_editor.cpp msgid "Scale From Cursor" -msgstr "" +msgstr "ZmeniÅ¥ veľkosÅ¥ od kurzora" #: editor/animation_editor.cpp msgid "Goto Next Step" -msgstr "" +msgstr "PrejsÅ¥ na Äalšà krok" #: editor/animation_editor.cpp msgid "Goto Prev Step" -msgstr "" +msgstr "PrejsÅ¥ na predchádzajúci krok" #: editor/animation_editor.cpp editor/plugins/curve_editor_plugin.cpp #: editor/property_editor.cpp @@ -159,23 +158,25 @@ msgstr "" #: editor/animation_editor.cpp msgid "In" -msgstr "" +msgstr "V" #: editor/animation_editor.cpp msgid "Out" -msgstr "" +msgstr "Von" #: editor/animation_editor.cpp +#, fuzzy msgid "In-Out" -msgstr "" +msgstr "V-Von" #: editor/animation_editor.cpp +#, fuzzy msgid "Out-In" -msgstr "" +msgstr "Von-V" #: editor/animation_editor.cpp msgid "Transitions" -msgstr "" +msgstr "Prechody" #: editor/animation_editor.cpp msgid "Optimize Animation" @@ -495,7 +496,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -910,11 +911,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1051,11 +1052,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1126,7 +1127,7 @@ msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp #, fuzzy -msgid "New Folder.." +msgid "New Folder..." msgstr "VytvoriÅ¥ adresár" #: editor/editor_file_dialog.cpp @@ -1394,12 +1395,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1604,11 +1605,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1621,7 +1622,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1673,7 +1674,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1820,7 +1821,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1832,11 +1833,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1857,15 +1858,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2110,7 +2111,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2222,7 +2223,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2373,7 +2374,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2449,7 +2450,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2466,7 +2467,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2479,7 +2480,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2613,11 +2614,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2630,15 +2631,15 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "" #: editor/filesystem_dock.cpp @@ -2664,7 +2665,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2730,7 +2731,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2742,7 +2743,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2758,7 +2759,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2779,7 +2780,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3197,7 +3198,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3205,7 +3206,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3275,7 +3276,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3342,7 +3343,7 @@ msgid "Site:" msgstr "Stránka:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3532,6 +3533,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3958,7 +3960,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4165,7 +4167,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4530,7 +4532,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4628,7 +4630,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4834,15 +4836,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5297,11 +5299,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5558,7 +5556,7 @@ msgid "Remove All" msgstr "VÅ¡etky vybrané" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5626,7 +5624,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5817,7 +5815,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5907,6 +5905,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp #, fuzzy msgid "Couldn't create folder." msgstr "VytvoriÅ¥ adresár" @@ -6098,8 +6100,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6127,7 +6129,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6312,7 +6314,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6409,11 +6411,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6586,7 +6588,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8049,7 +8051,7 @@ msgstr "" #: scene/resources/dynamic_font.cpp msgid "Invalid font size." -msgstr "" +msgstr "Nesprávna veľkosÅ¥ pÃsma." #, fuzzy #~ msgid "Can't write file." diff --git a/editor/translations/sl.po b/editor/translations/sl.po index 74b469fc42..0fe619654f 100644 --- a/editor/translations/sl.po +++ b/editor/translations/sl.po @@ -2,17 +2,15 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # matevž lapajne <sivar.lapajne@gmail.com>, 2016-2018. # Matjaž Vitas <matjaz.vitas@gmail.com>, 2017-2018. # Miha Komatar <miha.komatar@gmail.com>, 2018. # Simon Å ander <simon.sand3r@gmail.com>, 2017. # Yahara Octanis <yaharao55@gmail.com>, 2018. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-05-03 07:41+0000\n" +"PO-Revision-Date: 2018-06-10 08:44+0000\n" "Last-Translator: matevž lapajne <sivar.lapajne@gmail.com>\n" "Language-Team: Slovenian <https://hosted.weblate.org/projects/godot-engine/" "godot/sl/>\n" @@ -21,7 +19,7 @@ msgstr "" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" "%100==4 ? 2 : 3;\n" -"X-Generator: Weblate 3.0-dev\n" +"X-Generator: Weblate 3.0.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -191,11 +189,11 @@ msgstr "PoÄisti Animacijo" #: editor/animation_editor.cpp msgid "Create NEW track for %s and insert key?" -msgstr "Ustvari NOVI trak za %s in vstavi kljuÄ?" +msgstr "Ustvarim NOVO sled za %s in vstavim kljuÄ?" #: editor/animation_editor.cpp msgid "Create %d NEW tracks and insert keys?" -msgstr "" +msgstr "Ustvarim %d NOVO sled in vstavim kljuÄe?" #: editor/animation_editor.cpp editor/create_dialog.cpp #: editor/editor_audio_buses.cpp editor/plugins/abstract_polygon_2d_editor.cpp @@ -207,43 +205,43 @@ msgstr "Ustvari" #: editor/animation_editor.cpp msgid "Anim Create & Insert" -msgstr "" +msgstr "Ustvari & Vstavi Animacijo" #: editor/animation_editor.cpp msgid "Anim Insert Track & Key" -msgstr "" +msgstr "V Animacijo Vstavi Sled & KljuÄ" #: editor/animation_editor.cpp msgid "Anim Insert Key" -msgstr "" +msgstr "V Animacijo Vstavi KljuÄ" #: editor/animation_editor.cpp msgid "Change Anim Len" -msgstr "" +msgstr "Spremeni Dolžino Animacije" #: editor/animation_editor.cpp msgid "Change Anim Loop" -msgstr "" +msgstr "Spremeni Zanko Animacije" #: editor/animation_editor.cpp msgid "Anim Create Typed Value Key" -msgstr "" +msgstr "V Animaciji Ustvari Vneseno Vrednost KljuÄa" #: editor/animation_editor.cpp msgid "Anim Insert" -msgstr "" +msgstr "Vstavi Animacijo" #: editor/animation_editor.cpp msgid "Anim Scale Keys" -msgstr "" +msgstr "Spremeni Obseg KljuÄev" #: editor/animation_editor.cpp msgid "Anim Add Call Track" -msgstr "" +msgstr "Dodaj KlicajoÄo Sled v Animacijo" #: editor/animation_editor.cpp msgid "Animation zoom." -msgstr "Približaj animacijo" +msgstr "Približaj animacijo." #: editor/animation_editor.cpp msgid "Length (s):" @@ -259,39 +257,39 @@ msgstr "Korak (s):" #: editor/animation_editor.cpp msgid "Cursor step snap (in seconds)." -msgstr "" +msgstr "Korak postavitve kazalca (v sekundah)." #: editor/animation_editor.cpp msgid "Enable/Disable looping in animation." -msgstr "" +msgstr "OmogoÄi/OnemogoÄi zankanje v animaciji." #: editor/animation_editor.cpp msgid "Add new tracks." -msgstr "" +msgstr "Dodaj Novo Sled." #: editor/animation_editor.cpp msgid "Move current track up." -msgstr "" +msgstr "Trenutno sled premakni gor." #: editor/animation_editor.cpp msgid "Move current track down." -msgstr "" +msgstr "Trenutno sled premakni dol." #: editor/animation_editor.cpp msgid "Remove selected track." -msgstr "" +msgstr "Odstrani izbrano sled." #: editor/animation_editor.cpp msgid "Track tools" -msgstr "" +msgstr "Orodja sledi" #: editor/animation_editor.cpp msgid "Enable editing of individual keys by clicking them." -msgstr "" +msgstr "S klikom na posamezne kljuÄe omogoÄite njihovo urejanje." #: editor/animation_editor.cpp msgid "Anim. Optimizer" -msgstr "" +msgstr "Optimizacija Animacije" #: editor/animation_editor.cpp msgid "Max. Linear Error:" @@ -307,11 +305,12 @@ msgstr "" #: editor/animation_editor.cpp msgid "Optimize" -msgstr "" +msgstr "Optimiziraj" #: editor/animation_editor.cpp msgid "Select an AnimationPlayer from the Scene Tree to edit animations." msgstr "" +"ÄŒe želite urediti animacije, izberite AnimationPlayer iz drevesa scene." #: editor/animation_editor.cpp msgid "Key" @@ -319,15 +318,15 @@ msgstr "ÄŒrka" #: editor/animation_editor.cpp msgid "Transition" -msgstr "" +msgstr "Prehod" #: editor/animation_editor.cpp msgid "Scale Ratio:" -msgstr "" +msgstr "Razmerje Obsega:" #: editor/animation_editor.cpp msgid "Call Functions in Which Node?" -msgstr "" +msgstr "Klic funkcije v katerem gradniku?" #: editor/animation_editor.cpp msgid "Remove invalid keys" @@ -335,7 +334,7 @@ msgstr "Odstrani nedovoljene ÄŒrke" #: editor/animation_editor.cpp msgid "Remove unresolved and empty tracks" -msgstr "" +msgstr "Odstrani nedoloÄene in prazne sledi" #: editor/animation_editor.cpp msgid "Clean-up all animations" @@ -343,11 +342,11 @@ msgstr "PobriÅ¡i vse animacije" #: editor/animation_editor.cpp msgid "Clean-Up Animation(s) (NO UNDO!)" -msgstr "IzbriÅ¡i Animacij(o/e) (BREZ VRNITVE)" +msgstr "IzbriÅ¡i Animacijo/e (BREZ VRNITVE!)" #: editor/animation_editor.cpp msgid "Clean-Up" -msgstr "PobriÅ¡i" +msgstr "PoÄisti" #: editor/array_property_edit.cpp msgid "Resize Array" @@ -355,35 +354,35 @@ msgstr "PoveÄaj Niz" #: editor/array_property_edit.cpp msgid "Change Array Value Type" -msgstr "" +msgstr "Spremeni Tip Vrednosti Niza" #: editor/array_property_edit.cpp msgid "Change Array Value" -msgstr "" +msgstr "Spremeni Vrednost Niza" #: editor/code_editor.cpp msgid "Go to Line" -msgstr "" +msgstr "Pojdi na Vrstico" #: editor/code_editor.cpp msgid "Line Number:" -msgstr "Å tevilka vrste:" +msgstr "Å tevilka Vrste:" #: editor/code_editor.cpp msgid "No Matches" -msgstr "" +msgstr "Ni Zadetkov" #: editor/code_editor.cpp msgid "Replaced %d occurrence(s)." -msgstr "" +msgstr "Zamenjana %d ponovitev/e." #: editor/code_editor.cpp msgid "Match Case" -msgstr "" +msgstr "Ujemanje Velikih ÄŒrk" #: editor/code_editor.cpp msgid "Whole Words" -msgstr "" +msgstr "Cele Besede" #: editor/code_editor.cpp msgid "Replace" @@ -391,51 +390,53 @@ msgstr "Zamenjaj" #: editor/code_editor.cpp msgid "Replace All" -msgstr "" +msgstr "Zamenjaj Vse" #: editor/code_editor.cpp msgid "Selection Only" -msgstr "" +msgstr "Samo Izbira" #: editor/code_editor.cpp msgid "Zoom In" -msgstr "" +msgstr "Približaj" #: editor/code_editor.cpp msgid "Zoom Out" -msgstr "" +msgstr "Oddalji" #: editor/code_editor.cpp msgid "Reset Zoom" -msgstr "" +msgstr "Ponastavi PoveÄavo/PomanjÅ¡avo" #: editor/code_editor.cpp editor/script_editor_debugger.cpp msgid "Line:" -msgstr "" +msgstr "Vrstica:" #: editor/code_editor.cpp msgid "Col:" -msgstr "" +msgstr "Stolpec:" #: editor/connections_dialog.cpp msgid "Method in target Node must be specified!" -msgstr "" +msgstr "Metoda v ciljnem gradniku mora biti navedena!" #: editor/connections_dialog.cpp msgid "" "Target method not found! Specify a valid method or attach a script to target " "Node." msgstr "" +"Ciljna metoda ni bila najdena! Navedite veljavno metodo ali priložite " +"skripto, ki cilja na Gradnik." #: editor/connections_dialog.cpp msgid "Connect To Node:" -msgstr "" +msgstr "Poveži se z Gradnikom:" #: editor/connections_dialog.cpp editor/editor_autoload_settings.cpp #: editor/groups_editor.cpp editor/plugins/item_list_editor_plugin.cpp #: editor/plugins/theme_editor_plugin.cpp editor/project_settings_editor.cpp msgid "Add" -msgstr "" +msgstr "Dodaj" #: editor/connections_dialog.cpp editor/dependency_editor.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -446,27 +447,27 @@ msgstr "Odstrani" #: editor/connections_dialog.cpp msgid "Add Extra Call Argument:" -msgstr "" +msgstr "Dodaj Dodaten Klicni Argument:" #: editor/connections_dialog.cpp msgid "Extra Call Arguments:" -msgstr "" +msgstr "Dodatni Klicni Argumenti:" #: editor/connections_dialog.cpp msgid "Path to Node:" -msgstr "" +msgstr "Pot do Gradnika:" #: editor/connections_dialog.cpp msgid "Make Function" -msgstr "" +msgstr "Naredi Funkcijo" #: editor/connections_dialog.cpp msgid "Deferred" -msgstr "" +msgstr "Odloženo" #: editor/connections_dialog.cpp msgid "Oneshot" -msgstr "" +msgstr "En Poizkus" #: editor/connections_dialog.cpp editor/dependency_editor.cpp #: editor/export_template_manager.cpp @@ -484,37 +485,36 @@ msgstr "Zapri" #: editor/connections_dialog.cpp msgid "Connect" -msgstr "" +msgstr "Poveži" #: editor/connections_dialog.cpp msgid "Connect '%s' to '%s'" -msgstr "" +msgstr "Poveži '%s' v '%s'" #: editor/connections_dialog.cpp msgid "Connecting Signal:" -msgstr "" +msgstr "Povezovanje Signala:" #: editor/connections_dialog.cpp msgid "Disconnect '%s' from '%s'" -msgstr "" +msgstr "Odklopite '%s' iz '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "" +msgid "Connect..." +msgstr "Poveži..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Disconnect" -msgstr "" +msgstr "Odklopi" #: editor/connections_dialog.cpp editor/editor_help.cpp editor/node_dock.cpp msgid "Signals" -msgstr "" +msgstr "Signali" #: editor/create_dialog.cpp -#, fuzzy msgid "Change %s Type" -msgstr "Osnovni Tip:" +msgstr "Spremeni Tip %s" #: editor/create_dialog.cpp editor/project_settings_editor.cpp #: modules/visual_script/visual_script_editor.cpp @@ -522,9 +522,8 @@ msgid "Change" msgstr "Spremeni" #: editor/create_dialog.cpp -#, fuzzy msgid "Create New %s" -msgstr "Ustvari" +msgstr "Ustvari Nov %s" #: editor/create_dialog.cpp editor/editor_file_dialog.cpp #: editor/filesystem_dock.cpp @@ -533,55 +532,59 @@ msgstr "Priljubljene:" #: editor/create_dialog.cpp editor/editor_file_dialog.cpp msgid "Recent:" -msgstr "" +msgstr "Nedavni:" #: editor/create_dialog.cpp editor/editor_node.cpp #: editor/plugins/asset_library_editor_plugin.cpp #: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp #: editor/quick_open.cpp msgid "Search:" -msgstr "" +msgstr "Iskanje:" #: editor/create_dialog.cpp editor/editor_help.cpp #: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp #: editor/quick_open.cpp msgid "Matches:" -msgstr "" +msgstr "Zadetki:" #: editor/create_dialog.cpp editor/editor_help.cpp #: editor/plugins/asset_library_editor_plugin.cpp editor/property_selector.cpp #: editor/script_editor_debugger.cpp msgid "Description:" -msgstr "" +msgstr "Opis:" #: editor/dependency_editor.cpp msgid "Search Replacement For:" -msgstr "" +msgstr "Iskanje Zamenjave Za:" #: editor/dependency_editor.cpp msgid "Dependencies For:" -msgstr "" +msgstr "Odvisnosti Za:" #: editor/dependency_editor.cpp msgid "" "Scene '%s' is currently being edited.\n" "Changes will not take effect unless reloaded." msgstr "" +"Scena '%s' je trenutno v urejanju.\n" +"Spremembe bodo zaÄele veljati, ko bodo znova naložene." #: editor/dependency_editor.cpp msgid "" "Resource '%s' is in use.\n" "Changes will take effect when reloaded." msgstr "" +"Vir '%s' je v uporabi.\n" +"Spremembe bodo zaÄele veljati, ko bodo znova naložene." #: editor/dependency_editor.cpp #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Dependencies" -msgstr "" +msgstr "Odvisnosti" #: editor/dependency_editor.cpp msgid "Resource" -msgstr "" +msgstr "Viri" #: editor/dependency_editor.cpp editor/editor_autoload_settings.cpp #: editor/project_manager.cpp editor/project_settings_editor.cpp @@ -591,34 +594,34 @@ msgstr "Pot" #: editor/dependency_editor.cpp msgid "Dependencies:" -msgstr "" +msgstr "Odvisnosti:" #: editor/dependency_editor.cpp msgid "Fix Broken" -msgstr "" +msgstr "Popravi Pokvarjeno" #: editor/dependency_editor.cpp msgid "Dependency Editor" -msgstr "" +msgstr "Urejevalnik Odvisnosti" #: editor/dependency_editor.cpp msgid "Search Replacement Resource:" -msgstr "" +msgstr "Iskanje Nadomestnih Virov:" #: editor/dependency_editor.cpp editor/editor_file_dialog.cpp #: editor/editor_help.cpp editor/editor_node.cpp editor/filesystem_dock.cpp #: editor/plugins/script_editor_plugin.cpp editor/property_selector.cpp #: editor/quick_open.cpp scene/gui/file_dialog.cpp msgid "Open" -msgstr "" +msgstr "Odpri" #: editor/dependency_editor.cpp msgid "Owners Of:" -msgstr "" +msgstr "Lastniki:" #: editor/dependency_editor.cpp msgid "Remove selected files from the project? (no undo)" -msgstr "" +msgstr "Odstranim izbrane datoteke iz projekta? (brez vrnitve)" #: editor/dependency_editor.cpp msgid "" @@ -626,46 +629,48 @@ msgid "" "work.\n" "Remove them anyway? (no undo)" msgstr "" +"Izbrisane datoteke so potrebne za delovanje drugih virov.\n" +"Ali jih vseeno odstranim? (brez vrnitve)" #: editor/dependency_editor.cpp msgid "Cannot remove:" -msgstr "" +msgstr "Ni mogoÄe odstraniti:" #: editor/dependency_editor.cpp msgid "Error loading:" -msgstr "" +msgstr "Napaka pri nalaganju:" #: editor/dependency_editor.cpp msgid "Scene failed to load due to missing dependencies:" -msgstr "" +msgstr "Nalaganje scene je spodletelo zaradi manjkajoÄih odvisnosti:" #: editor/dependency_editor.cpp editor/editor_node.cpp msgid "Open Anyway" -msgstr "" +msgstr "Vseeno Odpri" #: editor/dependency_editor.cpp msgid "Which action should be taken?" -msgstr "" +msgstr "Katere ukrepe je treba sprejeti?" #: editor/dependency_editor.cpp msgid "Fix Dependencies" -msgstr "" +msgstr "Popravi Odvisnosti" #: editor/dependency_editor.cpp msgid "Errors loading!" -msgstr "" +msgstr "Napake pri nalaganju!" #: editor/dependency_editor.cpp msgid "Permanently delete %d item(s)? (No undo!)" -msgstr "" +msgstr "Trajno izbriÅ¡em %d predmet(e)? (Brez vrnitve!)" #: editor/dependency_editor.cpp msgid "Owns" -msgstr "" +msgstr "Lastnik" #: editor/dependency_editor.cpp msgid "Resources Without Explicit Ownership:" -msgstr "" +msgstr "Viri Brez Izrecnega LastniÅ¡tva:" #: editor/dependency_editor.cpp editor/editor_node.cpp msgid "Orphan Resource Explorer" @@ -673,7 +678,7 @@ msgstr "Raziskovalec Osamljenih Virov" #: editor/dependency_editor.cpp msgid "Delete selected files?" -msgstr "" +msgstr "IzbriÅ¡em izbrane datoteke?" #: editor/dependency_editor.cpp editor/editor_audio_buses.cpp #: editor/editor_file_dialog.cpp editor/editor_node.cpp @@ -681,35 +686,35 @@ msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp #: editor/scene_tree_dock.cpp msgid "Delete" -msgstr "" +msgstr "IzbriÅ¡i" #: editor/dictionary_property_edit.cpp msgid "Change Dictionary Key" -msgstr "" +msgstr "Spremeni Slovarski KljuÄ" #: editor/dictionary_property_edit.cpp msgid "Change Dictionary Value" -msgstr "" +msgstr "Spremeni Slovarsko Vrednost" #: editor/editor_about.cpp msgid "Thanks from the Godot community!" -msgstr "" +msgstr "Zahvaljujemo se vam iz skupnosti Godota!" #: editor/editor_about.cpp msgid "Thanks!" -msgstr "" +msgstr "Hvala!" #: editor/editor_about.cpp msgid "Godot Engine contributors" -msgstr "" +msgstr "Godot Engine sodelovci" #: editor/editor_about.cpp msgid "Project Founders" -msgstr "" +msgstr "Ustanovitelji Projekta" #: editor/editor_about.cpp msgid "Lead Developer" -msgstr "" +msgstr "Vodilni Razvajalec" #: editor/editor_about.cpp msgid "Project Manager " @@ -717,47 +722,47 @@ msgstr "Upravljalnik Projekta " #: editor/editor_about.cpp msgid "Developers" -msgstr "" +msgstr "Razvajalci" #: editor/editor_about.cpp msgid "Authors" -msgstr "" +msgstr "Avtorji" #: editor/editor_about.cpp msgid "Platinum Sponsors" -msgstr "" +msgstr "Platina Sponzorji" #: editor/editor_about.cpp msgid "Gold Sponsors" -msgstr "" +msgstr "Zlati Sponzorji" #: editor/editor_about.cpp msgid "Mini Sponsors" -msgstr "" +msgstr "Majhni Sponzorji" #: editor/editor_about.cpp msgid "Gold Donors" -msgstr "" +msgstr "Zlati Donatorji" #: editor/editor_about.cpp msgid "Silver Donors" -msgstr "" +msgstr "Srebrni Donatorji" #: editor/editor_about.cpp msgid "Bronze Donors" -msgstr "" +msgstr "Bronasti Donatorji" #: editor/editor_about.cpp msgid "Donors" -msgstr "" +msgstr "Donatorji" #: editor/editor_about.cpp msgid "License" -msgstr "" +msgstr "Licenca" #: editor/editor_about.cpp msgid "Thirdparty License" -msgstr "" +msgstr "Licenca Tretjih Oseb" #: editor/editor_about.cpp msgid "" @@ -766,125 +771,125 @@ msgid "" "is an exhaustive list of all such thirdparty components with their " "respective copyright statements and license terms." msgstr "" +"Godot Engine se nanaÅ¡a na Å¡tevilne brezplaÄne in odprokodne knjižnice tretih " +"oseb, ki so združljive z doloÄili MIT licence. SledeÄi obÅ¡iren seznam " +"predstavi komponente tretjih oseb s pripadajoÄimi izjavami o avtorskih " +"pravicah in licenÄnimi pogoji." #: editor/editor_about.cpp msgid "All Components" -msgstr "" +msgstr "Vse Komponente" #: editor/editor_about.cpp msgid "Components" -msgstr "" +msgstr "Komponente" #: editor/editor_about.cpp msgid "Licenses" -msgstr "" +msgstr "Licence" #: editor/editor_asset_installer.cpp editor/project_manager.cpp msgid "Error opening package file, not in zip format." -msgstr "" +msgstr "Napaka pri odpiranju datoteke paketa, ker ni v formatu zip." #: editor/editor_asset_installer.cpp msgid "Uncompressing Assets" -msgstr "" +msgstr "RazÅ¡irjenje Dodatkov" #: editor/editor_asset_installer.cpp editor/project_manager.cpp msgid "Package Installed Successfully!" -msgstr "" +msgstr "Paket je UspeÅ¡no NameÅ¡Äen!" #: editor/editor_asset_installer.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Success!" -msgstr "" +msgstr "Uspelo je!" #: editor/editor_asset_installer.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Install" -msgstr "" +msgstr "Namesti" #: editor/editor_asset_installer.cpp msgid "Package Installer" -msgstr "" +msgstr "Namestnik Paketov" #: editor/editor_audio_buses.cpp msgid "Speakers" -msgstr "" +msgstr "ZvoÄniki" #: editor/editor_audio_buses.cpp msgid "Add Effect" -msgstr "" +msgstr "Dodaj UÄinek" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Rename Audio Bus" -msgstr "Preimenuj Funkcijo" +msgstr "Preimenuj ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Change Audio Bus Volume" -msgstr "Preimenuj Funkcijo" +msgstr "Spremeni Glasnost ZvoÄnega Vodila" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Solo" -msgstr "" +msgstr "Preklopi samo na ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Mute" -msgstr "" +msgstr "Preklopi na Tihi NaÄin ZvoÄnega Vodila" #: editor/editor_audio_buses.cpp msgid "Toggle Audio Bus Bypass Effects" -msgstr "" +msgstr "Preklopi na UÄinke Prehoda ZvoÄnega Vodila" #: editor/editor_audio_buses.cpp msgid "Select Audio Bus Send" -msgstr "" +msgstr "Izberi PoÅ¡lji možnost ZvoÄnega vodila" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus Effect" -msgstr "" +msgstr "Dodaj uÄinek ZvoÄnega Vodila" #: editor/editor_audio_buses.cpp msgid "Move Bus Effect" -msgstr "" +msgstr "Premakni uÄinek Vodila" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Delete Bus Effect" -msgstr "IzbriÅ¡i Izbrano" +msgstr "IzbriÅ¡i uÄinek Vodila" #: editor/editor_audio_buses.cpp msgid "Audio Bus, Drag and Drop to rearrange." -msgstr "" +msgstr "ZvoÄno Vodilo, Povelci in Izpusti za preureditev." #: editor/editor_audio_buses.cpp msgid "Solo" -msgstr "" +msgstr "Sam" #: editor/editor_audio_buses.cpp msgid "Mute" -msgstr "" +msgstr "Nem" #: editor/editor_audio_buses.cpp msgid "Bypass" -msgstr "" +msgstr "Prehod" #: editor/editor_audio_buses.cpp msgid "Bus options" -msgstr "" +msgstr "Možnosti Vodila" #: editor/editor_audio_buses.cpp editor/filesystem_dock.cpp #: editor/plugins/tile_map_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Duplicate" -msgstr "" +msgstr "Podvoji" #: editor/editor_audio_buses.cpp msgid "Reset Volume" -msgstr "" +msgstr "Ponastavi Glasnost" #: editor/editor_audio_buses.cpp -#, fuzzy msgid "Delete Effect" -msgstr "IzbriÅ¡i Izbrano" +msgstr "IzbriÅ¡i UÄinek" #: editor/editor_audio_buses.cpp msgid "Audio" @@ -892,154 +897,156 @@ msgstr "Zvok" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus" -msgstr "" +msgstr "Dodaj ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp msgid "Master bus can't be deleted!" -msgstr "" +msgstr "Glavno vodilo ni mogoÄe izbrisati!" #: editor/editor_audio_buses.cpp msgid "Delete Audio Bus" -msgstr "" +msgstr "IzbriÅ¡i ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp msgid "Duplicate Audio Bus" -msgstr "" +msgstr "Podvoji ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp msgid "Reset Bus Volume" -msgstr "" +msgstr "Ponastavi Glasnost Vodila" #: editor/editor_audio_buses.cpp msgid "Move Audio Bus" -msgstr "" +msgstr "Premakni ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "" +msgid "Save Audio Bus Layout As..." +msgstr "Shrani Postavitev ZvoÄnega Vodila Kot..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "" +msgid "Location for New Layout..." +msgstr "Lokacija za Novo Postavitev..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" -msgstr "" +msgstr "Odpri ZvoÄno Vodilo" #: editor/editor_audio_buses.cpp msgid "There is no 'res://default_bus_layout.tres' file." -msgstr "" +msgstr "Datoteka 'res://default_bus_layout.tres' ne obstaja." #: editor/editor_audio_buses.cpp msgid "Invalid file, not an audio bus layout." -msgstr "" +msgstr "Neveljavna datoteka, ker ni postavitve zvoÄnega vodila." #: editor/editor_audio_buses.cpp msgid "Add Bus" -msgstr "" +msgstr "Dodaj Vodilo" #: editor/editor_audio_buses.cpp msgid "Create a new Bus Layout." -msgstr "" +msgstr "Ustvari novo Postavitev Vodila." #: editor/editor_audio_buses.cpp editor/property_editor.cpp #: editor/script_create_dialog.cpp msgid "Load" -msgstr "" +msgstr "Naloži" #: editor/editor_audio_buses.cpp msgid "Load an existing Bus Layout." -msgstr "" +msgstr "Naloži obstojeÄo Postavitev Vodila." #: editor/editor_audio_buses.cpp #: editor/plugins/animation_player_editor_plugin.cpp msgid "Save As" -msgstr "" +msgstr "Shrani Kot" #: editor/editor_audio_buses.cpp msgid "Save this Bus Layout to a file." -msgstr "" +msgstr "Shrani to Postavitev Vodila v datoteko." #: editor/editor_audio_buses.cpp editor/import_dock.cpp msgid "Load Default" -msgstr "" +msgstr "Naložite Prevzeto" #: editor/editor_audio_buses.cpp msgid "Load the default Bus Layout." -msgstr "" +msgstr "Naloži prevezeto Postavitev Vodila." #: editor/editor_autoload_settings.cpp msgid "Invalid name." -msgstr "" +msgstr "Neveljavno ime." #: editor/editor_autoload_settings.cpp msgid "Valid characters:" -msgstr "" +msgstr "Veljavni znaki:" #: editor/editor_autoload_settings.cpp msgid "Invalid name. Must not collide with an existing engine class name." -msgstr "" +msgstr "Neveljavno ime. Ne sme se prekrivati z obstojeÄim imenom razreda." #: editor/editor_autoload_settings.cpp msgid "Invalid name. Must not collide with an existing buit-in type name." msgstr "" +"Neveljavno ime. Ne sme se prekrivati z obstojeÄim vgrajenim imenom tipa." #: editor/editor_autoload_settings.cpp msgid "Invalid name. Must not collide with an existing global constant name." msgstr "" +"Neveljavno ime. Ne sme se prekrivati z obstojeÄim imenom globalne konstante." #: editor/editor_autoload_settings.cpp msgid "Invalid Path." -msgstr "" +msgstr "Neveljavna Pot." #: editor/editor_autoload_settings.cpp msgid "File does not exist." -msgstr "" +msgstr "Datoteka ne obstaja." #: editor/editor_autoload_settings.cpp msgid "Not in resource path." -msgstr "" +msgstr "Ni na poti virov." #: editor/editor_autoload_settings.cpp msgid "Add AutoLoad" -msgstr "" +msgstr "Dodaj SamodejnoNalaganje" #: editor/editor_autoload_settings.cpp msgid "Autoload '%s' already exists!" -msgstr "" +msgstr "SamodejnoNalaganje '%s' že obstaja!" #: editor/editor_autoload_settings.cpp msgid "Rename Autoload" -msgstr "" +msgstr "Preimenuj SamodejnoNalaganje" #: editor/editor_autoload_settings.cpp msgid "Toggle AutoLoad Globals" -msgstr "" +msgstr "Preklopi na Globalno SamodejnoNalaganje" #: editor/editor_autoload_settings.cpp msgid "Move Autoload" -msgstr "" +msgstr "Premakni SamodejnoNalaganje" #: editor/editor_autoload_settings.cpp msgid "Remove Autoload" -msgstr "" +msgstr "Odstrani SamodejnoNalaganje" #: editor/editor_autoload_settings.cpp msgid "Enable" -msgstr "" +msgstr "OmogoÄi" #: editor/editor_autoload_settings.cpp msgid "Rearrange Autoloads" -msgstr "" +msgstr "Preuredi SamodejnoNalaganje" #: editor/editor_autoload_settings.cpp editor/editor_file_dialog.cpp #: scene/gui/file_dialog.cpp msgid "Path:" -msgstr "" +msgstr "Pot:" #: editor/editor_autoload_settings.cpp msgid "Node Name:" -msgstr "" +msgstr "Ime Gradnika:" #: editor/editor_autoload_settings.cpp editor/editor_profiler.cpp #: editor/project_manager.cpp editor/settings_config_dialog.cpp @@ -1048,35 +1055,35 @@ msgstr "Ime" #: editor/editor_autoload_settings.cpp msgid "Singleton" -msgstr "" +msgstr "Posameznik" #: editor/editor_data.cpp msgid "Updating Scene" msgstr "Posodabljanje Scene" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "" +msgid "Storing local changes..." +msgstr "Shranjevanje lokalnih sprememb..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Posodabljanje scene.." +msgid "Updating scene..." +msgstr "Posodabljanje scene..." #: editor/editor_data.cpp msgid "[empty]" -msgstr "[prazen]" +msgstr "[prazno]" #: editor/editor_data.cpp msgid "[unsaved]" -msgstr "" +msgstr "[neshranjeno]" #: editor/editor_dir_dialog.cpp msgid "Please select a base directory first" -msgstr "" +msgstr "Najprej izberi osnovno mapo" #: editor/editor_dir_dialog.cpp msgid "Choose a Directory" -msgstr "" +msgstr "Izberi Mapo" #: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp #: editor/filesystem_dock.cpp scene/gui/file_dialog.cpp @@ -1088,32 +1095,32 @@ msgstr "Ustvarite Mapo" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp #: scene/gui/file_dialog.cpp msgid "Name:" -msgstr "" +msgstr "Ime:" #: editor/editor_dir_dialog.cpp editor/editor_file_dialog.cpp #: editor/filesystem_dock.cpp scene/gui/file_dialog.cpp msgid "Could not create folder." -msgstr "" +msgstr "Mape ni mogoÄe ustvariti." #: editor/editor_dir_dialog.cpp msgid "Choose" -msgstr "" +msgstr "Izberi" #: editor/editor_export.cpp msgid "Storing File:" -msgstr "" +msgstr "Shranjevanje Datoteke:" #: editor/editor_export.cpp msgid "Packing" -msgstr "" +msgstr "Pakiranje" #: editor/editor_export.cpp platform/javascript/export/export.cpp msgid "Template file not found:" -msgstr "" +msgstr "Predloge ni mogoÄe najti:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "File Exists, Overwrite?" -msgstr "" +msgstr "Datoteka Obstaja, PrepiÅ¡em?" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Select Current Folder" @@ -1121,23 +1128,23 @@ msgstr "Izberite Trenutno Mapo" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp msgid "Copy Path" -msgstr "" +msgstr "Kopiraj Pot" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp msgid "Show In File Manager" -msgstr "" +msgstr "Pokaži V Upravitelju Datotek" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "" +msgid "New Folder..." +msgstr "Nova Mapa..." #: editor/editor_file_dialog.cpp msgid "Refresh" -msgstr "" +msgstr "Osveži" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Recognized" -msgstr "" +msgstr "Vse Prepoznano" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Files (*)" @@ -1145,69 +1152,69 @@ msgstr "Vse Datoteke (*)" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a File" -msgstr "" +msgstr "Odpri v Datoteki" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open File(s)" -msgstr "" +msgstr "Odpri Datotek(o/e)" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a Directory" -msgstr "" +msgstr "Odpri v Mapi" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a File or Directory" -msgstr "" +msgstr "Odpri Datoteko ali Mapo" #: editor/editor_file_dialog.cpp editor/editor_node.cpp #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/script_editor_plugin.cpp scene/gui/file_dialog.cpp msgid "Save" -msgstr "" +msgstr "Shrani" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Save a File" -msgstr "" +msgstr "Shrani Datoteko" #: editor/editor_file_dialog.cpp msgid "Go Back" -msgstr "" +msgstr "Pojdi Nazaj" #: editor/editor_file_dialog.cpp msgid "Go Forward" -msgstr "" +msgstr "Pojdi Naprej" #: editor/editor_file_dialog.cpp msgid "Go Up" -msgstr "" +msgstr "Pojdi Navzgor" #: editor/editor_file_dialog.cpp msgid "Toggle Hidden Files" -msgstr "" +msgstr "Preklopi na Skrite Datoteke" #: editor/editor_file_dialog.cpp msgid "Toggle Favorite" -msgstr "" +msgstr "Preklopi na NajljubÅ¡e" #: editor/editor_file_dialog.cpp msgid "Toggle Mode" -msgstr "" +msgstr "Preklopi NaÄin" #: editor/editor_file_dialog.cpp msgid "Focus Path" -msgstr "" +msgstr "Poudari Pot" #: editor/editor_file_dialog.cpp msgid "Move Favorite Up" -msgstr "" +msgstr "Premakni Priljubljeno Navzgor" #: editor/editor_file_dialog.cpp msgid "Move Favorite Down" -msgstr "" +msgstr "Premakni Priljubljeno Navzdol" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Go to parent folder" -msgstr "" +msgstr "Pojdi v nadrejeno mapo" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Directories & Files:" @@ -1215,7 +1222,7 @@ msgstr "Mape & Datoteke:" #: editor/editor_file_dialog.cpp msgid "Preview:" -msgstr "" +msgstr "Predogled:" #: editor/editor_file_dialog.cpp editor/script_editor_debugger.cpp #: scene/gui/file_dialog.cpp @@ -1224,53 +1231,52 @@ msgstr "Datoteka:" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Must use a valid extension." -msgstr "" +msgstr "Uporabiti moraÅ¡ valjavno razÅ¡iritev." #: editor/editor_file_system.cpp msgid "ScanSources" -msgstr "" +msgstr "BranjeVirov" #: editor/editor_file_system.cpp msgid "(Re)Importing Assets" -msgstr "" +msgstr "Uvoz Dodatkov" #: editor/editor_help.cpp editor/editor_node.cpp #: editor/plugins/script_editor_plugin.cpp msgid "Search Help" -msgstr "" +msgstr "IÅ¡Äi PomoÄ" #: editor/editor_help.cpp msgid "Class List:" -msgstr "" +msgstr "Seznam Razredov:" #: editor/editor_help.cpp msgid "Search Classes" -msgstr "" +msgstr "IÅ¡Äi Razrede" #: editor/editor_help.cpp editor/plugins/spatial_editor_plugin.cpp msgid "Top" -msgstr "" +msgstr "Vrh" #: editor/editor_help.cpp editor/property_editor.cpp msgid "Class:" -msgstr "" +msgstr "Razred:" #: editor/editor_help.cpp editor/scene_tree_editor.cpp msgid "Inherits:" -msgstr "" +msgstr "Dedovanja:" #: editor/editor_help.cpp msgid "Inherited by:" -msgstr "" +msgstr "Podedovano od:" #: editor/editor_help.cpp msgid "Brief Description:" -msgstr "" +msgstr "Kratek Opis:" #: editor/editor_help.cpp -#, fuzzy msgid "Members" -msgstr "ÄŒlani:" +msgstr "ÄŒlani" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Members:" @@ -1278,53 +1284,51 @@ msgstr "ÄŒlani:" #: editor/editor_help.cpp msgid "Public Methods" -msgstr "" +msgstr "Javne Metode" #: editor/editor_help.cpp msgid "Public Methods:" -msgstr "" +msgstr "Javne Metode:" #: editor/editor_help.cpp msgid "GUI Theme Items" -msgstr "" +msgstr "Elementi GUI Teme" #: editor/editor_help.cpp msgid "GUI Theme Items:" -msgstr "" +msgstr "Elementi GUI Teme:" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Signals:" msgstr "Signali:" #: editor/editor_help.cpp -#, fuzzy msgid "Enumerations" -msgstr "Funkcije:" +msgstr "OÅ¡tevilÄenja" #: editor/editor_help.cpp -#, fuzzy msgid "Enumerations:" -msgstr "Funkcije:" +msgstr "OÅ¡tevilÄenja:" #: editor/editor_help.cpp msgid "enum " -msgstr "" +msgstr "oÅ¡tevil " #: editor/editor_help.cpp msgid "Constants" -msgstr "" +msgstr "Konstante" #: editor/editor_help.cpp msgid "Constants:" -msgstr "" +msgstr "Konstante:" #: editor/editor_help.cpp msgid "Description" -msgstr "" +msgstr "Opis" #: editor/editor_help.cpp msgid "Online Tutorials:" -msgstr "" +msgstr "Spletne Vaje:" #: editor/editor_help.cpp msgid "" @@ -1332,42 +1336,48 @@ msgid "" "$url]contribute one[/url][/color] or [color=$color][url=$url2]request one[/" "url][/color]." msgstr "" +"Trenutno ni vaj za ta razred, lahko ga [color=$color][url=$url]prispevate[/" +"url][/color] ali [color=$color][url=$url2]zahtevate enega[/url][/color]." #: editor/editor_help.cpp msgid "Properties" -msgstr "" +msgstr "Lastnosti" #: editor/editor_help.cpp msgid "Property Description:" -msgstr "" +msgstr "Opis lastnosti:" #: editor/editor_help.cpp msgid "" "There is currently no description for this property. Please help us by " "[color=$color][url=$url]contributing one[/url][/color]!" msgstr "" +"Trenutno ni opisa za to lastnost. Pomagajte nam s [color=$color][url=" +"$url]prispevkom[/url][/color]!" #: editor/editor_help.cpp msgid "Methods" -msgstr "" +msgstr "Metode" #: editor/editor_help.cpp msgid "Method Description:" -msgstr "" +msgstr "Opis Metode:" #: editor/editor_help.cpp msgid "" "There is currently no description for this method. Please help us by [color=" "$color][url=$url]contributing one[/url][/color]!" msgstr "" +"Trenutno ni opisa za to metodo. Pomagajte nam s [color=$color][url=" +"$url]prispevkom[/url][/color]!" #: editor/editor_help.cpp msgid "Search Text" -msgstr "" +msgstr "IÅ¡Äi Besedilo" #: editor/editor_help.cpp msgid "Find" -msgstr "" +msgstr "Najdi" #: editor/editor_log.cpp msgid "Output:" @@ -1378,60 +1388,60 @@ msgstr "Izhod:" #: modules/gdnative/gdnative_library_editor_plugin.cpp scene/gui/line_edit.cpp #: scene/gui/text_edit.cpp msgid "Clear" -msgstr "" +msgstr "PoÄisti" #: editor/editor_log.cpp msgid "Clear Output" -msgstr "" +msgstr "PoÄisti Izhod" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Izvoz projekta ni uspelo s kodno napako %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" -msgstr "" +msgstr "Napaka pri shranjevanju virov!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "" +msgid "Save Resource As..." +msgstr "Shrani Vire Kot..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "" +msgid "I see..." +msgstr "Vidim..." #: editor/editor_node.cpp msgid "Can't open file for writing:" -msgstr "" +msgstr "Datoteke ni mogoÄe odpreti za pisanje:" #: editor/editor_node.cpp msgid "Requested file format unknown:" -msgstr "" +msgstr "Zahtevan format datoteke ni znan:" #: editor/editor_node.cpp msgid "Error while saving." -msgstr "" +msgstr "Napaka med shranjevanjem." #: editor/editor_node.cpp msgid "Can't open '%s'." -msgstr "" +msgstr "Ni mogoÄe odpreti '%s'." #: editor/editor_node.cpp msgid "Error while parsing '%s'." -msgstr "" +msgstr "Napaka pri razÄlenjevanju '%s'." #: editor/editor_node.cpp msgid "Unexpected end of file '%s'." -msgstr "" +msgstr "NepriÄakovan konec datoteke '%s'." #: editor/editor_node.cpp msgid "Missing '%s' or its dependencies." -msgstr "" +msgstr "Manjka '%s' ali njegove odvisnosti." #: editor/editor_node.cpp msgid "Error while loading '%s'." -msgstr "" +msgstr "Napaka pri nalaganju '%s'." #: editor/editor_node.cpp msgid "Saving Scene" @@ -1439,57 +1449,59 @@ msgstr "Shranjevanje Scene" #: editor/editor_node.cpp msgid "Analyzing" -msgstr "" +msgstr "Analiziranje" #: editor/editor_node.cpp msgid "Creating Thumbnail" -msgstr "" +msgstr "Ustvarjanje SliÄic" #: editor/editor_node.cpp msgid "This operation can't be done without a tree root." -msgstr "" +msgstr "Te operacije ne moremo storiti brez osnovnega drevesa." #: editor/editor_node.cpp msgid "" "Couldn't save scene. Likely dependencies (instances or inheritance) couldn't " "be satisfied." msgstr "" +"Ni mogoÄe shraniti scene. Najverjetneje odvisnosti (primeri ali dedovanja) " +"ne morejo biti izpolnjene." #: editor/editor_node.cpp msgid "Failed to load resource." -msgstr "" +msgstr "Napaka pri nalaganju vira." #: editor/editor_node.cpp msgid "Can't load MeshLibrary for merging!" -msgstr "" +msgstr "Knjižnice Modelov ni mogoÄe naložiti za združitev!" #: editor/editor_node.cpp msgid "Error saving MeshLibrary!" -msgstr "" +msgstr "Napaka pri shranjevanju Knjižnice Modelov!" #: editor/editor_node.cpp msgid "Can't load TileSet for merging!" -msgstr "" +msgstr "PloÅ¡ÄniNiz ni mogoÄe naložiti za združitev!" #: editor/editor_node.cpp msgid "Error saving TileSet!" -msgstr "" +msgstr "Napaka pri shranjevanju PloÅ¡ÄnegaNiza!" #: editor/editor_node.cpp msgid "Error trying to save layout!" -msgstr "" +msgstr "Napaka pri shranjevanju postavitev!" #: editor/editor_node.cpp msgid "Default editor layout overridden." -msgstr "" +msgstr "Privzeti urejevalnik postavitev je bil prepisan." #: editor/editor_node.cpp msgid "Layout name not found!" -msgstr "" +msgstr "Ime postavitve ni mogoÄe najti!" #: editor/editor_node.cpp msgid "Restored default layout to base settings." -msgstr "" +msgstr "Privzeta postavitev je bila ponastavljena na osnovne nastaviteve." #: editor/editor_node.cpp msgid "" @@ -1497,18 +1509,24 @@ msgid "" "Please read the documentation relevant to importing scenes to better " "understand this workflow." msgstr "" +"Ta vir pripada uvoženi sceni, zato ga ne moremo spreminjati.\n" +"Za boljÅ¡e razumevanje preberi dokumentacijo namenjeno za uvažanje scen." #: editor/editor_node.cpp msgid "" "This resource belongs to a scene that was instanced or inherited.\n" "Changes to it will not be kept when saving the current scene." msgstr "" +"Ta vir pripada sceni, ki je dedovana ali je primer druge.\n" +"Pri shranjevanju trenutne scene se spremembe ne bodo ohranile." #: editor/editor_node.cpp msgid "" "This resource was imported, so it's not editable. Change its settings in the " "import panel and then re-import." msgstr "" +"Ta vir je bil uvožen tako, da ga ne morete spreminjati. Spremenite svoje " +"nastavitve na ploÅ¡Äi za uvoz in nato znova uvozite." #: editor/editor_node.cpp msgid "" @@ -1517,6 +1535,9 @@ msgid "" "Please read the documentation relevant to importing scenes to better " "understand this workflow." msgstr "" +"Ta scena je bila uvožena tako, da spremembe ne bodo shranjene.\n" +"Primer druge ali dedovanje bo omogoÄilo spremembe v njem.\n" +"Za boljÅ¡e razumevanje preberi dokumentacijo namenjeno za uvažanje scen." #: editor/editor_node.cpp msgid "" @@ -1524,46 +1545,48 @@ msgid "" "Please read the documentation relevant to debugging to better understand " "this workflow." msgstr "" +"To je objekt odprt na daljavo, zato spremembe v njem ne bodo shranjene.\n" +"Za boljÅ¡e razumevanje preberi dokumentacijo namenjeno razhroÅ¡Äevanju." #: editor/editor_node.cpp msgid "Expand all properties" -msgstr "" +msgstr "RazÅ¡iri vse lastnosti" #: editor/editor_node.cpp msgid "Collapse all properties" -msgstr "" +msgstr "SkrÄi vse lastnosti" #: editor/editor_node.cpp msgid "Copy Params" -msgstr "" +msgstr "Kopiraj Parametre" #: editor/editor_node.cpp msgid "Paste Params" -msgstr "" +msgstr "Prilepi Parametre" #: editor/editor_node.cpp editor/plugins/resource_preloader_editor_plugin.cpp msgid "Paste Resource" -msgstr "" +msgstr "Prilepi Vir" #: editor/editor_node.cpp msgid "Copy Resource" -msgstr "" +msgstr "Kopiraj Vir" #: editor/editor_node.cpp msgid "Make Built-In" -msgstr "" +msgstr "Naredi Vgrajeno" #: editor/editor_node.cpp msgid "Make Sub-Resources Unique" -msgstr "" +msgstr "Naredi Pod-Vire Samostojne" #: editor/editor_node.cpp msgid "Open in Help" -msgstr "" +msgstr "Odpri v PomoÄi" #: editor/editor_node.cpp msgid "There is no defined scene to run." -msgstr "" +msgstr "Ni doloÄene scene za zagon." #: editor/editor_node.cpp msgid "" @@ -1571,6 +1594,9 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" +"Glavna scena ni bila doloÄena, izberem eno?\n" +"Kasneje jo lahko spremeniÅ¡ v \"Nastavitve Projekta\" pod kategorijo " +"'aplikacija'." #: editor/editor_node.cpp msgid "" @@ -1578,6 +1604,9 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" +"Izbrana scena '%s' ne obstaja, izberem veljavno?\n" +"Kasneje jo lahko spremeniÅ¡ v \"Nastavitve Projekta\" pod kategorijo " +"'aplikacija'." #: editor/editor_node.cpp msgid "" @@ -1585,14 +1614,17 @@ msgid "" "You can change it later in \"Project Settings\" under the 'application' " "category." msgstr "" +"Izbrana scena '%s' ni datoteka scene, izberem veljavno?\n" +"Kasneje jo lahko spremeniÅ¡ v \"Nastavitve Projekta\" pod kategorijo " +"'aplikacija'." #: editor/editor_node.cpp msgid "Current scene was never saved, please save it prior to running." -msgstr "" +msgstr "Trenutna scena ni bila shranjena, shranite jo pred zagonom." #: editor/editor_node.cpp msgid "Could not start subprocess!" -msgstr "" +msgstr "Nemorem zaÄeti podprocesa!" #: editor/editor_node.cpp msgid "Open Scene" @@ -1603,76 +1635,76 @@ msgid "Open Base Scene" msgstr "Odpri Osnovno Sceno" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Hitro Odpri Sceno.." +msgid "Quick Open Scene..." +msgstr "Hitro Odpri Sceno..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Hitro Odpri Skripto.." +msgid "Quick Open Script..." +msgstr "Hitro Odpri Skripto..." #: editor/editor_node.cpp msgid "Save & Close" -msgstr "" +msgstr "Shrani & Zapri" #: editor/editor_node.cpp msgid "Save changes to '%s' before closing?" -msgstr "" +msgstr "Shranim spremembe v '%s' pred zapiranjem?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Shrani Sceno Kot.." +msgid "Save Scene As..." +msgstr "Shrani Sceno Kot..." #: editor/editor_node.cpp msgid "No" -msgstr "" +msgstr "Ne" #: editor/editor_node.cpp msgid "Yes" -msgstr "" +msgstr "Da" #: editor/editor_node.cpp msgid "This scene has never been saved. Save before running?" -msgstr "" +msgstr "Ta scena ni bila nikoli shranjena. Shranim pred zagonom?" #: editor/editor_node.cpp editor/scene_tree_dock.cpp msgid "This operation can't be done without a scene." -msgstr "" +msgstr "Ta operacija ni mogoÄa brez scene." #: editor/editor_node.cpp msgid "Export Mesh Library" -msgstr "" +msgstr "Izvozi Knjižnico Modelov" #: editor/editor_node.cpp msgid "This operation can't be done without a root node." -msgstr "" +msgstr "Ta operacija ni mogoÄa brez osnovnega gradnika." #: editor/editor_node.cpp msgid "Export Tile Set" -msgstr "" +msgstr "Izvozi PloÅ¡Äno Zbirko" #: editor/editor_node.cpp msgid "This operation can't be done without a selected node." -msgstr "" +msgstr "Te operacije ne moremo storiti brez izbranega gradnika." #: editor/editor_node.cpp msgid "Current scene not saved. Open anyway?" -msgstr "" +msgstr "Trenutna scena ni shranjena. Vseeno odprem?" #: editor/editor_node.cpp msgid "Can't reload a scene that was never saved." -msgstr "" +msgstr "Ni mogoÄe osvežiti scene, ki ni bila shranjena." #: editor/editor_node.cpp msgid "Revert" -msgstr "" +msgstr "Povrni" #: editor/editor_node.cpp msgid "This action cannot be undone. Revert anyway?" -msgstr "" +msgstr "Tega dejanja ni mogoÄe razveljaviti. Vseeno povrni?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Hitro Zaženi Sceno.." +msgid "Quick Run Scene..." +msgstr "Hitro Zaženi Sceno..." #: editor/editor_node.cpp msgid "Quit" @@ -1680,187 +1712,200 @@ msgstr "Zapri" #: editor/editor_node.cpp msgid "Exit the editor?" -msgstr "" +msgstr "Zaprem urejevalnik?" #: editor/editor_node.cpp msgid "Open Project Manager?" -msgstr "" +msgstr "Odprem Upravljalnik Projekta?" #: editor/editor_node.cpp msgid "Save & Quit" -msgstr "" +msgstr "Shrani & Zapri" #: editor/editor_node.cpp msgid "Save changes to the following scene(s) before quitting?" -msgstr "" +msgstr "Shranim spremembe na sledeÄih scenah pred zaprtjem?" #: editor/editor_node.cpp msgid "Save changes the following scene(s) before opening Project Manager?" msgstr "" +"Shranim spremembe na sledeÄih scenah pred odpiranjem Upravljalnika Projekta?" #: editor/editor_node.cpp msgid "" "This option is deprecated. Situations where refresh must be forced are now " "considered a bug. Please report." msgstr "" +"Ta možnost je zastarela. Situacije, kjer je treba osvežitev prisiliti, se " +"zdaj Å¡tejejo za napako. Prosimo, prijavite." #: editor/editor_node.cpp msgid "Pick a Main Scene" -msgstr "" +msgstr "Izberi Glavno Sceno" #: editor/editor_node.cpp msgid "Unable to enable addon plugin at: '%s' parsing of config failed." msgstr "" +"Ni mogoÄe omogoÄiti dodatnega vtiÄnika na: '%s'. RazÄlenjevanje " +"konfiguracije ni uspelo." #: editor/editor_node.cpp msgid "Unable to find script field for addon plugin at: 'res://addons/%s'." msgstr "" +"Ni mogoÄe najti polja skripte za dodatni vtiÄnik na: 'res://addons/%s'." #: editor/editor_node.cpp msgid "Unable to load addon script from path: '%s'." -msgstr "" +msgstr "Ni mogoÄe naložiti dodatno skripto iz poti: '%s'." #: editor/editor_node.cpp msgid "" "Unable to load addon script from path: '%s' Base type is not EditorPlugin." msgstr "" +"Ni mogoÄe naložiti dodatno skripto iz poti: '%s' Osnovni tip ni " +"UrejevalniVtiÄnik." #: editor/editor_node.cpp msgid "Unable to load addon script from path: '%s' Script is not in tool mode." msgstr "" +"Ni mogoÄe naložiti dodatno skripto iz poti: '%s' Skripta ni v naÄinu orodje." #: editor/editor_node.cpp msgid "" "Scene '%s' was automatically imported, so it can't be modified.\n" "To make changes to it, a new inherited scene can be created." msgstr "" +"Scena '%s' je bila samodejno uvožena, zato je ni mogoÄe spremeniti.\n" +"ÄŒe želite narediti spremembe, lahko ustvarite novo podedovano sceno." #: editor/editor_node.cpp editor/plugins/canvas_item_editor_plugin.cpp #: editor/plugins/spatial_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ugh" -msgstr "" +msgstr "Uh" #: editor/editor_node.cpp msgid "" "Error loading scene, it must be inside the project path. Use 'Import' to " "open the scene, then save it inside the project path." msgstr "" +"Napaka pri nalaganju prizora, zato ker ni znotraj poti projekta. Uporabite " +"'Uvoz', da odprete prizor in ga nato shranite znotraj poti projekta." #: editor/editor_node.cpp msgid "Scene '%s' has broken dependencies:" -msgstr "" +msgstr "Prizor '%s' ima pretrgane odvisnosti:" #: editor/editor_node.cpp msgid "Clear Recent Scenes" -msgstr "PoÄisti Nedavne Scene" +msgstr "PoÄisti Nedavne Prizore" #: editor/editor_node.cpp msgid "Save Layout" -msgstr "" +msgstr "Shrani Postavitev" #: editor/editor_node.cpp msgid "Delete Layout" -msgstr "" +msgstr "IzbriÅ¡i Postavitev" #: editor/editor_node.cpp editor/import_dock.cpp #: editor/script_create_dialog.cpp msgid "Default" -msgstr "" +msgstr "Prevzeto" #: editor/editor_node.cpp msgid "Switch Scene Tab" -msgstr "" +msgstr "Preklopi na zavihek Prizor" #: editor/editor_node.cpp msgid "%d more files or folders" -msgstr "" +msgstr "%d veÄ datotek ali map" #: editor/editor_node.cpp msgid "%d more folders" -msgstr "" +msgstr "%d veÄ map" #: editor/editor_node.cpp msgid "%d more files" -msgstr "" +msgstr "%d veÄ datotek" #: editor/editor_node.cpp msgid "Dock Position" -msgstr "" +msgstr "Položaj Sidranja" #: editor/editor_node.cpp msgid "Distraction Free Mode" -msgstr "" +msgstr "NaÄin Brez Motenj" #: editor/editor_node.cpp msgid "Toggle distraction-free mode." -msgstr "" +msgstr "Preklop naÄin pisanja brez motenj." #: editor/editor_node.cpp msgid "Add a new scene." -msgstr "Dodaj novo Sceno." +msgstr "Dodaj nov Prizor." #: editor/editor_node.cpp msgid "Scene" -msgstr "Scena" +msgstr "Prizor" #: editor/editor_node.cpp msgid "Go to previously opened scene." -msgstr "Pojdite na predhodno odprte scene." +msgstr "Pojdi na predhodno odprti prizor." #: editor/editor_node.cpp msgid "Next tab" -msgstr "" +msgstr "Naslednji zavihek" #: editor/editor_node.cpp msgid "Previous tab" -msgstr "" +msgstr "PrejÅ¡nji zavihek" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "" +msgid "Filter Files..." +msgstr "Filtriraj datoteke..." #: editor/editor_node.cpp msgid "Operations with scene files." -msgstr "" +msgstr "Operacije z datotekami prizora." #: editor/editor_node.cpp msgid "New Scene" -msgstr "Nova Scena" +msgstr "Nov Prizor" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Nova Podedovana Scena.." +msgid "New Inherited Scene..." +msgstr "Nov Podedovan Prizor..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Odpri Sceno.." +msgid "Open Scene..." +msgstr "Odpri Prizor..." #: editor/editor_node.cpp msgid "Save Scene" -msgstr "Shrani Sceno" +msgstr "Shrani Prizor" #: editor/editor_node.cpp msgid "Save all Scenes" -msgstr "Shrani vse Scene" +msgstr "Shrani vse Prizore" #: editor/editor_node.cpp msgid "Close Scene" -msgstr "Zapri Sceno" +msgstr "Zapri Prizor" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Open Recent" -msgstr "Odpri Nedavno" +msgstr "Odpri Nedavne" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Pretvori V.." +msgid "Convert To..." +msgstr "Pretvori V..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "" +msgid "MeshLibrary..." +msgstr "Knjižnica Modelov..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -1875,11 +1920,11 @@ msgstr "Ponovi" #: editor/editor_node.cpp msgid "Revert Scene" -msgstr "Povrni Sceno" +msgstr "Povrni Prizor" #: editor/editor_node.cpp msgid "Miscellaneous project or scene-wide tools." -msgstr "" +msgstr "RazliÄna projektna ali prizorska orodja." #: editor/editor_node.cpp msgid "Project" @@ -1891,7 +1936,7 @@ msgstr "Nastavitve Projekta" #: editor/editor_node.cpp msgid "Run Script" -msgstr "" +msgstr "Zaženi Skripto" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export" @@ -1918,6 +1963,8 @@ msgid "" "When exporting or deploying, the resulting executable will attempt to " "connect to the IP of this computer in order to be debugged." msgstr "" +"Pri izvažanju ali uvajanju se bo konÄna izvrÅ¡ljiva datoteka razhroÅ¡Äevala, " +"tako da se bo skuÅ¡ala povezati z IP-jem tega raÄunalnika." #: editor/editor_node.cpp msgid "Small Deploy with Network FS" @@ -1932,30 +1979,39 @@ msgid "" "On Android, deploy will use the USB cable for faster performance. This " "option speeds up testing for games with a large footprint." msgstr "" +"Ko je ta možnost omogoÄena, se bo pri izvozu ali uvajanju ustvarila " +"minimalna izvrÅ¡ljiva datoteka.\n" +"DatoteÄni sistem bo iz projekta zagotovljen z urejevalnikom preko omrežja.\n" +"Na Androidu bo uvajanje zaradi hitrejÅ¡ega delovanja potekalo preko kabla " +"USB. Ta možnost pospeÅ¡i testiranje iger z velikim odtisom." #: editor/editor_node.cpp msgid "Visible Collision Shapes" -msgstr "" +msgstr "Vidne Oblike Trka" #: editor/editor_node.cpp msgid "" "Collision shapes and raycast nodes (for 2D and 3D) will be visible on the " "running game if this option is turned on." msgstr "" +"Gradniki oblike trka in prikaz žarka (za 2D in 3D) bodo vidni pri poteku " +"igre, Äe je ta možnost vklopljena." #: editor/editor_node.cpp msgid "Visible Navigation" -msgstr "" +msgstr "Vidna Navigacija" #: editor/editor_node.cpp msgid "" "Navigation meshes and polygons will be visible on the running game if this " "option is turned on." msgstr "" +"ÄŒe je ta možnost vkljuÄena, bodo navigacijske oblike in poligoni vidni pri " +"poteku igre." #: editor/editor_node.cpp msgid "Sync Scene Changes" -msgstr "" +msgstr "Usklajuj Spremembe Prizora" #: editor/editor_node.cpp msgid "" @@ -1964,10 +2020,12 @@ msgid "" "When used remotely on a device, this is more efficient with network " "filesystem." msgstr "" +"Ko je ta možnost vkljuÄena, bodo vse spremebe v prizoru ali urejevalniku " +"ponovljene med potekom igre." #: editor/editor_node.cpp msgid "Sync Script Changes" -msgstr "" +msgstr "Usklajuj Spremembe Skript" #: editor/editor_node.cpp msgid "" @@ -1976,6 +2034,10 @@ msgid "" "When used remotely on a device, this is more efficient with network " "filesystem." msgstr "" +"ÄŒe je ta možnost vkljuÄena, bo vsaka shranjena skripta ponovno naložena v " +"igro, ki se izvaja.\n" +"ÄŒe se uporablja napravo na daljavo, je to bolj uÄinkovito pri omrežnem " +"datoteÄnem sistemu." #: editor/editor_node.cpp msgid "Editor" @@ -1983,19 +2045,19 @@ msgstr "Urejevalnik" #: editor/editor_node.cpp editor/settings_config_dialog.cpp msgid "Editor Settings" -msgstr "" +msgstr "Nastavitve Urejevalnika" #: editor/editor_node.cpp msgid "Editor Layout" -msgstr "" +msgstr "Postavitev Urejevalnika" #: editor/editor_node.cpp msgid "Toggle Fullscreen" -msgstr "" +msgstr "Preklopi na Celozaslonski NaÄin" #: editor/editor_node.cpp editor/project_export.cpp msgid "Manage Export Templates" -msgstr "" +msgstr "Upravljaj Izvozne Predloge" #: editor/editor_node.cpp msgid "Help" @@ -2003,7 +2065,7 @@ msgstr "PomoÄ" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Classes" -msgstr "" +msgstr "Razredi" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp #: editor/plugins/script_editor_plugin.cpp @@ -2014,119 +2076,119 @@ msgstr "Iskanje" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp msgid "Online Docs" -msgstr "" +msgstr "Spletna Dokumentacija" #: editor/editor_node.cpp msgid "Q&A" -msgstr "" +msgstr "V&O" #: editor/editor_node.cpp msgid "Issue Tracker" -msgstr "" +msgstr "Sledilnik Napak" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp msgid "Community" -msgstr "" +msgstr "Skupnost" #: editor/editor_node.cpp msgid "About" -msgstr "" +msgstr "O Programu" #: editor/editor_node.cpp msgid "Play the project." -msgstr "" +msgstr "Zaženi projekt." #: editor/editor_node.cpp msgid "Play" -msgstr "" +msgstr "Zaženi" #: editor/editor_node.cpp msgid "Pause the scene" -msgstr "" +msgstr "Zaustavi prizor" #: editor/editor_node.cpp msgid "Pause Scene" -msgstr "" +msgstr "Zaustavi prizor" #: editor/editor_node.cpp msgid "Stop the scene." -msgstr "" +msgstr "Ustavi Prizor." #: editor/editor_node.cpp msgid "Stop" -msgstr "" +msgstr "Ustavi" #: editor/editor_node.cpp msgid "Play the edited scene." -msgstr "" +msgstr "Zaženi prizor u urejanju." #: editor/editor_node.cpp msgid "Play Scene" -msgstr "" +msgstr "Zaženi Prizor" #: editor/editor_node.cpp msgid "Play custom scene" -msgstr "" +msgstr "Zaženi prizor po meri" #: editor/editor_node.cpp msgid "Play Custom Scene" -msgstr "" +msgstr "Zaženi Prizor po Meri" #: editor/editor_node.cpp msgid "Spins when the editor window repaints!" -msgstr "" +msgstr "Vrti se ob spremembi okna urejevalnika!" #: editor/editor_node.cpp msgid "Update Always" -msgstr "" +msgstr "Posodobi Vedno" #: editor/editor_node.cpp msgid "Update Changes" -msgstr "" +msgstr "Posodobi Spremembe" #: editor/editor_node.cpp msgid "Disable Update Spinner" -msgstr "" +msgstr "OnemogoÄi Posodobitve Kolesca" #: editor/editor_node.cpp msgid "Inspector" -msgstr "" +msgstr "Nadzornik" #: editor/editor_node.cpp msgid "Create a new resource in memory and edit it." -msgstr "" +msgstr "Ustvari nov vir v pomnilniku in ga uredi." #: editor/editor_node.cpp msgid "Load an existing resource from disk and edit it." -msgstr "" +msgstr "Naloži obstojeÄi vir iz spomina in ga uredi." #: editor/editor_node.cpp msgid "Save the currently edited resource." -msgstr "" +msgstr "Shrani trenutno urejani vir." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "" +msgid "Save As..." +msgstr "Shrani Kot..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." -msgstr "" +msgstr "Pojdi na prejÅ¡nji urejani objekt v zgodovini." #: editor/editor_node.cpp msgid "Go to the next edited object in history." -msgstr "" +msgstr "Pojdi na naslednji urejani objekt v zgodovini." #: editor/editor_node.cpp msgid "History of recently edited objects." -msgstr "" +msgstr "Zgodovina nedavno urejanih objektov." #: editor/editor_node.cpp msgid "Object properties." -msgstr "" +msgstr "Lastnosti objekta." #: editor/editor_node.cpp msgid "Changes may be lost!" -msgstr "" +msgstr "Spremembe se lahko izgubijo!" #: editor/editor_node.cpp editor/plugins/asset_library_editor_plugin.cpp #: editor/project_manager.cpp @@ -2135,7 +2197,7 @@ msgstr "Uvozi" #: editor/editor_node.cpp msgid "Node" -msgstr "" +msgstr "Gradnik" #: editor/editor_node.cpp msgid "FileSystem" @@ -2147,521 +2209,526 @@ msgstr "Izhod" #: editor/editor_node.cpp msgid "Don't Save" -msgstr "" +msgstr "Ne Shrani" #: editor/editor_node.cpp msgid "Import Templates From ZIP File" -msgstr "" +msgstr "Uvozi Predloge iz ZIP Datoteke" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export Project" -msgstr "" +msgstr "Izvozi Projekt" #: editor/editor_node.cpp msgid "Export Library" -msgstr "" +msgstr "Izvozi Knjižnico" #: editor/editor_node.cpp msgid "Merge With Existing" -msgstr "" +msgstr "Spoji z ObstojeÄim" #: editor/editor_node.cpp msgid "Password:" -msgstr "" +msgstr "Geslo:" #: editor/editor_node.cpp msgid "Open & Run a Script" -msgstr "" +msgstr "Odpri & Zaženi Skripto" #: editor/editor_node.cpp msgid "New Inherited" -msgstr "" +msgstr "Novo Podedovano" #: editor/editor_node.cpp msgid "Load Errors" -msgstr "" +msgstr "Napake pri Nalaganju" #: editor/editor_node.cpp editor/plugins/tile_map_editor_plugin.cpp msgid "Select" -msgstr "" +msgstr "Izberi" #: editor/editor_node.cpp msgid "Open 2D Editor" -msgstr "" +msgstr "Odpri 2D Urejevalnik" #: editor/editor_node.cpp msgid "Open 3D Editor" -msgstr "" +msgstr "Odpri 3D Urejevalnik" #: editor/editor_node.cpp msgid "Open Script Editor" -msgstr "" +msgstr "Odpri Urejevalnik Skript" #: editor/editor_node.cpp editor/project_manager.cpp msgid "Open Asset Library" -msgstr "Odprite Asset Library" +msgstr "Odpri Knjižnico Dodatkov" #: editor/editor_node.cpp msgid "Open the next Editor" -msgstr "" +msgstr "Odpri naslednji Urejevalnik" #: editor/editor_node.cpp msgid "Open the previous Editor" -msgstr "" +msgstr "Odpri prejÅ¡nji Urejevalnik" #: editor/editor_plugin.cpp msgid "Creating Mesh Previews" -msgstr "" +msgstr "Ustvari Predogled Modela" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "" +msgid "Thumbnail..." +msgstr "SliÄica..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" -msgstr "" +msgstr "NameÅ¡Äeni VtiÄniki:" #: editor/editor_plugin_settings.cpp msgid "Update" -msgstr "" +msgstr "Posodobi" #: editor/editor_plugin_settings.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Version:" -msgstr "" +msgstr "RazliÄica:" #: editor/editor_plugin_settings.cpp msgid "Author:" -msgstr "" +msgstr "Avtor:" #: editor/editor_plugin_settings.cpp msgid "Status:" -msgstr "" +msgstr "Stanje:" #: editor/editor_profiler.cpp msgid "Stop Profiling" -msgstr "" +msgstr "Ustavi Modeliranje" #: editor/editor_profiler.cpp msgid "Start Profiling" -msgstr "" +msgstr "ZaÄni Modeliranje" #: editor/editor_profiler.cpp msgid "Measure:" -msgstr "" +msgstr "Mera:" #: editor/editor_profiler.cpp msgid "Frame Time (sec)" -msgstr "" +msgstr "Okvirni ÄŒas (sek)" #: editor/editor_profiler.cpp msgid "Average Time (sec)" -msgstr "" +msgstr "PovpreÄni ÄŒas (sek)" #: editor/editor_profiler.cpp msgid "Frame %" -msgstr "" +msgstr "Okvir %" #: editor/editor_profiler.cpp msgid "Physics Frame %" -msgstr "" +msgstr "Fizikalni Okvir %" #: editor/editor_profiler.cpp editor/script_editor_debugger.cpp msgid "Time:" -msgstr "" +msgstr "ÄŒas:" #: editor/editor_profiler.cpp msgid "Inclusive" -msgstr "" +msgstr "VkljuÄno" #: editor/editor_profiler.cpp msgid "Self" -msgstr "" +msgstr "Samo" #: editor/editor_profiler.cpp msgid "Frame #:" -msgstr "" +msgstr "Okvir #:" #: editor/editor_profiler.cpp msgid "Time" -msgstr "" +msgstr "ÄŒas" #: editor/editor_profiler.cpp msgid "Calls" -msgstr "" +msgstr "Klici" #: editor/editor_run_native.cpp msgid "Select device from the list" -msgstr "" +msgstr "Izberite napravo s seznama" #: editor/editor_run_native.cpp msgid "" "No runnable export preset found for this platform.\n" "Please add a runnable preset in the export menu." msgstr "" +"Za to platformo ni mogoÄe najti obstojeÄih izvoznih nastavitev.\n" +"V izvoznem meniju dodajte svoje nastavitve." #: editor/editor_run_script.cpp msgid "Write your logic in the _run() method." -msgstr "" +msgstr "NapiÅ¡ite svojo logiko v metodi _run() ." #: editor/editor_run_script.cpp msgid "There is an edited scene already." -msgstr "" +msgstr "Tu že obstaja prizor v urejanju." #: editor/editor_run_script.cpp msgid "Couldn't instance script:" -msgstr "" +msgstr "Ni mogoÄe ustvariti primera skripte:" #: editor/editor_run_script.cpp msgid "Did you forget the 'tool' keyword?" -msgstr "" +msgstr "Ali si pozabil kljuÄno besedo 'orodje'?" #: editor/editor_run_script.cpp msgid "Couldn't run script:" -msgstr "" +msgstr "Ni mogoÄe zagnati skripte:" #: editor/editor_run_script.cpp msgid "Did you forget the '_run' method?" -msgstr "" +msgstr "Ali si pozabil metodo '_run' ?" #: editor/editor_settings.cpp msgid "Default (Same as Editor)" -msgstr "" +msgstr "Privzeto (Enako kot Urejevalnik)" #: editor/editor_sub_scene.cpp msgid "Select Node(s) to Import" -msgstr "" +msgstr "Izberi Gradnik(e) za Uvoz" #: editor/editor_sub_scene.cpp msgid "Scene Path:" -msgstr "" +msgstr "Pot Prizora:" #: editor/editor_sub_scene.cpp msgid "Import From Node:" -msgstr "" +msgstr "Uvozi iz Gradnika:" #: editor/export_template_manager.cpp msgid "Re-Download" -msgstr "" +msgstr "Ponovno Prenesi" #: editor/export_template_manager.cpp msgid "Uninstall" -msgstr "" +msgstr "Odstrani" #: editor/export_template_manager.cpp msgid "(Installed)" -msgstr "" +msgstr "(NameÅ¡Äeno)" #: editor/export_template_manager.cpp msgid "Download" -msgstr "" +msgstr "Prenesi" #: editor/export_template_manager.cpp msgid "(Missing)" -msgstr "" +msgstr "(ManjkajoÄe)" #: editor/export_template_manager.cpp msgid "(Current)" -msgstr "" +msgstr "(Trenutno)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "" +msgid "Retrieving mirrors, please wait..." +msgstr "Pridobivanje virov, poÄakajte..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" -msgstr "" +msgstr "ŽeliÅ¡ odstraniti predlogo razliÄice '%s'?" #: editor/export_template_manager.cpp msgid "Can't open export templates zip." -msgstr "" +msgstr "Ne morem odpreti zip izvozne predloge." #: editor/export_template_manager.cpp msgid "Invalid version.txt format inside templates." -msgstr "" +msgstr "Neveljaven format version.txt znotraj predloge." #: editor/export_template_manager.cpp msgid "No version.txt found inside templates." -msgstr "" +msgstr "Datoteke version.txt ni v predlogi." #: editor/export_template_manager.cpp msgid "Error creating path for templates:" -msgstr "" +msgstr "Napaka pri ustvarjanju poti za predloge:" #: editor/export_template_manager.cpp msgid "Extracting Export Templates" -msgstr "" +msgstr "RazÅ¡irjanje Izvoznih Predlog" #: editor/export_template_manager.cpp msgid "Importing:" -msgstr "" +msgstr "Uvažanje:" #: editor/export_template_manager.cpp msgid "" "No download links found for this version. Direct download is only available " "for official releases." msgstr "" +"Za to razliÄico ni mogoÄe najti linkov za prenos. Neposredni prenos je na " +"voljo samo za uradne izdaje." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't resolve." -msgstr "" +msgstr "Ni mogoÄe razreÅ¡iti." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't connect." -msgstr "" +msgstr "NemogoÄe se je povezati." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "No response." -msgstr "" +msgstr "Ni odgovora." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request Failed." -msgstr "" +msgstr "Zahteva Ni Uspela." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Redirect Loop." -msgstr "" +msgstr "Preusmeritev Zanke." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp msgid "Failed:" -msgstr "" +msgstr "Spodletelo:" #: editor/export_template_manager.cpp msgid "Download Complete." -msgstr "" +msgstr "Prenos je DokonÄan." #: editor/export_template_manager.cpp msgid "Error requesting url: " -msgstr "" +msgstr "Napaka pri zahtevi URL-ja: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "" +msgid "Connecting to Mirror..." +msgstr "Povezovanje z Virom..." #: editor/export_template_manager.cpp msgid "Disconnected" -msgstr "" +msgstr "Nepovezano" #: editor/export_template_manager.cpp msgid "Resolving" -msgstr "" +msgstr "RazreÅ¡evanje" #: editor/export_template_manager.cpp msgid "Can't Resolve" -msgstr "" +msgstr "Ni MogoÄe RazreÅ¡iti" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "" +msgid "Connecting..." +msgstr "Povezovanje..." #: editor/export_template_manager.cpp msgid "Can't Connect" -msgstr "" +msgstr "NemogoÄe se je Povezati" #: editor/export_template_manager.cpp msgid "Connected" -msgstr "" +msgstr "Povezano" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "" +msgid "Requesting..." +msgstr "Zahtevam..." #: editor/export_template_manager.cpp msgid "Downloading" -msgstr "" +msgstr "PrenaÅ¡anje" #: editor/export_template_manager.cpp msgid "Connection Error" -msgstr "" +msgstr "Napaka Pri Povezavi" #: editor/export_template_manager.cpp msgid "SSL Handshake Error" -msgstr "" +msgstr "Napaka Pri Usklanjevanju SSH" #: editor/export_template_manager.cpp msgid "Current Version:" -msgstr "" +msgstr "Trenutna RazliÄica:" #: editor/export_template_manager.cpp msgid "Installed Versions:" -msgstr "" +msgstr "NameÅ¡Äene RazliÄice:" #: editor/export_template_manager.cpp msgid "Install From File" -msgstr "" +msgstr "Namesti Iz Datoteke" #: editor/export_template_manager.cpp -#, fuzzy msgid "Remove Template" -msgstr "Odstrani Spremenljivko" +msgstr "Odstrani Predlogo" #: editor/export_template_manager.cpp msgid "Select template file" -msgstr "" +msgstr "Izberi datoteko predloge" #: editor/export_template_manager.cpp msgid "Export Template Manager" -msgstr "" +msgstr "Izvozni Upravitelj Predlog" #: editor/export_template_manager.cpp -#, fuzzy msgid "Download Templates" -msgstr "Odstrani Spremenljivko" +msgstr "Prenesi Predloge" #: editor/export_template_manager.cpp msgid "Select mirror from list: " -msgstr "" +msgstr "Izberi vire s seznama: " #: editor/file_type_cache.cpp msgid "Can't open file_type_cache.cch for writing, not saving file type cache!" msgstr "" +"Za pisanje ni mogoÄe odpreti file_type_cache.cch, ne da bi shranili " +"predpomnilnik tipa datoteke!" #: editor/filesystem_dock.cpp msgid "Cannot navigate to '%s' as it has not been found in the file system!" msgstr "" +"Ne morem se postaviti na mesto '%s', ker ni bilo najdeno v datoteÄnem " +"sistemu!" #: editor/filesystem_dock.cpp msgid "View items as a grid of thumbnails" -msgstr "" +msgstr "Oglejte si elemente, kot mrežo sliÄic" #: editor/filesystem_dock.cpp msgid "View items as a list" -msgstr "" +msgstr "Oglejte si elemente v seznamu" #: editor/filesystem_dock.cpp msgid "Status: Import of file failed. Please fix file and reimport manually." msgstr "" +"Stanje: Uvoz datoteke ni uspel. Popravi datoteko in ponovno roÄno uvozi." #: editor/filesystem_dock.cpp msgid "Cannot move/rename resources root." -msgstr "" +msgstr "Ni mogoÄe premakniti/preimenovati osnovne vire." #: editor/filesystem_dock.cpp msgid "Cannot move a folder into itself." -msgstr "" +msgstr "Mape ni mogoÄe premakniti vase." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Error moving:" -msgstr "Napaka naložitve pisave." +msgstr "Napaka pri premikanju:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Error duplicating:" -msgstr "Preimenuj Spremenljivko" +msgstr "Napaka pri podvajanju:" #: editor/filesystem_dock.cpp msgid "Unable to update dependencies:" -msgstr "" +msgstr "Odvisnosti ni mogoÄe posodobiti:" #: editor/filesystem_dock.cpp msgid "No name provided" -msgstr "" +msgstr "Ime ni na voljo" #: editor/filesystem_dock.cpp msgid "Provided name contains invalid characters" -msgstr "" +msgstr "VneÅ¡eno ime vsebuje neveljavne znake" #: editor/filesystem_dock.cpp msgid "No name provided." -msgstr "" +msgstr "Ime ni doloÄeno." #: editor/filesystem_dock.cpp msgid "Name contains invalid characters." -msgstr "" +msgstr "Ime vsebuje neveljavne znake." #: editor/filesystem_dock.cpp msgid "A file or folder with this name already exists." -msgstr "" +msgstr "Datoteka ali mapa s tem imenom že obstaja." #: editor/filesystem_dock.cpp -#, fuzzy msgid "Renaming file:" -msgstr "Preimenuj Spremenljivko" +msgstr "Preimenovanje Datoteke:" #: editor/filesystem_dock.cpp msgid "Renaming folder:" -msgstr "" +msgstr "Preimenovanje mape:" #: editor/filesystem_dock.cpp -#, fuzzy msgid "Duplicating file:" -msgstr "Preimenuj Spremenljivko" +msgstr "Podvajanje datoteke:" #: editor/filesystem_dock.cpp msgid "Duplicating folder:" -msgstr "" +msgstr "Podvajanje mape:" #: editor/filesystem_dock.cpp msgid "Expand all" -msgstr "" +msgstr "RazÅ¡iri vse" #: editor/filesystem_dock.cpp msgid "Collapse all" -msgstr "" +msgstr "SkrÄi vse" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "" +msgid "Rename..." +msgstr "Preimenuj..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "" +msgid "Move To..." +msgstr "Premakni V..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" -msgstr "" +msgstr "Odpri Prizor(e)" #: editor/filesystem_dock.cpp msgid "Instance" -msgstr "" +msgstr "Primer" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "" +msgid "Edit Dependencies..." +msgstr "Uredi Odvisnosti..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "" +msgid "View Owners..." +msgstr "Poglej Lastnike..." #: editor/filesystem_dock.cpp -#, fuzzy -msgid "Duplicate.." -msgstr "Podvoji Izbrano" +msgid "Duplicate..." +msgstr "Podvoji..." #: editor/filesystem_dock.cpp msgid "Previous Directory" -msgstr "" +msgstr "PrejÅ¡na Mapa" #: editor/filesystem_dock.cpp msgid "Next Directory" -msgstr "" +msgstr "Naslednja Mapa" #: editor/filesystem_dock.cpp msgid "Re-Scan Filesystem" -msgstr "" +msgstr "Ponovno Preglej DatoteÄni Sistem" #: editor/filesystem_dock.cpp msgid "Toggle folder status as Favorite" -msgstr "" +msgstr "Nastavi mapo status kot Priljubljeno" #: editor/filesystem_dock.cpp msgid "Instance the selected scene(s) as child of the selected node." msgstr "" +"Naredi primer iz izbranih prizorov, ki bo naslednik izbranega gradnika." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" +"Pregledovanje Datotek,\n" +"Prosimo, PoÄakajte..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2674,153 +2741,153 @@ msgstr "Preimenuj" #: editor/groups_editor.cpp msgid "Add to Group" -msgstr "" +msgstr "Dodaj v Skupino" #: editor/groups_editor.cpp msgid "Remove from Group" -msgstr "" +msgstr "Odstrani iz Skupine" #: editor/import/resource_importer_scene.cpp msgid "Import as Single Scene" -msgstr "" +msgstr "Uvozi kot En Prizor" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Animations" -msgstr "" +msgstr "Uvozi z LoÄenimi Animacijami" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials" -msgstr "" +msgstr "Uvozi z LoÄenimi Materiali" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects" -msgstr "" +msgstr "Uvozi z LoÄenimi Objekti" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Materials" -msgstr "" +msgstr "Uvozi z LoÄenimi Objekti+Materiali" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Animations" -msgstr "" +msgstr "Uvozi z LoÄenimi Objekti+Animacijami" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials+Animations" -msgstr "" +msgstr "Uvozi z LoÄenimi Materiali+Animacijami" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Materials+Animations" -msgstr "" +msgstr "Uvozi z LoÄenimi Objekti+Materiali+Animacijami" #: editor/import/resource_importer_scene.cpp msgid "Import as Multiple Scenes" -msgstr "" +msgstr "Uvozi kot VeÄ Prizorov" #: editor/import/resource_importer_scene.cpp msgid "Import as Multiple Scenes+Materials" -msgstr "" +msgstr "Uvozi kot VeÄkratnik Prizorov+Materialov" #: editor/import/resource_importer_scene.cpp #: editor/plugins/cube_grid_theme_editor_plugin.cpp msgid "Import Scene" -msgstr "" +msgstr "Uvozi Prizor" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "" +msgid "Importing Scene..." +msgstr "Uvažanje Prizora..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" -msgstr "" +msgstr "Ustvarjanje Svetlobnih Kart" #: editor/import/resource_importer_scene.cpp msgid "Generating for Mesh: " -msgstr "" +msgstr "Ustvarjanje za Model: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "" +msgid "Running Custom Script..." +msgstr "Izvajanje Skripte Po Meri..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" -msgstr "" +msgstr "Skripte po uvozu ni bilo mogoÄe naložiti:" #: editor/import/resource_importer_scene.cpp msgid "Invalid/broken script for post-import (check console):" -msgstr "" +msgstr "Neveljavna/pokvarjena skripta za naknadno uvažanje (Glej konzolo):" #: editor/import/resource_importer_scene.cpp msgid "Error running post-import script:" -msgstr "" +msgstr "Napaka pri zagonu skripte po uvozu:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "" +msgid "Saving..." +msgstr "Shranjevanje..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" -msgstr "" +msgstr "Nastavi kot Privzeto za '%s'" #: editor/import_dock.cpp msgid "Clear Default for '%s'" -msgstr "" +msgstr "PoÄisti privzeto za '%s'" #: editor/import_dock.cpp msgid " Files" -msgstr "" +msgstr " Datoteke" #: editor/import_dock.cpp msgid "Import As:" -msgstr "" +msgstr "Uvozi Kot:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "" +msgid "Preset..." +msgstr "Prednastavitev..." #: editor/import_dock.cpp msgid "Reimport" -msgstr "" +msgstr "Ponovno Uvozi" #: editor/multi_node_edit.cpp msgid "MultiNode Set" -msgstr "" +msgstr "Niz VeÄkratnih Gradnikov" #: editor/node_dock.cpp msgid "Groups" -msgstr "" +msgstr "Skupine" #: editor/node_dock.cpp msgid "Select a Node to edit Signals and Groups." -msgstr "" +msgstr "Za urejanje Signalov in Skupin izberi Gradnik." #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Create Poly" -msgstr "" +msgstr "Ustvarite Poligon" #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/collision_polygon_editor_plugin.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit Poly" -msgstr "" +msgstr "Uredi Poligon" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Insert Point" -msgstr "" +msgstr "Ustavi ToÄko" #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/collision_polygon_editor_plugin.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit Poly (Remove Point)" -msgstr "" +msgstr "Uredi Poligon (Odstrani ToÄko)" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Remove Poly And Point" -msgstr "" +msgstr "Odstrani Poligon in ToÄko" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Create a new polygon from scratch" -msgstr "" +msgstr "Ustvari nov poligon od zaÄetka" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "" @@ -2829,199 +2896,201 @@ msgid "" "Ctrl+LMB: Split Segment.\n" "RMB: Erase Point." msgstr "" +"Uredi obstojeÄi poligon:\n" +"LMG: Premakni ToÄko.\n" +"Ctrl+LMG: Razdeli Älen.\n" +"DMG: ZbriÅ¡i ToÄko." #: editor/plugins/abstract_polygon_2d_editor.cpp -#, fuzzy msgid "Delete points" -msgstr "IzbriÅ¡i Izbrano" +msgstr "IzbriÅ¡i toÄke" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Toggle Autoplay" -msgstr "" +msgstr "Preklop funkcije Samodejno Predvajanje" #: editor/plugins/animation_player_editor_plugin.cpp msgid "New Animation Name:" -msgstr "" +msgstr "Novo Ime Animacije:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "New Anim" -msgstr "" +msgstr "Nova Animacija" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Animation Name:" -msgstr "" +msgstr "Spremeni Ime Animacije:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Delete Animation?" -msgstr "" +msgstr "IzbriÅ¡em animacijo?" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Remove Animation" -msgstr "" +msgstr "Odstrani Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Invalid animation name!" -msgstr "" +msgstr "Napaka: Neveljavno ime animacije!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Animation name already exists!" -msgstr "" +msgstr "NAPAKA: Animacija s tem imenom že obstaja!" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Rename Animation" -msgstr "" +msgstr "Preimenuj Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp msgid "Add Animation" -msgstr "" +msgstr "Dodaj Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Blend Next Changed" -msgstr "" +msgstr "Naslednjo MeÅ¡anje se je Spremenilo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Blend Time" -msgstr "" +msgstr "Spremeni MeÅ¡alni ÄŒas" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load Animation" -msgstr "" +msgstr "Naloži Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Duplicate Animation" -msgstr "" +msgstr "Podvoji Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to copy!" -msgstr "" +msgstr "NAPAKA: Ni animacije za kopiranje!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation resource on clipboard!" -msgstr "" +msgstr "NAPAKA: Ni animacije virov na odložiÅ¡Äu!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Pasted Animation" -msgstr "" +msgstr "Prilepljena Animacija" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Paste Animation" -msgstr "" +msgstr "Prilepi animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to edit!" -msgstr "" +msgstr "NAPAKA: Ni animacije za urejanje!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation backwards from current pos. (A)" -msgstr "" +msgstr "Predvajaj izbrano animacijo nazaj od trenutnega položaja. (A)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation backwards from end. (Shift+A)" -msgstr "" +msgstr "Predvajaj izbrano animacijo nazaj od konca. (Shift+A)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Stop animation playback. (S)" -msgstr "" +msgstr "Ustavi predvajanje animacije. (S)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation from start. (Shift+D)" -msgstr "" +msgstr "Predvajaj izbrano animacijo od zaÄetka. (Shift+D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Play selected animation from current pos. (D)" -msgstr "" +msgstr "Predvajaj izbrano animacijo iz trenutne pozicije. (D)" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation position (in seconds)." -msgstr "" +msgstr "Mesto animacije (v sekundah)." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Scale animation playback globally for the node." -msgstr "" +msgstr "Spremeni velikost predvajanja za gradnike globalno." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create new animation in player." -msgstr "" +msgstr "Ustvari novo animacijo v predvajalniku." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load animation from disk." -msgstr "" +msgstr "Naloži animacijo z diska." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load an animation from disk." -msgstr "" +msgstr "Naloži animacijo z diska." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Save the current animation" -msgstr "" +msgstr "Shrani trenutno animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Display list of animations in player." -msgstr "" +msgstr "Prikaži seznam animacij v predvajalniku." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Autoplay on Load" -msgstr "" +msgstr "Samodejno predvajaj ob nalaganju" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Edit Target Blend Times" -msgstr "" +msgstr "Uredi Äas meÅ¡anice cilja" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation Tools" -msgstr "" +msgstr "Animacijska Orodja" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Copy Animation" -msgstr "" +msgstr "Kopiraj Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Onion Skinning" -msgstr "" +msgstr "Lupljenje ÄŒebule" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Enable Onion Skinning" -msgstr "" +msgstr "OmogoÄi Lupljenje ÄŒebule" #: editor/plugins/animation_player_editor_plugin.cpp -#, fuzzy msgid "Directions" -msgstr "Funkcije:" +msgstr "Smeri" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Past" -msgstr "" +msgstr "Preteklost" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Future" -msgstr "" +msgstr "Prihodnost" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Depth" -msgstr "" +msgstr "Globina" #: editor/plugins/animation_player_editor_plugin.cpp msgid "1 step" -msgstr "" +msgstr "1 korak" #: editor/plugins/animation_player_editor_plugin.cpp msgid "2 steps" -msgstr "" +msgstr "2 koraka" #: editor/plugins/animation_player_editor_plugin.cpp msgid "3 steps" -msgstr "" +msgstr "3 koraki" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Differences Only" -msgstr "" +msgstr "Samo Razlike" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Force White Modulate" -msgstr "" +msgstr "Sile Bele Modulacije" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Include Gizmos (3D)" @@ -3029,30 +3098,30 @@ msgstr "" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create New Animation" -msgstr "" +msgstr "Ustvari Novo Animacijo" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Animation Name:" -msgstr "" +msgstr "Ime Animacije:" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/resource_preloader_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp editor/property_editor.cpp #: editor/script_create_dialog.cpp msgid "Error!" -msgstr "" +msgstr "Napaka!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Blend Times:" -msgstr "" +msgstr "ÄŒas MeÅ¡anja:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Next (Auto Queue):" -msgstr "" +msgstr "Naprej (Samodejna Razvrstitev):" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Cross-Animation Blend Times" -msgstr "" +msgstr "Navzkrižna Animacija ÄŒasa MeÅ¡anice" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/canvas_item_editor_plugin.cpp @@ -3061,78 +3130,77 @@ msgstr "Animacija" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "New name:" -msgstr "" +msgstr "Novo ime:" #: editor/plugins/animation_tree_editor_plugin.cpp -#, fuzzy msgid "Edit Filters" -msgstr "Uredi Spremenljivko:" +msgstr "Uredi Filtre" #: editor/plugins/animation_tree_editor_plugin.cpp #: editor/plugins/multimesh_editor_plugin.cpp msgid "Scale:" -msgstr "" +msgstr "Prilagodi Velikost:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Fade In (s):" -msgstr "" +msgstr "Postopno Prikazovanje (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Fade Out (s):" -msgstr "" +msgstr "Postopno Izginevanje (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend" -msgstr "" +msgstr "ZmeÅ¡aj" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix" -msgstr "" +msgstr "MeÅ¡aj" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Auto Restart:" -msgstr "" +msgstr "Samodejni Ponovni Zagon:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Restart (s):" -msgstr "" +msgstr "Znova Zaženi (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Random Restart (s):" -msgstr "" +msgstr "NakljuÄno Zaženi (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Start!" -msgstr "" +msgstr "Zaženi!" #: editor/plugins/animation_tree_editor_plugin.cpp #: editor/plugins/multimesh_editor_plugin.cpp msgid "Amount:" -msgstr "" +msgstr "KoliÄina:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend:" -msgstr "" +msgstr "ZmeÅ¡aj:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend 0:" -msgstr "" +msgstr "ZmeÅ¡aj 0:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend 1:" -msgstr "" +msgstr "ZmeÅ¡aj 1:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "X-Fade Time (s):" -msgstr "" +msgstr "ÄŒas X-Bledenja (s):" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Current:" -msgstr "" +msgstr "Trenutno:" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Add Input" -msgstr "" +msgstr "Dodaj Vnos" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Clear Auto-Advance" @@ -3140,136 +3208,135 @@ msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Set Auto-Advance" -msgstr "" +msgstr "Nastavi Samodejno-Napredovanje" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Delete Input" -msgstr "" +msgstr "IzbriÅ¡i Vnos" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation tree is valid." -msgstr "" +msgstr "Drevo animacije je veljavno." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation tree is invalid." -msgstr "" +msgstr "Drevo animacije ni veljavno." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Animation Node" -msgstr "" +msgstr "Animacijski Gradnik" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "OneShot Node" -msgstr "" +msgstr "Gradnik EnPoizkus" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Mix Node" -msgstr "" +msgstr "Gradnik MeÅ¡anica" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend2 Node" -msgstr "" +msgstr "Gradnik ZmeÅ¡aj2" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend3 Node" -msgstr "" +msgstr "Gradnik ZmeÅ¡aj3" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Blend4 Node" -msgstr "" +msgstr "Gradnik ZmeÅ¡aj4" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeScale Node" -msgstr "" +msgstr "Gradnik ÄŒasovnoMerilo" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "TimeSeek Node" -msgstr "" +msgstr "Gradnik ÄŒasovniIskalnik" #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Transition Node" -msgstr "" +msgstr "Gradnik Prehod" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "" +msgid "Import Animations..." +msgstr "Uvozi Animacije..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" -msgstr "" +msgstr "Uredi Gradnike Filtri" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "" +msgid "Filters..." +msgstr "Filtri..." #: editor/plugins/animation_tree_editor_plugin.cpp -#, fuzzy msgid "AnimationTree" -msgstr "Približaj Animacijo" +msgstr "AnimacijskoDrevo" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Free" -msgstr "" +msgstr "Prosto" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Contents:" -msgstr "" +msgstr "Vsebina:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "View Files" -msgstr "" +msgstr "Ogled datotek" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't resolve hostname:" -msgstr "" +msgstr "Ne morem razreÅ¡iti imena gostitelja:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Connection error, please try again." -msgstr "" +msgstr "Napaka pri povezavi, poskusi znova." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't connect to host:" -msgstr "" +msgstr "NemogoÄe se je povezati z gostiteljem:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "No response from host:" -msgstr "" +msgstr "Gostitelj se ne odziva:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request failed, return code:" -msgstr "" +msgstr "Zahteva ni uspela, povratna koda:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Request failed, too many redirects" -msgstr "" +msgstr "Zahteva ni uspela, preveÄ preusmeritev" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Bad download hash, assuming file has been tampered with." -msgstr "" +msgstr "Slab prenos hash kode, predvidevamo, da je bila datoteka spremenjena." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Expected:" -msgstr "" +msgstr "PriÄakovano:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Got:" -msgstr "" +msgstr "Dobil:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Failed sha256 hash check" -msgstr "" +msgstr "NeuspeÅ¡no preverjanje preizkusa sha256" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Asset Download Error:" -msgstr "" +msgstr "Napaka pri prenosu sredstev:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "Fetching:" -msgstr "" +msgstr "Pridobivanje:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "" +msgid "Resolving..." +msgstr "RazreÅ¡evanje..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3335,8 +3402,8 @@ msgid "Site:" msgstr "Stran:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "Podpora.." +msgid "Support..." +msgstr "Podpora..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3362,6 +3429,8 @@ msgid "" "No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake " "Light' flag is on." msgstr "" +"Brez modelov za peko. Poskrbi, da vsebujejo kanal UV2 in da je vkljuÄena " +"oznaka 'ZapeÄi Svetlobo'." #: editor/plugins/baked_lightmap_editor_plugin.cpp msgid "Failed creating lightmap images, make sure path is writable." @@ -3415,9 +3484,8 @@ msgid "Create new vertical guide" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Remove vertical guide" -msgstr "Odstrani Spremenljivko" +msgstr "Odstranite navpiÄni vodnik" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Move horizontal guide" @@ -3428,9 +3496,8 @@ msgid "Create new horizontal guide" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Remove horizontal guide" -msgstr "Odstrani Spremenljivko" +msgstr "Odstrani vodoravno vodilo" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Create new horizontal and vertical guides" @@ -3506,9 +3573,8 @@ msgid "Pan Mode" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Toggles snapping" -msgstr "Preklopi na Zaustavitev" +msgstr "Preklopi pripenjanje" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Use Snap" @@ -3527,8 +3593,9 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "" +msgstr "Preoblikuj Zaskok..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3667,9 +3734,8 @@ msgid "Drag pivot from mouse position" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Set pivot at mouse position" -msgstr "Odstrani Signal" +msgstr "Nastavite toÄko na položaj miÅ¡ke" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Multiply grid step by 2" @@ -3778,14 +3844,12 @@ msgid "Load Curve Preset" msgstr "" #: editor/plugins/curve_editor_plugin.cpp -#, fuzzy msgid "Add point" -msgstr "Dodaj Signal" +msgstr "Dodaj toÄko" #: editor/plugins/curve_editor_plugin.cpp -#, fuzzy msgid "Remove point" -msgstr "Odstrani Signal" +msgstr "Odstrani toÄko" #: editor/plugins/curve_editor_plugin.cpp msgid "Left linear" @@ -3800,9 +3864,8 @@ msgid "Load preset" msgstr "" #: editor/plugins/curve_editor_plugin.cpp -#, fuzzy msgid "Remove Curve Point" -msgstr "Odstrani Signal" +msgstr "Odstrani Krivuljno ToÄko" #: editor/plugins/curve_editor_plugin.cpp msgid "Toggle Curve Linear Tangent" @@ -3869,11 +3932,11 @@ msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh is empty!" -msgstr "" +msgstr "Model je prazen!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Static Trimesh Body" -msgstr "" +msgstr "Ustvari StatiÄno Telo TriModel" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Create Static Convex Body" @@ -3952,7 +4015,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4157,7 +4220,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4336,19 +4399,16 @@ msgid "Curve Point #" msgstr "" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve Point Position" -msgstr "Odstrani Signal" +msgstr "Nastavi Položaj Krivuljne ToÄke" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve In Position" -msgstr "Odstrani Signal" +msgstr "Nastavi Krivuljo na Položaj" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Set Curve Out Position" -msgstr "Odstrani Signal" +msgstr "Nastavi Krivuljo iz Položaja" #: editor/plugins/path_editor_plugin.cpp msgid "Split Path" @@ -4359,9 +4419,8 @@ msgid "Remove Path Point" msgstr "" #: editor/plugins/path_editor_plugin.cpp -#, fuzzy msgid "Remove Out-Control Point" -msgstr "Odstrani Funkcijo" +msgstr "Odstrani ToÄko Izven Nadzora" #: editor/plugins/path_editor_plugin.cpp msgid "Remove In-Control Point" @@ -4522,7 +4581,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4602,9 +4661,8 @@ msgid "Close Docs" msgstr "" #: editor/plugins/script_editor_plugin.cpp -#, fuzzy msgid "Close All" -msgstr "Zapri" +msgstr "Zapri Vse" #: editor/plugins/script_editor_plugin.cpp msgid "Close Other Tabs" @@ -4620,7 +4678,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4745,9 +4803,8 @@ msgid "Select All" msgstr "" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Delete Line" -msgstr "IzbriÅ¡i Izbrano" +msgstr "IzbriÅ¡i Vrstico" #: editor/plugins/script_text_editor.cpp msgid "Indent Left" @@ -4762,9 +4819,8 @@ msgid "Toggle Comment" msgstr "" #: editor/plugins/script_text_editor.cpp -#, fuzzy msgid "Fold/Unfold Line" -msgstr "IzbriÅ¡i Izbrano" +msgstr "Pregibna/Nepregibna ÄŒrta" #: editor/plugins/script_text_editor.cpp msgid "Fold All Lines" @@ -4828,15 +4884,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5028,9 +5084,8 @@ msgid "Material Changes" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Shader Changes" -msgstr "Spremeni" +msgstr "Spremebe v Shader" #: editor/plugins/spatial_editor_plugin.cpp msgid "Surface Changes" @@ -5267,9 +5322,8 @@ msgid "Align Selection With View" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Tool Select" -msgstr "IzbriÅ¡i Izbrano" +msgstr "Izbira Orodja" #: editor/plugins/spatial_editor_plugin.cpp msgid "Tool Move" @@ -5284,21 +5338,16 @@ msgid "Tool Scale" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -#, fuzzy msgid "Toggle Freelook" -msgstr "Preklopi na Zaustavitev" +msgstr "Preklopi Svobodni Pregled" #: editor/plugins/spatial_editor_plugin.cpp msgid "Transform" msgstr "Preoblikovanje" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Preoblikuj Zaskok.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Preoblikovanje Dialoga.." +msgid "Transform Dialog..." +msgstr "Preoblikovanje Dialoga..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5542,17 +5591,15 @@ msgid "Remove Item" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Remove All Items" -msgstr "Odstrani Spremenljivko" +msgstr "Odstrani Vse Stvari" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Remove All" -msgstr "Odstrani Signal" +msgstr "Odstrani Vse" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5620,7 +5667,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5776,9 +5823,8 @@ msgid "" msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp -#, fuzzy msgid "Select current edited sub-tile." -msgstr "Dodaj Setter Lastnost" +msgstr "Izberi trenutno pod-ploÅ¡Äo v urejanju." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Select sub-tile to change its priority." @@ -5809,7 +5855,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5899,6 +5945,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "Ime Projekta:" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -5933,9 +5984,8 @@ msgid "The following files failed extraction from package:" msgstr "" #: editor/project_manager.cpp -#, fuzzy msgid "Rename Project" -msgstr "Preimenuj Funkcijo" +msgstr "Preimenuj Projekt" #: editor/project_manager.cpp msgid "New Game Project" @@ -6088,8 +6138,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6117,7 +6167,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6213,9 +6263,8 @@ msgid "Wheel Down." msgstr "" #: editor/project_settings_editor.cpp -#, fuzzy msgid "Add Global Property" -msgstr "Dodaj Getter Lastnost" +msgstr "Dodaj Globalno Lastnost" #: editor/project_settings_editor.cpp msgid "Select a setting item first!" @@ -6230,9 +6279,8 @@ msgid "Setting '%s' is internal, and it can't be deleted." msgstr "" #: editor/project_settings_editor.cpp -#, fuzzy msgid "Delete Item" -msgstr "IzbriÅ¡i Izbrano" +msgstr "IzbriÅ¡i Predmet" #: editor/project_settings_editor.cpp msgid "Already existing" @@ -6303,7 +6351,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6399,11 +6447,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6411,9 +6459,8 @@ msgid "Assign" msgstr "" #: editor/property_editor.cpp -#, fuzzy msgid "Select Node" -msgstr "Dodaj Setter Lastnost" +msgstr "Izberi Gradnik" #: editor/property_editor.cpp msgid "New Script" @@ -6468,9 +6515,8 @@ msgid "Properties:" msgstr "" #: editor/property_selector.cpp -#, fuzzy msgid "Select Property" -msgstr "Dodaj Setter Lastnost" +msgstr "Izberi Lastnost" #: editor/property_selector.cpp msgid "Select Virtual Method" @@ -6576,7 +6622,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -6692,9 +6738,8 @@ msgid "Clear a script for the selected node." msgstr "" #: editor/scene_tree_dock.cpp -#, fuzzy msgid "Remote" -msgstr "Odstrani Signal" +msgstr "Upravljalnik" #: editor/scene_tree_dock.cpp msgid "Local" @@ -6823,18 +6868,16 @@ msgid "Wrong extension chosen" msgstr "" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Invalid Path" -msgstr ": Neveljavni argumenti: " +msgstr "Neveljavna Pot" #: editor/script_create_dialog.cpp msgid "Invalid class name" msgstr "" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Invalid inherited parent name or path" -msgstr "Neveljaven indeks lastnosti imena." +msgstr "Neveljaveno prevzeto ime ali pot nadrejenega" #: editor/script_create_dialog.cpp msgid "Script valid" @@ -6869,9 +6912,8 @@ msgid "Class Name" msgstr "" #: editor/script_create_dialog.cpp -#, fuzzy msgid "Template" -msgstr "Odstrani Spremenljivko" +msgstr "Predloga" #: editor/script_create_dialog.cpp msgid "Built-in Script" @@ -6882,9 +6924,8 @@ msgid "Attach Node Script" msgstr "" #: editor/script_editor_debugger.cpp -#, fuzzy msgid "Remote " -msgstr "Odstrani Signal" +msgstr "Upravljalnik " #: editor/script_editor_debugger.cpp msgid "Bytes:" @@ -7075,9 +7116,8 @@ msgid "Select dependencies of the library for this entry" msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp -#, fuzzy msgid "Remove current entry" -msgstr "Odstrani Signal" +msgstr "Odstrani trenutni vnos" #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Double click to create a new entry" @@ -7191,9 +7231,8 @@ msgid "Floor:" msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "GridMap Delete Selection" -msgstr "IzbriÅ¡i Izbrano" +msgstr "GridMap IzbriÅ¡i Izbor" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "GridMap Duplicate Selection" @@ -7272,9 +7311,8 @@ msgid "Erase Area" msgstr "" #: modules/gridmap/grid_map_editor_plugin.cpp -#, fuzzy msgid "Clear Selection" -msgstr "IzbriÅ¡i Izbrano" +msgstr "PoÄisti izbrano" #: modules/gridmap/grid_map_editor_plugin.cpp msgid "GridMap Settings" @@ -7345,8 +7383,8 @@ msgid "" "A node yielded without working memory, please read the docs on how to yield " "properly!" msgstr "" -"VozliÅ¡Äe se je ustavilo brez delovnega spomina! Prosimo preberite si v " -"dokumentaciji, kako pravilno ustaviti vozliÅ¡Äe." +"Gradnik je bil ustavljen brez delovnega spomina, v dokumentaciji si " +"preberite kako ga pravilno ustaviti!" #: modules/visual_script/visual_script.cpp msgid "" @@ -7378,9 +7416,8 @@ msgid "Stack overflow with stack depth: " msgstr "Sklad prepoln z stack depth: " #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Signal Arguments" -msgstr "Uredi Argumente Signala:" +msgstr "Spremeni Argumente Signala" #: modules/visual_script/visual_script_editor.cpp msgid "Change Argument Type" @@ -7395,9 +7432,8 @@ msgid "Set Variable Default Value" msgstr "" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Set Variable Type" -msgstr "Uredi Spremenljivko:" +msgstr "Nastavite Tip Spremenljivke" #: modules/visual_script/visual_script_editor.cpp msgid "Functions:" @@ -7448,9 +7484,8 @@ msgid "Add Node" msgstr "Dodaj vozliÅ¡Äe" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Remove VisualScript Nodes" -msgstr "Odstrani Spremenljivko" +msgstr "Odstrani Gradnike VizualnaSkripta" #: modules/visual_script/visual_script_editor.cpp msgid "Duplicate VisualScript Nodes" @@ -7485,9 +7520,8 @@ msgid "Add Preload Node" msgstr "Dodaj prednaloženo vozliÅ¡Äe" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Add Node(s) From Tree" -msgstr "Dodaj vozliÅ¡Äe(a) iz drevesa" +msgstr "Dodaj Gradnik(e) iz Drevesa" #: modules/visual_script/visual_script_editor.cpp msgid "Add Getter Property" @@ -7498,18 +7532,16 @@ msgid "Add Setter Property" msgstr "Dodaj Setter Lastnost" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Change Base Type" -msgstr "Osnovni Tip:" +msgstr "Spremeni Osnovni Tip" #: modules/visual_script/visual_script_editor.cpp msgid "Move Node(s)" msgstr "" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Remove VisualScript Node" -msgstr "Odstrani Spremenljivko" +msgstr "Odstrani Gradnik VizualnaSkripta" #: modules/visual_script/visual_script_editor.cpp msgid "Connect Nodes" @@ -7572,18 +7604,16 @@ msgid "Remove Function" msgstr "Odstrani Funkcijo" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Edit Variable" -msgstr "Uredi Spremenljivko:" +msgstr "Uredi Spremenljivko" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Variable" msgstr "Odstrani Spremenljivko" #: modules/visual_script/visual_script_editor.cpp -#, fuzzy msgid "Edit Signal" -msgstr "Urejanje Signala:" +msgstr "Uredi Signal" #: modules/visual_script/visual_script_editor.cpp msgid "Remove Signal" @@ -7710,9 +7740,8 @@ msgid "Could not open template for export:" msgstr "" #: platform/javascript/export/export.cpp -#, fuzzy msgid "Invalid export template:" -msgstr "Neveljaven indeks lastnosti imena." +msgstr "Neveljavna izvozna predloga:" #: platform/javascript/export/export.cpp msgid "Could not read custom HTML shell:" @@ -8051,6 +8080,14 @@ msgstr "Napaka nalaganja pisave." msgid "Invalid font size." msgstr "Neveljavna velikost pisave." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "PrejÅ¡nji zavihek" + +#, fuzzy +#~ msgid "Next" +#~ msgstr "Naslednji zavihek" + #~ msgid "Not found!" #~ msgstr "Ni Zadetka!" diff --git a/editor/translations/sr_Cyrl.po b/editor/translations/sr_Cyrl.po index 2c2b1eb001..c838174131 100644 --- a/editor/translations/sr_Cyrl.po +++ b/editor/translations/sr_Cyrl.po @@ -500,7 +500,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Повежи '%s' Ñа '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "Повежи..." #: editor/connections_dialog.cpp @@ -925,11 +925,11 @@ msgid "Move Audio Bus" msgstr "Помери звучни баÑ" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "Сачувај раÑпоред звучног баÑа као..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "Локација за нови раÑпоред..." #: editor/editor_audio_buses.cpp @@ -1065,11 +1065,11 @@ msgid "Updating Scene" msgstr "Ðжурирање Ñцене" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "Чувам локалне промене..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "Ðжурирам Ñцену..." #: editor/editor_data.cpp @@ -1140,7 +1140,7 @@ msgid "Show In File Manager" msgstr "Покажи у менаџеру датотека" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "Ðови директоријум..." #: editor/editor_file_dialog.cpp @@ -1411,12 +1411,12 @@ msgid "Error saving resource!" msgstr "Грешка при чувању реÑурÑа!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "Сачувај реÑÑƒÑ€Ñ ÐºÐ°Ð¾..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "Разумем..." #: editor/editor_node.cpp @@ -1645,11 +1645,11 @@ msgid "Open Base Scene" msgstr "Отвори базну Ñцену" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "Брзо отварање Ñцене..." #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "Брзо отварање Ñкриптице..." #: editor/editor_node.cpp @@ -1661,7 +1661,7 @@ msgid "Save changes to '%s' before closing?" msgstr "Сачувај промене '%s' пре излаÑка?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "Сачувај Ñцену као..." #: editor/editor_node.cpp @@ -1713,7 +1713,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Ова акција Ñе не може опозвати. ÐаÑтави?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "Брзо покретање Ñцене..." #: editor/editor_node.cpp @@ -1869,7 +1869,7 @@ msgid "Previous tab" msgstr "Претходни таб" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Филтрирај датотеке..." #: editor/editor_node.cpp @@ -1881,11 +1881,11 @@ msgid "New Scene" msgstr "Ðова Ñцена" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "Ðова наÑлеђена Ñцена..." #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "Отвори Ñцену..." #: editor/editor_node.cpp @@ -1905,15 +1905,15 @@ msgid "Open Recent" msgstr "Отвори недавно коришћено" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "Конвертуј у..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2177,7 +2177,7 @@ msgid "Save the currently edited resource." msgstr "Сачувај тренутно измењени реÑурÑ." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Сачувај као..." #: editor/editor_node.cpp @@ -2286,7 +2286,7 @@ msgid "Creating Mesh Previews" msgstr "Ðаправи приказ мрежа" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "Сличица..." #: editor/editor_plugin_settings.cpp @@ -2441,7 +2441,7 @@ msgid "(Current)" msgstr "(Тренутно)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "Прихватам одредишта, молим Ñачекајте..." #: editor/export_template_manager.cpp @@ -2521,7 +2521,7 @@ msgid "Error requesting url: " msgstr "Грешка при захтеву url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "Повезивање Ñа одредиштем..." #: editor/export_template_manager.cpp @@ -2538,7 +2538,7 @@ msgstr "Ðе могу решити" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "Повезивање..." #: editor/export_template_manager.cpp @@ -2552,7 +2552,7 @@ msgstr "Повезан" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "Захтевање..." #: editor/export_template_manager.cpp @@ -2696,11 +2696,11 @@ msgid "Collapse all" msgstr "Умањи Ñве" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "Преименуј..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Помери у..." #: editor/filesystem_dock.cpp @@ -2713,16 +2713,16 @@ msgid "Instance" msgstr "Додај инÑтанцу" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "Измени завиÑноÑти..." #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "Погледај влаÑнике..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Дуплирај" #: editor/filesystem_dock.cpp @@ -2748,7 +2748,7 @@ msgstr "Ðаправи Ñледећу Ñцену/е као дете одабра #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Скенирање датотека,\n" "Молим Ñачекајте..." @@ -2816,7 +2816,7 @@ msgid "Import Scene" msgstr "Увези Ñцену" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "Увожење Ñцеене..." #: editor/import/resource_importer_scene.cpp @@ -2830,7 +2830,7 @@ msgid "Generating for Mesh: " msgstr "ГенериÑање оÑног поравнаног граничниог оквира (AABB)" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "Обрађивање Ñкриптице..." #: editor/import/resource_importer_scene.cpp @@ -2846,7 +2846,7 @@ msgid "Error running post-import script:" msgstr "Грешка при обрађивању поÑÑ‚-увозне Ñкриптице:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Чување..." #: editor/import_dock.cpp @@ -2866,7 +2866,7 @@ msgid "Import As:" msgstr "Увези као:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "ПоÑтавке..." #: editor/import_dock.cpp @@ -3284,7 +3284,7 @@ msgid "Transition Node" msgstr "Transition чвор" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "Увези анимације..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3292,7 +3292,7 @@ msgid "Edit Node Filters" msgstr "Измени филтере чвора" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Филтери..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3361,7 +3361,7 @@ msgid "Fetching:" msgstr "Преузимање:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "Решавање..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3428,7 +3428,7 @@ msgid "Site:" msgstr "Веб Ñтраница:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Подршка..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3618,6 +3618,7 @@ msgid "Use Rotation Snap" msgstr "КориÑти лепљење ротације" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "ПоÑтавке лепљења..." @@ -4045,7 +4046,7 @@ msgid "Create Convex Collision Sibling" msgstr "Ðаправи конвекÑног Ñударног брата" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "Ðаправи ивичну мрежу..." #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4253,7 +4254,7 @@ msgid "Error loading image:" msgstr "Грешка при учитавању Ñлике:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "У Ñлици нема пикÑела Ñа транÑпарентношћу већом од 128..." #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4618,7 +4619,7 @@ msgid "Import Theme" msgstr "Увези тему" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Сачувај тему као..." #: editor/plugins/script_editor_plugin.cpp @@ -4717,7 +4718,7 @@ msgstr "Прикажи панел Ñкриптица" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "Тражи..." #: editor/plugins/script_editor_plugin.cpp @@ -4928,15 +4929,15 @@ msgid "Find Previous" msgstr "Ðађи претходни" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "Замени..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "Иди на функцију..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "Иди на линију..." #: editor/plugins/script_text_editor.cpp @@ -5393,11 +5394,7 @@ msgid "Transform" msgstr "ТранÑформација" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Конфигуриши лепљење..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "Прозор транÑформације..." #: editor/plugins/spatial_editor_plugin.cpp @@ -5653,7 +5650,7 @@ msgid "Remove All" msgstr "Обриши Ñве" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Измени тему..." #: editor/plugins/theme_editor_plugin.cpp @@ -5725,7 +5722,8 @@ msgid "Options" msgstr "Опција" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +#, fuzzy +msgid "Has,Many,Options" msgstr "Има,много,неколико,опција!" #: editor/plugins/theme_editor_plugin.cpp @@ -5924,7 +5922,7 @@ msgid "Presets" msgstr "ПоÑтавке" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Додај..." #: editor/project_export.cpp @@ -6020,6 +6018,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Ðеважеће име." + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "ÐеуÑпех при прављењу директоријума." @@ -6209,8 +6212,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6238,7 +6241,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6423,7 +6426,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6519,11 +6522,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6695,7 +6698,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -8146,6 +8149,13 @@ msgstr "" msgid "Invalid font size." msgstr "Ðеважећа величина фонта." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Претходни таб" + +#~ msgid "Next" +#~ msgstr "Следеће" + #~ msgid "" #~ "Invalid version.txt format inside templates. Revision is not a valid " #~ "identifier." @@ -8156,9 +8166,6 @@ msgstr "Ðеважећа величина фонта." #~ msgid "Can't write file." #~ msgstr "ÐеуÑпех при запиÑивању датотеке." -#~ msgid "Next" -#~ msgstr "Следеће" - #~ msgid "Not found!" #~ msgstr "Ðије пронађено!" diff --git a/editor/translations/sr_Latn.po b/editor/translations/sr_Latn.po index d7cb85af1b..975418d4fb 100644 --- a/editor/translations/sr_Latn.po +++ b/editor/translations/sr_Latn.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-25 14:41+0000\n" +"PO-Revision-Date: 2018-05-15 08:41+0000\n" "Last-Translator: Milos Ponjavusic <brane@branegames.com>\n" "Language-Team: Serbian (latin) <https://hosted.weblate.org/projects/godot-" "engine/godot/sr_Latn/>\n" @@ -215,7 +215,7 @@ msgstr "Animacija dodaj kljuÄ" #: editor/animation_editor.cpp msgid "Change Anim Len" -msgstr "" +msgstr "Promijeni Dužinu Animacije" #: editor/animation_editor.cpp msgid "Change Anim Loop" @@ -223,15 +223,15 @@ msgstr "" #: editor/animation_editor.cpp msgid "Anim Create Typed Value Key" -msgstr "" +msgstr "Animacija Napravit Tip Vrijednosni KljuÄ" #: editor/animation_editor.cpp msgid "Anim Insert" -msgstr "" +msgstr "Animacija Umetni" #: editor/animation_editor.cpp msgid "Anim Scale Keys" -msgstr "" +msgstr "Animacija Skaliraj KljuÄeve" #: editor/animation_editor.cpp msgid "Anim Add Call Track" @@ -495,7 +495,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -905,11 +905,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1045,11 +1045,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1118,7 +1118,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1380,12 +1380,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1590,11 +1590,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1606,7 +1606,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1658,7 +1658,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1803,7 +1803,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1815,11 +1815,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1839,15 +1839,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2092,7 +2092,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2201,7 +2201,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2352,7 +2352,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2428,7 +2428,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2445,7 +2445,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2458,7 +2458,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2590,11 +2590,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2606,15 +2606,15 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "" #: editor/filesystem_dock.cpp @@ -2640,7 +2640,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2706,7 +2706,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2718,7 +2718,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2734,7 +2734,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2754,7 +2754,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3168,7 +3168,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3176,7 +3176,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3244,7 +3244,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3311,7 +3311,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3498,6 +3498,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3919,7 +3920,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4124,7 +4125,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4485,7 +4486,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4582,7 +4583,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4788,15 +4789,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5247,11 +5248,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5504,7 +5501,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5572,7 +5569,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5760,7 +5757,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5850,6 +5847,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6036,8 +6037,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6065,7 +6066,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6249,7 +6250,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6345,11 +6346,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6520,7 +6521,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/sv.po b/editor/translations/sv.po index 4a861d1b76..9ec654128a 100644 --- a/editor/translations/sv.po +++ b/editor/translations/sv.po @@ -3,21 +3,23 @@ # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. # -# bergmarklund <davemcgroin@gmail.com>, 2017. +# bergmarklund <davemcgroin@gmail.com>, 2017, 2018. # Christoffer Sundbom <christoffer_karlsson@live.se>, 2017. +# Jakob Sinclair <sinclair.jakob@mailbox.org>, 2018. +# . <grenoscar@gmail.com>, 2018. # msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2017-12-01 23:50+0000\n" -"Last-Translator: bergmarklund <davemcgroin@gmail.com>\n" +"PO-Revision-Date: 2018-05-07 11:42+0000\n" +"Last-Translator: anonymous <>\n" "Language-Team: Swedish <https://hosted.weblate.org/projects/godot-engine/" "godot/sv/>\n" "Language: sv\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 2.18-dev\n" +"X-Generator: Weblate 3.0-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -41,17 +43,16 @@ msgid "Anim Change Transform" msgstr "Anim Ändra Transformation" #: editor/animation_editor.cpp -#, fuzzy msgid "Anim Change Keyframe Value" -msgstr "Anim Ändra Värde" +msgstr "Anim Ändra Värde PÃ¥ Tidsnyckeln" #: editor/animation_editor.cpp msgid "Anim Change Call" -msgstr "Anim Ändra Samtal" +msgstr "Anim Ändra Anrop" #: editor/animation_editor.cpp msgid "Anim Add Track" -msgstr "Lägg till spÃ¥r" +msgstr "Anim Lägg till spÃ¥r" #: editor/animation_editor.cpp msgid "Anim Duplicate Keys" @@ -59,11 +60,11 @@ msgstr "Anim Duplicera Nycklar" #: editor/animation_editor.cpp msgid "Move Anim Track Up" -msgstr "Flytta Anim SpÃ¥ra Upp" +msgstr "Flytta Anim SpÃ¥ra UppÃ¥t" #: editor/animation_editor.cpp msgid "Move Anim Track Down" -msgstr "Flytta Anim SpÃ¥r Ner" +msgstr "Flytta Anim SpÃ¥r NerÃ¥t" #: editor/animation_editor.cpp msgid "Remove Anim Track" @@ -83,16 +84,15 @@ msgstr "Anim Ändra SpÃ¥rets Interpolation" #: editor/animation_editor.cpp msgid "Anim Track Change Value Mode" -msgstr "" +msgstr "Ändra Anim SpÃ¥rets Värde Läge" #: editor/animation_editor.cpp msgid "Anim Track Change Wrap Mode" msgstr "" #: editor/animation_editor.cpp -#, fuzzy msgid "Edit Node Curve" -msgstr "Redigera Node-Kurva" +msgstr "Redigera Nodkurva" #: editor/animation_editor.cpp #, fuzzy @@ -491,7 +491,7 @@ msgstr "Skapa Funktion" #: editor/connections_dialog.cpp msgid "Deferred" -msgstr "" +msgstr "Uppskjuten" #: editor/connections_dialog.cpp #, fuzzy @@ -531,8 +531,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Anslut '%s' till '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Anslut.." +msgid "Connect..." +msgstr "Anslut..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -733,7 +733,7 @@ msgstr "Resurser Utan Explicit Ägande:" #: editor/dependency_editor.cpp editor/editor_node.cpp msgid "Orphan Resource Explorer" -msgstr "" +msgstr "Föräldralös Resursutforskare" #: editor/dependency_editor.cpp msgid "Delete selected files?" @@ -988,7 +988,7 @@ msgstr "Ta bort Effekt" #: editor/editor_audio_buses.cpp msgid "Audio" -msgstr "" +msgstr "Ljud" #: editor/editor_audio_buses.cpp #, fuzzy @@ -1022,13 +1022,13 @@ msgstr "Flytta Ljud-Buss" #: editor/editor_audio_buses.cpp #, fuzzy -msgid "Save Audio Bus Layout As.." -msgstr "Spara Ljud-Buss Layout Som.." +msgid "Save Audio Bus Layout As..." +msgstr "Spara Ljud-Buss Layout Som..." #: editor/editor_audio_buses.cpp #, fuzzy -msgid "Location for New Layout.." -msgstr "Plats för Ny Layout.." +msgid "Location for New Layout..." +msgstr "Plats för Ny Layout..." #: editor/editor_audio_buses.cpp #, fuzzy @@ -1193,12 +1193,12 @@ msgstr "Uppdaterar Scen" #: editor/editor_data.cpp #, fuzzy -msgid "Storing local changes.." -msgstr "Lagrar lokala ändringar.." +msgid "Storing local changes..." +msgstr "Lagrar lokala ändringar..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Uppdaterar scen.." +msgid "Updating scene..." +msgstr "Uppdaterar scen..." #: editor/editor_data.cpp #, fuzzy @@ -1276,8 +1276,8 @@ msgid "Show In File Manager" msgstr "Visa I Filhanteraren" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Ny Mapp.." +msgid "New Folder..." +msgstr "Ny Mapp..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1285,7 +1285,7 @@ msgstr "Uppdatera" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Recognized" -msgstr "" +msgstr "Alla Erkända" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Files (*)" @@ -1348,7 +1348,7 @@ msgstr "Växla Läge" #: editor/editor_file_dialog.cpp msgid "Focus Path" -msgstr "" +msgstr "Fokusera pÃ¥ Sökväg" #: editor/editor_file_dialog.cpp msgid "Move Favorite Up" @@ -1360,7 +1360,7 @@ msgstr "Flytta Favorit Ner" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Go to parent folder" -msgstr "" +msgstr "GÃ¥ till överordnad mapp" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp #, fuzzy @@ -1464,11 +1464,11 @@ msgstr "Signaler:" #: editor/editor_help.cpp msgid "Enumerations" -msgstr "" +msgstr "Enumerations" #: editor/editor_help.cpp msgid "Enumerations:" -msgstr "" +msgstr "Enumerations:" #: editor/editor_help.cpp #, fuzzy @@ -1570,7 +1570,7 @@ msgstr "Output:" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Projekt exporten misslyckades med följande felmeddelande %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp #, fuzzy @@ -1578,13 +1578,13 @@ msgid "Error saving resource!" msgstr "Fel vid sparande av resurs!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Spara Resurs Som.." +msgid "Save Resource As..." +msgstr "Spara Resurs Som..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Jag förstÃ¥r.." +msgid "I see..." +msgstr "Jag förstÃ¥r..." #: editor/editor_node.cpp #, fuzzy @@ -1845,13 +1845,13 @@ msgstr "Öppna Bas-Scen" #: editor/editor_node.cpp #, fuzzy -msgid "Quick Open Scene.." -msgstr "Snabböppna Scen.." +msgid "Quick Open Scene..." +msgstr "Snabböppna Scen..." #: editor/editor_node.cpp #, fuzzy -msgid "Quick Open Script.." -msgstr "Snabböppna Skript.." +msgid "Quick Open Script..." +msgstr "Snabböppna Skript..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1863,8 +1863,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Spara ändringar i '%s' innan stängning?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Spara Scen Som.." +msgid "Save Scene As..." +msgstr "Spara Scen Som..." #: editor/editor_node.cpp msgid "No" @@ -1925,8 +1925,8 @@ msgstr "Ã…tgärden kan inte Ã¥ngras. Ã…terställ ändÃ¥?" #: editor/editor_node.cpp #, fuzzy -msgid "Quick Run Scene.." -msgstr "Snabbkör Scen.." +msgid "Quick Run Scene..." +msgstr "Snabbkör Scen..." #: editor/editor_node.cpp msgid "Quit" @@ -2102,8 +2102,8 @@ msgid "Previous tab" msgstr "FöregÃ¥ende flik" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Filtrera Filer.." +msgid "Filter Files..." +msgstr "Filtrera Filer..." #: editor/editor_node.cpp #, fuzzy @@ -2116,12 +2116,12 @@ msgstr "Ny Scen" #: editor/editor_node.cpp #, fuzzy -msgid "New Inherited Scene.." -msgstr "Ny Ärvd Scen.." +msgid "New Inherited Scene..." +msgstr "Ny Ärvd Scen..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Öppna Scen.." +msgid "Open Scene..." +msgstr "Öppna Scen..." #: editor/editor_node.cpp msgid "Save Scene" @@ -2141,18 +2141,18 @@ msgid "Open Recent" msgstr "Öppna Senaste" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Konvertera Till.." +msgid "Convert To..." +msgstr "Konvertera Till..." #: editor/editor_node.cpp #, fuzzy -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp #, fuzzy -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2340,7 +2340,7 @@ msgstr "" #: editor/editor_node.cpp msgid "Play" -msgstr "" +msgstr "Spela" #: editor/editor_node.cpp msgid "Pause the scene" @@ -2366,7 +2366,7 @@ msgstr "Spela den redigerade scenen." #: editor/editor_node.cpp msgid "Play Scene" -msgstr "" +msgstr "Spela Scen" #: editor/editor_node.cpp msgid "Play custom scene" @@ -2412,8 +2412,8 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Spara Som.." +msgid "Save As..." +msgstr "Spara Som..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2526,8 +2526,8 @@ msgstr "" #: editor/editor_plugin.cpp #, fuzzy -msgid "Thumbnail.." -msgstr "Miniatyr.." +msgid "Thumbnail..." +msgstr "Miniatyr..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2564,7 +2564,7 @@ msgstr "" #: editor/editor_profiler.cpp msgid "Frame Time (sec)" -msgstr "" +msgstr "Bildrutetid (sek)" #: editor/editor_profiler.cpp msgid "Average Time (sec)" @@ -2686,7 +2686,7 @@ msgid "(Current)" msgstr "(Nuvarande)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2767,7 +2767,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2786,8 +2786,8 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Connecting.." -msgstr "Ansluter.." +msgid "Connecting..." +msgstr "Ansluter..." #: editor/export_template_manager.cpp #, fuzzy @@ -2801,7 +2801,7 @@ msgstr "Ansluten" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2951,13 +2951,13 @@ msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Rename.." -msgstr "Byt namn.." +msgid "Rename..." +msgstr "Byt namn..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Move To.." -msgstr "Flytta Till.." +msgid "Move To..." +msgstr "Flytta Till..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2970,17 +2970,17 @@ msgid "Instance" msgstr "Instans" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "View Owners.." -msgstr "Visa Ägare.." +msgid "View Owners..." +msgstr "Visa Ägare..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Duplicera" #: editor/filesystem_dock.cpp @@ -3007,7 +3007,7 @@ msgstr "Instansiera valda scen(er) som barn till vald Node." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -3077,8 +3077,8 @@ msgstr "Importera Scen" #: editor/import/resource_importer_scene.cpp #, fuzzy -msgid "Importing Scene.." -msgstr "Importerar Scen.." +msgid "Importing Scene..." +msgstr "Importerar Scen..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -3089,7 +3089,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -3105,8 +3105,8 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "Sparar.." +msgid "Saving..." +msgstr "Sparar..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -3126,7 +3126,7 @@ msgid "Import As:" msgstr "Importera Som:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3156,25 +3156,26 @@ msgstr "" #: editor/plugins/collision_polygon_editor_plugin.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit Poly" -msgstr "" +msgstr "Redigera Polygon" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Insert Point" -msgstr "" +msgstr "Infoga Punkt" #: editor/plugins/abstract_polygon_2d_editor.cpp #: editor/plugins/collision_polygon_editor_plugin.cpp #: editor/plugins/light_occluder_2d_editor_plugin.cpp msgid "Edit Poly (Remove Point)" -msgstr "" +msgstr "Redigera Polygon (ta bort punkt)" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Remove Poly And Point" -msgstr "" +msgstr "Ta bort Polygon och Punkt" #: editor/plugins/abstract_polygon_2d_editor.cpp +#, fuzzy msgid "Create a new polygon from scratch" -msgstr "" +msgstr "Skapa ny polygon frÃ¥n grunden" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "" @@ -3186,7 +3187,7 @@ msgstr "" #: editor/plugins/abstract_polygon_2d_editor.cpp msgid "Delete points" -msgstr "" +msgstr "Radera punkter" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Toggle Autoplay" @@ -3194,7 +3195,7 @@ msgstr "" #: editor/plugins/animation_player_editor_plugin.cpp msgid "New Animation Name:" -msgstr "" +msgstr "Nytt Animationsnamn:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "New Anim" @@ -3202,7 +3203,7 @@ msgstr "Ny Anim" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Animation Name:" -msgstr "" +msgstr "Ändra Animationsnamn:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Delete Animation?" @@ -3215,11 +3216,11 @@ msgstr "Ta bort Animation" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Invalid animation name!" -msgstr "" +msgstr "ERROR: Ogiltigt animationsnamn!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: Animation name already exists!" -msgstr "" +msgstr "ERROR: Animationsnamn finns redan!" #: editor/plugins/animation_player_editor_plugin.cpp #: editor/plugins/sprite_frames_editor_plugin.cpp @@ -3558,8 +3559,8 @@ msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy -msgid "Import Animations.." -msgstr "Importera Animationer.." +msgid "Import Animations..." +msgstr "Importera Animationer..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3568,8 +3569,8 @@ msgstr "Redigera Node-Filter" #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy -msgid "Filters.." -msgstr "Filter.." +msgid "Filters..." +msgstr "Filter..." #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy @@ -3638,7 +3639,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3667,8 +3668,9 @@ msgid "first" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp +#, fuzzy msgid "prev" -msgstr "" +msgstr "förhandsgranska" #: editor/plugins/asset_library_editor_plugin.cpp msgid "next" @@ -3707,7 +3709,7 @@ msgid "Site:" msgstr "Webbplats:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3895,6 +3897,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -4328,7 +4331,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4497,7 +4500,7 @@ msgstr "" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Partitioning..." -msgstr "Partitionerar.." +msgstr "Partitionerar..." #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy @@ -4542,7 +4545,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4914,8 +4917,8 @@ msgstr "Importera Tema" #: editor/plugins/script_editor_plugin.cpp #, fuzzy -msgid "Save Theme As.." -msgstr "Spara Tema Som.." +msgid "Save Theme As..." +msgstr "Spara Tema Som..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -5023,8 +5026,8 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp #, fuzzy -msgid "Find.." -msgstr "Hitta.." +msgid "Find..." +msgstr "Hitta..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -5244,15 +5247,15 @@ msgstr "" #: editor/plugins/script_text_editor.cpp #, fuzzy -msgid "Replace.." -msgstr "Ersätt.." +msgid "Replace..." +msgstr "Ersätt..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5733,11 +5736,7 @@ msgid "Transform" msgstr "Transformera" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5999,8 +5998,8 @@ msgstr "Ta bort Alla" #: editor/plugins/theme_editor_plugin.cpp #, fuzzy -msgid "Edit theme.." -msgstr "Redigera tema.." +msgid "Edit theme..." +msgstr "Redigera tema..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -6069,8 +6068,9 @@ msgid "Options" msgstr "Alternativ" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "Alternativ" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -6201,7 +6201,7 @@ msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet.." +msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -6267,8 +6267,8 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Lägg till.." +msgid "Add..." +msgstr "Lägg till..." #: editor/project_export.cpp msgid "Resources" @@ -6362,6 +6362,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "Projektnamn:" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "Kunde inte skapa mapp." @@ -6572,8 +6577,8 @@ msgstr "Musknapp" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6602,8 +6607,8 @@ msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp #, fuzzy -msgid "Press a Key.." -msgstr "Tryck pÃ¥ en Knapp.." +msgid "Press a Key..." +msgstr "Tryck pÃ¥ en Knapp..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6791,7 +6796,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6892,11 +6897,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." -msgstr "Fil.." +msgid "File..." +msgstr "Fil..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -7082,8 +7087,8 @@ msgstr "" #: editor/scene_tree_dock.cpp #, fuzzy -msgid "Save New Scene As.." -msgstr "Spara Ny Scen Som.." +msgid "Save New Scene As..." +msgstr "Spara Ny Scen Som..." #: editor/scene_tree_dock.cpp #, fuzzy @@ -8646,6 +8651,10 @@ msgstr "Fel vid laddning av font." msgid "Invalid font size." msgstr "Ogiltig teckenstorlek." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "FöregÃ¥ende flik" + #~ msgid "Next" #~ msgstr "Nästa" @@ -8681,10 +8690,6 @@ msgstr "Ogiltig teckenstorlek." #~ msgid "That's a BINGO!" #~ msgstr "Det är en BINGO!" -#, fuzzy -#~ msgid "preview" -#~ msgstr "förhandsgranska" - #~ msgid "Move Add Key" #~ msgstr "Flytta Lägg Till Nyckel" diff --git a/editor/translations/ta.po b/editor/translations/ta.po index e7269ffa0e..d7910c2c87 100644 --- a/editor/translations/ta.po +++ b/editor/translations/ta.po @@ -496,7 +496,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -906,11 +906,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1046,11 +1046,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1119,7 +1119,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1381,12 +1381,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1591,11 +1591,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1607,7 +1607,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1659,7 +1659,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1804,7 +1804,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1816,11 +1816,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1840,15 +1840,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2093,7 +2093,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2202,7 +2202,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2353,7 +2353,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2429,7 +2429,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2446,7 +2446,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2459,7 +2459,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2591,11 +2591,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2607,16 +2607,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "அசைவூடà¯à®Ÿà¯ போலிபசà¯à®šà®¾à®µà®¿à®•ளà¯" #: editor/filesystem_dock.cpp @@ -2642,7 +2642,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2708,7 +2708,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2720,7 +2720,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2736,7 +2736,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2756,7 +2756,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3170,7 +3170,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3178,7 +3178,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3246,7 +3246,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3313,7 +3313,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3500,6 +3500,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3921,7 +3922,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4126,7 +4127,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4487,7 +4488,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4584,7 +4585,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4790,15 +4791,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5249,11 +5250,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5506,7 +5503,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5574,7 +5571,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5762,7 +5759,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5852,6 +5849,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6038,8 +6039,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6067,7 +6068,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6251,7 +6252,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6347,11 +6348,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6522,7 +6523,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/th.po b/editor/translations/th.po index 74e2270f2c..4db8459f1b 100644 --- a/editor/translations/th.po +++ b/editor/translations/th.po @@ -495,8 +495,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "ลบà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¹‚ยง '%s' à¸à¸±à¸š '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "เชื่à¸à¸¡à¹‚ยง.." +msgid "Connect..." +msgstr "เชื่à¸à¸¡à¹‚ยง..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -914,12 +914,12 @@ msgid "Move Audio Bus" msgstr "ย้าย Audio Bus" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "บันทึà¸à¹€à¸¥à¸¢à¹Œà¹€à¸à¸²à¸•์ขà¸à¸‡ Audio Bus เป็น.." +msgid "Save Audio Bus Layout As..." +msgstr "บันทึà¸à¹€à¸¥à¸¢à¹Œà¹€à¸à¸²à¸•์ขà¸à¸‡ Audio Bus เป็น..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "ตำà¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¹€à¸¥à¸¢à¹Œà¹€à¸à¸²à¸•์ใหม่.." +msgid "Location for New Layout..." +msgstr "ตำà¹à¸«à¸™à¹ˆà¸‡à¸‚à¸à¸‡à¹€à¸¥à¸¢à¹Œà¹€à¸à¸²à¸•์ใหม่..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1054,12 +1054,12 @@ msgid "Updating Scene" msgstr "à¸à¸±à¸žà¹€à¸”ทฉาà¸" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "เà¸à¹‡à¸šà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸ ายใน.." +msgid "Storing local changes..." +msgstr "เà¸à¹‡à¸šà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸ ายใน..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "à¸à¸±à¸žà¹€à¸”ทฉาà¸.." +msgid "Updating scene..." +msgstr "à¸à¸±à¸žà¹€à¸”ทฉาà¸..." #: editor/editor_data.cpp msgid "[empty]" @@ -1127,8 +1127,8 @@ msgid "Show In File Manager" msgstr "à¹à¸ªà¸”งในตัวจัดà¸à¸²à¸£à¹„ฟล์" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "สร้างโฟลเดà¸à¸£à¹Œ.." +msgid "New Folder..." +msgstr "สร้างโฟลเดà¸à¸£à¹Œ..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1391,13 +1391,13 @@ msgid "Error saving resource!" msgstr "บันทึà¸à¸£à¸µà¸‹à¸à¸£à¹Œà¸ªà¸œà¸´à¸”พลาด!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "บันทึà¸à¸£à¸µà¸‹à¸à¸£à¹Œà¸ªà¹€à¸›à¹‡à¸™.." +msgid "Save Resource As..." +msgstr "บันทึà¸à¸£à¸µà¸‹à¸à¸£à¹Œà¸ªà¹€à¸›à¹‡à¸™..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "ตà¸à¸¥à¸‡.." +msgid "I see..." +msgstr "ตà¸à¸¥à¸‡..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1616,12 +1616,12 @@ msgid "Open Base Scene" msgstr "เปิดไฟล์ฉาà¸à¸—ี่ใช้สืบทà¸à¸”" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "เปิดไฟล์ฉาà¸à¸”่วน.." +msgid "Quick Open Scene..." +msgstr "เปิดไฟล์ฉาà¸à¸”่วน..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "เปิดไฟล์สคริปต์ด่วน.." +msgid "Quick Open Script..." +msgstr "เปิดไฟล์สคริปต์ด่วน..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1632,8 +1632,8 @@ msgid "Save changes to '%s' before closing?" msgstr "บันทึภ'%s' à¸à¹ˆà¸à¸™à¸›à¸´à¸”โปรà¹à¸à¸£à¸¡à¸«à¸£à¸·à¸à¹„ม่?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "บันทึà¸à¸‰à¸²à¸à¹€à¸›à¹‡à¸™.." +msgid "Save Scene As..." +msgstr "บันทึà¸à¸‰à¸²à¸à¹€à¸›à¹‡à¸™..." #: editor/editor_node.cpp msgid "No" @@ -1684,8 +1684,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "à¸à¸²à¸£à¸„ืนà¸à¸¥à¸±à¸šà¹„ม่สามารถยà¸à¹€à¸¥à¸´à¸à¹„ด้ คืนà¸à¸¥à¸±à¸š?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "เริ่มฉาà¸à¸”่วน.." +msgid "Quick Run Scene..." +msgstr "เริ่มฉาà¸à¸”่วน..." #: editor/editor_node.cpp msgid "Quit" @@ -1834,8 +1834,8 @@ msgid "Previous tab" msgstr "à¹à¸—็บà¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "คัดà¸à¸£à¸à¸‡à¹„ฟล์.." +msgid "Filter Files..." +msgstr "คัดà¸à¸£à¸à¸‡à¹„ฟล์..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1846,12 +1846,12 @@ msgid "New Scene" msgstr "ฉาà¸à¹ƒà¸«à¸¡à¹ˆ" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "สืบทà¸à¸”ฉาà¸à¹ƒà¸«à¸¡à¹ˆ.." +msgid "New Inherited Scene..." +msgstr "สืบทà¸à¸”ฉาà¸à¹ƒà¸«à¸¡à¹ˆ..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "เปิดไฟล์ฉาà¸.." +msgid "Open Scene..." +msgstr "เปิดไฟล์ฉาà¸..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1870,16 +1870,16 @@ msgid "Open Recent" msgstr "เปิดไฟล์ล่าสุด" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "à¹à¸›à¸¥à¸‡à¹€à¸›à¹‡à¸™.." +msgid "Convert To..." +msgstr "à¹à¸›à¸¥à¸‡à¹€à¸›à¹‡à¸™..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2130,8 +2130,8 @@ msgid "Save the currently edited resource." msgstr "บันทึà¸à¸£à¸µà¸‹à¸à¸£à¹Œà¸ªà¸—ี่à¸à¸³à¸¥à¸±à¸‡à¸›à¸£à¸±à¸šà¹à¸•่ง" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "บันทึà¸à¹€à¸›à¹‡à¸™.." +msgid "Save As..." +msgstr "บันทึà¸à¹€à¸›à¹‡à¸™..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2239,8 +2239,8 @@ msgid "Creating Mesh Previews" msgstr "à¸à¸³à¸¥à¸±à¸‡à¸ªà¸£à¹‰à¸²à¸‡à¸ าพตัวà¸à¸¢à¹ˆà¸²à¸‡ Mesh" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "รูปตัวà¸à¸¢à¹ˆà¸²à¸‡.." +msgid "Thumbnail..." +msgstr "รูปตัวà¸à¸¢à¹ˆà¸²à¸‡..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2392,8 +2392,8 @@ msgid "(Current)" msgstr "(ปัจจุบัน)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¹€à¸£à¸µà¸¢à¸à¸‚้à¸à¸¡à¸¹à¸¥ โปรดรà¸.." +msgid "Retrieving mirrors, please wait..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¹€à¸£à¸µà¸¢à¸à¸‚้à¸à¸¡à¸¹à¸¥ โปรดรà¸..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2468,8 +2468,8 @@ msgid "Error requesting url: " msgstr "ผิดพลาดขณะร้à¸à¸‡à¸‚à¸à¸—ี่à¸à¸¢à¸¹à¹ˆ: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸.." +msgid "Connecting to Mirror..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2477,7 +2477,7 @@ msgstr "à¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸à¸ªà¸´à¹‰à¸™à¸ªà¸¸à¸”" #: editor/export_template_manager.cpp msgid "Resolving" -msgstr "à¸à¸³à¸¥à¸±à¸‡à¸„้นหา.." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¸„้นหา..." #: editor/export_template_manager.cpp msgid "Can't Resolve" @@ -2485,8 +2485,8 @@ msgstr "ค้นหาไม่สำเร็จ" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸.." +msgid "Connecting..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¹€à¸Šà¸·à¹ˆà¸à¸¡à¸•่à¸..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2498,8 +2498,8 @@ msgstr "เชื่à¸à¸¡à¸•่à¸à¹à¸¥à¹‰à¸§" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¸£à¹‰à¸à¸‡à¸‚à¸.." +msgid "Requesting..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¸£à¹‰à¸à¸‡à¸‚à¸..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2630,12 +2630,12 @@ msgid "Collapse all" msgstr "ยุบโฟลเดà¸à¸£à¹Œ" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "เปลี่ยนชื่à¸.." +msgid "Rename..." +msgstr "เปลี่ยนชื่à¸..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "ย้ายไป.." +msgid "Move To..." +msgstr "ย้ายไป..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2646,16 +2646,16 @@ msgid "Instance" msgstr "à¸à¸´à¸™à¸ªà¹à¸•นซ์" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "à¹à¸à¹‰à¹„ขà¸à¸²à¸£à¸à¹‰à¸²à¸‡à¸à¸´à¸‡.." +msgid "Edit Dependencies..." +msgstr "à¹à¸à¹‰à¹„ขà¸à¸²à¸£à¸à¹‰à¸²à¸‡à¸à¸´à¸‡..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "ดูเจ้าขà¸à¸‡.." +msgid "View Owners..." +msgstr "ดูเจ้าขà¸à¸‡..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "ทำซ้ำ.." +msgid "Duplicate..." +msgstr "ทำซ้ำ..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2680,10 +2680,10 @@ msgstr "à¸à¸´à¸™à¸ªà¹à¸•นซ์ฉาà¸à¸—ี่เลืà¸à¸à¹ƒà¸«à¹‰à¹€ #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "à¸à¸³à¸¥à¸±à¸‡à¸ªà¹à¸à¸™à¹„ฟล์,\n" -"à¸à¸£à¸¸à¸“ารà¸.." +"à¸à¸£à¸¸à¸“ารà¸..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2748,8 +2748,8 @@ msgid "Import Scene" msgstr "นำเข้าฉาà¸" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¸™à¸³à¹€à¸‚้าฉาà¸.." +msgid "Importing Scene..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¸™à¸³à¹€à¸‚้าฉาà¸..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2760,8 +2760,8 @@ msgid "Generating for Mesh: " msgstr "สร้างสำหรับพื้นผิว: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¸£à¸±à¸™à¸ªà¸„ริปต์.." +msgid "Running Custom Script..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¸£à¸±à¸™à¸ªà¸„ริปต์..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2776,8 +2776,8 @@ msgid "Error running post-import script:" msgstr "ผิดพลาดขณะรันสคริปต์หลังนำเข้า:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¸šà¸±à¸™à¸—ึà¸.." +msgid "Saving..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¸šà¸±à¸™à¸—ึà¸..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2796,8 +2796,8 @@ msgid "Import As:" msgstr "นำเข้าเป็น:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "à¹à¸šà¸š.." +msgid "Preset..." +msgstr "à¹à¸šà¸š..." #: editor/import_dock.cpp msgid "Reimport" @@ -3214,16 +3214,16 @@ msgid "Transition Node" msgstr "โหนดทรานสิชัน" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "นำเข้าà¹à¸à¸™à¸´à¹€à¸¡à¸Šà¸±à¸™.." +msgid "Import Animations..." +msgstr "นำเข้าà¹à¸à¸™à¸´à¹€à¸¡à¸Šà¸±à¸™..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "à¹à¸à¹‰à¹„ขตัวà¸à¸£à¸à¸‡à¹‚หนด" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "ตัวà¸à¸£à¸à¸‡.." +msgid "Filters..." +msgstr "ตัวà¸à¸£à¸à¸‡..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3290,8 +3290,8 @@ msgid "Fetching:" msgstr "à¸à¸³à¸¥à¸±à¸‡à¸£à¸±à¸šà¸‚้à¸à¸¡à¸¹à¸¥:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "à¸à¸³à¸¥à¸±à¸‡à¸„้นหา.." +msgid "Resolving..." +msgstr "à¸à¸³à¸¥à¸±à¸‡à¸„้นหา..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3357,8 +3357,8 @@ msgid "Site:" msgstr "ไซต์:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™.." +msgid "Support..." +msgstr "à¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3549,6 +3549,7 @@ msgid "Use Rotation Snap" msgstr "จำà¸à¸±à¸”à¸à¸²à¸£à¸«à¸¡à¸¸à¸™" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "ตั้งค่าà¸à¸²à¸£à¸ˆà¸³à¸à¸±à¸”..." @@ -3976,8 +3977,8 @@ msgid "Create Convex Collision Sibling" msgstr "สร้างรูปทรงตันà¸à¸²à¸¢à¸ าพเป็นโหนดà¸à¸²à¸•ิ" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "สร้างเส้นขà¸à¸š Mesh.." +msgid "Create Outline Mesh..." +msgstr "สร้างเส้นขà¸à¸š Mesh..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4181,8 +4182,8 @@ msgid "Error loading image:" msgstr "ผิดพลาดขณะโหลดรูป:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "รูปไม่มีพิà¸à¹€à¸‹à¸¥à¹ƒà¸”ที่ความโปร่งà¹à¸ªà¸‡ > 128 .." +msgid "No pixels with transparency > 128 in image..." +msgstr "รูปไม่มีพิà¸à¹€à¸‹à¸¥à¹ƒà¸”ที่ความโปร่งà¹à¸ªà¸‡ > 128 ..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4542,7 +4543,7 @@ msgid "Import Theme" msgstr "นำเข้าธีม" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "บันทึà¸à¸˜à¸µà¸¡à¹€à¸›à¹‡à¸™" #: editor/plugins/script_editor_plugin.cpp @@ -4639,8 +4640,8 @@ msgstr "เปิด/ปิดà¹à¸œà¸‡à¸ªà¸„ริปต์" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "ค้นหา.." +msgid "Find..." +msgstr "ค้นหา..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4847,16 +4848,16 @@ msgid "Find Previous" msgstr "ค้นหาà¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "à¹à¸—นที่.." +msgid "Replace..." +msgstr "à¹à¸—นที่..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "ไปยังฟังà¸à¹Œà¸Šà¸±à¸™.." +msgid "Goto Function..." +msgstr "ไปยังฟังà¸à¹Œà¸Šà¸±à¸™..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "ไปยังบรรทัด.." +msgid "Goto Line..." +msgstr "ไปยังบรรทัด..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5309,12 +5310,8 @@ msgid "Transform" msgstr "เคลื่à¸à¸™à¸¢à¹‰à¸²à¸¢" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "ตั้งค่าà¸à¸²à¸£à¸ˆà¸³à¸à¸±à¸”.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "เครื่à¸à¸‡à¸¡à¸·à¸à¹€à¸„ลื่à¸à¸™à¸¢à¹‰à¸²à¸¢.." +msgid "Transform Dialog..." +msgstr "เครื่à¸à¸‡à¸¡à¸·à¸à¹€à¸„ลื่à¸à¸™à¸¢à¹‰à¸²à¸¢..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5566,8 +5563,8 @@ msgid "Remove All" msgstr "ลบทั้งหมด" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "à¹à¸à¹‰à¹„ขธีม.." +msgid "Edit theme..." +msgstr "à¹à¸à¹‰à¹„ขธีม..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5636,7 +5633,8 @@ msgid "Options" msgstr "ตัวเลืà¸à¸" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +#, fuzzy +msgid "Has,Many,Options" msgstr "มี,มาà¸à¸¡à¸²à¸¢,หลาย,ตัวเลืà¸à¸!" #: editor/plugins/theme_editor_plugin.cpp @@ -5826,8 +5824,8 @@ msgid "Presets" msgstr "à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸à¸à¸" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "เพิ่ม.." +msgid "Add..." +msgstr "เพิ่ม..." #: editor/project_export.cpp msgid "Resources" @@ -5916,6 +5914,11 @@ msgid "Imported Project" msgstr "นำเข้าโปรเจà¸à¸•์à¹à¸¥à¹‰à¸§" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "ชื่à¸à¹‚ปรเจà¸à¸•์:" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "ไม่สามารถสร้างโฟลเดà¸à¸£à¹Œ" @@ -6111,8 +6114,8 @@ msgstr "ปุ่มเมาส์" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6140,8 +6143,8 @@ msgid "Control+" msgstr "Control+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "à¸à¸”ปุ่ม.." +msgid "Press a Key..." +msgstr "à¸à¸”ปุ่ม..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6324,8 +6327,8 @@ msgid "Property:" msgstr "คุณสมบัติ:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "à¸à¸³à¸«à¸™à¸”เฉพาะ.." +msgid "Override For..." +msgstr "à¸à¸³à¸«à¸™à¸”เฉพาะ..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6420,12 +6423,12 @@ msgid "Easing Out-In" msgstr "à¸à¸à¸-เข้านุ่มนวล" #: editor/property_editor.cpp -msgid "File.." -msgstr "ไฟล์.." +msgid "File..." +msgstr "ไฟล์..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "โฟลเดà¸à¸£à¹Œ.." +msgid "Dir..." +msgstr "โฟลเดà¸à¸£à¹Œ..." #: editor/property_editor.cpp msgid "Assign" @@ -6595,8 +6598,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "ทำà¸à¸±à¸šà¸‰à¸²à¸à¸—ี่เป็นà¸à¸´à¸™à¸ªà¹à¸•นซ์ไม่ได้" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "บันทึà¸à¸‰à¸²à¸à¹ƒà¸«à¸¡à¹ˆà¹€à¸›à¹‡à¸™.." +msgid "Save New Scene As..." +msgstr "บันทึà¸à¸‰à¸²à¸à¹ƒà¸«à¸¡à¹ˆà¹€à¸›à¹‡à¸™..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -8074,6 +8077,13 @@ msgstr "ผิดพลาดขณะโหลดฟà¸à¸™à¸•์" msgid "Invalid font size." msgstr "ขนาดฟà¸à¸™à¸•์ผิดพลาด" +#, fuzzy +#~ msgid "Previous" +#~ msgstr "à¹à¸—็บà¸à¹ˆà¸à¸™à¸«à¸™à¹‰à¸²" + +#~ msgid "Next" +#~ msgstr "ต่à¸à¹„ป" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "ใช้ชื่à¸à¸™à¸µà¹‰à¹„ม่ได้ (มี '/' หรืภ':')" @@ -8097,9 +8107,6 @@ msgstr "ขนาดฟà¸à¸™à¸•์ผิดพลาด" #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "ไม่พบไฟล์ project.godot" -#~ msgid "Next" -#~ msgstr "ต่à¸à¹„ป" - #~ msgid "Not found!" #~ msgstr "ไม่พบ!" @@ -8242,8 +8249,8 @@ msgstr "ขนาดฟà¸à¸™à¸•์ผิดพลาด" #~ msgid "Exporting for %s" #~ msgstr "ส่งà¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸š %s" -#~ msgid "Setting Up.." -#~ msgstr "à¸à¸³à¸¥à¸±à¸‡à¸•ั้งค่า.." +#~ msgid "Setting Up..." +#~ msgstr "à¸à¸³à¸¥à¸±à¸‡à¸•ั้งค่า..." #~ msgid "Error loading scene." #~ msgstr "ผิดพลาดขณะโหลดฉาà¸" @@ -8303,8 +8310,8 @@ msgstr "ขนาดฟà¸à¸™à¸•์ผิดพลาด" #~ msgid "Info" #~ msgstr "ข้à¸à¸¡à¸¹à¸¥" -#~ msgid "Re-Import.." -#~ msgstr "นำเข้าà¸à¸µà¸à¸„รั้ง.." +#~ msgid "Re-Import..." +#~ msgstr "นำเข้าà¸à¸µà¸à¸„รั้ง..." #~ msgid "No bit masks to import!" #~ msgstr "ไม่มีบิตà¹à¸¡à¸ªà¸à¹Œà¹ƒà¸«à¹‰à¸™à¸³à¹€à¸‚้า!" @@ -8684,14 +8691,14 @@ msgstr "ขนาดฟà¸à¸™à¸•์ผิดพลาด" #~ msgid "Zoom (%):" #~ msgstr "ซูม (%):" -#~ msgid "Skeleton.." -#~ msgstr "โครงà¸à¸£à¸°à¸”ูà¸.." +#~ msgid "Skeleton..." +#~ msgstr "โครงà¸à¸£à¸°à¸”ูà¸..." #~ msgid "Zoom Reset" #~ msgstr "รีเซ็ตà¸à¸²à¸£à¸‹à¸¹à¸¡" -#~ msgid "Zoom Set.." -#~ msgstr "ตั้งค่าà¸à¸²à¸£à¸‹à¸¹à¸¡.." +#~ msgid "Zoom Set..." +#~ msgstr "ตั้งค่าà¸à¸²à¸£à¸‹à¸¹à¸¡..." #~ msgid "Set a Value" #~ msgstr "เซ็ตค่า" @@ -9105,8 +9112,8 @@ msgstr "ขนาดฟà¸à¸™à¸•์ผิดพลาด" #~ msgid "Export Project PCK" #~ msgstr "ส่งà¸à¸à¸ PCK โปรเจà¸à¸•์" -#~ msgid "Export.." -#~ msgstr "ส่งà¸à¸à¸.." +#~ msgid "Export..." +#~ msgstr "ส่งà¸à¸à¸..." #~ msgid "Project Export" #~ msgstr "ส่งà¸à¸à¸à¹‚ปรเจà¸à¸•์" diff --git a/editor/translations/tr.po b/editor/translations/tr.po index 5e4a18ce28..292cec4063 100644 --- a/editor/translations/tr.po +++ b/editor/translations/tr.po @@ -2,32 +2,33 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Aprın Çor Tigin <kabusturk38@gmail.com>, 2016-2017. +# Aykut YILDIRIM <aykutyildirim@windowslive.com>, 2018. # Ceyhun Can Ulker <ceyhuncanu@gmail.com>, 2016. # Enes Kaya Öcal <ekayaocal@hotmail.com>, 2016. # Enescan Yerlikaya <enescanyerlikaya@gmail.com>, 2017. # Fatih Mert DoÄŸancan <fatihmertdogancan@hotmail.com>, 2017. # hubbyist <hub@legrud.net>, 2017. # H.Hüseyin CİHANGİR <hashusfb@gmail.com>, 2018. +# Kaan Gül <qaantum@hotmail.com>, 2018. # M. Yavuz Uzun <myavuzuzun@yandex.com>, 2016. +# monolifed <monolifed@gmail.com>, 2018. # Orkun Turan <holygatestudio@yandex.com>, 2016-2017. # razah <icnikerazah@gmail.com>, 2017-2018. # stnmycri <satenmeycri@gmail.com>, 2017-2018. # Yavuz Günay <yavuzgunay@gmail.com>, 2017. -# msgid "" msgstr "" "Project-Id-Version: Godot Engine editor\n" -"PO-Revision-Date: 2018-04-19 12:41+0000\n" -"Last-Translator: H.Hüseyin CİHANGİR <hashusfb@gmail.com>\n" +"PO-Revision-Date: 2018-06-10 09:46+0000\n" +"Last-Translator: Aykut YILDIRIM <aykutyildirim@windowslive.com>\n" "Language-Team: Turkish <https://hosted.weblate.org/projects/godot-engine/" "godot/tr/>\n" "Language: tr\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8-bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 3.0-dev\n" +"X-Generator: Weblate 3.0.1-dev\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -509,7 +510,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "Åžunun: '%s' ÅŸununla: '%s' baÄŸlantısını kes" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "BaÄŸlan..." #: editor/connections_dialog.cpp @@ -929,12 +930,12 @@ msgid "Move Audio Bus" msgstr "Audio Bus'ı Taşı" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Audio Bus YerleÅŸim Düzenini Farklı Kaydet.." +msgid "Save Audio Bus Layout As..." +msgstr "Audio Bus YerleÅŸim Düzenini Farklı Kaydet..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Yeni YerleÅŸim Düzeni için Konum.." +msgid "Location for New Layout..." +msgstr "Yeni YerleÅŸim Düzeni için Konum..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -1069,12 +1070,12 @@ msgid "Updating Scene" msgstr "Sahne Güncelleniyor" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Yerel deÄŸiÅŸiklikler kayıt ediliyor.." +msgid "Storing local changes..." +msgstr "Yerel deÄŸiÅŸiklikler kayıt ediliyor..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "Sahne güncelleniyor.." +msgid "Updating scene..." +msgstr "Sahne güncelleniyor..." #: editor/editor_data.cpp msgid "[empty]" @@ -1142,8 +1143,8 @@ msgid "Show In File Manager" msgstr "Dosya Yöneticisinde Göster" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Yeni Klasör.." +msgid "New Folder..." +msgstr "Yeni Klasör..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1404,20 +1405,20 @@ msgstr "Çıktıyı Temizle" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Proje dışa aktarımı %d hata koduyla baÅŸarısız." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Kaynak kaydedilirken hata!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Kaynağı Farklı Kaydet.." +msgid "Save Resource As..." +msgstr "Kaynağı Farklı Kaydet..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Anlıyorum.." +msgid "I see..." +msgstr "Anlıyorum..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1648,12 +1649,12 @@ msgid "Open Base Scene" msgstr "Ana Sahneyi Aç" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Sahneyi Hızlı Aç.." +msgid "Quick Open Scene..." +msgstr "Sahneyi Hızlı Aç..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "BetiÄŸi Hızlı Aç.." +msgid "Quick Open Script..." +msgstr "BetiÄŸi Hızlı Aç..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1664,8 +1665,8 @@ msgid "Save changes to '%s' before closing?" msgstr "Kapatmadan önce deÄŸiÅŸklikler buraya '%s' kaydedilsin mi?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Sahneyi Farklı Kaydet.." +msgid "Save Scene As..." +msgstr "Sahneyi Farklı Kaydet..." #: editor/editor_node.cpp msgid "No" @@ -1716,8 +1717,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Bu eylem geri alınamaz. Yine de geri dönsün mü?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Sahneyi Hızlı Çalıştır.." +msgid "Quick Run Scene..." +msgstr "Sahneyi Hızlı Çalıştır..." #: editor/editor_node.cpp msgid "Quit" @@ -1872,8 +1873,8 @@ msgid "Previous tab" msgstr "Önceki sekme" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "Dosyaları Süz.." +msgid "Filter Files..." +msgstr "Dosyaları Süz..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1884,12 +1885,12 @@ msgid "New Scene" msgstr "Yeni Sahne" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Yeni Miras Alınmış Sahne .." +msgid "New Inherited Scene..." +msgstr "Yeni Miras Alınmış Sahne ..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Sahne Aç.." +msgid "Open Scene..." +msgstr "Sahne Aç..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1908,16 +1909,16 @@ msgid "Open Recent" msgstr "En Sonuncuyu Aç" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Åžuna Dönüştür.." +msgid "Convert To..." +msgstr "Åžuna Dönüştür..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary .." +msgid "MeshLibrary..." +msgstr "MeshLibrary ..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet .." +msgid "TileSet..." +msgstr "TileSet ..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1966,9 +1967,8 @@ msgid "Debug" msgstr "Hata Ayıklama" #: editor/editor_node.cpp -#, fuzzy msgid "Deploy with Remote Debug" -msgstr "Uzaktan Hata Ayıklama ile Aç" +msgstr "Uzaktan Hata Ayıklama ile Dağıt" #: editor/editor_node.cpp msgid "" @@ -2181,8 +2181,8 @@ msgid "Save the currently edited resource." msgstr "Düzenlenen kaynağı kaydedin." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "Farklı Kaydet.." +msgid "Save As..." +msgstr "Farklı Kaydet..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2290,8 +2290,8 @@ msgid "Creating Mesh Previews" msgstr "Mesh Önizlemeleri OluÅŸturuluyor" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Küçük Resim.." +msgid "Thumbnail..." +msgstr "Küçük Resim..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2443,8 +2443,8 @@ msgid "(Current)" msgstr "(Åžuanki)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "Aynalar alınıyor, lütfen bekleyin.." +msgid "Retrieving mirrors, please wait..." +msgstr "Aynalar alınıyor, lütfen bekleyin..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" @@ -2521,8 +2521,8 @@ msgid "Error requesting url: " msgstr "Url isteÄŸi hatası: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "Aynaya baÄŸlanılıyor.." +msgid "Connecting to Mirror..." +msgstr "Aynaya baÄŸlanılıyor..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2538,8 +2538,8 @@ msgstr "Çözümlenemedi" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "BaÄŸlanılıyor.." +msgid "Connecting..." +msgstr "BaÄŸlanılıyor..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2551,8 +2551,8 @@ msgstr "BaÄŸlı" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." -msgstr "İsteniyor.." +msgid "Requesting..." +msgstr "İsteniyor..." #: editor/export_template_manager.cpp msgid "Downloading" @@ -2687,12 +2687,12 @@ msgid "Collapse all" msgstr "Hepsini daralt" #: editor/filesystem_dock.cpp -msgid "Rename.." -msgstr "Yeniden Adlandır.." +msgid "Rename..." +msgstr "Yeniden Adlandır..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "Åžuraya Taşı.." +msgid "Move To..." +msgstr "Åžuraya Taşı..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2703,16 +2703,16 @@ msgid "Instance" msgstr "Örnek" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Bağımlılıkları Düzenle.." +msgid "Edit Dependencies..." +msgstr "Bağımlılıkları Düzenle..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Sahipleri Görüntüle.." +msgid "View Owners..." +msgstr "Sahipleri Görüntüle..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "ÇoÄŸalt.." +msgid "Duplicate..." +msgstr "ÇoÄŸalt..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2737,10 +2737,10 @@ msgstr "Seçilen sahneyi/sahneleri seçilen düğüme çocuk olarak örneklendir #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Dosyalar Taranıyor,\n" -"Lütfen Bekleyiniz.." +"Lütfen Bekleyiniz..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2805,7 +2805,7 @@ msgid "Import Scene" msgstr "Sahneyi İçe Aktar" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "Sahneyi İçe Aktarıyor..." #: editor/import/resource_importer_scene.cpp @@ -2817,8 +2817,8 @@ msgid "Generating for Mesh: " msgstr "Örüntü için Üretiliyor: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "Çalışan Özel Betik.." +msgid "Running Custom Script..." +msgstr "Çalışan Özel Betik..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2835,7 +2835,7 @@ msgid "Error running post-import script:" msgstr "sonradan-içe aktarılmış betik çalıştırılırken hata:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "Kaydediliyor..." #: editor/import_dock.cpp @@ -2855,8 +2855,8 @@ msgid "Import As:" msgstr "Åžu Åžekilde İçe Aktar:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Ön ayar.." +msgid "Preset..." +msgstr "Ön ayar..." #: editor/import_dock.cpp msgid "Reimport" @@ -3273,15 +3273,15 @@ msgid "Transition Node" msgstr "GeçiÅŸ Düğümü" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Animasyonları İçe Aktar.." +msgid "Import Animations..." +msgstr "Animasyonları İçe Aktar..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Düğüm Süzgeçlerini Düzenle" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Süzgeçler..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3349,7 +3349,7 @@ msgid "Fetching:" msgstr "Alınıyor:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "Çözümleniyor..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3416,7 +3416,7 @@ msgid "Site:" msgstr "Yer:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Destek..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3614,6 +3614,7 @@ msgid "Use Rotation Snap" msgstr "Döndürme Yapışması Kullan" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "Yapışmayı Yapılandır..." @@ -3710,14 +3711,12 @@ msgid "Show Guides" msgstr "Kılavuzları göster" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "BaÅŸlatım Görünümü" +msgstr "BaÅŸlatımı Göster" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 Görüntükapısı" +msgstr "Görüntükapısını Göster" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3773,7 +3772,7 @@ msgstr "Ekle %s" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Adding %s..." -msgstr "Ekliyor %s.." +msgstr "Ekliyor %s..." #: editor/plugins/canvas_item_editor_plugin.cpp editor/scene_tree_dock.cpp msgid "Ok" @@ -4010,7 +4009,7 @@ msgstr "Örüntü anahat oluÅŸturmak için bir yüzeye sahip deÄŸil!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Örüntü ilkel türü PRIMITIVE_TRIANGLES deÄŸil!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4041,8 +4040,8 @@ msgid "Create Convex Collision Sibling" msgstr "Dışbükey Çarpışma KardeÅŸi OluÅŸtur" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Anahat Örüntüsü OluÅŸtur.." +msgid "Create Outline Mesh..." +msgstr "Anahat Örüntüsü OluÅŸtur..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4246,8 +4245,8 @@ msgid "Error loading image:" msgstr "Resim yüklenirken hata:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Saydamlığı olan nokta yok > 128 bedizde.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Saydamlığı olan nokta yok > 128 bedizde..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4607,8 +4606,8 @@ msgid "Import Theme" msgstr "Kalıbı İçe Aktar" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "Temayı Farklı Kaydet.." +msgid "Save Theme As..." +msgstr "Temayı Farklı Kaydet..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4704,8 +4703,8 @@ msgstr "Betikler Panelini Aç/Kapa" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Bul.." +msgid "Find..." +msgstr "Bul..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4914,16 +4913,16 @@ msgid "Find Previous" msgstr "Öncekini Bul" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "DeÄŸiÅŸtir.." +msgid "Replace..." +msgstr "DeÄŸiÅŸtir..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "İşleve Git.." +msgid "Goto Function..." +msgstr "İşleve Git..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Dizeye Git.." +msgid "Goto Line..." +msgstr "Dizeye Git..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5376,12 +5375,8 @@ msgid "Transform" msgstr "Dönüşüm" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Yapışmayı Yapılandır.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "Dönüştürme İletiÅŸim Kutusu.." +msgid "Transform Dialog..." +msgstr "Dönüştürme İletiÅŸim Kutusu..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5633,8 +5628,8 @@ msgid "Remove All" msgstr "Tümünü Kaldır" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "Tema düzenle.." +msgid "Edit theme..." +msgstr "Tema düzenle..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5681,14 +5676,12 @@ msgid "Checked Item" msgstr "Denetlenen Öğe" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Öğe Ekle" +msgstr "Radyo Ögesi" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Denetlenen Öğe" +msgstr "Seçili Radyo Ögesi" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5703,7 +5696,8 @@ msgid "Options" msgstr "Seçenekler" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +#, fuzzy +msgid "Has,Many,Options" msgstr "Bir Çok,Seçenek,Var!" #: editor/plugins/theme_editor_plugin.cpp @@ -5895,8 +5889,8 @@ msgid "Presets" msgstr "Önayarlar" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "Ekle.." +msgid "Add..." +msgstr "Ekle..." #: editor/project_export.cpp msgid "Resources" @@ -5989,6 +5983,10 @@ msgid "Imported Project" msgstr "İçe Aktarılan Proje" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Geçersiz Proje Adı." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Klasör oluÅŸturulamadı." @@ -6188,9 +6186,10 @@ msgstr "Fare Düğmesi" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Geçersiz iÅŸlem adı. BoÅŸ olamaz ve '/', ':', '=', '\\' veya '\"' içeremez." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6217,8 +6216,8 @@ msgid "Control+" msgstr "Denetim+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "Bir Dokunaca Basın.." +msgid "Press a Key..." +msgstr "Bir Dokunaca Basın..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6401,8 +6400,8 @@ msgid "Property:" msgstr "Özellik:" #: editor/project_settings_editor.cpp -msgid "Override For.." -msgstr "Åžunun Üzerine Yaz.." +msgid "Override For..." +msgstr "Åžunun Üzerine Yaz..." #: editor/project_settings_editor.cpp msgid "Input Map" @@ -6497,12 +6496,12 @@ msgid "Easing Out-In" msgstr "Kararma Açılma" #: editor/property_editor.cpp -msgid "File.." -msgstr "Dosya.." +msgid "File..." +msgstr "Dosya..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "Diz.." +msgid "Dir..." +msgstr "Diz..." #: editor/property_editor.cpp msgid "Assign" @@ -6674,8 +6673,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "Bu iÅŸlem örneklenmiÅŸ sahnelerde yapılamaz." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "Yeni Sahneyi Farklı Kaydet .." +msgid "Save New Scene As..." +msgstr "Yeni Sahneyi Farklı Kaydet ..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7389,11 +7388,11 @@ msgstr "Uzaklık Seç:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Sınıf ismi ayrılmış anahtar kelime olamaz" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." -msgstr "solü oluÅŸturuluyor..." +msgstr "Çözüm oluÅŸturuluyor..." #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating C# project..." @@ -7420,9 +7419,8 @@ msgid "Mono" msgstr "Tekli" #: modules/mono/editor/godotsharp_editor.cpp -#, fuzzy msgid "About C# support" -msgstr "C# hakkında destek" +msgstr "C# desteÄŸi hakkında" #: modules/mono/editor/godotsharp_editor.cpp msgid "Create C# solution" @@ -7442,7 +7440,7 @@ msgstr "Uyarılar" #: modules/mono/mono_gd/gd_mono_utils.cpp msgid "End of inner exception stack trace" -msgstr "" +msgstr "İç özel durum yığını izlemesinin sonu" #: modules/visual_script/visual_script.cpp msgid "" @@ -7995,12 +7993,11 @@ msgstr "ARVROrigin bir ARVRCamera çocuk düğümü gerektirir" #: scene/3d/baked_lightmap.cpp msgid "%d%%" -msgstr "" +msgstr "%d%%" #: scene/3d/baked_lightmap.cpp -#, fuzzy msgid "(Time Left: %d:%02d s)" -msgstr "(Kalan Zaman:%d:%02d s)" +msgstr "(Kalan Zaman:%d:%02d sn)" #: scene/3d/baked_lightmap.cpp msgid "Plotting Meshes: " @@ -8102,7 +8099,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment bir Environment kaynağı gerektirir." #: scene/3d/scenario_fx.cpp msgid "" @@ -8116,6 +8113,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Bu WorldEnvironment yoksayıldı. (3B sahneler için) Bir Kamera ekleyin veya " +"(2B sahneler için) bu ortamın Arkaplan Kipini Canvas olarak ayarlayın." #: scene/3d/sprite_3d.cpp msgid "" @@ -8213,6 +8212,13 @@ msgstr "Yazıtipi yükleme hatası." msgid "Invalid font size." msgstr "Geçersiz yazıtipi boyutu." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Önceki sekme" + +#~ msgid "Next" +#~ msgstr "Sonraki" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Geçersiz iÅŸlem (her ÅŸey ancak ÅŸu '/' ya da ÅŸuna ':' gider)." @@ -8238,9 +8244,6 @@ msgstr "Geçersiz yazıtipi boyutu." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Proje yolunda proje.godot alınamadı." -#~ msgid "Next" -#~ msgstr "Sonraki" - #~ msgid "Not found!" #~ msgstr "Bulunamadı!" @@ -8380,7 +8383,7 @@ msgstr "Geçersiz yazıtipi boyutu." #~ msgid "Exporting for %s" #~ msgstr "%s için Dışa Aktarım" -#~ msgid "Setting Up.." +#~ msgid "Setting Up..." #~ msgstr "Kurulum..." #~ msgid "Error loading scene." @@ -8435,8 +8438,8 @@ msgstr "Geçersiz yazıtipi boyutu." #~ msgid "Info" #~ msgstr "Bilgi" -#~ msgid "Re-Import.." -#~ msgstr "Yeniden İçe Aktar.." +#~ msgid "Re-Import..." +#~ msgstr "Yeniden İçe Aktar..." #~ msgid "No bit masks to import!" #~ msgstr "Alınacak hiç bit örteci yok!" @@ -8832,14 +8835,14 @@ msgstr "Geçersiz yazıtipi boyutu." #~ msgid "Zoom (%):" #~ msgstr "YaklaÅŸ (%):" -#~ msgid "Skeleton.." -#~ msgstr "İskelet.." +#~ msgid "Skeleton..." +#~ msgstr "İskelet..." #~ msgid "Zoom Reset" #~ msgstr "YakınlaÅŸmayı Sıfırla" -#~ msgid "Zoom Set.." -#~ msgstr "YakınlaÅŸmayı Ayarla.." +#~ msgid "Zoom Set..." +#~ msgstr "YakınlaÅŸmayı Ayarla..." #~ msgid "Set a Value" #~ msgstr "Bir DeÄŸer Ata" @@ -8980,7 +8983,7 @@ msgstr "Geçersiz yazıtipi boyutu." #~ "Download and install export templates." #~ msgstr "" #~ "Hiçbir dışa aktarım kalıbı bulunamadı.\n" -#~ "Dışa aktarım kalıplarını indirin ve yükleyin.." +#~ "Dışa aktarım kalıplarını indirin ve yükleyin..." #~ msgid "Custom debug package not found." #~ msgstr "Özel kusur ayıklama çıkını bulunmadı." @@ -9303,8 +9306,8 @@ msgstr "Geçersiz yazıtipi boyutu." #~ msgid "Export Project PCK" #~ msgstr "Tasarı PCK Dışa Aktar" -#~ msgid "Export.." -#~ msgstr "Dışa Aktar.." +#~ msgid "Export..." +#~ msgstr "Dışa Aktar..." #~ msgid "Project Export" #~ msgstr "Tasarı Dışa Aktar" diff --git a/editor/translations/uk.po b/editor/translations/uk.po index 45138cd5de..067c7be724 100644 --- a/editor/translations/uk.po +++ b/editor/translations/uk.po @@ -2,7 +2,6 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # Aleksandr <XpycT.TOP@gmail.com>, 2017. # Yuri Chornoivan <yurchor@ukr.net>, 2018. # Ðндрій Бандура <andriykopanytsia@gmail.com>, 2018. @@ -10,11 +9,10 @@ # МакÑим Якимчук <xpinovo@gmail.com>, 2018. # ÐœÐ°Ñ€Ñ Ð¯Ð¼Ð±Ð°Ñ€ <mjambarmeta@gmail.com>, 2017-2018. # ОлекÑандр Пилипчук <pilipchukap@rambler.ru>, 2018. -# msgid "" msgstr "" "Project-Id-Version: Ukrainian (Godot Engine)\n" -"PO-Revision-Date: 2018-04-20 18:42+0000\n" +"PO-Revision-Date: 2018-06-06 04:03+0000\n" "Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n" "Language-Team: Ukrainian <https://hosted.weblate.org/projects/godot-engine/" "godot/uk/>\n" @@ -23,7 +21,7 @@ msgstr "" "Content-Transfer-Encoding: 8-bit\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.0-dev\n" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -345,7 +343,7 @@ msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… анімації" #: editor/animation_editor.cpp msgid "Clean-Up Animation(s) (NO UNDO!)" -msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð°Ð½Ñ–Ð¼Ð°Ñ†Ñ–Ñ—(Ñ–) (не ÑкаÑувати!)" +msgstr "ОчиÑтити анімацію(Ñ—) (ÐЕ СКÐСУВÐТИ!)" #: editor/animation_editor.cpp msgid "Clean-Up" @@ -492,7 +490,7 @@ msgstr "З'єднати" #: editor/connections_dialog.cpp msgid "Connect '%s' to '%s'" -msgstr "З'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ '%s' Ð´Ð»Ñ %s'" +msgstr "Приєднати '%s' до %s'" #: editor/connections_dialog.cpp msgid "Connecting Signal:" @@ -503,8 +501,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "Від'єднати '%s' від '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "Приєднати.." +msgid "Connect..." +msgstr "Приєднати..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -666,7 +664,7 @@ msgstr "Помилки завантаженнÑ!" #: editor/dependency_editor.cpp msgid "Permanently delete %d item(s)? (No undo!)" -msgstr "ОÑтаточно вилучити %d об'єкти (неможливо ÑкаÑувати)" +msgstr "ОÑтаточно вилучити %d об'єкт(и)? (Ðеможливо ÑкаÑувати)" #: editor/dependency_editor.cpp msgid "Owns" @@ -706,7 +704,7 @@ msgstr "СпаÑибі від Ñпільноти Godot!" #: editor/editor_about.cpp msgid "Thanks!" -msgstr "ДÑкую!" +msgstr "ПодÑка!" #: editor/editor_about.cpp msgid "Godot Engine contributors" @@ -776,7 +774,7 @@ msgid "" "respective copyright statements and license terms." msgstr "" "Рушій Godot ÑпираєтьÑÑ Ð½Ð° Ñ€Ñд Ñторонніх безкоштовних Ñ– відкритих бібліотек, " -"ÑуміÑних з умовами ліцензії mit. Ðижче наводитьÑÑ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð½Ð¸Ð¹ ÑпиÑок вÑÑ–Ñ… " +"ÑуміÑних з умовами ліцензії MIT. Ðижче наводитьÑÑ Ð²Ð¸Ñ‡ÐµÑ€Ð¿Ð½Ð¸Ð¹ ÑпиÑок вÑÑ–Ñ… " "таких Ñторонніх компонентів з відповідними заÑвами авторÑьких прав Ñ– умов " "ліцензійної угоди." @@ -824,7 +822,7 @@ msgstr "Динаміки" #: editor/editor_audio_buses.cpp msgid "Add Effect" -msgstr "Додати ефект" +msgstr "Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ ÐµÑ„ÐµÐºÑ‚Ñƒ" #: editor/editor_audio_buses.cpp msgid "Rename Audio Bus" @@ -852,7 +850,7 @@ msgstr "Вибір передачі аудіо шини" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus Effect" -msgstr "Додати ефект аудіо шини" +msgstr "Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ ÐµÑ„ÐµÐºÑ‚Ñƒ аудіо шини" #: editor/editor_audio_buses.cpp msgid "Move Bus Effect" @@ -897,11 +895,11 @@ msgstr "Видалити ефект" #: editor/editor_audio_buses.cpp msgid "Audio" -msgstr "Звук" +msgstr "Ðудіо" #: editor/editor_audio_buses.cpp msgid "Add Audio Bus" -msgstr "Додати аудіо шину" +msgstr "Ð”Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð°ÑƒÐ´Ñ–Ð¾ шини" #: editor/editor_audio_buses.cpp msgid "Master bus can't be deleted!" @@ -924,16 +922,16 @@ msgid "Move Audio Bus" msgstr "ПереміÑтити аудіо шину" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "Зберегти макет аудіо шини Ñк.." +msgid "Save Audio Bus Layout As..." +msgstr "Зберегти ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÑƒÐ´Ñ–Ð¾ шини Ñк..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "Ð Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ макета..." +msgid "Location for New Layout..." +msgstr "Ð Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ компонуваннÑ..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" -msgstr "Відкрити макет аудіо шини" +msgstr "Відкрити ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÑƒÐ´Ñ–Ð¾ шини" #: editor/editor_audio_buses.cpp msgid "There is no 'res://default_bus_layout.tres' file." @@ -941,7 +939,7 @@ msgstr "Файл 'res: //default_bus_layout.tres' не знайдено." #: editor/editor_audio_buses.cpp msgid "Invalid file, not an audio bus layout." -msgstr "ÐеприпуÑтимий файл, це не макет аудіо-шини." +msgstr "ÐеприпуÑтимий файл, це не ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÑƒÐ´Ñ–Ð¾-шини." #: editor/editor_audio_buses.cpp msgid "Add Bus" @@ -949,7 +947,7 @@ msgstr "Додати шину" #: editor/editor_audio_buses.cpp msgid "Create a new Bus Layout." -msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ макету шини." +msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÐ¸Ð½Ð¸." #: editor/editor_audio_buses.cpp editor/property_editor.cpp #: editor/script_create_dialog.cpp @@ -958,16 +956,16 @@ msgstr "Завантажити" #: editor/editor_audio_buses.cpp msgid "Load an existing Bus Layout." -msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ–Ñнуючого макета шини." +msgstr "Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ–Ñнуючого ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÐ¸Ð½Ð¸." #: editor/editor_audio_buses.cpp #: editor/plugins/animation_player_editor_plugin.cpp msgid "Save As" -msgstr "Зберегти Як" +msgstr "Зберегти Ñк" #: editor/editor_audio_buses.cpp msgid "Save this Bus Layout to a file." -msgstr "Зберегти цей макет шини у файлі." +msgstr "Зберегти це ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÐ¸Ð½Ð¸ у файлі." #: editor/editor_audio_buses.cpp editor/import_dock.cpp msgid "Load Default" @@ -975,7 +973,7 @@ msgstr "Завантажити типовий" #: editor/editor_audio_buses.cpp msgid "Load the default Bus Layout." -msgstr "Завантажити типовий макет шини." +msgstr "Завантажити типове ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÐ¸Ð½Ð¸." #: editor/editor_autoload_settings.cpp msgid "Invalid name." @@ -998,7 +996,7 @@ msgstr "" #: editor/editor_autoload_settings.cpp msgid "Invalid name. Must not collide with an existing global constant name." msgstr "" -"Ðеправильне ім'Ñ. Ðе повинно збігатиÑÑŒ з іменем Ñ–Ñнуючої глобальної " +"ÐеприпуÑтиме ім'Ñ. Ðе повинно збігатиÑÑŒ з іменем Ñ–Ñнуючої глобальної " "конÑтанти." #: editor/editor_autoload_settings.cpp @@ -1068,12 +1066,12 @@ msgid "Updating Scene" msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñцени" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "Ð—Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¸Ñ… змін.." +msgid "Storing local changes..." +msgstr "Ð—Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¸Ñ… змін..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñцени.." +msgid "Updating scene..." +msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñцени..." #: editor/editor_data.cpp msgid "[empty]" @@ -1141,8 +1139,8 @@ msgid "Show In File Manager" msgstr "Показати в файловому менеджері" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "Створити теку.." +msgid "New Folder..." +msgstr "Створити теку..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1154,7 +1152,7 @@ msgstr "УÑе розпізнано" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "All Files (*)" -msgstr "УÑÑ– фали (*)" +msgstr "УÑÑ– файли (*)" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Open a File" @@ -1180,11 +1178,11 @@ msgstr "Зберегти" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Save a File" -msgstr "Ð—Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ" +msgstr "Зберегти файл" #: editor/editor_file_dialog.cpp msgid "Go Back" -msgstr "ПовертатиÑÑ" +msgstr "ПовернутиÑÑ Ð½Ð°Ð·Ð°Ð´" #: editor/editor_file_dialog.cpp msgid "Go Forward" @@ -1212,11 +1210,11 @@ msgstr "ФокуÑувати шлÑÑ…" #: editor/editor_file_dialog.cpp msgid "Move Favorite Up" -msgstr "ПереміÑтити обране вгору" +msgstr "ПереміÑтити вибране вгору" #: editor/editor_file_dialog.cpp msgid "Move Favorite Down" -msgstr "ПереміÑтити обране вниз" +msgstr "ПереміÑтити вибране вниз" #: editor/editor_file_dialog.cpp scene/gui/file_dialog.cpp msgid "Go to parent folder" @@ -1245,7 +1243,7 @@ msgstr "Сканувати Ñирці" #: editor/editor_file_system.cpp msgid "(Re)Importing Assets" -msgstr "(Re)Імпорт активів" +msgstr "Ð†Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ñ–Ð²" #: editor/editor_help.cpp editor/editor_node.cpp #: editor/plugins/script_editor_plugin.cpp @@ -1258,7 +1256,7 @@ msgstr "СпиÑок клаÑів:" #: editor/editor_help.cpp msgid "Search Classes" -msgstr "Пошук клаÑу" +msgstr "Пошук клаÑів" #: editor/editor_help.cpp editor/plugins/spatial_editor_plugin.cpp msgid "Top" @@ -1274,19 +1272,19 @@ msgstr "УÑпадковує:" #: editor/editor_help.cpp msgid "Inherited by:" -msgstr "УÑпадкована:" +msgstr "УÑпадковано:" #: editor/editor_help.cpp msgid "Brief Description:" -msgstr "Короткий опиÑ:" +msgstr "СтиÑлий опиÑ:" #: editor/editor_help.cpp msgid "Members" -msgstr "УчаÑники" +msgstr "Члени" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Members:" -msgstr "УчаÑники:" +msgstr "Члени:" #: editor/editor_help.cpp msgid "Public Methods" @@ -1298,11 +1296,11 @@ msgstr "Публічні методи:" #: editor/editor_help.cpp msgid "GUI Theme Items" -msgstr "Елементи графічного інтерфейÑу теми" +msgstr "Тема елементів ГІК" #: editor/editor_help.cpp msgid "GUI Theme Items:" -msgstr "Елементи графічного інтерфейÑу теми:" +msgstr "Тема елементів ГІК:" #: editor/editor_help.cpp modules/visual_script/visual_script_editor.cpp msgid "Signals:" @@ -1310,15 +1308,15 @@ msgstr "Сигнали:" #: editor/editor_help.cpp msgid "Enumerations" -msgstr "Перелік" +msgstr "Перелічуваний" #: editor/editor_help.cpp msgid "Enumerations:" -msgstr "Перелік:" +msgstr "Перелічуваний:" #: editor/editor_help.cpp msgid "enum " -msgstr "перелік " +msgstr "перелічуваний " #: editor/editor_help.cpp msgid "Constants" @@ -1368,7 +1366,7 @@ msgstr "Методи" #: editor/editor_help.cpp msgid "Method Description:" -msgstr "ÐžÐ¿Ð¸Ñ Ð¼ÐµÑ‚Ð¾Ð´Ñƒ:" +msgstr "ÐžÐ¿Ð¸Ñ Ð¼ÐµÑ‚Ð¾Ð´Ñ–Ð²:" #: editor/editor_help.cpp msgid "" @@ -1403,20 +1401,20 @@ msgstr "ОчиÑтити вивід" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "Ðе вдалоÑÑ ÐµÐºÑпортувати проект, код помилки — %d." #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "Помилка Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ñ€ÐµÑурÑу!" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "Зберегти реÑÑƒÑ€Ñ Ñк.." +msgid "Save Resource As..." +msgstr "Зберегти реÑÑƒÑ€Ñ Ñк..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "Бачу.." +msgid "I see..." +msgstr "Бачу..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1496,7 +1494,7 @@ msgstr "Помилка Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð°Ð±Ð¾Ñ€Ñƒ тайлів!" #: editor/editor_node.cpp msgid "Error trying to save layout!" -msgstr "Помилка при Ñпробі зберегти макет!" +msgstr "Помилка при Ñпробі зберегти компонуваннÑ!" #: editor/editor_node.cpp msgid "Default editor layout overridden." @@ -1504,7 +1502,7 @@ msgstr "Типове ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¾Ñ€Ð° перевизР#: editor/editor_node.cpp msgid "Layout name not found!" -msgstr "Ðазву макета не знайдено!" +msgstr "Ðазву ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ðµ знайдено!" #: editor/editor_node.cpp msgid "Restored default layout to base settings." @@ -1516,7 +1514,7 @@ msgid "" "Please read the documentation relevant to importing scenes to better " "understand this workflow." msgstr "" -"Цей реÑÑƒÑ€Ñ Ð½Ð°Ð»ÐµÐ¶Ð¸Ñ‚ÑŒ до Ñцени, Ñкий було імпортовано, тому не можна " +"Цей реÑÑƒÑ€Ñ Ð½Ð°Ð»ÐµÐ¶Ð¸Ñ‚ÑŒ до Ñцени, Ñкий було імпортовано, тому його не можна " "редагувати.\n" "Будь лаÑка, прочитайте документацію, що ÑтоÑуютьÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñцен, щоб " "краще зрозуміти цей робочий процеÑ." @@ -1597,7 +1595,7 @@ msgstr "Відкрити у довідці" #: editor/editor_node.cpp msgid "There is no defined scene to run." -msgstr "Ðе Ñ–Ñнує визначеної Ñцени Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑку." +msgstr "Ðемає визначеної Ñцени Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ." #: editor/editor_node.cpp msgid "" @@ -1647,12 +1645,12 @@ msgid "Open Base Scene" msgstr "Відкрити оÑновну Ñцену" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "Швидке Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñцени.." +msgid "Quick Open Scene..." +msgstr "Швидке Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñцени..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "Швидке Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñкрипту.." +msgid "Quick Open Script..." +msgstr "Швидке Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñкрипту..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1660,11 +1658,11 @@ msgstr "Зберегти та закрити" #: editor/editor_node.cpp msgid "Save changes to '%s' before closing?" -msgstr "Зберегти зміни, внеÑені до '%s ' перед закриттÑм?" +msgstr "Зберегти зміни, внеÑені до '%s' перед закриттÑм?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Зберегти Ñцени, Ñк..." +msgid "Save Scene As..." +msgstr "Зберегти Ñцену Ñк..." #: editor/editor_node.cpp msgid "No" @@ -1715,8 +1713,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "Цю дію не можна ÑкаÑувати. ПовернутиÑÑ Ð² будь-Ñкому випадку?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "Швидкий запуÑк Ñцени.." +msgid "Quick Run Scene..." +msgstr "Швидкий запуÑк Ñцени..." #: editor/editor_node.cpp msgid "Quit" @@ -1815,11 +1813,11 @@ msgstr "ОчиÑтити недавні Ñцени" #: editor/editor_node.cpp msgid "Save Layout" -msgstr "Зберегти макет" +msgstr "Зберегти компонуваннÑ" #: editor/editor_node.cpp msgid "Delete Layout" -msgstr "Видалити макет" +msgstr "Видалити компонуваннÑ" #: editor/editor_node.cpp editor/import_dock.cpp #: editor/script_create_dialog.cpp @@ -1828,7 +1826,7 @@ msgstr "Типовий" #: editor/editor_node.cpp msgid "Switch Scene Tab" -msgstr "Перемкнути вкладку \"Сцена\"" +msgstr "ÐŸÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ð½Ð½Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ¸ \"Сцена\"" #: editor/editor_node.cpp msgid "%d more files or folders" @@ -1875,7 +1873,7 @@ msgid "Previous tab" msgstr "ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð½Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ°" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "Фільтрувати файли..." #: editor/editor_node.cpp @@ -1887,12 +1885,12 @@ msgid "New Scene" msgstr "Ðова Ñцена" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Ðова уÑпадкована Ñцена.." +msgid "New Inherited Scene..." +msgstr "Ðова уÑпадкована Ñцена..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "Відкрити Ñцену.." +msgid "Open Scene..." +msgstr "Відкрити Ñцену..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1911,16 +1909,16 @@ msgid "Open Recent" msgstr "Відкрити оÑтанні" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "Перетворити на.." +msgid "Convert To..." +msgstr "Перетворити на..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "Бібліотека Ñітки.." +msgid "MeshLibrary..." +msgstr "Бібліотека Ñітки..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "Ðабір тайлів.." +msgid "TileSet..." +msgstr "Ðабір тайлів..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -1930,7 +1928,7 @@ msgstr "СкаÑувати" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp msgid "Redo" -msgstr "Повторити" +msgstr "Повернути" #: editor/editor_node.cpp msgid "Revert Scene" @@ -1954,7 +1952,7 @@ msgstr "ЗапуÑтити Ñкрипт" #: editor/editor_node.cpp editor/project_export.cpp msgid "Export" -msgstr "ЕкÑпортувати" +msgstr "ЕкÑпортуваннÑ" #: editor/editor_node.cpp msgid "Tools" @@ -2001,14 +1999,14 @@ msgstr "" #: editor/editor_node.cpp msgid "Visible Collision Shapes" -msgstr "Видимі форми зіткнень" +msgstr "Видимі контури зіткнень" #: editor/editor_node.cpp msgid "" "Collision shapes and raycast nodes (for 2D and 3D) will be visible on the " "running game if this option is turned on." msgstr "" -"Форми Ð·Ñ–Ñ‚ÐºÐ½ÐµÐ½Ð½Ñ Ñ‚Ð° вузли raycast (Ð´Ð»Ñ 2D та 3D) будуть видно в роботі гри, " +"Контури Ð·Ñ–Ñ‚ÐºÐ½ÐµÐ½Ð½Ñ Ñ‚Ð° вузли raycast (Ð´Ð»Ñ 2D та 3D) буде видно в роботі гри, " "Ñкщо Ñ†Ñ Ð¾Ð¿Ñ†Ñ–Ñ ÑƒÐ²Ñ–Ð¼ÐºÐ½ÐµÐ½Ð°." #: editor/editor_node.cpp @@ -2036,8 +2034,8 @@ msgid "" msgstr "" "Якщо цей параметр увімкнено, будь-Ñкі зміни, внеÑені в Ñцену в редакторі, " "будуть відтворені в роботі гри.\n" -"Коли він викориÑтовуєтьÑÑ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¾ на приÑтрої, це більш ефективно з " -"мережевою файловою ÑиÑтемою." +"При віддаленому викориÑтанні на приÑтрої, це більш ефективно з мережевою " +"файловою ÑиÑтемою." #: editor/editor_node.cpp msgid "Sync Script Changes" @@ -2052,8 +2050,8 @@ msgid "" msgstr "" "Якщо цей параметр увімкнено, будь-Ñкий Ñкрипт, Ñкий буде збережений, буде " "перезавантажений у поточній грі.\n" -"Коли він викориÑтовуєтьÑÑ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¾ на приÑтрої, це більш ефективно з " -"мережевою файловою ÑиÑтемою." +"При віддаленому викориÑтанні на приÑтрої, це більш ефективно з мережевою " +"файловою ÑиÑтемою." #: editor/editor_node.cpp msgid "Editor" @@ -2065,7 +2063,7 @@ msgstr "Параметри редактора" #: editor/editor_node.cpp msgid "Editor Layout" -msgstr "Редактор макетів" +msgstr "Редактор компонуваннÑ" #: editor/editor_node.cpp msgid "Toggle Fullscreen" @@ -2108,7 +2106,7 @@ msgstr "Спільнота" #: editor/editor_node.cpp msgid "About" -msgstr "Про програму" +msgstr "Про" #: editor/editor_node.cpp msgid "Play the project." @@ -2160,7 +2158,7 @@ msgstr "Завжди оновлювати" #: editor/editor_node.cpp msgid "Update Changes" -msgstr "Оновити зміни" +msgstr "Оновлювати зміни" #: editor/editor_node.cpp msgid "Disable Update Spinner" @@ -2183,7 +2181,7 @@ msgid "Save the currently edited resource." msgstr "Зберегти поточний редагований реÑурÑ." #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "Зберегти Ñк..." #: editor/editor_node.cpp @@ -2257,7 +2255,7 @@ msgstr "Ðовий уÑпадкований" #: editor/editor_node.cpp msgid "Load Errors" -msgstr "Завантажити помилки" +msgstr "Помилки завантаженнÑ" #: editor/editor_node.cpp editor/plugins/tile_map_editor_plugin.cpp msgid "Select" @@ -2292,12 +2290,12 @@ msgid "Creating Mesh Previews" msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ð¾Ð¿ÐµÑ€ÐµÐ´Ð½ÑŒÐ¾Ð³Ð¾ переглÑду Ñітки" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "Мініатюра.." +msgid "Thumbnail..." +msgstr "Мініатюра..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" -msgstr "Ð’Ñтановлені плагіни:" +msgstr "Ð’Ñтановлені плаґіни:" #: editor/editor_plugin_settings.cpp msgid "Update" @@ -2342,7 +2340,7 @@ msgstr "Кадр %" #: editor/editor_profiler.cpp msgid "Physics Frame %" -msgstr "Фізика кадрів %" +msgstr "Фізичний кадр %" #: editor/editor_profiler.cpp editor/script_editor_debugger.cpp msgid "Time:" @@ -2386,7 +2384,7 @@ msgstr "Ðапишіть Ñвою логіку в методі _run ()." #: editor/editor_run_script.cpp msgid "There is an edited scene already." -msgstr "Є вже редагована Ñцена." +msgstr "Редагована Ñцена вже Ñ–Ñнує." #: editor/editor_run_script.cpp msgid "Couldn't instance script:" @@ -2394,7 +2392,7 @@ msgstr "Ðеможливо Ñтворити екземплÑÑ€ Ñкрипту:" #: editor/editor_run_script.cpp msgid "Did you forget the 'tool' keyword?" -msgstr "Ви забули ключове Ñлово \"інÑтрумент\"?" +msgstr "Ви забули ключове Ñлово 'tool'?" #: editor/editor_run_script.cpp msgid "Couldn't run script:" @@ -2402,7 +2400,7 @@ msgstr "Ðе вдалоÑÑ Ð·Ð°Ð¿ÑƒÑтити Ñкрипт:" #: editor/editor_run_script.cpp msgid "Did you forget the '_run' method?" -msgstr "Ви забули метод \"_run\"?" +msgstr "Ви забули метод '_run'?" #: editor/editor_settings.cpp msgid "Default (Same as Editor)" @@ -2445,12 +2443,12 @@ msgid "(Current)" msgstr "(Поточний)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." -msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð», будь лаÑка, зачекайте.." +msgid "Retrieving mirrors, please wait..." +msgstr "ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð», будь лаÑка, зачекайте..." #: editor/export_template_manager.cpp msgid "Remove template version '%s'?" -msgstr "Видалити верÑÑ–ÑŽ шаблону '%s'?" +msgstr "Видалити верÑÑ–ÑŽ шаблону '%s'?" #: editor/export_template_manager.cpp msgid "Can't open export templates zip." @@ -2470,7 +2468,7 @@ msgstr "Помилка ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ ÑˆÐ»Ñху Ð´Ð»Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñ–Ð²:" #: editor/export_template_manager.cpp msgid "Extracting Export Templates" -msgstr "ВитÑг шаблонів екÑпорту" +msgstr "Ð Ð¾Ð·Ð¿Ð°ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÑˆÐ°Ð±Ð»Ð¾Ð½Ñ–Ð² екÑпорту" #: editor/export_template_manager.cpp msgid "Importing:" @@ -2481,8 +2479,8 @@ msgid "" "No download links found for this version. Direct download is only available " "for official releases." msgstr "" -"Ðемає поÑилань на Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— верÑÑ–Ñ—. ПрÑме Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð¾Ñтупне " -"лише Ð´Ð»Ñ Ð¾Ñ„Ñ–Ñ†Ñ–Ð¹Ð½Ð¸Ñ… випуÑків." +"Ðе знайдено поÑилань Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— верÑÑ–Ñ—. ПрÑме Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ " +"доÑтупне лише Ð´Ð»Ñ Ð¾Ñ„Ñ–Ñ†Ñ–Ð¹Ð½Ð¸Ñ… випуÑків." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp @@ -2523,8 +2521,8 @@ msgid "Error requesting url: " msgstr "Помилка запиту url: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." -msgstr "ÐŸÑ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ дзеркала.." +msgid "Connecting to Mirror..." +msgstr "ÐŸÑ–Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð´Ð¾ дзеркала..." #: editor/export_template_manager.cpp msgid "Disconnected" @@ -2540,8 +2538,8 @@ msgstr "Ðе вдаєтьÑÑ Ð²Ð¸Ñ€Ñ–ÑˆÐ¸Ñ‚Ð¸" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "З’єднаннÑ.." +msgid "Connecting..." +msgstr "З’єднаннÑ..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2553,7 +2551,7 @@ msgstr "З’єднано" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "Запит..." #: editor/export_template_manager.cpp @@ -2566,7 +2564,7 @@ msgstr "Помилка з'єднаннÑ" #: editor/export_template_manager.cpp msgid "SSL Handshake Error" -msgstr "Помилка SSL Handshake" +msgstr "Помилка SSL РукоÑтиÑканнÑ" #: editor/export_template_manager.cpp msgid "Current Version:" @@ -2586,7 +2584,7 @@ msgstr "Видалити шаблон" #: editor/export_template_manager.cpp msgid "Select template file" -msgstr "Виберіть файл шаблону" +msgstr "Вибрати файл шаблону" #: editor/export_template_manager.cpp msgid "Export Template Manager" @@ -2603,18 +2601,17 @@ msgstr "Виберіть дзеркало зі ÑпиÑку: " #: editor/file_type_cache.cpp msgid "Can't open file_type_cache.cch for writing, not saving file type cache!" msgstr "" -"Ðе вдаєтьÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ файл file_type_cache.cch Ð´Ð»Ñ Ð½Ð°Ð¿Ð¸ÑаннÑ, кеш тип файлу " -"не буде збережений!" +"Ðе вдаєтьÑÑ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¸ file_type_cache.cch Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу, не буде збережений файл " +"тип кешу!" #: editor/filesystem_dock.cpp msgid "Cannot navigate to '%s' as it has not been found in the file system!" msgstr "" -"Ðеможливо перейти до \"'%s' , оÑкільки він не був знайдений в файлової " -"ÑиÑтемі!" +"Ðеможливо перейти до '%s' , оÑкільки він не був знайдений в файловій ÑиÑтемі!" #: editor/filesystem_dock.cpp msgid "View items as a grid of thumbnails" -msgstr "ПереглÑд елементів у виглÑді Ñітки еÑкізів" +msgstr "ПереглÑд елементів у виглÑді Ñітки мініатюр" #: editor/filesystem_dock.cpp msgid "View items as a list" @@ -2623,8 +2620,8 @@ msgstr "ПереглÑд елементів Ñк ÑпиÑок" #: editor/filesystem_dock.cpp msgid "Status: Import of file failed. Please fix file and reimport manually." msgstr "" -"СтатуÑ: не вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ файл. Виправте файл та повторно імпортуйте " -"вручну." +"СтатуÑ: не вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ файл. Будь лаÑка, виправте файл та повторно " +"імпортуйте вручну." #: editor/filesystem_dock.cpp msgid "Cannot move/rename resources root." @@ -2684,18 +2681,18 @@ msgstr "Ð”ÑƒÐ±Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ñ‚ÐµÐºÐ¸:" #: editor/filesystem_dock.cpp msgid "Expand all" -msgstr "Розгорнути вÑÑ–" +msgstr "Розгорнути вÑе" #: editor/filesystem_dock.cpp msgid "Collapse all" -msgstr "Згорнути вÑÑ–" +msgstr "Згорнути вÑе" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "Перейменувати..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "ПереміÑтити до..." #: editor/filesystem_dock.cpp @@ -2707,16 +2704,16 @@ msgid "Instance" msgstr "ЕкземплÑÑ€" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Редагувати залежноÑті.." +msgid "Edit Dependencies..." +msgstr "Редагувати залежноÑті..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "ПереглÑнути влаÑників.." +msgid "View Owners..." +msgstr "ПереглÑнути влаÑників..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "Дублювати.." +msgid "Duplicate..." +msgstr "Дублювати..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2728,20 +2725,20 @@ msgstr "ÐаÑтупний каталог" #: editor/filesystem_dock.cpp msgid "Re-Scan Filesystem" -msgstr "Повторне ÑÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ñ— ÑиÑтеми" +msgstr "ПереÑÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ñ— ÑиÑтеми" #: editor/filesystem_dock.cpp msgid "Toggle folder status as Favorite" -msgstr "Переключити ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ‚ÐµÐºÐ¸ у вибране" +msgstr "Переключити ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ‚ÐµÐºÐ¸ Ñк обране" #: editor/filesystem_dock.cpp msgid "Instance the selected scene(s) as child of the selected node." -msgstr "Додати обрану Ñцену(и), Ñк нащадка обраного вузла." +msgstr "Додати вибрану Ñцену(и), Ñк нащадка вибраного вузла." #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Ð¡ÐºÐ°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñ–Ð²,\n" "будь лаÑка, зачекайте..." @@ -2809,8 +2806,8 @@ msgid "Import Scene" msgstr "Імпортувати Ñцену" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "Ð†Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñцени.." +msgid "Importing Scene..." +msgstr "Ð†Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñцени..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2821,8 +2818,8 @@ msgid "Generating for Mesh: " msgstr "Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ñітки: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "ЗапуÑк кориÑтувацького Ñкрипту.." +msgid "Running Custom Script..." +msgstr "ЗапуÑк кориÑтувацького Ñкрипту..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2837,16 +2834,16 @@ msgid "Error running post-import script:" msgstr "Помилка запуÑку піÑÐ»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ Ñкрипту:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "ЗбереженнÑ..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" -msgstr "Ð’Ñтановити за замовчуваннÑм Ð´Ð»Ñ \"%s\"" +msgstr "Ð’Ñтановити Ñк типове Ð´Ð»Ñ '%s'" #: editor/import_dock.cpp msgid "Clear Default for '%s'" -msgstr "ОчиÑтити за замовчуваннÑм Ð´Ð»Ñ '%s'" +msgstr "ОчиÑтити типове Ð´Ð»Ñ '%s'" #: editor/import_dock.cpp msgid " Files" @@ -2857,12 +2854,12 @@ msgid "Import As:" msgstr "Імпортувати Ñк:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "Заздалегідь уÑтановлений.." +msgid "Preset..." +msgstr "Заздалегідь уÑтановлений..." #: editor/import_dock.cpp msgid "Reimport" -msgstr "Переімпортивути" +msgstr "Переімпортувати" #: editor/multi_node_edit.cpp msgid "MultiNode Set" @@ -2935,7 +2932,7 @@ msgstr "Ðова анімаціÑ" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Change Animation Name:" -msgstr "Змініть ім'Ñ Ð°Ð½Ñ–Ð¼Ð°Ñ†Ñ–Ñ—:" +msgstr "Змінити ім'Ñ Ð°Ð½Ñ–Ð¼Ð°Ñ†Ñ–Ñ—:" #: editor/plugins/animation_player_editor_plugin.cpp msgid "Delete Animation?" @@ -2982,7 +2979,7 @@ msgstr "Дублювати анімацію" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation to copy!" -msgstr "ПОМИЛКÐ: Ðемає копії анімації!" +msgstr "ПОМИЛКÐ: Ðемає анімації Ð´Ð»Ñ ÐºÐ¾Ð¿Ñ–ÑŽÐ²Ð°Ð½Ð½Ñ!" #: editor/plugins/animation_player_editor_plugin.cpp msgid "ERROR: No animation resource on clipboard!" @@ -3031,7 +3028,7 @@ msgstr "Шкала Ð²Ñ–Ð´Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð¾ анімації д #: editor/plugins/animation_player_editor_plugin.cpp msgid "Create new animation in player." -msgstr "Створювати нові анімації у програвачі." +msgstr "Створити нову анімацію у програвачі." #: editor/plugins/animation_player_editor_plugin.cpp msgid "Load animation from disk." @@ -3276,15 +3273,15 @@ msgid "Transition Node" msgstr "Вузол переходу" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "Імпортувати анімації.." +msgid "Import Animations..." +msgstr "Імпортувати анімації..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "Редагувати фільтри вузла" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "Фільтри..." #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3352,7 +3349,7 @@ msgid "Fetching:" msgstr "ВидобуваннÑ:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "ВирішеннÑ..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3419,7 +3416,7 @@ msgid "Site:" msgstr "Сайт:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "Підтримка..." #: editor/plugins/asset_library_editor_plugin.cpp @@ -3617,6 +3614,7 @@ msgid "Use Rotation Snap" msgstr "ВикориÑÑ‚Ð°Ð½Ð½Ñ Ð¾Ð±ÐµÑ€Ñ‚Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²'Ñзки" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²'Ñзки..." @@ -3713,14 +3711,12 @@ msgid "Show Guides" msgstr "Показати напрÑмні" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" -msgstr "ПереглÑд центра" +msgstr "Показати центр" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1 панель переглÑду" +msgstr "Показати панель переглÑду" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -4014,7 +4010,7 @@ msgstr "Сітка не має поверхні, щоб Ñтворити ÐºÐ¾Ð½Ñ #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "Типом Ñітки примітива не Ñ” PRIMITIVE_TRIANGLES!" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -4045,8 +4041,8 @@ msgid "Create Convex Collision Sibling" msgstr "Створити опуклу облаÑть зіткненнÑ" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "Створити контурну Ñітку .." +msgid "Create Outline Mesh..." +msgstr "Створити контурну Ñітку ..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4251,8 +4247,8 @@ msgid "Error loading image:" msgstr "Помилка Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "Ð’ зображенні немає пікÑелів з прозоріÑтю > 128.." +msgid "No pixels with transparency > 128 in image..." +msgstr "Ð’ зображенні немає пікÑелів з прозоріÑтю > 128..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4612,7 +4608,7 @@ msgid "Import Theme" msgstr "Імпортувати тему" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "Зберегти тему Ñк..." #: editor/plugins/script_editor_plugin.cpp @@ -4709,8 +4705,8 @@ msgstr "Перемкнути панель Ñценаріїв" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "Знайти.." +msgid "Find..." +msgstr "Знайти..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4835,7 +4831,7 @@ msgstr "Копіювати" #: editor/plugins/script_text_editor.cpp scene/gui/line_edit.cpp #: scene/gui/text_edit.cpp msgid "Select All" -msgstr "Вибрати вÑе" +msgstr "Виділити вÑе" #: editor/plugins/script_text_editor.cpp msgid "Delete Line" @@ -4919,16 +4915,16 @@ msgid "Find Previous" msgstr "Знайти попереднє" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "Замінити.." +msgid "Replace..." +msgstr "Замінити..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "Перейти до функції.." +msgid "Goto Function..." +msgstr "Перейти до функції..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "Перейти до Ñ€Ñдка.." +msgid "Goto Line..." +msgstr "Перейти до Ñ€Ñдка..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5381,11 +5377,7 @@ msgid "Transform" msgstr "ПеретвореннÑ" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "Ðалаштувати прилипаннÑ..." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "Вікно перетвореннÑ..." #: editor/plugins/spatial_editor_plugin.cpp @@ -5638,7 +5630,7 @@ msgid "Remove All" msgstr "Вилучити уÑÑ–" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "Редагувати тему..." #: editor/plugins/theme_editor_plugin.cpp @@ -5686,14 +5678,12 @@ msgid "Checked Item" msgstr "Позначений елемент" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "Додати елемент" +msgstr "Пункт варіанта" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "Позначений елемент" +msgstr "Позначений пункт варіанта" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5705,11 +5695,11 @@ msgstr "Багато" #: editor/plugins/theme_editor_plugin.cpp editor/project_export.cpp msgid "Options" -msgstr "Параметрів" +msgstr "Параметри" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "Має,Багато,Декілька,Параметрів!" +msgid "Has,Many,Options" +msgstr "Має,Багато,Параметрів" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5901,7 +5891,7 @@ msgid "Presets" msgstr "Ðабори" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "Додати..." #: editor/project_export.cpp @@ -5996,6 +5986,10 @@ msgid "Imported Project" msgstr "Імпортований проект" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "Ðекоректна назва проекту." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "Ðеможливо Ñтворити теку." @@ -6196,9 +6190,11 @@ msgstr "Кнопка миші" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" +"Ðекоректна назва дії. Ðазва не може бути порожньою Ñ– не може міÑтити " +"Ñимволів «/», «:», «=», «\\» та «\"»." #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6225,7 +6221,7 @@ msgid "Control+" msgstr "Ctrl+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "ÐатиÑніть клавішу,..." #: editor/project_settings_editor.cpp @@ -6402,14 +6398,14 @@ msgstr "Параметри проекту (project.godot)" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp msgid "General" -msgstr "Загальне" +msgstr "\"Загальне\"" #: editor/project_settings_editor.cpp editor/property_editor.cpp msgid "Property:" msgstr "ВлаÑтивіÑть:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "Перевизначити на..." #: editor/project_settings_editor.cpp @@ -6454,19 +6450,19 @@ msgstr "ПереÑпрÑÐ¼ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð° локаллю:" #: editor/project_settings_editor.cpp msgid "Locale" -msgstr "Локаль" +msgstr "Мова" #: editor/project_settings_editor.cpp msgid "Locales Filter" -msgstr "Фільтр локалей" +msgstr "Фільтр локалізацій" #: editor/project_settings_editor.cpp msgid "Show all locales" -msgstr "Показати уÑÑ– локалі" +msgstr "Показати уÑÑ– локалізації" #: editor/project_settings_editor.cpp msgid "Show only selected locales" -msgstr "Показати лише позначені локалі" +msgstr "Показати лише позначені локалізації" #: editor/project_settings_editor.cpp msgid "Filter mode:" @@ -6474,7 +6470,7 @@ msgstr "Режим фільтруваннÑ:" #: editor/project_settings_editor.cpp msgid "Locales:" -msgstr "Локалі:" +msgstr "Мови:" #: editor/project_settings_editor.cpp msgid "AutoLoad" @@ -6505,11 +6501,11 @@ msgid "Easing Out-In" msgstr "Перейти з-у" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "Файл..." #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "Каталог..." #: editor/property_editor.cpp @@ -6683,7 +6679,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "Цю дію не можна виконувати над Ñценами з екземплÑрами." #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "Зберегти нову Ñцену Ñк..." #: editor/scene_tree_dock.cpp @@ -7401,7 +7397,7 @@ msgstr "ВідÑтань вибору:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "Ðазвою клаÑу не може бути зарезервоване ключове Ñлово" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -8114,7 +8110,7 @@ msgstr "" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment потребує реÑурÑу Environment." #: scene/3d/scenario_fx.cpp msgid "" @@ -8128,6 +8124,9 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"Цей Ð·Ð°Ð¿Ð¸Ñ WorldEnvironment проігноровано. Ðбо додайте Ð·Ð°Ð¿Ð¸Ñ Camera (Ð´Ð»Ñ " +"проÑторових Ñцен) або вÑтановіть Ð´Ð»Ñ Background Mode цього Ñередовища " +"Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Canvas (Ð´Ð»Ñ Ð´Ð²Ð¾Ð²Ð¸Ð¼Ñ–Ñ€Ð½Ð¸Ñ… Ñцен)." #: scene/3d/sprite_3d.cpp msgid "" @@ -8226,6 +8225,13 @@ msgstr "Помилка Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÑˆÑ€Ð¸Ñ„Ñ‚Ñƒ." msgid "Invalid font size." msgstr "Ðекоректний розмір шрифту." +#, fuzzy +#~ msgid "Previous" +#~ msgstr "ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð½Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ°" + +#~ msgid "Next" +#~ msgstr "Далі" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Ðекоректна Ð´Ñ–Ñ (можна уÑе, окрім «/» або «:»)." @@ -8252,9 +8258,6 @@ msgstr "Ðекоректний розмір шрифту." #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ project.godot у каталозі проекту." -#~ msgid "Next" -#~ msgstr "Далі" - #~ msgid "Not found!" #~ msgstr "Ðе знайдено!" diff --git a/editor/translations/ur_PK.po b/editor/translations/ur_PK.po index 4f03e8a387..0162eb0788 100644 --- a/editor/translations/ur_PK.po +++ b/editor/translations/ur_PK.po @@ -495,7 +495,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -909,11 +909,11 @@ msgid "Move Audio Bus" msgstr "ایکشن منتقل کریں" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1051,11 +1051,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1124,7 +1124,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1391,12 +1391,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1601,11 +1601,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1617,7 +1617,7 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "" #: editor/editor_node.cpp @@ -1669,7 +1669,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1815,7 +1815,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1827,11 +1827,11 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1851,15 +1851,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2104,7 +2104,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2214,7 +2214,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2365,7 +2365,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2441,7 +2441,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2458,7 +2458,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2471,7 +2471,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2605,11 +2605,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2621,15 +2621,15 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "" #: editor/filesystem_dock.cpp @@ -2655,7 +2655,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2721,7 +2721,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2733,7 +2733,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2749,7 +2749,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2769,7 +2769,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3185,7 +3185,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3193,7 +3193,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3261,7 +3261,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3328,7 +3328,7 @@ msgid "Site:" msgstr "سائٹ:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr ".سپورٹ" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3519,6 +3519,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3944,7 +3945,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4149,7 +4150,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4514,7 +4515,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4612,7 +4613,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4818,15 +4819,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5280,11 +5281,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5541,7 +5538,7 @@ msgid "Remove All" msgstr ".تمام کا انتخاب" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5609,7 +5606,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5798,7 +5795,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5888,6 +5885,10 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "" + +#: editor/project_manager.cpp #, fuzzy msgid "Couldn't create folder." msgstr "سب سکریپشن بنائیں" @@ -6078,8 +6079,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6107,7 +6108,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6292,7 +6293,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6388,11 +6389,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6564,7 +6565,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp diff --git a/editor/translations/vi.po b/editor/translations/vi.po index d6284d640e..6651bd170c 100644 --- a/editor/translations/vi.po +++ b/editor/translations/vi.po @@ -498,7 +498,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "" #: editor/connections_dialog.cpp @@ -908,11 +908,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1048,11 +1048,11 @@ msgid "Updating Scene" msgstr "" #: editor/editor_data.cpp -msgid "Storing local changes.." +msgid "Storing local changes..." msgstr "" #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "" #: editor/editor_data.cpp @@ -1121,7 +1121,7 @@ msgid "Show In File Manager" msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." +msgid "New Folder..." msgstr "" #: editor/editor_file_dialog.cpp @@ -1383,12 +1383,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "" #: editor/editor_node.cpp @@ -1593,11 +1593,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1609,8 +1609,8 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "Lưu Scene vá»›i tên.." +msgid "Save Scene As..." +msgstr "Lưu Scene vá»›i tên..." #: editor/editor_node.cpp msgid "No" @@ -1661,7 +1661,7 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." +msgid "Quick Run Scene..." msgstr "" #: editor/editor_node.cpp @@ -1808,7 +1808,7 @@ msgid "Previous tab" msgstr "" #: editor/editor_node.cpp -msgid "Filter Files.." +msgid "Filter Files..." msgstr "" #: editor/editor_node.cpp @@ -1820,11 +1820,11 @@ msgid "New Scene" msgstr "Tạo Scene Má»›i" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "Tạo Scene Con.." +msgid "New Inherited Scene..." +msgstr "Tạo Scene Con..." #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "" #: editor/editor_node.cpp @@ -1844,15 +1844,15 @@ msgid "Open Recent" msgstr "" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "" #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2097,7 +2097,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2206,7 +2206,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2357,7 +2357,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2433,7 +2433,7 @@ msgid "Error requesting url: " msgstr "" #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "" #: editor/export_template_manager.cpp @@ -2450,7 +2450,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." +msgid "Connecting..." msgstr "" #: editor/export_template_manager.cpp @@ -2463,7 +2463,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2595,11 +2595,11 @@ msgid "Collapse all" msgstr "Thu gá»n tất cả" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "Äổi tên..." #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "Di chuyển đến..." #: editor/filesystem_dock.cpp @@ -2611,15 +2611,15 @@ msgid "Instance" msgstr "Thêm và o scene" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "Chỉnh sá»a các File phụ thuá»™c.." +msgid "Edit Dependencies..." +msgstr "Chỉnh sá»a các File phụ thuá»™c..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "Xem các scene sở hữu.." +msgid "View Owners..." +msgstr "Xem các scene sở hữu..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." +msgid "Duplicate..." msgstr "Nhân đôi..." #: editor/filesystem_dock.cpp @@ -2645,10 +2645,10 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "Äang quét file,\n" -"Chá» môt chút.." +"Chá» môt chút..." #: editor/filesystem_dock.cpp msgid "Move" @@ -2713,7 +2713,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2725,7 +2725,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2741,7 +2741,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2761,7 +2761,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3175,7 +3175,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3183,7 +3183,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3251,7 +3251,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3318,7 +3318,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3505,6 +3505,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3926,7 +3927,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4131,7 +4132,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4492,7 +4493,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4589,7 +4590,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4795,15 +4796,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5254,11 +5255,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5511,7 +5508,7 @@ msgid "Remove All" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5579,7 +5576,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5767,7 +5764,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5857,6 +5854,11 @@ msgid "Imported Project" msgstr "" #: editor/project_manager.cpp +#, fuzzy +msgid "Invalid Project Name." +msgstr "KÃch thước font không hợp lệ." + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "" @@ -6045,8 +6047,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6074,7 +6076,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6258,7 +6260,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6354,11 +6356,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6529,7 +6531,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -7965,3 +7967,7 @@ msgstr "Lá»—i tải font." #: scene/resources/dynamic_font.cpp msgid "Invalid font size." msgstr "KÃch thước font không hợp lệ." + +#, fuzzy +#~ msgid "Previous" +#~ msgstr "Thư mục trước" diff --git a/editor/translations/zh_CN.po b/editor/translations/zh_CN.po index 45d2d81505..48e30ceab3 100644 --- a/editor/translations/zh_CN.po +++ b/editor/translations/zh_CN.po @@ -2,7 +2,6 @@ # Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. # Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) # This file is distributed under the same license as the Godot source code. -# # 360119124 <360119124@qq.com>, 2018. # æŸ æª¬æ€æ‰‹ <lemonkiller@gmail.com>, 2018. # 纯æ´çš„å蛋 <tqj.zyy@gmail.com>, 2016. @@ -12,9 +11,12 @@ # Bruce Guo <guoboism@hotmail.com>, 2016. # dragonandy <dragonandy@foxmail.com>, 2017-2018. # Geequlim <geequlim@gmail.com>, 2016-2018. +# jie Shi <meishijiemeimeimei@gmail.com>, 2018. +# Jingtian Pan <panjingtian@126.com>, 2018. # lalalaring <783482203@qq.com>, 2017. -# Luo Jun <vipsbpig@gmail.com>, 2016-2017. +# Luo Jun <vipsbpig@gmail.com>, 2016-2017, 2018. # oberon-tonya <360119124@qq.com>, 2016. +# plumsky <x-wolf@163.com>, 2018. # Qichunren <whyruby@gmail.com>, 2017. # seanfy <everxiao@qq.com>, 2018. # sersoong <seraphim945@qq.com>, 2017-2018. @@ -23,13 +25,13 @@ # Youmu <konpaku.w@gmail.com>, 2017. # yuetian <18829280955@163.com>, 2018. # Zae Chao <zae.vito@live.com>, 2018. -# +# zwj36028 <23732399@qq.com>, 2018. msgid "" msgstr "" "Project-Id-Version: Chinese (Simplified) (Godot Engine)\n" "POT-Creation-Date: 2018-01-20 12:15+0200\n" -"PO-Revision-Date: 2018-05-03 08:59+0000\n" -"Last-Translator: Geequlim <geequlim@gmail.com>\n" +"PO-Revision-Date: 2018-06-09 03:55+0000\n" +"Last-Translator: zwj36028 <23732399@qq.com>\n" "Language-Team: Chinese (Simplified) <https://hosted.weblate.org/projects/" "godot-engine/godot/zh_Hans/>\n" "Language: zh_CN\n" @@ -37,7 +39,7 @@ msgstr "" "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" +"X-Generator: Weblate 3.0\n" #: editor/animation_editor.cpp msgid "Disabled" @@ -515,8 +517,8 @@ msgid "Disconnect '%s' from '%s'" msgstr "å–æ¶ˆ'%s'的连接'%s'" #: editor/connections_dialog.cpp -msgid "Connect.." -msgstr "连接信å·.." +msgid "Connect..." +msgstr "连接信å·..." #: editor/connections_dialog.cpp #: editor/plugins/animation_tree_editor_plugin.cpp @@ -927,12 +929,12 @@ msgid "Move Audio Bus" msgstr "移动音频总线" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." -msgstr "将音频Bus布局ä¿å˜ä¸º.." +msgid "Save Audio Bus Layout As..." +msgstr "将音频Bus布局ä¿å˜ä¸º..." #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." -msgstr "新布局的ä½ç½®.." +msgid "Location for New Layout..." +msgstr "新布局的ä½ç½®..." #: editor/editor_audio_buses.cpp msgid "Open Audio Bus Layout" @@ -970,7 +972,7 @@ msgstr "å¦å˜ä¸º" #: editor/editor_audio_buses.cpp msgid "Save this Bus Layout to a file." -msgstr "将音频Bus布局ä¿å˜ä¸º.." +msgstr "将音频Bus布局ä¿å˜ä¸º..." #: editor/editor_audio_buses.cpp editor/import_dock.cpp msgid "Load Default" @@ -1067,12 +1069,12 @@ msgid "Updating Scene" msgstr "更新场景" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "ä¿å˜ä¿®æ”¹ä¸.." +msgid "Storing local changes..." +msgstr "ä¿å˜ä¿®æ”¹ä¸..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "更新场景ä¸.." +msgid "Updating scene..." +msgstr "更新场景ä¸..." #: editor/editor_data.cpp msgid "[empty]" @@ -1140,8 +1142,8 @@ msgid "Show In File Manager" msgstr "在资æºç®¡ç†å™¨ä¸æ‰“å¼€" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp -msgid "New Folder.." -msgstr "新建文件夹 .." +msgid "New Folder..." +msgstr "新建文件夹 ..." #: editor/editor_file_dialog.cpp msgid "Refresh" @@ -1401,20 +1403,20 @@ msgstr "清空输出" #: editor/editor_node.cpp msgid "Project export failed with error code %d." -msgstr "" +msgstr "项目导出失败,错误代ç %d。" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp msgid "Error saving resource!" msgstr "ä¿å˜èµ„æºå‡ºé”™ï¼" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." -msgstr "资æºå¦å˜ä¸º.." +msgid "Save Resource As..." +msgstr "资æºå¦å˜ä¸º..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." -msgstr "好å§.." +msgid "I see..." +msgstr "好å§..." #: editor/editor_node.cpp msgid "Can't open file for writing:" @@ -1634,12 +1636,12 @@ msgid "Open Base Scene" msgstr "打开父场景" #: editor/editor_node.cpp -msgid "Quick Open Scene.." -msgstr "快速打开场景.." +msgid "Quick Open Scene..." +msgstr "快速打开场景..." #: editor/editor_node.cpp -msgid "Quick Open Script.." -msgstr "快速打开脚本.." +msgid "Quick Open Script..." +msgstr "快速打开脚本..." #: editor/editor_node.cpp msgid "Save & Close" @@ -1650,8 +1652,8 @@ msgid "Save changes to '%s' before closing?" msgstr "在关é—å‰ä¿å˜æ›´æ”¹åˆ° %s å—?" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "场景å¦å˜ä¸º.." +msgid "Save Scene As..." +msgstr "场景å¦å˜ä¸º..." #: editor/editor_node.cpp msgid "No" @@ -1702,8 +1704,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "æ¤æ“ä½œæ— æ³•æ’¤é”€ï¼Œç¡®å®šè¦ç»§ç»å—?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "快速è¿è¡Œåœºæ™¯.." +msgid "Quick Run Scene..." +msgstr "快速è¿è¡Œåœºæ™¯..." #: editor/editor_node.cpp msgid "Quit" @@ -1850,8 +1852,8 @@ msgid "Previous tab" msgstr "上一个目录" #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "ç›é€‰æ–‡ä»¶.." +msgid "Filter Files..." +msgstr "ç›é€‰æ–‡ä»¶..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1862,12 +1864,12 @@ msgid "New Scene" msgstr "新建场景" #: editor/editor_node.cpp -msgid "New Inherited Scene.." -msgstr "从现有场景ä¸åˆ›å»º.." +msgid "New Inherited Scene..." +msgstr "从现有场景ä¸åˆ›å»º..." #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "打开场景.." +msgid "Open Scene..." +msgstr "打开场景..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1886,16 +1888,16 @@ msgid "Open Recent" msgstr "最近打开" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "转æ¢ä¸º.." +msgid "Convert To..." +msgstr "转æ¢ä¸º..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary(ç½‘æ ¼åº“).." +msgid "MeshLibrary..." +msgstr "MeshLibrary(ç½‘æ ¼åº“)..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "ç –å—集.." +msgid "TileSet..." +msgstr "ç –å—集..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2148,8 +2150,8 @@ msgid "Save the currently edited resource." msgstr "ä¿å˜å½“å‰ç¼–辑的资æºã€‚" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "å¦å˜ä¸º.." +msgid "Save As..." +msgstr "å¦å˜ä¸º..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2257,8 +2259,8 @@ msgid "Creating Mesh Previews" msgstr "åˆ›å»ºç½‘æ ¼é¢„è§ˆ" #: editor/editor_plugin.cpp -msgid "Thumbnail.." -msgstr "缩略图.." +msgid "Thumbnail..." +msgstr "缩略图..." #: editor/editor_plugin_settings.cpp msgid "Installed Plugins:" @@ -2410,7 +2412,7 @@ msgid "(Current)" msgstr "(当å‰)" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "检索镜åƒï¼Œè¯·ç‰å¾…..." #: editor/export_template_manager.cpp @@ -2486,7 +2488,7 @@ msgid "Error requesting url: " msgstr "请求链接错误: " #: editor/export_template_manager.cpp -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "æ£åœ¨è¿žæŽ¥é•œåƒç½‘站。。" #: editor/export_template_manager.cpp @@ -2503,8 +2505,8 @@ msgstr "æ— æ³•è§£æž" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Connecting.." -msgstr "连接ä¸.." +msgid "Connecting..." +msgstr "连接ä¸..." #: editor/export_template_manager.cpp msgid "Can't Connect" @@ -2516,7 +2518,7 @@ msgstr "已连接" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "æ£åœ¨è¯·æ±‚。。" #: editor/export_template_manager.cpp @@ -2648,12 +2650,12 @@ msgid "Collapse all" msgstr "收起所有" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "é‡å‘½å为..." #: editor/filesystem_dock.cpp -msgid "Move To.." -msgstr "移动.." +msgid "Move To..." +msgstr "移动..." #: editor/filesystem_dock.cpp msgid "Open Scene(s)" @@ -2664,16 +2666,16 @@ msgid "Instance" msgstr "创建实例节点" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." -msgstr "编辑ä¾èµ–.." +msgid "Edit Dependencies..." +msgstr "编辑ä¾èµ–..." #: editor/filesystem_dock.cpp -msgid "View Owners.." -msgstr "查看所有者.." +msgid "View Owners..." +msgstr "查看所有者..." #: editor/filesystem_dock.cpp -msgid "Duplicate.." -msgstr "æ‹·è´.." +msgid "Duplicate..." +msgstr "æ‹·è´..." #: editor/filesystem_dock.cpp msgid "Previous Directory" @@ -2698,7 +2700,7 @@ msgstr "将选ä¸çš„场景实例为选ä¸èŠ‚ç‚¹çš„å节点。" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" "æ‰«ææ–‡ä»¶ï¼Œ\n" "请ç¨å€™ã€‚" @@ -2746,11 +2748,11 @@ msgstr "导入独立的物体和动画" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Materials+Animations" -msgstr "导入独立的æè´¨å’ŒåŠ¨ç”»" +msgstr "与独立的æè´¨å’ŒåŠ¨ç”»ä¸€åŒå¯¼å…¥" #: editor/import/resource_importer_scene.cpp msgid "Import with Separate Objects+Materials+Animations" -msgstr "å¯¼å…¥ç‹¬ç«‹çš„ç‰©ä½“ã€æè´¨å’ŒåŠ¨ç”»" +msgstr "ä¸Žç‹¬ç«‹çš„ç‰©ä½“ã€æè´¨å’ŒåŠ¨ç”»ä¸€åŒå¯¼å…¥" #: editor/import/resource_importer_scene.cpp msgid "Import as Multiple Scenes" @@ -2766,8 +2768,8 @@ msgid "Import Scene" msgstr "导入场景" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." -msgstr "导入场景.." +msgid "Importing Scene..." +msgstr "导入场景..." #: editor/import/resource_importer_scene.cpp msgid "Generating Lightmaps" @@ -2778,8 +2780,8 @@ msgid "Generating for Mesh: " msgstr "æ£åœ¨ç”ŸæˆMesh: " #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." -msgstr "执行自定义脚本.." +msgid "Running Custom Script..." +msgstr "执行自定义脚本..." #: editor/import/resource_importer_scene.cpp msgid "Couldn't load post-import script:" @@ -2794,7 +2796,7 @@ msgid "Error running post-import script:" msgstr "åŽå¤„ç†è„šæœ¬è¿è¡Œå‘生错误:" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "ä¿å˜ä¸..." #: editor/import_dock.cpp @@ -2814,8 +2816,8 @@ msgid "Import As:" msgstr "导入为:" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." -msgstr "预设.." +msgid "Preset..." +msgstr "预设..." #: editor/import_dock.cpp msgid "Reimport" @@ -3232,16 +3234,16 @@ msgid "Transition Node" msgstr "过渡节点" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." -msgstr "导入动画.." +msgid "Import Animations..." +msgstr "导入动画..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "Edit Node Filters" msgstr "编辑节点ç›é€‰" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." -msgstr "ç›é€‰.." +msgid "Filters..." +msgstr "ç›é€‰..." #: editor/plugins/animation_tree_editor_plugin.cpp msgid "AnimationTree" @@ -3308,8 +3310,8 @@ msgid "Fetching:" msgstr "获å–:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." -msgstr "è§£æžä¸.." +msgid "Resolving..." +msgstr "è§£æžä¸..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Error making request" @@ -3375,8 +3377,8 @@ msgid "Site:" msgstr "站点:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." -msgstr "支æŒ.." +msgid "Support..." +msgstr "支æŒ..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Official" @@ -3565,8 +3567,9 @@ msgid "Use Rotation Snap" msgstr "使用旋转å¸é™„" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." -msgstr "设置å¸é™„.." +msgstr "设置å¸é™„..." #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Snap Relative" @@ -3661,14 +3664,12 @@ msgid "Show Guides" msgstr "显示引导" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Origin" msgstr "显示原点" #: editor/plugins/canvas_item_editor_plugin.cpp -#, fuzzy msgid "Show Viewport" -msgstr "1个视å£" +msgstr "显示视图窗å£" #: editor/plugins/canvas_item_editor_plugin.cpp msgid "Center Selection" @@ -3852,7 +3853,7 @@ msgstr "æŒ‰ä½ Shift å¯å•独编辑切线" #: editor/plugins/gi_probe_editor_plugin.cpp msgid "Bake GI Probe" -msgstr "烘焙GI Probe" +msgstr "渲染GI Probe" #: editor/plugins/gradient_editor_plugin.cpp msgid "Add/Remove Color Ramp Point" @@ -3961,7 +3962,7 @@ msgstr "Mesh(ç½‘æ ¼)æ²¡æœ‰è¡¨é¢æ¥åˆ›å»ºè½®å»“(outlines)ï¼" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Mesh primitive type is not PRIMITIVE_TRIANGLES!" -msgstr "" +msgstr "ç½‘æ ¼åŽŸå§‹ç±»åž‹ä¸æ˜¯ PRIMITIVE_TRIANGLES(ä¸‰è§’å½¢ç½‘æ ¼)ï¼" #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Could not create outline!" @@ -3992,8 +3993,8 @@ msgid "Create Convex Collision Sibling" msgstr "创建凸(Convex)碰撞åŒçº§" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." -msgstr "åˆ›å»ºè½®å»“ç½‘æ ¼(Outline Mesh).." +msgid "Create Outline Mesh..." +msgstr "åˆ›å»ºè½®å»“ç½‘æ ¼(Outline Mesh)..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "View UV1" @@ -4197,8 +4198,8 @@ msgid "Error loading image:" msgstr "åŠ è½½å›¾ç‰‡å‡ºé”™:" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." -msgstr "å›¾ç‰‡ä¸æ²¡æœ‰é€æ˜Žåº¦> 128çš„åƒç´ .." +msgid "No pixels with transparency > 128 in image..." +msgstr "å›¾ç‰‡ä¸æ²¡æœ‰é€æ˜Žåº¦> 128çš„åƒç´ ..." #: editor/plugins/particles_2d_editor_plugin.cpp msgid "Generate Visibility Rect" @@ -4558,8 +4559,8 @@ msgid "Import Theme" msgstr "导入主题" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." -msgstr "主题å¦å˜ä¸º.." +msgid "Save Theme As..." +msgstr "主题å¦å˜ä¸º..." #: editor/plugins/script_editor_plugin.cpp msgid " Class Reference" @@ -4655,8 +4656,8 @@ msgstr "切æ¢è„šæœ¬é¢æ¿" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." -msgstr "查找.." +msgid "Find..." +msgstr "查找..." #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp @@ -4863,16 +4864,16 @@ msgid "Find Previous" msgstr "查找上一项" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." -msgstr "替æ¢.." +msgid "Replace..." +msgstr "替æ¢..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." -msgstr "å‰å¾€å‡½æ•°.." +msgid "Goto Function..." +msgstr "å‰å¾€å‡½æ•°..." #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." -msgstr "å‰å¾€è¡Œ.." +msgid "Goto Line..." +msgstr "å‰å¾€è¡Œ..." #: editor/plugins/script_text_editor.cpp msgid "Contextual Help" @@ -5325,12 +5326,8 @@ msgid "Transform" msgstr "å˜æ¢" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "设置å¸é™„.." - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." -msgstr "å˜æ¢å¯¹è¯æ¡†.." +msgid "Transform Dialog..." +msgstr "å˜æ¢å¯¹è¯æ¡†..." #: editor/plugins/spatial_editor_plugin.cpp msgid "1 Viewport" @@ -5582,8 +5579,8 @@ msgid "Remove All" msgstr "移除全部" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." -msgstr "编辑主题.." +msgid "Edit theme..." +msgstr "编辑主题..." #: editor/plugins/theme_editor_plugin.cpp msgid "Theme editing menu." @@ -5630,14 +5627,12 @@ msgid "Checked Item" msgstr "已选项目(Checked Item)" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Radio Item" -msgstr "æ·»åŠ é¡¹ç›®" +msgstr "å•选项目" #: editor/plugins/theme_editor_plugin.cpp -#, fuzzy msgid "Checked Radio Item" -msgstr "已选项目(Checked Item)" +msgstr "已选å•选项目" #: editor/plugins/theme_editor_plugin.cpp msgid "Has" @@ -5652,8 +5647,8 @@ msgid "Options" msgstr "选项" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "æœ‰ï¼Œå¾ˆå¤šï¼Œå‡ ä¸ªï¼Œé€‰é¡¹(Have,Many,Several,Options)ï¼" +msgid "Has,Many,Options" +msgstr "有,很多,选项" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5843,8 +5838,8 @@ msgid "Presets" msgstr "预设" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." -msgstr "æ·»åŠ .." +msgid "Add..." +msgstr "æ·»åŠ ..." #: editor/project_export.cpp msgid "Resources" @@ -5933,6 +5928,10 @@ msgid "Imported Project" msgstr "已导入的项目" #: editor/project_manager.cpp +msgid "Invalid Project Name." +msgstr "æ— æ•ˆé¡¹ç›®å称。" + +#: editor/project_manager.cpp msgid "Couldn't create folder." msgstr "æ— æ³•åˆ›å»ºæ–‡ä»¶å¤¹ã€‚" @@ -6128,9 +6127,9 @@ msgstr "é¼ æ ‡æŒ‰é”®" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" -msgstr "" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." +msgstr "æ— æ•ˆçš„æ“作å称。它ä¸èƒ½æ˜¯ç©ºçš„也ä¸èƒ½åŒ…å« '/', ':', '=', '\\' 或者 '\"'。" #: editor/project_settings_editor.cpp msgid "Action '%s' already exists!" @@ -6157,8 +6156,8 @@ msgid "Control+" msgstr "Ctrl+" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." -msgstr "按下一个键.." +msgid "Press a Key..." +msgstr "按下一个键..." #: editor/project_settings_editor.cpp msgid "Mouse Button Index:" @@ -6341,7 +6340,7 @@ msgid "Property:" msgstr "属性:" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "é‡å†™çš„......" #: editor/project_settings_editor.cpp @@ -6386,7 +6385,7 @@ msgstr "地区é‡å®šå‘:" #: editor/project_settings_editor.cpp msgid "Locale" -msgstr "地区" +msgstr "区域" #: editor/project_settings_editor.cpp msgid "Locales Filter" @@ -6437,12 +6436,12 @@ msgid "Easing Out-In" msgstr "å缓入缓出" #: editor/property_editor.cpp -msgid "File.." -msgstr "文件.." +msgid "File..." +msgstr "文件..." #: editor/property_editor.cpp -msgid "Dir.." -msgstr "目录.." +msgid "Dir..." +msgstr "目录..." #: editor/property_editor.cpp msgid "Assign" @@ -6612,8 +6611,8 @@ msgid "This operation can't be done on instanced scenes." msgstr "æ¤æ“作ä¸èƒ½åº”用于实例化的场景。" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." -msgstr "将新场景å¦å˜ä¸º.." +msgid "Save New Scene As..." +msgstr "将新场景å¦å˜ä¸º..." #: editor/scene_tree_dock.cpp msgid "Editable Children" @@ -7323,7 +7322,7 @@ msgstr "拾å–è·ç¦»:" #: modules/mono/csharp_script.cpp msgid "Class name can't be a reserved keyword" -msgstr "" +msgstr "ç±»åä¸èƒ½æ˜¯ä¿ç•™å…³é”®å—" #: modules/mono/editor/godotsharp_editor.cpp msgid "Generating solution..." @@ -7347,7 +7346,7 @@ msgstr "完æˆ" #: modules/mono/editor/godotsharp_editor.cpp msgid "Failed to create C# project." -msgstr "创建C#项目失败" +msgstr "创建C#项目失败。" #: modules/mono/editor/godotsharp_editor.cpp msgid "Mono" @@ -7992,7 +7991,7 @@ msgstr "path属性必须指å‘ä¸€ä¸ªåˆæ³•çš„Spatial节点æ‰èƒ½æ£å¸¸å·¥ä½œã€‚" #: scene/3d/scenario_fx.cpp msgid "WorldEnvironment needs an Environment resource." -msgstr "" +msgstr "WorldEnvironment需è¦ä¸€ä¸ªçŽ¯å¢ƒèµ„æºã€‚" #: scene/3d/scenario_fx.cpp msgid "" @@ -8004,6 +8003,8 @@ msgid "" "This WorldEnvironment is ignored. Either add a Camera (for 3D scenes) or set " "this environment's Background Mode to Canvas (for 2D scenes)." msgstr "" +"这个WorldEnvironmentè¢«å¿½ç•¥ã€‚æ·»åŠ æ‘„åƒå¤´ï¼ˆç”¨äºŽ3D场景)或将æ¤çŽ¯å¢ƒçš„èƒŒæ™¯æ¨¡å¼è®¾ç½®" +"为画布(用于2D场景)。" #: scene/3d/sprite_3d.cpp msgid "" @@ -8096,6 +8097,13 @@ msgstr "åŠ è½½å—体出错。" msgid "Invalid font size." msgstr "å—体大å°éžæ³•。" +#, fuzzy +#~ msgid "Previous" +#~ msgstr "上一个目录" + +#~ msgid "Next" +#~ msgstr "下一项" + #~ msgid "Invalid action (anything goes but '/' or ':')." #~ msgstr "Actionåéžæ³•(ä¸å¾—包å«'/'或':')。" @@ -8119,9 +8127,6 @@ msgstr "å—体大å°éžæ³•。" #~ msgid "Couldn't get project.godot in the project path." #~ msgstr "æ— æ³•åœ¨é¡¹ç›®ç›®å½•ä¸‹æ‰¾åˆ°project.godot文件。" -#~ msgid "Next" -#~ msgstr "下一项" - #~ msgid "Not found!" #~ msgstr "未找到ï¼" @@ -8268,8 +8273,8 @@ msgstr "å—体大å°éžæ³•。" #~ msgid "Exporting for %s" #~ msgstr "æ£åœ¨å¯¼å‡º %s" -#~ msgid "Setting Up.." -#~ msgstr "é…ç½®.." +#~ msgid "Setting Up..." +#~ msgstr "é…ç½®..." #~ msgid "Error loading scene." #~ msgstr "åŠ è½½åœºæ™¯å‡ºé”™ã€‚" @@ -8329,8 +8334,8 @@ msgstr "å—体大å°éžæ³•。" #~ msgid "Info" #~ msgstr "ä¿¡æ¯" -#~ msgid "Re-Import.." -#~ msgstr "釿–°å¯¼å…¥.." +#~ msgid "Re-Import..." +#~ msgstr "釿–°å¯¼å…¥..." #~ msgid "No bit masks to import!" #~ msgstr "没有è¦å¯¼å…¥çš„bit masksï¼" @@ -8721,14 +8726,14 @@ msgstr "å—体大å°éžæ³•。" #~ msgid "Zoom (%):" #~ msgstr "缩放(%):" -#~ msgid "Skeleton.." -#~ msgstr "骨骼.." +#~ msgid "Skeleton..." +#~ msgstr "骨骼..." #~ msgid "Zoom Reset" #~ msgstr "é‡ç½®ç¼©æ”¾" -#~ msgid "Zoom Set.." -#~ msgstr "设置缩放.." +#~ msgid "Zoom Set..." +#~ msgstr "设置缩放..." #~ msgid "Set a Value" #~ msgstr "设置值" @@ -9196,8 +9201,8 @@ msgstr "å—体大å°éžæ³•。" #~ msgid "Export Project PCK" #~ msgstr "导出项目PCK文件" -#~ msgid "Export.." -#~ msgstr "导出.." +#~ msgid "Export..." +#~ msgstr "导出..." #~ msgid "Project Export" #~ msgstr "项目导出" @@ -9286,7 +9291,7 @@ msgstr "å—体大å°éžæ³•。" #~ msgid "Reload Tool Script (Soft)" #~ msgstr "釿–°åŠ è½½Tool脚本(Soft)" -#~ msgid "Edit Connections.." +#~ msgid "Edit Connections..." #~ msgstr "编辑事件连接" #~ msgid "Set Params" diff --git a/editor/translations/zh_HK.po b/editor/translations/zh_HK.po index f4c6a39788..568390a7a8 100644 --- a/editor/translations/zh_HK.po +++ b/editor/translations/zh_HK.po @@ -534,7 +534,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "ç”± '%s' 連到 '%s'" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "連到..." #: editor/connections_dialog.cpp @@ -972,11 +972,11 @@ msgid "Move Audio Bus" msgstr "移動" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1126,11 +1126,11 @@ msgid "Updating Scene" msgstr "æ›´æ–°å ´æ™¯" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "å„²å˜æœ¬åœ°æ›´æ”¹.." +msgid "Storing local changes..." +msgstr "å„²å˜æœ¬åœ°æ›´æ”¹..." #: editor/editor_data.cpp -msgid "Updating scene.." +msgid "Updating scene..." msgstr "æ£åœ¨æ›´æ–°å ´æ™¯..." #: editor/editor_data.cpp @@ -1203,7 +1203,7 @@ msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp #, fuzzy -msgid "New Folder.." +msgid "New Folder..." msgstr "新增資料夾" #: editor/editor_file_dialog.cpp @@ -1481,12 +1481,12 @@ msgid "Error saving resource!" msgstr "儲å˜è³‡æºæ™‚出ç¾éŒ¯èª¤ï¼" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "把資æºå¦å˜ç‚º..." #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "如來如æ¤" #: editor/editor_node.cpp @@ -1713,11 +1713,11 @@ msgid "Open Base Scene" msgstr "é–‹å•ŸåŸºç¤Žå ´æ™¯" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "å¿«é€Ÿé–‹å•Ÿå ´æ™¯ï¼Žï¼Ž" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "快速開啟腳本.." #: editor/editor_node.cpp @@ -1731,7 +1731,7 @@ msgid "Save changes to '%s' before closing?" msgstr "關閉å‰è¦å…ˆå„²å˜å° '%s' 任何更改嗎?" #: editor/editor_node.cpp -msgid "Save Scene As.." +msgid "Save Scene As..." msgstr "æŠŠå ´æ™¯å¦å˜ç‚º" #: editor/editor_node.cpp @@ -1783,8 +1783,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "快速é‹è¡Œå ´æ™¯.." +msgid "Quick Run Scene..." +msgstr "快速é‹è¡Œå ´æ™¯..." #: editor/editor_node.cpp msgid "Quit" @@ -1943,8 +1943,8 @@ msgstr "上一個tab" #: editor/editor_node.cpp #, fuzzy -msgid "Filter Files.." -msgstr "ç¯©é¸æª”案.." +msgid "Filter Files..." +msgstr "ç¯©é¸æª”案..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1955,11 +1955,11 @@ msgid "New Scene" msgstr "æ–°å¢žå ´æ™¯" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." +msgid "Open Scene..." msgstr "é–‹å•“å ´æ™¯" #: editor/editor_node.cpp @@ -1979,16 +1979,16 @@ msgid "Open Recent" msgstr "開啓最近的" #: editor/editor_node.cpp -msgid "Convert To.." +msgid "Convert To..." msgstr "轉為..." #: editor/editor_node.cpp -msgid "MeshLibrary.." -msgstr "MeshLibrary.." +msgid "MeshLibrary..." +msgstr "MeshLibrary..." #: editor/editor_node.cpp -msgid "TileSet.." -msgstr "TileSet.." +msgid "TileSet..." +msgstr "TileSet..." #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp #: scene/gui/line_edit.cpp scene/gui/text_edit.cpp @@ -2236,8 +2236,8 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." -msgstr "å¦å˜ç‚º.." +msgid "Save As..." +msgstr "å¦å˜ç‚º..." #: editor/editor_node.cpp msgid "Go to the previous edited object in history." @@ -2351,7 +2351,7 @@ msgstr "" #: editor/editor_plugin.cpp #, fuzzy -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "縮圖" #: editor/editor_plugin_settings.cpp @@ -2507,7 +2507,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2591,7 +2591,7 @@ msgstr "請求時出ç¾éŒ¯èª¤" #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "連到..." #: editor/export_template_manager.cpp @@ -2610,7 +2610,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Connecting.." +msgid "Connecting..." msgstr "連到..." #: editor/export_template_manager.cpp @@ -2625,7 +2625,7 @@ msgstr "連到" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "請求ä¸..." #: editor/export_template_manager.cpp @@ -2771,13 +2771,13 @@ msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Rename.." -msgstr "釿–°å‘½å.." +msgid "Rename..." +msgstr "釿–°å‘½å..." #: editor/filesystem_dock.cpp #, fuzzy -msgid "Move To.." -msgstr "æ¬åˆ°.." +msgid "Move To..." +msgstr "æ¬åˆ°..." #: editor/filesystem_dock.cpp #, fuzzy @@ -2789,16 +2789,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "複製" #: editor/filesystem_dock.cpp @@ -2824,7 +2824,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2834,7 +2834,7 @@ msgstr "移動" #: editor/filesystem_dock.cpp editor/plugins/animation_tree_editor_plugin.cpp #: editor/project_manager.cpp msgid "Rename" -msgstr "釿–°å‘½å.." +msgstr "釿–°å‘½å..." #: editor/groups_editor.cpp msgid "Add to Group" @@ -2891,7 +2891,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2903,7 +2903,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2919,8 +2919,8 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." -msgstr "儲å˜ä¸.." +msgid "Saving..." +msgstr "儲å˜ä¸..." #: editor/import_dock.cpp msgid "Set as Default for '%s'" @@ -2941,7 +2941,7 @@ msgid "Import As:" msgstr "å°Žå…¥" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3364,7 +3364,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3372,7 +3372,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3444,7 +3444,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3511,7 +3511,7 @@ msgid "Site:" msgstr "地å€:" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3701,6 +3701,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -4129,7 +4130,7 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp @@ -4281,7 +4282,7 @@ msgstr "" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Marking walkable triangles..." -msgstr "å„²å˜æœ¬åœ°æ›´æ”¹.." +msgstr "å„²å˜æœ¬åœ°æ›´æ”¹..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." @@ -4337,7 +4338,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4704,7 +4705,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4806,7 +4807,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -5019,15 +5020,15 @@ msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5491,11 +5492,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5751,7 +5748,7 @@ msgid "Remove All" msgstr "移除" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5819,8 +5816,9 @@ msgid "Options" msgstr "é¸é …" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" -msgstr "" +#, fuzzy +msgid "Has,Many,Options" +msgstr "é¸é …" #: editor/plugins/theme_editor_plugin.cpp msgid "Tab 1" @@ -5947,7 +5945,7 @@ msgstr "" #: editor/plugins/tile_set_editor_plugin.cpp #, fuzzy msgid "Tile Set" -msgstr "TileSet.." +msgstr "TileSet..." #: editor/plugins/tile_set_editor_plugin.cpp msgid "Create from Scene" @@ -6014,7 +6012,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "æ·»åŠ ..." #: editor/project_export.cpp @@ -6109,6 +6107,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "無效å稱" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "無法新增資料夾" @@ -6303,8 +6306,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6332,7 +6335,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6519,7 +6522,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6616,11 +6619,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6798,7 +6801,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -7331,7 +7334,7 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "Dynamic Library" -msgstr "MeshLibrary.." +msgstr "MeshLibrary..." #: modules/gdnative/gdnative_library_editor_plugin.cpp msgid "Add an architecture entry" @@ -7340,12 +7343,12 @@ msgstr "" #: modules/gdnative/gdnative_library_editor_plugin.cpp #, fuzzy msgid "GDNativeLibrary" -msgstr "MeshLibrary.." +msgstr "MeshLibrary..." #: modules/gdnative/gdnative_library_singleton_editor.cpp #, fuzzy msgid "Library" -msgstr "MeshLibrary.." +msgstr "MeshLibrary..." #: modules/gdnative/gdnative_library_singleton_editor.cpp msgid "Status" @@ -8292,6 +8295,13 @@ msgid "Invalid font size." msgstr "無效å—åž‹" #, fuzzy +#~ msgid "Previous" +#~ msgstr "上一個tab" + +#~ msgid "Next" +#~ msgstr "下一個" + +#, fuzzy #~ msgid "Can't contain '/' or ':'" #~ msgstr "ä¸èƒ½é€£åˆ°ä¸»æ©Ÿï¼š" @@ -8299,9 +8309,6 @@ msgstr "無效å—åž‹" #~ msgid "Can't write file." #~ msgstr "無法新增資料夾" -#~ msgid "Next" -#~ msgstr "下一個" - #~ msgid "Not found!" #~ msgstr "找ä¸åˆ°!" @@ -8452,7 +8459,7 @@ msgstr "無效å—åž‹" #~ msgid "Cannot go into subdir:" #~ msgstr "無法進入次è¦è³‡æ–™å¤¾" -#~ msgid "Edit Connections.." +#~ msgid "Edit Connections..." #~ msgstr "編輯連接" #~ msgid "Live Editing" diff --git a/editor/translations/zh_TW.po b/editor/translations/zh_TW.po index ccbd45bf31..38b565a37f 100644 --- a/editor/translations/zh_TW.po +++ b/editor/translations/zh_TW.po @@ -510,7 +510,7 @@ msgid "Disconnect '%s' from '%s'" msgstr "å°‡ '%s' 從 '%s' 䏿–·é€£æŽ¥" #: editor/connections_dialog.cpp -msgid "Connect.." +msgid "Connect..." msgstr "連接..." #: editor/connections_dialog.cpp @@ -936,11 +936,11 @@ msgid "Move Audio Bus" msgstr "" #: editor/editor_audio_buses.cpp -msgid "Save Audio Bus Layout As.." +msgid "Save Audio Bus Layout As..." msgstr "" #: editor/editor_audio_buses.cpp -msgid "Location for New Layout.." +msgid "Location for New Layout..." msgstr "" #: editor/editor_audio_buses.cpp @@ -1076,12 +1076,12 @@ msgid "Updating Scene" msgstr "æ›´æ–°å ´æ™¯" #: editor/editor_data.cpp -msgid "Storing local changes.." -msgstr "æ£åœ¨å„²å˜è®Šæ›´.." +msgid "Storing local changes..." +msgstr "æ£åœ¨å„²å˜è®Šæ›´..." #: editor/editor_data.cpp -msgid "Updating scene.." -msgstr "æ›´æ–°å ´æ™¯ä¸.." +msgid "Updating scene..." +msgstr "æ›´æ–°å ´æ™¯ä¸..." #: editor/editor_data.cpp msgid "[empty]" @@ -1151,7 +1151,7 @@ msgstr "" #: editor/editor_file_dialog.cpp editor/filesystem_dock.cpp #, fuzzy -msgid "New Folder.." +msgid "New Folder..." msgstr "新增資料夾" #: editor/editor_file_dialog.cpp @@ -1415,12 +1415,12 @@ msgid "Error saving resource!" msgstr "" #: editor/editor_node.cpp editor/plugins/animation_player_editor_plugin.cpp -msgid "Save Resource As.." +msgid "Save Resource As..." msgstr "" #: editor/editor_node.cpp editor/plugins/spatial_editor_plugin.cpp #: editor/scene_tree_dock.cpp -msgid "I see.." +msgid "I see..." msgstr "我知é“了" #: editor/editor_node.cpp @@ -1626,11 +1626,11 @@ msgid "Open Base Scene" msgstr "" #: editor/editor_node.cpp -msgid "Quick Open Scene.." +msgid "Quick Open Scene..." msgstr "å¿«é€Ÿé–‹å•Ÿå ´æ™¯" #: editor/editor_node.cpp -msgid "Quick Open Script.." +msgid "Quick Open Script..." msgstr "" #: editor/editor_node.cpp @@ -1643,8 +1643,8 @@ msgid "Save changes to '%s' before closing?" msgstr "" #: editor/editor_node.cpp -msgid "Save Scene As.." -msgstr "å¦å˜å ´æ™¯ç‚º.." +msgid "Save Scene As..." +msgstr "å¦å˜å ´æ™¯ç‚º..." #: editor/editor_node.cpp msgid "No" @@ -1696,8 +1696,8 @@ msgid "This action cannot be undone. Revert anyway?" msgstr "æ¤æ“作無法復原, 確定è¦é‚„原嗎?" #: editor/editor_node.cpp -msgid "Quick Run Scene.." -msgstr "å¿«é€ŸåŸ·è¡Œå ´æ™¯.." +msgid "Quick Run Scene..." +msgstr "å¿«é€ŸåŸ·è¡Œå ´æ™¯..." #: editor/editor_node.cpp msgid "Quit" @@ -1827,7 +1827,7 @@ msgstr "" #: editor/editor_node.cpp #, fuzzy msgid "Add a new scene." -msgstr "æ›´æ–°å ´æ™¯ä¸.." +msgstr "æ›´æ–°å ´æ™¯ä¸..." #: editor/editor_node.cpp #, fuzzy @@ -1847,8 +1847,8 @@ msgid "Previous tab" msgstr "上個分é " #: editor/editor_node.cpp -msgid "Filter Files.." -msgstr "éŽæ¿¾æª”案.." +msgid "Filter Files..." +msgstr "éŽæ¿¾æª”案..." #: editor/editor_node.cpp msgid "Operations with scene files." @@ -1859,12 +1859,12 @@ msgid "New Scene" msgstr "" #: editor/editor_node.cpp -msgid "New Inherited Scene.." +msgid "New Inherited Scene..." msgstr "" #: editor/editor_node.cpp -msgid "Open Scene.." -msgstr "é–‹å•Ÿå ´æ™¯.." +msgid "Open Scene..." +msgstr "é–‹å•Ÿå ´æ™¯..." #: editor/editor_node.cpp msgid "Save Scene" @@ -1883,15 +1883,15 @@ msgid "Open Recent" msgstr "開啟最近å˜å–" #: editor/editor_node.cpp -msgid "Convert To.." -msgstr "è½‰æ›æˆ.." +msgid "Convert To..." +msgstr "è½‰æ›æˆ..." #: editor/editor_node.cpp -msgid "MeshLibrary.." +msgid "MeshLibrary..." msgstr "" #: editor/editor_node.cpp -msgid "TileSet.." +msgid "TileSet..." msgstr "" #: editor/editor_node.cpp editor/plugins/script_text_editor.cpp @@ -2137,7 +2137,7 @@ msgid "Save the currently edited resource." msgstr "" #: editor/editor_node.cpp editor/plugins/script_editor_plugin.cpp -msgid "Save As.." +msgid "Save As..." msgstr "" #: editor/editor_node.cpp @@ -2248,7 +2248,7 @@ msgid "Creating Mesh Previews" msgstr "" #: editor/editor_plugin.cpp -msgid "Thumbnail.." +msgid "Thumbnail..." msgstr "" #: editor/editor_plugin_settings.cpp @@ -2399,7 +2399,7 @@ msgid "(Current)" msgstr "" #: editor/export_template_manager.cpp -msgid "Retrieving mirrors, please wait.." +msgid "Retrieving mirrors, please wait..." msgstr "" #: editor/export_template_manager.cpp @@ -2479,7 +2479,7 @@ msgstr "è¼‰å…¥å ´æ™¯æ™‚ç™¼ç”ŸéŒ¯èª¤" #: editor/export_template_manager.cpp #, fuzzy -msgid "Connecting to Mirror.." +msgid "Connecting to Mirror..." msgstr "連接..." #: editor/export_template_manager.cpp @@ -2498,7 +2498,7 @@ msgstr "" #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy -msgid "Connecting.." +msgid "Connecting..." msgstr "連接..." #: editor/export_template_manager.cpp @@ -2513,7 +2513,7 @@ msgstr "連接..." #: editor/export_template_manager.cpp #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Requesting.." +msgid "Requesting..." msgstr "" #: editor/export_template_manager.cpp @@ -2653,11 +2653,11 @@ msgid "Collapse all" msgstr "" #: editor/filesystem_dock.cpp -msgid "Rename.." +msgid "Rename..." msgstr "" #: editor/filesystem_dock.cpp -msgid "Move To.." +msgid "Move To..." msgstr "" #: editor/filesystem_dock.cpp @@ -2670,16 +2670,16 @@ msgid "Instance" msgstr "" #: editor/filesystem_dock.cpp -msgid "Edit Dependencies.." +msgid "Edit Dependencies..." msgstr "" #: editor/filesystem_dock.cpp -msgid "View Owners.." +msgid "View Owners..." msgstr "" #: editor/filesystem_dock.cpp #, fuzzy -msgid "Duplicate.." +msgid "Duplicate..." msgstr "複製動畫關éµç•«æ ¼" #: editor/filesystem_dock.cpp @@ -2705,7 +2705,7 @@ msgstr "" #: editor/filesystem_dock.cpp msgid "" "Scanning Files,\n" -"Please Wait.." +"Please Wait..." msgstr "" #: editor/filesystem_dock.cpp @@ -2772,7 +2772,7 @@ msgid "Import Scene" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Importing Scene.." +msgid "Importing Scene..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2784,7 +2784,7 @@ msgid "Generating for Mesh: " msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Running Custom Script.." +msgid "Running Custom Script..." msgstr "" #: editor/import/resource_importer_scene.cpp @@ -2800,7 +2800,7 @@ msgid "Error running post-import script:" msgstr "" #: editor/import/resource_importer_scene.cpp -msgid "Saving.." +msgid "Saving..." msgstr "" #: editor/import_dock.cpp @@ -2820,7 +2820,7 @@ msgid "Import As:" msgstr "" #: editor/import_dock.cpp editor/property_editor.cpp -msgid "Preset.." +msgid "Preset..." msgstr "" #: editor/import_dock.cpp @@ -3112,7 +3112,7 @@ msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp #, fuzzy msgid "Edit Filters" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/plugins/animation_tree_editor_plugin.cpp #: editor/plugins/multimesh_editor_plugin.cpp @@ -3237,7 +3237,7 @@ msgid "Transition Node" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Import Animations.." +msgid "Import Animations..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3245,7 +3245,7 @@ msgid "Edit Node Filters" msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp -msgid "Filters.." +msgid "Filters..." msgstr "" #: editor/plugins/animation_tree_editor_plugin.cpp @@ -3263,7 +3263,7 @@ msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp #, fuzzy msgid "View Files" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/plugins/asset_library_editor_plugin.cpp msgid "Can't resolve hostname:" @@ -3314,7 +3314,7 @@ msgid "Fetching:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Resolving.." +msgid "Resolving..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3343,8 +3343,9 @@ msgid "first" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp +#, fuzzy msgid "prev" -msgstr "" +msgstr "é 覽:" #: editor/plugins/asset_library_editor_plugin.cpp msgid "next" @@ -3382,7 +3383,7 @@ msgid "Site:" msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp -msgid "Support.." +msgid "Support..." msgstr "" #: editor/plugins/asset_library_editor_plugin.cpp @@ -3571,6 +3572,7 @@ msgid "Use Rotation Snap" msgstr "" #: editor/plugins/canvas_item_editor_plugin.cpp +#: editor/plugins/spatial_editor_plugin.cpp msgid "Configure Snap..." msgstr "" @@ -3997,18 +3999,18 @@ msgid "Create Convex Collision Sibling" msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp -msgid "Create Outline Mesh.." +msgid "Create Outline Mesh..." msgstr "" #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy msgid "View UV1" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/plugins/mesh_instance_editor_plugin.cpp #, fuzzy msgid "View UV2" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/plugins/mesh_instance_editor_plugin.cpp msgid "Unwrap UV2 for Lightmap/AO" @@ -4149,7 +4151,7 @@ msgstr "" #: editor/plugins/navigation_mesh_generator.cpp #, fuzzy msgid "Marking walkable triangles..." -msgstr "æ£åœ¨å„²å˜è®Šæ›´.." +msgstr "æ£åœ¨å„²å˜è®Šæ›´..." #: editor/plugins/navigation_mesh_generator.cpp msgid "Constructing compact heightfield..." @@ -4205,7 +4207,7 @@ msgid "Error loading image:" msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp -msgid "No pixels with transparency > 128 in image.." +msgid "No pixels with transparency > 128 in image..." msgstr "" #: editor/plugins/particles_2d_editor_plugin.cpp @@ -4571,7 +4573,7 @@ msgid "Import Theme" msgstr "" #: editor/plugins/script_editor_plugin.cpp -msgid "Save Theme As.." +msgid "Save Theme As..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4669,7 +4671,7 @@ msgstr "" #: editor/plugins/script_editor_plugin.cpp #: editor/plugins/script_text_editor.cpp -msgid "Find.." +msgid "Find..." msgstr "" #: editor/plugins/script_editor_plugin.cpp @@ -4765,7 +4767,7 @@ msgstr "" #: editor/plugins/script_text_editor.cpp #, fuzzy msgid "Convert Case" -msgstr "è½‰æ›æˆ.." +msgstr "è½‰æ›æˆ..." #: editor/plugins/script_text_editor.cpp msgid "Uppercase" @@ -4869,27 +4871,27 @@ msgstr "" #: editor/plugins/script_text_editor.cpp #, fuzzy msgid "Convert To Uppercase" -msgstr "è½‰æ›æˆ.." +msgstr "è½‰æ›æˆ..." #: editor/plugins/script_text_editor.cpp #, fuzzy msgid "Convert To Lowercase" -msgstr "è½‰æ›æˆ.." +msgstr "è½‰æ›æˆ..." #: editor/plugins/script_text_editor.cpp msgid "Find Previous" msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Replace.." +msgid "Replace..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Function.." +msgid "Goto Function..." msgstr "" #: editor/plugins/script_text_editor.cpp -msgid "Goto Line.." +msgid "Goto Line..." msgstr "" #: editor/plugins/script_text_editor.cpp @@ -5079,7 +5081,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy msgid "Material Changes" -msgstr "æ£åœ¨å„²å˜è®Šæ›´.." +msgstr "æ£åœ¨å„²å˜è®Šæ›´..." #: editor/plugins/spatial_editor_plugin.cpp msgid "Shader Changes" @@ -5192,7 +5194,7 @@ msgstr "" #: editor/plugins/spatial_editor_plugin.cpp #, fuzzy msgid "View FPS" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/plugins/spatial_editor_plugin.cpp msgid "Half Resolution" @@ -5346,11 +5348,7 @@ msgid "Transform" msgstr "" #: editor/plugins/spatial_editor_plugin.cpp -msgid "Configure Snap.." -msgstr "" - -#: editor/plugins/spatial_editor_plugin.cpp -msgid "Transform Dialog.." +msgid "Transform Dialog..." msgstr "" #: editor/plugins/spatial_editor_plugin.cpp @@ -5604,7 +5602,7 @@ msgid "Remove All" msgstr "移除" #: editor/plugins/theme_editor_plugin.cpp -msgid "Edit theme.." +msgid "Edit theme..." msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5672,7 +5670,7 @@ msgid "Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp -msgid "Have,Many,Several,Options!" +msgid "Has,Many,Options" msgstr "" #: editor/plugins/theme_editor_plugin.cpp @@ -5863,7 +5861,7 @@ msgid "Presets" msgstr "" #: editor/project_export.cpp editor/project_settings_editor.cpp -msgid "Add.." +msgid "Add..." msgstr "" #: editor/project_export.cpp @@ -5957,6 +5955,11 @@ msgstr "" #: editor/project_manager.cpp #, fuzzy +msgid "Invalid Project Name." +msgstr "ä¸èƒ½ä½¿ç”¨çš„å稱。" + +#: editor/project_manager.cpp +#, fuzzy msgid "Couldn't create folder." msgstr "無法新增資料夾" @@ -6148,8 +6151,8 @@ msgstr "" #: editor/project_settings_editor.cpp msgid "" -"Invalid action name. it cannot be empty nor contain '/', ':', '=', '\\' or " -"'\"'" +"Invalid action name. It cannot be empty nor contain '/', ':', '=', '\\' or " +"'\"'." msgstr "" #: editor/project_settings_editor.cpp @@ -6177,7 +6180,7 @@ msgid "Control+" msgstr "" #: editor/project_settings_editor.cpp editor/settings_config_dialog.cpp -msgid "Press a Key.." +msgid "Press a Key..." msgstr "" #: editor/project_settings_editor.cpp @@ -6365,7 +6368,7 @@ msgid "Property:" msgstr "" #: editor/project_settings_editor.cpp -msgid "Override For.." +msgid "Override For..." msgstr "" #: editor/project_settings_editor.cpp @@ -6427,7 +6430,7 @@ msgstr "" #: editor/project_settings_editor.cpp #, fuzzy msgid "Filter mode:" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/project_settings_editor.cpp msgid "Locales:" @@ -6462,11 +6465,11 @@ msgid "Easing Out-In" msgstr "" #: editor/property_editor.cpp -msgid "File.." +msgid "File..." msgstr "" #: editor/property_editor.cpp -msgid "Dir.." +msgid "Dir..." msgstr "" #: editor/property_editor.cpp @@ -6496,7 +6499,7 @@ msgstr "" #: editor/property_editor.cpp #, fuzzy msgid "Convert To %s" -msgstr "è½‰æ›æˆ.." +msgstr "è½‰æ›æˆ..." #: editor/property_editor.cpp msgid "Error loading file: Not a resource!" @@ -6638,7 +6641,7 @@ msgid "This operation can't be done on instanced scenes." msgstr "" #: editor/scene_tree_dock.cpp -msgid "Save New Scene As.." +msgid "Save New Scene As..." msgstr "" #: editor/scene_tree_dock.cpp @@ -6745,7 +6748,7 @@ msgstr "" #: editor/scene_tree_dock.cpp #, fuzzy msgid "Filter nodes" -msgstr "éŽæ¿¾æª”案.." +msgstr "éŽæ¿¾æª”案..." #: editor/scene_tree_dock.cpp msgid "Attach a new or existing script for the selected node." @@ -8120,6 +8123,10 @@ msgstr "讀å–å—體錯誤。" msgid "Invalid font size." msgstr "無效的å—體大å°ã€‚" +#, fuzzy +#~ msgid "Previous" +#~ msgstr "上個分é " + #~ msgid "Next" #~ msgstr "下一個" @@ -8138,10 +8145,6 @@ msgstr "無效的å—體大å°ã€‚" #~ msgid "Skip" #~ msgstr "è·³éŽ" -#, fuzzy -#~ msgid "preview" -#~ msgstr "é 覽:" - #~ msgid "List:" #~ msgstr "列表:" diff --git a/main/SCsub b/main/SCsub index e2bf03234f..0692175799 100644 --- a/main/SCsub +++ b/main/SCsub @@ -131,20 +131,20 @@ env.add_source_files(env.main_sources, "*.cpp") controller_databases = ["#main/gamecontrollerdb.txt", "#main/gamecontrollerdb_205.txt", "#main/gamecontrollerdb_204.txt", "#main/godotcontrollerdb.txt"] env.Depends("#main/default_controller_mappings.gen.cpp", controller_databases) -env.Command("#main/default_controller_mappings.gen.cpp", controller_databases, make_default_controller_mappings) +env.CommandNoCache("#main/default_controller_mappings.gen.cpp", controller_databases, make_default_controller_mappings) env.main_sources.append("#main/default_controller_mappings.gen.cpp") Export('env') env.Depends("#main/splash.gen.h", "#main/splash.png") -env.Command("#main/splash.gen.h", "#main/splash.png", make_splash) +env.CommandNoCache("#main/splash.gen.h", "#main/splash.png", make_splash) env.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png") -env.Command("#main/splash_editor.gen.h", "#main/splash_editor.png", make_splash_editor) +env.CommandNoCache("#main/splash_editor.gen.h", "#main/splash_editor.png", make_splash_editor) env.Depends("#main/app_icon.gen.h", "#main/app_icon.png") -env.Command("#main/app_icon.gen.h", "#main/app_icon.png", make_app_icon) +env.CommandNoCache("#main/app_icon.gen.h", "#main/app_icon.png", make_app_icon) SConscript('tests/SCsub') diff --git a/main/app_icon.png b/main/app_icon.png Binary files differindex 1d75cdc710..cf31af18a4 100644 --- a/main/app_icon.png +++ b/main/app_icon.png diff --git a/main/main.cpp b/main/main.cpp index ad49e1f5bd..e2b3bb8e6f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -263,6 +263,7 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print("Standalone tools:\n"); OS::get_singleton()->print(" -s, --script <script> Run a script.\n"); + OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n"); #ifdef TOOLS_ENABLED OS::get_singleton()->print(" --export <target> Export the project using the given export target. Export only main pack if path ends with .pck or .zip'.\n"); OS::get_singleton()->print(" --export-debug <target> Like --export, but use debug template.\n"); @@ -538,6 +539,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } else if (I->get() == "--build-solutions") { // Build the scripting solution such C# auto_build_solutions = true; + editor = true; #endif } else if (I->get() == "--no-window") { // disable window creation, Windows only @@ -865,6 +867,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/allow_per_pixel_transparency", false); video_mode.use_vsync = GLOBAL_DEF("display/window/vsync/use_vsync", true); + OS::get_singleton()->_use_vsync = video_mode.use_vsync; + video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency", false); video_mode.layered_splash = GLOBAL_DEF("display/window/per_pixel_transparency_splash", false); @@ -1239,6 +1243,7 @@ bool Main::start() { String test; String _export_preset; bool export_debug = false; + bool check_only = false; main_timer_sync.init(OS::get_singleton()->get_ticks_usec()); @@ -1261,6 +1266,8 @@ bool Main::start() { bool parsed_pair = true; if (args[i] == "-s" || args[i] == "--script") { script = args[i + 1]; + } else if (args[i] == "--check-only") { + check_only = true; } else if (args[i] == "--test") { test = args[i + 1]; #ifdef TOOLS_ENABLED @@ -1383,6 +1390,10 @@ bool Main::start() { ERR_EXPLAIN("Can't load script: " + script); ERR_FAIL_COND_V(script_res.is_null(), false); + if (check_only) { + return false; + } + if (script_res->can_instance() /*&& script_res->inherits_from("SceneTreeScripted")*/) { StringName instance_type = script_res->get_instance_base_type(); @@ -1706,7 +1717,7 @@ bool Main::start() { uint64_t Main::last_ticks = 0; uint64_t Main::target_ticks = 0; -Array Main::frame_times = Array(); +uint32_t Main::frames = 0; uint32_t Main::frame = 0; bool Main::force_redraw_requested = false; @@ -1807,9 +1818,6 @@ bool Main::iteration() { } } - if (AudioServer::get_singleton()) - AudioServer::get_singleton()->update(); - idle_process_ticks = OS::get_singleton()->get_ticks_usec() - idle_begin; idle_process_max = MAX(idle_process_ticks, idle_process_max); uint64_t frame_time = OS::get_singleton()->get_ticks_usec() - ticks; @@ -1825,19 +1833,10 @@ bool Main::iteration() { script_debugger->idle_poll(); } + frames++; Engine::get_singleton()->_idle_frames++; - // FPS counter - frame_times.push_back(ticks); - int frames = frame_times.size(); - - while (frame_times.size() > 0 && (int)frame_times.get(0) <= ticks - 1000000) { - frame_times.pop_front(); - } - - int update_frequency = MAX(1, (int)GLOBAL_GET("debug/settings/performance/update_frequency_msec")); - - if (frame > update_frequency * 1000) { + if (frame > 1000000) { if (editor || project_manager) { if (print_fps) { @@ -1853,7 +1852,8 @@ bool Main::iteration() { idle_process_max = 0; physics_process_max = 0; - frame %= update_frequency * 1000; + frame %= 1000000; + frames = 0; } if (fixed_fps != -1) diff --git a/main/main.h b/main/main.h index 8f264d7720..c20592bf3b 100644 --- a/main/main.h +++ b/main/main.h @@ -44,7 +44,7 @@ class Main { static void print_help(const char *p_binary); static uint64_t last_ticks; static uint64_t target_ticks; - static Array frame_times; + static uint32_t frames; static uint32_t frame; static bool force_redraw_requested; diff --git a/main/splash.png b/main/splash.png Binary files differindex 34be46557f..32960db65f 100644 --- a/main/splash.png +++ b/main/splash.png diff --git a/main/splash_editor.png b/main/splash_editor.png Binary files differindex d8677f1749..f003995d6f 100644 --- a/main/splash_editor.png +++ b/main/splash_editor.png diff --git a/main/tests/test_main.cpp b/main/tests/test_main.cpp index c9431a1a09..cbc1107acb 100644 --- a/main/tests/test_main.cpp +++ b/main/tests/test_main.cpp @@ -50,15 +50,20 @@ const char **tests_get_names() { static const char *test_names[] = { "string", - "containers", "math", + "physics", + "physics_2d", "render", - "multimesh", + "oa_hash_map", "gui", "io", "shaderlang", - "physics", - "oa_hash_map", + "gd_tokenizer", + "gd_parser", + "gd_compiler", + "gd_bytecode", + "image", + "ordered_hash_map", NULL }; diff --git a/methods.py b/methods.py index 7cdc160075..227a17d312 100644 --- a/methods.py +++ b/methods.py @@ -1,5 +1,5 @@ import os -from compat import iteritems +from compat import iteritems, itervalues, open_utf8, escape_string def add_source_files(self, sources, filetype, lib_env=None, shared=False): @@ -515,6 +515,232 @@ def build_gles2_headers(target, source, env): for x in source: build_legacygl_header(str(x), include="drivers/gles2/shader_gles2.h", class_suffix="GLES2", output_attribs=True, gles2=True) +def make_authors_header(target, source, env): + + sections = ["Project Founders", "Lead Developer", "Project Manager", "Developers"] + sections_id = ["AUTHORS_FOUNDERS", "AUTHORS_LEAD_DEVELOPERS", "AUTHORS_PROJECT_MANAGERS", "AUTHORS_DEVELOPERS"] + + src = source[0].srcnode().abspath + dst = target[0].srcnode().abspath + f = open_utf8(src, "r") + g = open_utf8(dst, "w") + + g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") + g.write("#ifndef _EDITOR_AUTHORS_H\n") + g.write("#define _EDITOR_AUTHORS_H\n") + + current_section = "" + reading = False + + def close_section(): + g.write("\t0\n") + g.write("};\n") + + for line in f: + if reading: + if line.startswith(" "): + g.write("\t\"" + escape_string(line.strip()) + "\",\n") + continue + if line.startswith("## "): + if reading: + close_section() + reading = False + for i in range(len(sections)): + if line.strip().endswith(sections[i]): + current_section = escape_string(sections_id[i]) + reading = True + g.write("const char *const " + current_section + "[] = {\n") + break + + if reading: + close_section() + + g.write("#endif\n") + + g.close() + f.close() + +def make_donors_header(target, source, env): + + sections = ["Platinum sponsors", "Gold sponsors", "Mini sponsors", + "Gold donors", "Silver donors", "Bronze donors"] + sections_id = ["DONORS_SPONSOR_PLAT", "DONORS_SPONSOR_GOLD", "DONORS_SPONSOR_MINI", + "DONORS_GOLD", "DONORS_SILVER", "DONORS_BRONZE"] + + src = source[0].srcnode().abspath + dst = target[0].srcnode().abspath + f = open_utf8(src, "r") + g = open_utf8(dst, "w") + + g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") + g.write("#ifndef _EDITOR_DONORS_H\n") + g.write("#define _EDITOR_DONORS_H\n") + + current_section = "" + reading = False + + def close_section(): + g.write("\t0\n") + g.write("};\n") + + for line in f: + if reading >= 0: + if line.startswith(" "): + g.write("\t\"" + escape_string(line.strip()) + "\",\n") + continue + if line.startswith("## "): + if reading: + close_section() + reading = False + for i in range(len(sections)): + if line.strip().endswith(sections[i]): + current_section = escape_string(sections_id[i]) + reading = True + g.write("const char *const " + current_section + "[] = {\n") + break + + if reading: + close_section() + + g.write("#endif\n") + + g.close() + f.close() + + +def make_license_header(target, source, env): + src_copyright = source[0].srcnode().abspath + src_license = source[1].srcnode().abspath + dst = target[0].srcnode().abspath + + class LicenseReader: + def __init__(self, license_file): + self._license_file = license_file + self.line_num = 0 + self.current = self.next_line() + + def next_line(self): + line = self._license_file.readline() + self.line_num += 1 + while line.startswith("#"): + line = self._license_file.readline() + self.line_num += 1 + self.current = line + return line + + def next_tag(self): + if not ':' in self.current: + return ('',[]) + tag, line = self.current.split(":", 1) + lines = [line.strip()] + while self.next_line() and self.current.startswith(" "): + lines.append(self.current.strip()) + return (tag, lines) + + from collections import OrderedDict + projects = OrderedDict() + license_list = [] + + with open_utf8(src_copyright, "r") as copyright_file: + reader = LicenseReader(copyright_file) + part = {} + while reader.current: + tag, content = reader.next_tag() + if tag in ("Files", "Copyright", "License"): + part[tag] = content[:] + elif tag == "Comment": + # attach part to named project + projects[content[0]] = projects.get(content[0], []) + [part] + + if not tag or not reader.current: + # end of a paragraph start a new part + if "License" in part and not "Files" in part: + # no Files tag in this one, so assume standalone license + license_list.append(part["License"]) + part = {} + reader.next_line() + + data_list = [] + for project in itervalues(projects): + for part in project: + part["file_index"] = len(data_list) + data_list += part["Files"] + part["copyright_index"] = len(data_list) + data_list += part["Copyright"] + + with open_utf8(dst, "w") as f: + + f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") + f.write("#ifndef _EDITOR_LICENSE_H\n") + f.write("#define _EDITOR_LICENSE_H\n") + f.write("const char *const GODOT_LICENSE_TEXT =") + + with open_utf8(src_license, "r") as license_file: + for line in license_file: + escaped_string = escape_string(line.strip()) + f.write("\n\t\t\"" + escaped_string + "\\n\"") + f.write(";\n\n") + + f.write("struct ComponentCopyrightPart {\n" + "\tconst char *license;\n" + "\tconst char *const *files;\n" + "\tconst char *const *copyright_statements;\n" + "\tint file_count;\n" + "\tint copyright_count;\n" + "};\n\n") + + f.write("struct ComponentCopyright {\n" + "\tconst char *name;\n" + "\tconst ComponentCopyrightPart *parts;\n" + "\tint part_count;\n" + "};\n\n") + + f.write("const char *const COPYRIGHT_INFO_DATA[] = {\n") + for line in data_list: + f.write("\t\"" + escape_string(line) + "\",\n") + f.write("};\n\n") + + f.write("const ComponentCopyrightPart COPYRIGHT_PROJECT_PARTS[] = {\n") + part_index = 0 + part_indexes = {} + for project_name, project in iteritems(projects): + part_indexes[project_name] = part_index + for part in project: + f.write("\t{ \"" + escape_string(part["License"][0]) + "\", " + + "©RIGHT_INFO_DATA[" + str(part["file_index"]) + "], " + + "©RIGHT_INFO_DATA[" + str(part["copyright_index"]) + "], " + + str(len(part["Files"])) + ", " + + str(len(part["Copyright"])) + " },\n") + part_index += 1 + f.write("};\n\n") + + f.write("const int COPYRIGHT_INFO_COUNT = " + str(len(projects)) + ";\n") + + f.write("const ComponentCopyright COPYRIGHT_INFO[] = {\n") + for project_name, project in iteritems(projects): + f.write("\t{ \"" + escape_string(project_name) + "\", " + + "©RIGHT_PROJECT_PARTS[" + str(part_indexes[project_name]) + "], " + + str(len(project)) + " },\n") + f.write("};\n\n") + + f.write("const int LICENSE_COUNT = " + str(len(license_list)) + ";\n") + + f.write("const char *const LICENSE_NAMES[] = {\n") + for l in license_list: + f.write("\t\"" + escape_string(l[0]) + "\",\n") + f.write("};\n\n") + + f.write("const char *const LICENSE_BODIES[] = {\n\n") + for l in license_list: + for line in l[1:]: + if line == ".": + f.write("\t\"\\n\"\n") + else: + f.write("\t\"" + escape_string(line) + "\\n\"\n") + f.write("\t\"\",\n\n") + f.write("};\n\n") + + f.write("#endif\n") def add_module_version_string(self,s): self.module_version_string += "." + s @@ -1103,3 +1329,8 @@ def add_program(env, name, sources, **args): program = env.Program(name, sources, **args) env.NoCache(program) return program + +def CommandNoCache(env, target, sources, command, **args): + result = env.Command(target, sources, command, **args) + env.NoCache(result) + return result diff --git a/misc/dist/appimage/AppRun b/misc/dist/appimage/AppRun deleted file mode 100755 index db3398a92a..0000000000 --- a/misc/dist/appimage/AppRun +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -HERE="$(dirname "$(readlink -f "${0}")")" -"${HERE}"/godot $@ diff --git a/misc/dist/appimage/godot.desktop b/misc/dist/appimage/godot.desktop deleted file mode 100644 index 545c491256..0000000000 --- a/misc/dist/appimage/godot.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Name=Godot Engine -GenericName=Libre game engine -Comment=Multi-platform 2D and 3D game engine with a feature rich editor -Exec=godot -pm -Icon=godot -Terminal=false -Type=Application -Categories=Development;IDE; diff --git a/misc/dist/appimage/godot.png b/misc/dist/appimage/godot.png Binary files differdeleted file mode 100644 index e334f5fa78..0000000000 --- a/misc/dist/appimage/godot.png +++ /dev/null diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-480h@2x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-480h@2x.png Binary files differindex 6b9b10daae..1299ceaee5 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-480h@2x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-480h@2x.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png Binary files differindex 50497496b7..604a7ba701 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png Binary files differindex 1c489de69f..bffb8c9fde 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-736h@3x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-736h@3x.png Binary files differindex d82dfce936..47826cd683 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-736h@3x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-736h@3x.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-X.png Binary files differindex 5120595df8..0f44a704b5 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-X.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape-X.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape.png Binary files differindex cf6cf1347a..07ab777bc2 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png Binary files differindex 4eb167ae18..774b9c5bbf 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-736h@3x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-736h@3x.png Binary files differindex a9f951deac..fff36679c7 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-736h@3x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-736h@3x.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-X.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-X.png Binary files differindex 06d16412e2..0804519faa 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-X.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait-X.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait.png Binary files differindex d70cab17be..833142222c 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait.png diff --git a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png Binary files differindex f934971074..4c934c4a53 100644 --- a/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png +++ b/misc/dist/ios_xcode/godot_ios/Images.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png diff --git a/misc/dist/uwp_template/Assets/SplashScreen.scale-100.png b/misc/dist/uwp_template/Assets/SplashScreen.scale-100.png Binary files differindex 540bfb1c01..0c27fda8e7 100644 --- a/misc/dist/uwp_template/Assets/SplashScreen.scale-100.png +++ b/misc/dist/uwp_template/Assets/SplashScreen.scale-100.png diff --git a/misc/dist/uwp_template/Assets/Square150x150Logo.scale-100.png b/misc/dist/uwp_template/Assets/Square150x150Logo.scale-100.png Binary files differindex 6e307e5eb8..96871f7413 100644 --- a/misc/dist/uwp_template/Assets/Square150x150Logo.scale-100.png +++ b/misc/dist/uwp_template/Assets/Square150x150Logo.scale-100.png diff --git a/misc/dist/uwp_template/Assets/Square310x310Logo.scale-100.png b/misc/dist/uwp_template/Assets/Square310x310Logo.scale-100.png Binary files differindex cb2516d7a0..96494b1020 100644 --- a/misc/dist/uwp_template/Assets/Square310x310Logo.scale-100.png +++ b/misc/dist/uwp_template/Assets/Square310x310Logo.scale-100.png diff --git a/misc/dist/uwp_template/Assets/Square44x44Logo.scale-100.png b/misc/dist/uwp_template/Assets/Square44x44Logo.scale-100.png Binary files differindex 6e14223e87..d21bc42009 100644 --- a/misc/dist/uwp_template/Assets/Square44x44Logo.scale-100.png +++ b/misc/dist/uwp_template/Assets/Square44x44Logo.scale-100.png diff --git a/misc/dist/uwp_template/Assets/Square71x71Logo.scale-100.png b/misc/dist/uwp_template/Assets/Square71x71Logo.scale-100.png Binary files differindex 0d4bd54da8..22a43cf95f 100644 --- a/misc/dist/uwp_template/Assets/Square71x71Logo.scale-100.png +++ b/misc/dist/uwp_template/Assets/Square71x71Logo.scale-100.png diff --git a/misc/dist/uwp_template/Assets/StoreLogo.scale-100.png b/misc/dist/uwp_template/Assets/StoreLogo.scale-100.png Binary files differindex 1501a09557..3960b0424b 100644 --- a/misc/dist/uwp_template/Assets/StoreLogo.scale-100.png +++ b/misc/dist/uwp_template/Assets/StoreLogo.scale-100.png diff --git a/misc/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png b/misc/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png Binary files differindex 593568e980..d836e113b1 100644 --- a/misc/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png +++ b/misc/dist/uwp_template/Assets/Wide310x150Logo.scale-100.png diff --git a/misc/scripts/make_icons.sh b/misc/scripts/make_icons.sh index 71037cd1c3..5f3ea40d6a 100644 --- a/misc/scripts/make_icons.sh +++ b/misc/scripts/make_icons.sh @@ -1,5 +1,24 @@ -convert -resize 32x32 ../../icon.svg icon32.ico -convert -resize 32x32 ../../icon.svg icon32.icns -for s in 16 24 32 64 96 128 256; do convert -resize ${s}x$s ../../icon.svg icon$s.png; done -zip icons.zip icon*.png -rm icon*.png +# Generate .ico, .icns and .zip set of icons for Steam + +# Make icons with transparent backgrounds and all sizes +for s in 16 24 32 48 64 128 256 512 1024; do + convert -resize ${s}x$s -antialias \ + -background transparent \ + ../../icon.svg icon$s.png +done + +# 16px tga file for library +convert icon16.png icon16.tga + +# zip for Linux +zip godot-icons.zip icon*.png + +# ico for Windows +# Not including biggest ones or it blows up in size +icotool -c -o godot-icon.ico icon{16,24,32,48,64,128,256}.png + +# icns for macOS +# Only some sizes: http://iconhandbook.co.uk/reference/chart/osx/ +png2icns godot-icon.icns icon{16,32,128,256,512,1024}.png + +rm -f icon*.png diff --git a/modules/bmp/config.py b/modules/bmp/config.py index fb920482f5..1c8cd12a2d 100644 --- a/modules/bmp/config.py +++ b/modules/bmp/config.py @@ -1,7 +1,5 @@ - -def can_build(platform): +def can_build(env, platform): return True - def configure(env): pass diff --git a/modules/bullet/config.py b/modules/bullet/config.py index 0a31c2e503..92dbcf5cb0 100644 --- a/modules/bullet/config.py +++ b/modules/bullet/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 3a1f5d78dd..971fd39509 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -841,20 +841,19 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f } #endif - btTransform body_safe_position; - G_TO_B(p_from, body_safe_position); - UNSCALE_BT_BASIS(body_safe_position); + btTransform body_transform; + G_TO_B(p_from, body_transform); + UNSCALE_BT_BASIS(body_transform); - btVector3 recover_initial_position(0, 0, 0); + btVector3 initial_recover_motion(0, 0, 0); { /// Phase one - multi shapes depenetration using margin for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) { - if (!recover_from_penetration(p_body, body_safe_position, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, recover_initial_position)) { + if (!recover_from_penetration(p_body, body_transform, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, initial_recover_motion)) { break; } } - // Add recover movement in order to make it safe - body_safe_position.getOrigin() += recover_initial_position; + body_transform.getOrigin() += initial_recover_motion; } btVector3 motion; @@ -885,7 +884,7 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f } btConvexShape *convex_shape_test(static_cast<btConvexShape *>(p_body->get_bt_shape(shIndex))); - btTransform shape_world_from = body_safe_position * p_body->get_kinematic_utilities()->shapes[shIndex].transform; + btTransform shape_world_from = body_transform * p_body->get_kinematic_utilities()->shapes[shIndex].transform; btTransform shape_world_to(shape_world_from); shape_world_to.getOrigin() += motion; @@ -903,62 +902,49 @@ bool SpaceBullet::test_body_motion(RigidBodyBullet *p_body, const Transform &p_f } } - body_safe_position.getOrigin() += motion; + body_transform.getOrigin() += motion; } bool has_penetration = false; - { /// Phase three - Recover + contact test with margin + { /// Phase three - contact test with margin - btVector3 delta_recover_movement(0, 0, 0); + btVector3 __rec(0, 0, 0); RecoverResult r_recover_result; - bool l_has_penetration; - real_t l_penetration_distance = 1e20; - for (int t(RECOVERING_MOVEMENT_CYCLES); 0 < t; --t) { - l_has_penetration = recover_from_penetration(p_body, body_safe_position, RECOVERING_MOVEMENT_SCALE, p_infinite_inertia, delta_recover_movement, &r_recover_result); + has_penetration = recover_from_penetration(p_body, body_transform, 0, p_infinite_inertia, __rec, &r_recover_result); - if (r_result) { - B_TO_G(motion + delta_recover_movement + recover_initial_position, r_result->motion); + // Parse results + if (r_result) { + B_TO_G(motion + initial_recover_motion, r_result->motion); - if (l_has_penetration) { - has_penetration = true; - if (l_penetration_distance <= r_recover_result.penetration_distance) { - continue; - } + if (has_penetration) { - l_penetration_distance = r_recover_result.penetration_distance; + const btRigidBody *btRigid = static_cast<const btRigidBody *>(r_recover_result.other_collision_object); + CollisionObjectBullet *collisionObject = static_cast<CollisionObjectBullet *>(btRigid->getUserPointer()); - const btRigidBody *btRigid = static_cast<const btRigidBody *>(r_recover_result.other_collision_object); - CollisionObjectBullet *collisionObject = static_cast<CollisionObjectBullet *>(btRigid->getUserPointer()); + B_TO_G(motion, r_result->remainder); // is the remaining movements + r_result->remainder = p_motion - r_result->remainder; - B_TO_G(motion, r_result->remainder); // is the remaining movements - r_result->remainder = p_motion - r_result->remainder; - B_TO_G(r_recover_result.pointWorld, r_result->collision_point); - B_TO_G(r_recover_result.normal, r_result->collision_normal); - B_TO_G(btRigid->getVelocityInLocalPoint(r_recover_result.pointWorld - btRigid->getWorldTransform().getOrigin()), r_result->collider_velocity); // It calculates velocity at point and assign it using special function Bullet_to_Godot - r_result->collider = collisionObject->get_self(); - r_result->collider_id = collisionObject->get_instance_id(); - r_result->collider_shape = r_recover_result.other_compound_shape_index; - r_result->collision_local_shape = r_recover_result.local_shape_most_recovered; + B_TO_G(r_recover_result.pointWorld, r_result->collision_point); + B_TO_G(r_recover_result.normal, r_result->collision_normal); + B_TO_G(btRigid->getVelocityInLocalPoint(r_recover_result.pointWorld - btRigid->getWorldTransform().getOrigin()), r_result->collider_velocity); // It calculates velocity at point and assign it using special function Bullet_to_Godot + r_result->collider = collisionObject->get_self(); + r_result->collider_id = collisionObject->get_instance_id(); + r_result->collider_shape = r_recover_result.other_compound_shape_index; + r_result->collision_local_shape = r_recover_result.local_shape_most_recovered; #if debug_test_motion - Vector3 sup_line2; - B_TO_G(motion, sup_line2); - normalLine->clear(); - normalLine->begin(Mesh::PRIMITIVE_LINES, NULL); - normalLine->add_vertex(r_result->collision_point); - normalLine->add_vertex(r_result->collision_point + r_result->collision_normal * 10); - normalLine->end(); + Vector3 sup_line2; + B_TO_G(motion, sup_line2); + normalLine->clear(); + normalLine->begin(Mesh::PRIMITIVE_LINES, NULL); + normalLine->add_vertex(r_result->collision_point); + normalLine->add_vertex(r_result->collision_point + r_result->collision_normal * 10); + normalLine->end(); #endif - } else { - r_result->remainder = Vector3(); - } } else { - if (!l_has_penetration) - break; - else - has_penetration = true; + r_result->remainder = Vector3(); } } } diff --git a/modules/csg/config.py b/modules/csg/config.py index 5e1d916790..38ccc66d91 100644 --- a/modules/csg/config.py +++ b/modules/csg/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 82db1871da..5f13474d2c 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -162,6 +162,10 @@ CSGBrush *CSGShape::_get_brush() { void CSGShape::_update_shape() { //print_line("updating shape for " + String(get_path())); + + if (parent) + return; + set_base(RID()); root_mesh.unref(); //byebye root mesh @@ -349,6 +353,10 @@ void CSGShape::_notification(int p_what) { Node *parentn = get_parent(); if (parentn) { parent = Object::cast_to<CSGShape>(parentn); + if (parent) { + set_base(RID()); + root_mesh.unref(); + } } if (use_collision && is_root_shape()) { @@ -371,6 +379,7 @@ void CSGShape::_notification(int p_what) { } if (p_what == NOTIFICATION_EXIT_TREE) { + if (parent) parent->_make_dirty(); parent = NULL; @@ -2011,7 +2020,7 @@ void CSGPolygon::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_depth", "get_depth"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "spin_degrees", PROPERTY_HINT_RANGE, "1,360,0.1"), "set_spin_degrees", "get_spin_degrees"); ADD_PROPERTY(PropertyInfo(Variant::INT, "spin_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_spin_sides", "get_spin_sides"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node"), "set_path_node", "get_path_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Path"), "set_path_node", "get_path_node"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_interval", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_path_interval", "get_path_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); diff --git a/modules/csg/register_types.cpp b/modules/csg/register_types.cpp index 020724ee59..0dea09808a 100644 --- a/modules/csg/register_types.cpp +++ b/modules/csg/register_types.cpp @@ -30,8 +30,8 @@ #include "register_types.h" -#include "csg_shape.h" #include "csg_gizmos.h" +#include "csg_shape.h" void register_csg_types() { @@ -51,9 +51,7 @@ void register_csg_types() { EditorPlugins::add_by_type<EditorPluginCSG>(); #endif #endif - } void unregister_csg_types() { - } diff --git a/modules/dds/config.py b/modules/dds/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/dds/config.py +++ b/modules/dds/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/enet/config.py b/modules/enet/config.py index 8031fbb4b6..3e30bbe778 100644 --- a/modules/enet/config.py +++ b/modules/enet/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index d5fd4bff09..fab4b05da9 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -7,8 +7,8 @@ A PacketPeer implementation that should be passed to [method SceneTree.set_network_peer] after being initialized as either a client or server. Events can then be handled by connecting to [SceneTree] signals. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/networking/high_level_multiplayer.html - http://enet.bespin.org/usergroup0.html + <link>http://docs.godotengine.org/en/3.0/tutorials/networking/high_level_multiplayer.html</link> + <link>http://enet.bespin.org/usergroup0.html</link> </tutorials> <demos> </demos> diff --git a/modules/etc/config.py b/modules/etc/config.py index 395fc1bb02..098f1eafa9 100644 --- a/modules/etc/config.py +++ b/modules/etc/config.py @@ -1,9 +1,5 @@ -def can_build(platform): - return True +def can_build(env, platform): + return env['tools'] def configure(env): - # Tools only, disabled for non-tools - # TODO: Find a cleaner way to achieve that - if not env['tools']: - env['module_etc_enabled'] = False - env.disabled_modules.append("etc") + pass diff --git a/modules/freetype/config.py b/modules/freetype/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/freetype/config.py +++ b/modules/freetype/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index 6d2f8ce8ad..116a86b27b 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -5,6 +5,7 @@ Import('env') gdn_env = env.Clone() gdn_env.add_source_files(env.modules_sources, "gdnative.cpp") gdn_env.add_source_files(env.modules_sources, "register_types.cpp") +gdn_env.add_source_files(env.modules_sources, "android/*.cpp") gdn_env.add_source_files(env.modules_sources, "gdnative/*.cpp") gdn_env.add_source_files(env.modules_sources, "nativescript/*.cpp") gdn_env.add_source_files(env.modules_sources, "gdnative_library_singleton_editor.cpp") @@ -12,6 +13,7 @@ gdn_env.add_source_files(env.modules_sources, "gdnative_library_editor_plugin.cp gdn_env.Append(CPPPATH=['#modules/gdnative/include/']) +SConscript("net/SCsub") SConscript("arvr/SCsub") SConscript("pluginscript/SCsub") @@ -49,6 +51,7 @@ def _build_gdnative_api_struct_header(api): '#define GODOT_GDNATIVE_API_STRUCT_H', '', '#include <gdnative/gdnative.h>', + '#include <android/godot_android.h>', '#include <arvr/godot_arvr.h>', '#include <nativescript/godot_nativescript.h>', '#include <pluginscript/godot_pluginscript.h>', @@ -194,7 +197,7 @@ def build_gdnative_api_struct(target, source, env): with open(source.path, 'w') as fd: fd.write(_build_gdnative_api_struct_source(api)) -_, gensource = gdn_env.Command(['include/gdnative_api_struct.gen.h', 'gdnative_api_struct.gen.cpp'], +_, gensource = gdn_env.CommandNoCache(['include/gdnative_api_struct.gen.h', 'gdnative_api_struct.gen.cpp'], 'gdnative_api.json', build_gdnative_api_struct) gdn_env.add_source_files(env.modules_sources, [gensource]) @@ -275,7 +278,7 @@ def build_gdnative_wrapper_code(target, source, env): if ARGUMENTS.get('gdnative_wrapper', False): #build wrapper code - gensource, = gdn_env.Command('gdnative_wrapper_code.gen.cpp', 'gdnative_api.json', build_gdnative_wrapper_code) + gensource, = gdn_env.CommandNoCache('gdnative_wrapper_code.gen.cpp', 'gdnative_api.json', build_gdnative_wrapper_code) gd_wrapper_env = env.Clone() gd_wrapper_env.Append(CPPPATH=['#modules/gdnative/include/']) diff --git a/modules/gdnative/android/android_gdn.cpp b/modules/gdnative/android/android_gdn.cpp new file mode 100644 index 0000000000..edc948e086 --- /dev/null +++ b/modules/gdnative/android/android_gdn.cpp @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* android_gdn.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "modules/gdnative/gdnative.h" + +// Code by Paritosh97 with minor tweaks by Mux213 +// These entry points are only for the android platform and are simple stubs in all others. + +#ifdef __ANDROID__ +#include "platform/android/thread_jandroid.h" +#else +#define JNIEnv void +#define jobject void * +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEnv *GDAPI godot_android_get_env() { +#ifdef __ANDROID__ + return ThreadAndroid::get_env(); +#else + return NULL; +#endif +} + +jobject GDAPI godot_android_get_activity() { +#ifdef __ANDROID__ + JNIEnv *env = ThreadAndroid::get_env(); + + jclass activityThread = env->FindClass("android/app/ActivityThread"); + jmethodID currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); + jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread); + jmethodID getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;"); + jobject context = env->CallObjectMethod(at, getApplication); + + return env->NewGlobalRef(context); +#else + return NULL; +#endif +} + +#ifdef __cplusplus +} +#endif
\ No newline at end of file diff --git a/modules/gdnative/arvr/arvr_interface_gdnative.cpp b/modules/gdnative/arvr/arvr_interface_gdnative.cpp index 49e0a19d9e..385c020a78 100644 --- a/modules/gdnative/arvr/arvr_interface_gdnative.cpp +++ b/modules/gdnative/arvr/arvr_interface_gdnative.cpp @@ -217,6 +217,10 @@ void ARVRInterfaceGDNative::process() { extern "C" { void GDAPI godot_arvr_register_interface(const godot_arvr_interface_gdnative *p_interface) { + // If our major version is 0 or bigger then 10, we're likely looking at our constructor pointer from an older plugin + ERR_EXPLAINC("GDNative ARVR interfaces build for Godot 3.0 are not supported"); + ERR_FAIL_COND((p_interface->version.major == 0) || (p_interface->version.major > 10)); + Ref<ARVRInterfaceGDNative> new_interface; new_interface.instance(); new_interface->set_interface((godot_arvr_interface_gdnative *const)p_interface); diff --git a/modules/gdnative/config.py b/modules/gdnative/config.py index 68148c4d87..c5b37d35b4 100644 --- a/modules/gdnative/config.py +++ b/modules/gdnative/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): @@ -10,6 +10,7 @@ def get_doc_classes(): "GDNative", "GDNativeLibrary", "NativeScript", + "PacketPeerGDNative", "PluginScript", ] diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 9fcf61af8a..217fd87c3e 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5962,11 +5962,34 @@ ] }, { + "name": "android", + "type": "ANDROID", + "version": { + "major": 1, + "minor": 0 + }, + "next": null, + "api": [ + { + "name": "godot_android_get_env", + "return_type": "JNIEnv*", + "arguments": [ + ] + }, + { + "name": "godot_android_get_activity", + "return_type": "jobject", + "arguments": [ + ] + } + ] + }, + { "name": "arvr", "type": "ARVR", "version": { "major": 1, - "minor": 0 + "minor": 1 }, "next": null, "api": [ diff --git a/modules/gdnative/include/android/godot_android.h b/modules/gdnative/include/android/godot_android.h new file mode 100644 index 0000000000..832dac9ac3 --- /dev/null +++ b/modules/gdnative/include/android/godot_android.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* godot_android.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef GODOT_ANDROID_GDN_H +#define GODOT_ANDROID_GDN_H + +#include <gdnative/gdnative.h> + +#ifdef __ANDROID__ +#include <jni.h> +#else +#define JNIEnv void +#define jobject void * +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEnv *GDAPI godot_android_get_env(); +jobject GDAPI godot_android_get_activity(); + +#ifdef __cplusplus +} +#endif + +#endif /* !GODOT_ANDROID_GDN_H */ diff --git a/modules/gdnative/include/arvr/godot_arvr.h b/modules/gdnative/include/arvr/godot_arvr.h index b9aedc0bef..63de62b507 100644 --- a/modules/gdnative/include/arvr/godot_arvr.h +++ b/modules/gdnative/include/arvr/godot_arvr.h @@ -37,7 +37,15 @@ extern "C" { #endif +// For future versions of the API we should only add new functions at the end of the structure and use the +// version info to detect whether a call is available + +// Use these to populate version in your plugin +#define GODOTVR_API_MAJOR 1 +#define GODOTVR_API_MINOR 0 + typedef struct { + godot_gdnative_api_version version; /* version of our API */ void *(*constructor)(godot_object *); void (*destructor)(void *); godot_string (*get_name)(const void *); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index cfbe16fa7d..f28ba352ab 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -43,6 +43,9 @@ typedef enum { GODOT_METHOD_RPC_MODE_SYNC, GODOT_METHOD_RPC_MODE_MASTER, GODOT_METHOD_RPC_MODE_SLAVE, + GODOT_METHOD_RPC_MODE_REMOTESYNC, + GODOT_METHOD_RPC_MODE_MASTERSYNC, + GODOT_METHOD_RPC_MODE_SLAVESYNC, } godot_method_rpc_mode; typedef enum { diff --git a/modules/gdnative/include/net/godot_net.h b/modules/gdnative/include/net/godot_net.h new file mode 100644 index 0000000000..bfa688592d --- /dev/null +++ b/modules/gdnative/include/net/godot_net.h @@ -0,0 +1,118 @@ +/*************************************************************************/ +/* godot_net.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef GODOT_NATIVENET_H +#define GODOT_NATIVENET_H + +#include <gdnative/gdnative.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// For future versions of the API we should only add new functions at the end of the structure and use the +// version info to detect whether a call is available + +// Use these to populate version in your plugin +#define GODOT_NET_API_MAJOR 3 +#define GODOT_NET_API_MINOR 1 + +typedef struct { + + godot_gdnative_api_version version; /* version of our API */ + godot_object *data; /* User reference */ + + /* This is StreamPeer */ + godot_error (*get_data)(void *user, uint8_t *p_buffer, int p_bytes); + godot_error (*get_partial_data)(void *user, uint8_t *p_buffer, int p_bytes, int &r_received); + godot_error (*put_data)(void *user, const uint8_t *p_data, int p_bytes); + godot_error (*put_partial_data)(void *user, const uint8_t *p_data, int p_bytes, int &r_sent); + + int (*get_available_bytes)(const void *user); + + void *next; /* For extension? */ +} godot_net_stream_peer; + +/* Binds a StreamPeerGDNative to the provided interface */ +void godot_net_bind_stream_peer(godot_object *p_obj, godot_net_stream_peer *p_interface); + +typedef struct { + godot_gdnative_api_version version; /* version of our API */ + + godot_object *data; /* User reference */ + + /* This is PacketPeer */ + godot_error (*get_packet)(void *, const uint8_t **, int &); + godot_error (*put_packet)(void *, const uint8_t *, int); + godot_int (*get_available_packet_count)(const void *); + godot_int (*get_max_packet_size)(const void *); + + void *next; /* For extension? */ +} godot_net_packet_peer; + +/* Binds a PacketPeerGDNative to the provided interface */ +void GDAPI godot_net_bind_packet_peer(godot_object *p_obj, const godot_net_packet_peer *); + +typedef struct { + godot_gdnative_api_version version; /* version of our API */ + + godot_object *data; /* User reference */ + + /* This is PacketPeer */ + godot_error (*get_packet)(void *, const uint8_t **, int &); + godot_error (*put_packet)(void *, const uint8_t *, int); + godot_int (*get_available_packet_count)(const void *); + godot_int (*get_max_packet_size)(const void *); + + /* This is NetworkedMultiplayerPeer */ + void (*set_transfer_mode)(void *, godot_int); + godot_int (*get_transfer_mode)(const void *); + // 0 = broadcast, 1 = server, <0 = all but abs(value) + void (*set_target_peer)(void *, godot_int); + godot_int (*get_packet_peer)(const void *); + godot_bool (*is_server)(const void *); + void (*poll)(void *); + // Must be > 0, 1 is for server + int32_t (*get_unique_id)(const void *); + void (*set_refuse_new_connections)(void *, godot_bool); + godot_bool (*is_refusing_new_connections)(const void *); + godot_int (*get_connection_status)(const void *); + + void *next; /* For extension? Or maybe not... */ +} godot_net_multiplayer_peer; + +/* Binds a MultiplayerPeerGDNative to the provided interface */ +void GDAPI godot_net_bind_multiplayer_peer(godot_object *p_obj, const godot_net_multiplayer_peer *); + +#ifdef __cplusplus +} +#endif + +#endif /* GODOT_NATIVENET_H */ diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index cf8977f3ea..d6abbc1bcf 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -747,7 +747,7 @@ Ref<Script> NativeScriptInstance::get_script() const { return script; } -NativeScriptInstance::RPCMode NativeScriptInstance::get_rpc_mode(const StringName &p_method) const { +MultiplayerAPI::RPCMode NativeScriptInstance::get_rpc_mode(const StringName &p_method) const { NativeScriptDesc *script_data = GET_SCRIPT_DESC(); @@ -757,27 +757,33 @@ NativeScriptInstance::RPCMode NativeScriptInstance::get_rpc_mode(const StringNam if (E) { switch (E->get().rpc_mode) { case GODOT_METHOD_RPC_MODE_DISABLED: - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; case GODOT_METHOD_RPC_MODE_REMOTE: - return RPC_MODE_REMOTE; + return MultiplayerAPI::RPC_MODE_REMOTE; case GODOT_METHOD_RPC_MODE_SYNC: - return RPC_MODE_SYNC; + return MultiplayerAPI::RPC_MODE_SYNC; case GODOT_METHOD_RPC_MODE_MASTER: - return RPC_MODE_MASTER; + return MultiplayerAPI::RPC_MODE_MASTER; case GODOT_METHOD_RPC_MODE_SLAVE: - return RPC_MODE_SLAVE; + return MultiplayerAPI::RPC_MODE_SLAVE; + case GODOT_METHOD_RPC_MODE_REMOTESYNC: + return MultiplayerAPI::RPC_MODE_REMOTESYNC; + case GODOT_METHOD_RPC_MODE_MASTERSYNC: + return MultiplayerAPI::RPC_MODE_MASTERSYNC; + case GODOT_METHOD_RPC_MODE_SLAVESYNC: + return MultiplayerAPI::RPC_MODE_SLAVESYNC; default: - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } } script_data = script_data->base_data; } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } -NativeScriptInstance::RPCMode NativeScriptInstance::get_rset_mode(const StringName &p_variable) const { +MultiplayerAPI::RPCMode NativeScriptInstance::get_rset_mode(const StringName &p_variable) const { NativeScriptDesc *script_data = GET_SCRIPT_DESC(); @@ -787,24 +793,24 @@ NativeScriptInstance::RPCMode NativeScriptInstance::get_rset_mode(const StringNa if (E) { switch (E.get().rset_mode) { case GODOT_METHOD_RPC_MODE_DISABLED: - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; case GODOT_METHOD_RPC_MODE_REMOTE: - return RPC_MODE_REMOTE; + return MultiplayerAPI::RPC_MODE_REMOTE; case GODOT_METHOD_RPC_MODE_SYNC: - return RPC_MODE_SYNC; + return MultiplayerAPI::RPC_MODE_SYNC; case GODOT_METHOD_RPC_MODE_MASTER: - return RPC_MODE_MASTER; + return MultiplayerAPI::RPC_MODE_MASTER; case GODOT_METHOD_RPC_MODE_SLAVE: - return RPC_MODE_SLAVE; + return MultiplayerAPI::RPC_MODE_SLAVE; default: - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } } script_data = script_data->base_data; } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } ScriptLanguage *NativeScriptInstance::get_language() { diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index 68a8126a32..b47962dc37 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -196,8 +196,8 @@ public: virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); virtual void notification(int p_notification); virtual Ref<Script> get_script() const; - virtual RPCMode get_rpc_mode(const StringName &p_method) const; - virtual RPCMode get_rset_mode(const StringName &p_variable) const; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; virtual ScriptLanguage *get_language(); virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); diff --git a/modules/gdnative/net/SCsub b/modules/gdnative/net/SCsub new file mode 100644 index 0000000000..53f9271128 --- /dev/null +++ b/modules/gdnative/net/SCsub @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +import os +import methods + +Import('env') +Import('env_modules') + +env_net_gdnative = env_modules.Clone() + +env_net_gdnative.Append(CPPPATH=['#modules/gdnative/include/']) +env_net_gdnative.add_source_files(env.modules_sources, '*.cpp') diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.cpp b/modules/gdnative/net/multiplayer_peer_gdnative.cpp new file mode 100644 index 0000000000..e2d710b5ad --- /dev/null +++ b/modules/gdnative/net/multiplayer_peer_gdnative.cpp @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* multiplayer_peer_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "multiplayer_peer_gdnative.h" + +MultiplayerPeerGDNative::MultiplayerPeerGDNative() { + interface = NULL; +} + +MultiplayerPeerGDNative::~MultiplayerPeerGDNative() { +} + +void MultiplayerPeerGDNative::set_native_multiplayer_peer(const godot_net_multiplayer_peer *p_interface) { + interface = p_interface; +} + +Error MultiplayerPeerGDNative::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->get_packet(interface->data, r_buffer, r_buffer_size); +} + +Error MultiplayerPeerGDNative::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->put_packet(interface->data, p_buffer, p_buffer_size); +} + +int MultiplayerPeerGDNative::get_max_packet_size() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_max_packet_size(interface->data); +} + +int MultiplayerPeerGDNative::get_available_packet_count() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_available_packet_count(interface->data); +} + +/* NetworkedMultiplayerPeer */ +void MultiplayerPeerGDNative::set_transfer_mode(TransferMode p_mode) { + ERR_FAIL_COND(interface == NULL); + interface->set_transfer_mode(interface->data, (godot_int)p_mode); +} + +NetworkedMultiplayerPeer::TransferMode MultiplayerPeerGDNative::get_transfer_mode() const { + ERR_FAIL_COND_V(interface == NULL, TRANSFER_MODE_UNRELIABLE); + return (TransferMode)interface->get_transfer_mode(interface->data); +} + +void MultiplayerPeerGDNative::set_target_peer(int p_peer_id) { + ERR_FAIL_COND(interface == NULL); + interface->set_target_peer(interface->data, p_peer_id); +} + +int MultiplayerPeerGDNative::get_packet_peer() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_packet_peer(interface->data); +} + +bool MultiplayerPeerGDNative::is_server() const { + ERR_FAIL_COND_V(interface == NULL, false); + return interface->is_server(interface->data); +} + +void MultiplayerPeerGDNative::poll() { + ERR_FAIL_COND(interface == NULL); + interface->poll(interface->data); +} + +int MultiplayerPeerGDNative::get_unique_id() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_unique_id(interface->data); +} + +void MultiplayerPeerGDNative::set_refuse_new_connections(bool p_enable) { + ERR_FAIL_COND(interface == NULL); + interface->set_refuse_new_connections(interface->data, p_enable); +} + +bool MultiplayerPeerGDNative::is_refusing_new_connections() const { + ERR_FAIL_COND_V(interface == NULL, true); + return interface->is_refusing_new_connections(interface->data); +} + +NetworkedMultiplayerPeer::ConnectionStatus MultiplayerPeerGDNative::get_connection_status() const { + ERR_FAIL_COND_V(interface == NULL, CONNECTION_DISCONNECTED); + return (ConnectionStatus)interface->get_connection_status(interface->data); +} + +void MultiplayerPeerGDNative::_bind_methods() { +} + +extern "C" { + +void GDAPI godot_net_bind_multiplayer_peer(godot_object *p_obj, const godot_net_multiplayer_peer *p_impl) { + + ((MultiplayerPeerGDNative *)p_obj)->set_native_multiplayer_peer(p_impl); +} +} diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.h b/modules/gdnative/net/multiplayer_peer_gdnative.h new file mode 100644 index 0000000000..c8c95b3dd7 --- /dev/null +++ b/modules/gdnative/net/multiplayer_peer_gdnative.h @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* multiplayer_peer_gdnative.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_PEER_GDNATIVE_H +#define MULTIPLAYER_PEER_GDNATIVE_H + +#include "core/io/networked_multiplayer_peer.h" +#include "modules/gdnative/gdnative.h" +#include "modules/gdnative/include/net/godot_net.h" + +class MultiplayerPeerGDNative : public NetworkedMultiplayerPeer { + GDCLASS(MultiplayerPeerGDNative, NetworkedMultiplayerPeer) + +protected: + static void _bind_methods(); + const godot_net_multiplayer_peer *interface; + +public: + MultiplayerPeerGDNative(); + ~MultiplayerPeerGDNative(); + + /* Sets the interface implementation from GDNative */ + void set_native_multiplayer_peer(const godot_net_multiplayer_peer *p_impl); + + /* Specific to PacketPeer */ + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + virtual int get_max_packet_size() const; + virtual int get_available_packet_count() const; + + /* Specific to NetworkedMultiplayerPeer */ + virtual void set_transfer_mode(TransferMode p_mode); + virtual TransferMode get_transfer_mode() const; + virtual void set_target_peer(int p_peer_id); + + virtual int get_packet_peer() const; + + virtual bool is_server() const; + + virtual void poll(); + + virtual int get_unique_id() const; + + virtual void set_refuse_new_connections(bool p_enable); + virtual bool is_refusing_new_connections() const; + + virtual ConnectionStatus get_connection_status() const; +}; + +#endif // MULTIPLAYER_PEER_GDNATIVE_H diff --git a/modules/gdnative/net/packet_peer_gdnative.cpp b/modules/gdnative/net/packet_peer_gdnative.cpp new file mode 100644 index 0000000000..ceae79edc0 --- /dev/null +++ b/modules/gdnative/net/packet_peer_gdnative.cpp @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* packet_peer_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "packet_peer_gdnative.h" + +PacketPeerGDNative::PacketPeerGDNative() { + interface = NULL; +} + +PacketPeerGDNative::~PacketPeerGDNative() { +} + +void PacketPeerGDNative::set_native_packet_peer(const godot_net_packet_peer *p_impl) { + interface = p_impl; +} + +void PacketPeerGDNative::_bind_methods() { +} + +Error PacketPeerGDNative::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->get_packet(interface->data, r_buffer, r_buffer_size); +} + +Error PacketPeerGDNative::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)interface->put_packet(interface->data, p_buffer, p_buffer_size); +} + +int PacketPeerGDNative::get_max_packet_size() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_max_packet_size(interface->data); +} + +int PacketPeerGDNative::get_available_packet_count() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_available_packet_count(interface->data); +} + +extern "C" { + +void GDAPI godot_net_bind_packet_peer(godot_object *p_obj, const godot_net_packet_peer *p_impl) { + + ((PacketPeerGDNative *)p_obj)->set_native_packet_peer(p_impl); +} +} diff --git a/modules/gdnative/net/packet_peer_gdnative.h b/modules/gdnative/net/packet_peer_gdnative.h new file mode 100644 index 0000000000..71814177ed --- /dev/null +++ b/modules/gdnative/net/packet_peer_gdnative.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* packet_peer_gdnative.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef PACKET_PEER_GDNATIVE_H +#define PACKET_PEER_GDNATIVE_H + +#include "core/io/packet_peer.h" +#include "modules/gdnative/gdnative.h" +#include "modules/gdnative/include/net/godot_net.h" + +class PacketPeerGDNative : public PacketPeer { + GDCLASS(PacketPeerGDNative, PacketPeer) + +protected: + static void _bind_methods(); + const godot_net_packet_peer *interface; + +public: + PacketPeerGDNative(); + ~PacketPeerGDNative(); + + /* Sets the interface implementation from GDNative */ + void set_native_packet_peer(const godot_net_packet_peer *p_impl); + + /* Specific to PacketPeer */ + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + virtual int get_max_packet_size() const; + virtual int get_available_packet_count() const; +}; + +#endif // PACKET_PEER_GDNATIVE_H diff --git a/modules/gdnative/net/register_types.cpp b/modules/gdnative/net/register_types.cpp new file mode 100644 index 0000000000..c3fb4d8008 --- /dev/null +++ b/modules/gdnative/net/register_types.cpp @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "register_types.h" +#include "multiplayer_peer_gdnative.h" +#include "packet_peer_gdnative.h" +#include "stream_peer_gdnative.h" + +void register_net_types() { + ClassDB::register_class<MultiplayerPeerGDNative>(); + ClassDB::register_class<PacketPeerGDNative>(); + ClassDB::register_class<StreamPeerGDNative>(); +} + +void unregister_net_types() { +} diff --git a/modules/gdnative/net/register_types.h b/modules/gdnative/net/register_types.h new file mode 100644 index 0000000000..9545a2ba8f --- /dev/null +++ b/modules/gdnative/net/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +void register_net_types(); +void unregister_net_types(); diff --git a/modules/gdnative/net/stream_peer_gdnative.cpp b/modules/gdnative/net/stream_peer_gdnative.cpp new file mode 100644 index 0000000000..4d1f2ec9a5 --- /dev/null +++ b/modules/gdnative/net/stream_peer_gdnative.cpp @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* stream_peer_gdnative.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "stream_peer_gdnative.h" + +StreamPeerGDNative::StreamPeerGDNative() { + interface = NULL; +} + +StreamPeerGDNative::~StreamPeerGDNative() { +} + +void StreamPeerGDNative::set_native_stream_peer(godot_net_stream_peer *p_interface) { + interface = p_interface; +} + +void StreamPeerGDNative::_bind_methods() { +} + +Error StreamPeerGDNative::put_data(const uint8_t *p_data, int p_bytes) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)(interface->put_data(interface->data, p_data, p_bytes)); +} + +Error StreamPeerGDNative::put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)(interface->put_partial_data(interface->data, p_data, p_bytes, r_sent)); +} + +Error StreamPeerGDNative::get_data(uint8_t *p_buffer, int p_bytes) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)(interface->get_data(interface->data, p_buffer, p_bytes)); +} + +Error StreamPeerGDNative::get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received) { + ERR_FAIL_COND_V(interface == NULL, ERR_UNCONFIGURED); + return (Error)(interface->get_partial_data(interface->data, p_buffer, p_bytes, r_received)); +} + +int StreamPeerGDNative::get_available_bytes() const { + ERR_FAIL_COND_V(interface == NULL, 0); + return interface->get_available_bytes(interface->data); +} + +extern "C" { + +void GDAPI godot_net_bind_stream_peer(godot_object *p_obj, godot_net_stream_peer *p_interface) { + ((StreamPeerGDNative *)p_obj)->set_native_stream_peer(p_interface); +} +} diff --git a/modules/gdnative/net/stream_peer_gdnative.h b/modules/gdnative/net/stream_peer_gdnative.h new file mode 100644 index 0000000000..654234e6ab --- /dev/null +++ b/modules/gdnative/net/stream_peer_gdnative.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* stream_peer_gdnative.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef STREAM_PEER_GDNATIVE_H +#define STREAM_PEER_GDNATIVE_H + +#include "core/io/stream_peer.h" +#include "modules/gdnative/gdnative.h" +#include "modules/gdnative/include/net/godot_net.h" + +class StreamPeerGDNative : public StreamPeer { + + GDCLASS(StreamPeerGDNative, StreamPeer); + +protected: + static void _bind_methods(); + godot_net_stream_peer *interface; + +public: + StreamPeerGDNative(); + ~StreamPeerGDNative(); + + /* Sets the interface implementation from GDNative */ + void set_native_stream_peer(godot_net_stream_peer *p_interface); + + /* Specific to StreamPeer */ + Error put_data(const uint8_t *p_data, int p_bytes); + Error put_partial_data(const uint8_t *p_data, int p_bytes, int &r_sent); + Error get_data(uint8_t *p_buffer, int p_bytes); + Error get_partial_data(uint8_t *p_buffer, int p_bytes, int &r_received); + int get_available_bytes() const; +}; + +#endif // STREAM_PEER_GDNATIVE_H diff --git a/modules/gdnative/pluginscript/pluginscript_instance.cpp b/modules/gdnative/pluginscript/pluginscript_instance.cpp index 931ab0bfe4..13026e8e7a 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.cpp +++ b/modules/gdnative/pluginscript/pluginscript_instance.cpp @@ -138,11 +138,11 @@ void PluginScriptInstance::notification(int p_notification) { _desc->notification(_data, p_notification); } -ScriptInstance::RPCMode PluginScriptInstance::get_rpc_mode(const StringName &p_method) const { +MultiplayerAPI::RPCMode PluginScriptInstance::get_rpc_mode(const StringName &p_method) const { return _script->get_rpc_mode(p_method); } -ScriptInstance::RPCMode PluginScriptInstance::get_rset_mode(const StringName &p_variable) const { +MultiplayerAPI::RPCMode PluginScriptInstance::get_rset_mode(const StringName &p_variable) const { return _script->get_rset_mode(p_variable); } diff --git a/modules/gdnative/pluginscript/pluginscript_instance.h b/modules/gdnative/pluginscript/pluginscript_instance.h index 3c7b360ad9..8be6a4ccaa 100644 --- a/modules/gdnative/pluginscript/pluginscript_instance.h +++ b/modules/gdnative/pluginscript/pluginscript_instance.h @@ -76,8 +76,8 @@ public: void set_path(const String &p_path); - virtual RPCMode get_rpc_mode(const StringName &p_method) const; - virtual RPCMode get_rset_mode(const StringName &p_variable) const; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; virtual void refcount_incremented(); virtual bool refcount_decremented(); diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 5ae7926f1b..c3a623e9a1 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -84,35 +84,20 @@ StringName PluginScript::get_instance_base_type() const { } void PluginScript::update_exports() { -// TODO #ifdef TOOLS_ENABLED -#if 0 ASSERT_SCRIPT_VALID(); - if (/*changed &&*/ placeholders.size()) { //hm :( + if (placeholders.size()) { //update placeholders if any Map<StringName, Variant> propdefvalues; List<PropertyInfo> propinfos; - const String *props = (const String *)pybind_get_prop_list(_py_exposed_class); - for (int i = 0; props[i] != ""; ++i) { - const String propname = props[i]; - pybind_get_prop_default_value(_py_exposed_class, propname.c_str(), (godot_variant *)&propdefvalues[propname]); - pybind_prop_info raw_info; - pybind_get_prop_info(_py_exposed_class, propname.c_str(), &raw_info); - PropertyInfo info; - info.type = (Variant::Type)raw_info.type; - info.name = propname; - info.hint = (PropertyHint)raw_info.hint; - info.hint_string = *(String *)&raw_info.hint_string; - info.usage = raw_info.usage; - propinfos.push_back(info); - } + + get_script_property_list(&propinfos); for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { - E->get()->update(propinfos, propdefvalues); + E->get()->update(propinfos, _properties_default_values); } } #endif -#endif } // TODO: rename p_this "p_owner" ? @@ -245,9 +230,9 @@ Error PluginScript::reload(bool p_keep_state) { // rpc_mode is passed as an optional field and is not part of MethodInfo Variant var = v["rpc_mode"]; if (var == Variant()) { - _methods_rpc_mode[mi.name] = ScriptInstance::RPC_MODE_DISABLED; + _methods_rpc_mode[mi.name] = MultiplayerAPI::RPC_MODE_DISABLED; } else { - _methods_rpc_mode[mi.name] = ScriptInstance::RPCMode(int(var)); + _methods_rpc_mode[mi.name] = MultiplayerAPI::RPCMode(int(var)); } } Array *signals = (Array *)&manifest.signals; @@ -265,9 +250,9 @@ Error PluginScript::reload(bool p_keep_state) { // rset_mode is passed as an optional field and is not part of PropertyInfo Variant var = v["rset_mode"]; if (var == Variant()) { - _methods_rpc_mode[pi.name] = ScriptInstance::RPC_MODE_DISABLED; + _methods_rpc_mode[pi.name] = MultiplayerAPI::RPC_MODE_DISABLED; } else { - _methods_rpc_mode[pi.name] = ScriptInstance::RPCMode(int(var)); + _methods_rpc_mode[pi.name] = MultiplayerAPI::RPCMode(int(var)); } } // Manifest's attributes must be explicitly freed @@ -402,23 +387,23 @@ int PluginScript::get_member_line(const StringName &p_member) const { return -1; } -ScriptInstance::RPCMode PluginScript::get_rpc_mode(const StringName &p_method) const { - ASSERT_SCRIPT_VALID_V(ScriptInstance::RPC_MODE_DISABLED); - const Map<StringName, ScriptInstance::RPCMode>::Element *e = _methods_rpc_mode.find(p_method); +MultiplayerAPI::RPCMode PluginScript::get_rpc_mode(const StringName &p_method) const { + ASSERT_SCRIPT_VALID_V(MultiplayerAPI::RPC_MODE_DISABLED); + const Map<StringName, MultiplayerAPI::RPCMode>::Element *e = _methods_rpc_mode.find(p_method); if (e != NULL) { return e->get(); } else { - return ScriptInstance::RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } } -ScriptInstance::RPCMode PluginScript::get_rset_mode(const StringName &p_variable) const { - ASSERT_SCRIPT_VALID_V(ScriptInstance::RPC_MODE_DISABLED); - const Map<StringName, ScriptInstance::RPCMode>::Element *e = _variables_rset_mode.find(p_variable); +MultiplayerAPI::RPCMode PluginScript::get_rset_mode(const StringName &p_variable) const { + ASSERT_SCRIPT_VALID_V(MultiplayerAPI::RPC_MODE_DISABLED); + const Map<StringName, MultiplayerAPI::RPCMode>::Element *e = _variables_rset_mode.find(p_variable); if (e != NULL) { return e->get(); } else { - return ScriptInstance::RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } } diff --git a/modules/gdnative/pluginscript/pluginscript_script.h b/modules/gdnative/pluginscript/pluginscript_script.h index 1be9e907c2..31c6c4d67f 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.h +++ b/modules/gdnative/pluginscript/pluginscript_script.h @@ -62,8 +62,8 @@ private: Map<StringName, PropertyInfo> _properties_info; Map<StringName, MethodInfo> _signals_info; Map<StringName, MethodInfo> _methods_info; - Map<StringName, ScriptInstance::RPCMode> _variables_rset_mode; - Map<StringName, ScriptInstance::RPCMode> _methods_rpc_mode; + Map<StringName, MultiplayerAPI::RPCMode> _variables_rset_mode; + Map<StringName, MultiplayerAPI::RPCMode> _methods_rpc_mode; Set<Object *> _instances; //exported members @@ -116,8 +116,8 @@ public: virtual int get_member_line(const StringName &p_member) const; - ScriptInstance::RPCMode get_rpc_mode(const StringName &p_method) const; - ScriptInstance::RPCMode get_rset_mode(const StringName &p_variable) const; + MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; PluginScript(); void init(PluginScriptLanguage *language); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index a0b6fbeb75..d18297f2f8 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -38,6 +38,7 @@ #include "arvr/register_types.h" #include "nativescript/register_types.h" +#include "net/register_types.h" #include "pluginscript/register_types.h" #include "core/engine.h" @@ -321,6 +322,7 @@ void register_gdnative_types() { GDNativeCallRegistry::singleton->register_native_call_type("standard_varcall", cb_standard_varcall); + register_net_types(); register_arvr_types(); register_nativescript_types(); register_pluginscript_types(); @@ -379,6 +381,7 @@ void unregister_gdnative_types() { unregister_pluginscript_types(); unregister_nativescript_types(); unregister_arvr_types(); + unregister_net_types(); memdelete(GDNativeCallRegistry::singleton); diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index 6496b59d75..95b40d90af 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 40a435f459..632970f8c0 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,7 +8,7 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/scripting/gdscript/index.html + <link>http://docs.godotengine.org/en/3.0/getting_started/scripting/gdscript/index.html</link> </tutorials> <demos> </demos> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ea3efff9cf..fedc510f01 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -312,8 +312,24 @@ void GDScriptSyntaxHighlighter::_update_cache() { number_color = text_editor->get_color("number_color"); member_color = text_editor->get_color("member_variable_color"); - function_definition_color = EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", Color::html("#01e1ff")); - node_path_color = EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", Color::html("#64c15a")); + EditorSettings *settings = EditorSettings::get_singleton(); + String text_editor_color_theme = settings->get("text_editor/theme/color_theme"); + + bool default_theme = text_editor_color_theme == "Default"; + bool dark_theme = settings->is_dark_theme(); + + function_definition_color = Color::html(default_theme ? "#01e1ff" : dark_theme ? "#01e1ff" : "#00a5ba"); + node_path_color = Color::html(default_theme ? "#64c15a" : dark_theme ? "64c15a" : "#518b4b"); + + EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); + EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); + if (text_editor_color_theme == "Adaptive" || default_theme) { + settings->set_initial_value("text_editor/highlighting/gdscript/function_definition_color", function_definition_color, true); + settings->set_initial_value("text_editor/highlighting/gdscript/node_path_color", node_path_color, true); + } + + function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); + node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color"); } SyntaxHighlighter *GDScriptSyntaxHighlighter::create() { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 14bdce50ec..f23e7854a5 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1220,7 +1220,7 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -GDScriptInstance::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_method) const { +MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_method) const { const GDScript *cscript = script.ptr(); @@ -1228,17 +1228,17 @@ GDScriptInstance::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_met const Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.find(p_method); if (E) { - if (E->get()->get_rpc_mode() != RPC_MODE_DISABLED) { + if (E->get()->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) { return E->get()->get_rpc_mode(); } } cscript = cscript->_base; } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } -GDScriptInstance::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_variable) const { +MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_variable) const { const GDScript *cscript = script.ptr(); @@ -1253,7 +1253,7 @@ GDScriptInstance::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_va cscript = cscript->_base; } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } void GDScriptInstance::reload_members() { @@ -1769,6 +1769,9 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "sync", "master", "slave", + "remotesync", + "mastersync", + "slavesync", 0 }; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 6885fbb7fe..a35b0a10d5 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -63,7 +63,7 @@ class GDScript : public Script { int index; StringName setter; StringName getter; - ScriptInstance::RPCMode rpc_mode; + MultiplayerAPI::RPCMode rpc_mode; }; friend class GDScriptInstance; @@ -248,8 +248,8 @@ public: void reload_members(); - virtual RPCMode get_rpc_mode(const StringName &p_method) const; - virtual RPCMode get_rset_mode(const StringName &p_variable) const; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; GDScriptInstance(); ~GDScriptInstance(); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 4286412c14..c0c3bd7b06 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -54,18 +54,18 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { } Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const { - String _template = String() + - "extends %BASE%\n\n" + - "# class member variables go here, for example:\n" + - "# var a = 2\n" + - "# var b = \"textvar\"\n\n" + - "func _ready():\n" + - "%TS%# Called when the node is added to the scene for the first time.\n" + - "%TS%# Initialization here.\n" + - "%TS%pass\n\n" + - "#func _process(delta):\n" + - "#%TS%# Called every frame. Delta is time since last frame.\n" + - "#%TS%# Update game logic here.\n" + + String _template = "extends %BASE%\n" + "\n" + "# Declare member variables here. Examples:\n" + "# var a = 2\n" + "# var b = \"text\"\n" + "\n" + "# Called when the node enters the scene tree for the first time.\n" + "func _ready():\n" + "%TS%pass # Replace with function body.\n" + "\n" + "# Called every frame. 'delta' is the elapsed time since the previous frame.\n" + "#func _process(delta):\n" "#%TS%pass\n"; _template = _template.replace("%BASE%", p_base_class_name); @@ -118,6 +118,13 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & funcs[cl->static_functions[i]->line] = cl->static_functions[i]->name; } + for (int i = 0; i < cl->subclasses.size(); i++) { + for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) { + + funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + String(cl->subclasses[i]->functions[j]->name); + } + } + for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) { r_functions->push_back(E->get() + ":" + itos(E->key())); diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index dac7da3a28..61130cb58f 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1455,7 +1455,7 @@ GDScriptFunction::GDScriptFunction() : _stack_size = 0; _call_size = 0; - rpc_mode = ScriptInstance::RPC_MODE_DISABLED; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; name = "<anonymous>"; #ifdef DEBUG_ENABLED _func_cname = NULL; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index ea009dcd96..836325f0fe 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -96,14 +96,6 @@ public: ADDR_TYPE_NIL = 9 }; - enum RPCMode { - RPC_DISABLED, - RPC_ENABLED, - RPC_SYNC, - RPC_SYNC_MASTER, - RPC_SYNC_SLAVE - }; - struct StackDebug { int line; @@ -135,7 +127,7 @@ private: int _call_size; int _initial_line; bool _static; - ScriptInstance::RPCMode rpc_mode; + MultiplayerAPI::RPCMode rpc_mode; GDScript *_script; @@ -230,7 +222,7 @@ public: Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Variant::CallError &r_err, CallState *p_state = NULL); - _FORCE_INLINE_ ScriptInstance::RPCMode get_rpc_mode() const { return rpc_mode; } + _FORCE_INLINE_ MultiplayerAPI::RPCMode get_rpc_mode() const { return rpc_mode; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e7b0700e76..9650563ee6 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -716,6 +716,14 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s expr = constant; bfn = true; } + + if (!bfn && GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { + //check from singletons + ConstantNode *constant = alloc_node<ConstantNode>(); + constant->value = GDScriptLanguage::get_singleton()->get_named_globals_map()[identifier]; + expr = constant; + bfn = true; + } } if (!bfn) { @@ -3365,7 +3373,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { function->line = fnline; function->rpc_mode = rpc_mode; - rpc_mode = ScriptInstance::RPC_MODE_DISABLED; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; if (_static) p_class->static_functions.push_back(function); @@ -3923,10 +3931,10 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); } - if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC) { + if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVESYNC) { current_export = PropertyInfo(); - _set_error("Expected 'var', 'onready', 'remote', 'master', 'slave' or 'sync'."); + _set_error("Expected 'var', 'onready', 'remote', 'master', 'slave', 'sync', 'remotesync', 'mastersync', 'slavesync'."); return; } @@ -3959,7 +3967,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } } - rpc_mode = ScriptInstance::RPC_MODE_REMOTE; + rpc_mode = MultiplayerAPI::RPC_MODE_REMOTE; continue; } break; @@ -3980,7 +3988,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } - rpc_mode = ScriptInstance::RPC_MODE_MASTER; + rpc_mode = MultiplayerAPI::RPC_MODE_MASTER; continue; } break; case GDScriptTokenizer::TK_PR_SLAVE: { @@ -4000,9 +4008,10 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } - rpc_mode = ScriptInstance::RPC_MODE_SLAVE; + rpc_mode = MultiplayerAPI::RPC_MODE_SLAVE; continue; } break; + case GDScriptTokenizer::TK_PR_REMOTESYNC: case GDScriptTokenizer::TK_PR_SYNC: { //may be fallthrough from export, ignore if so @@ -4015,7 +4024,37 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - rpc_mode = ScriptInstance::RPC_MODE_SYNC; + rpc_mode = MultiplayerAPI::RPC_MODE_SYNC; + continue; + } break; + case GDScriptTokenizer::TK_PR_MASTERSYNC: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { + if (current_export.type) + _set_error("Expected 'var'."); + else + _set_error("Expected 'var' or 'func'."); + return; + } + + rpc_mode = MultiplayerAPI::RPC_MODE_MASTERSYNC; + continue; + } break; + case GDScriptTokenizer::TK_PR_SLAVESYNC: { + + //may be fallthrough from export, ignore if so + tokenizer->advance(); + if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) { + if (current_export.type) + _set_error("Expected 'var'."); + else + _set_error("Expected 'var' or 'func'."); + return; + } + + rpc_mode = MultiplayerAPI::RPC_MODE_SLAVESYNC; continue; } break; case GDScriptTokenizer::TK_PR_VAR: { @@ -4045,7 +4084,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(); - rpc_mode = ScriptInstance::RPC_MODE_DISABLED; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { @@ -4054,7 +4093,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { #endif tokenizer->advance(); - Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport); + Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL); if (!subexpr) { if (_recover_from_completion()) { break; @@ -4470,7 +4509,7 @@ void GDScriptParser::clear() { current_class = NULL; completion_found = false; - rpc_mode = ScriptInstance::RPC_MODE_DISABLED; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; current_function = NULL; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 485ba1263d..b88a59537c 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -89,7 +89,7 @@ public: StringName getter; int line; Node *expression; - ScriptInstance::RPCMode rpc_mode; + MultiplayerAPI::RPCMode rpc_mode; }; struct Constant { StringName identifier; @@ -125,7 +125,7 @@ public: struct FunctionNode : public Node { bool _static; - ScriptInstance::RPCMode rpc_mode; + MultiplayerAPI::RPCMode rpc_mode; StringName name; Vector<StringName> arguments; Vector<Node *> default_values; @@ -134,7 +134,7 @@ public: FunctionNode() { type = TYPE_FUNCTION; _static = false; - rpc_mode = ScriptInstance::RPC_MODE_DISABLED; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; } }; @@ -493,7 +493,7 @@ private: PropertyInfo current_export; - ScriptInstance::RPCMode rpc_mode; + MultiplayerAPI::RPCMode rpc_mode; void _set_error(const String &p_error, int p_line = -1, int p_column = -1); bool _recover_from_completion(); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 6a844cd651..3c8e1ddbe4 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -110,6 +110,9 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "sync", "master", "slave", + "remotesync", + "mastersync", + "slavesync", "'['", "']'", "'{'", @@ -201,6 +204,9 @@ static const _kws _keyword_list[] = { { GDScriptTokenizer::TK_PR_MASTER, "master" }, { GDScriptTokenizer::TK_PR_SLAVE, "slave" }, { GDScriptTokenizer::TK_PR_SYNC, "sync" }, + { GDScriptTokenizer::TK_PR_REMOTESYNC, "remotesync" }, + { GDScriptTokenizer::TK_PR_MASTERSYNC, "mastersync" }, + { GDScriptTokenizer::TK_PR_SLAVESYNC, "slavesync" }, { GDScriptTokenizer::TK_PR_CONST, "const" }, { GDScriptTokenizer::TK_PR_ENUM, "enum" }, //controlflow @@ -247,6 +253,9 @@ bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const case TK_PR_MASTER: case TK_PR_SLAVE: case TK_PR_SYNC: + case TK_PR_REMOTESYNC: + case TK_PR_MASTERSYNC: + case TK_PR_SLAVESYNC: return true; // Literal for non-variables only: diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index b020c85199..c4f1f9fd94 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -115,6 +115,9 @@ public: TK_PR_SYNC, TK_PR_MASTER, TK_PR_SLAVE, + TK_PR_REMOTESYNC, + TK_PR_MASTERSYNC, + TK_PR_SLAVESYNC, TK_BRACKET_OPEN, TK_BRACKET_CLOSE, TK_CURLY_BRACKET_OPEN, diff --git a/modules/gridmap/config.py b/modules/gridmap/config.py index a93f4edb81..5022116c9b 100644 --- a/modules/gridmap/config.py +++ b/modules/gridmap/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index bb652f3bdf..d5f9563600 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -10,7 +10,7 @@ A GridMap is split into a sparse collection of octants for efficient rendering and physics processing. Every octant has the same dimensions and can contain several cells. </description> <tutorials> - http://docs.godotengine.org/en/3.0/tutorials/3d/using_gridmaps.html + <link>http://docs.godotengine.org/en/3.0/tutorials/3d/using_gridmaps.html</link> </tutorials> <demos> </demos> diff --git a/modules/hdr/config.py b/modules/hdr/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/hdr/config.py +++ b/modules/hdr/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/jpg/config.py b/modules/jpg/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/jpg/config.py +++ b/modules/jpg/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/mbedtls/config.py b/modules/mbedtls/config.py index 5f133eba90..1c8cd12a2d 100755 --- a/modules/mbedtls/config.py +++ b/modules/mbedtls/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/mobile_vr/config.py b/modules/mobile_vr/config.py index aa8ef111d3..4912457e2b 100644 --- a/modules/mobile_vr/config.py +++ b/modules/mobile_vr/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): # should probably change this to only be true on iOS and Android return False diff --git a/modules/mono/SCsub b/modules/mono/SCsub index a1dfcf6377..03e187e5b0 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -127,15 +127,24 @@ def find_msbuild_windows(): if not mono_root: raise RuntimeError('Cannot find mono root directory') + framework_path = os.path.join(mono_root, 'lib', 'mono', '4.5') + + mono_bin_dir = os.path.join(mono_root, 'bin') + + msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat') + + if os.path.isfile(msbuild_mono): + mono_msbuild_env = { + 'CscToolExe': os.path.join(mono_bin_dir, 'csc.bat'), + 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'), + 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat') + } + return (msbuild_mono, framework_path, mono_msbuild_env) + msbuild_tools_path = monoreg.find_msbuild_tools_path_reg() if msbuild_tools_path: - return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), os.path.join(mono_root, 'lib', 'mono', '4.5')) - else: - msbuild_mono = os.path.join(mono_root, 'bin', 'msbuild.bat') - - if os.path.isfile(msbuild_mono): - return (msbuild_mono, '') + return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {}) return None @@ -145,14 +154,21 @@ def mono_build_solution(source, target, env): import mono_reg_utils as monoreg from shutil import copyfile - framework_path_override = '' + framework_path = '' + + msbuild_env = os.environ.copy() + + # Needed when running from Developer Command Prompt for VS + if 'PLATFORM' in msbuild_env: + del msbuild_env['PLATFORM'] if os.name == 'nt': msbuild_info = find_msbuild_windows() if msbuild_info is None: raise RuntimeError('Cannot find MSBuild executable') msbuild_path = msbuild_info[0] - framework_path_override = msbuild_info[1] + framework_path = msbuild_info[1] + msbuild_env.update(msbuild_info[2]) else: msbuild_path = find_msbuild_unix('msbuild') if msbuild_path is None: @@ -183,14 +199,8 @@ def mono_build_solution(source, target, env): '/p:Configuration=' + build_config, ] - if framework_path_override: - msbuild_args += ['/p:FrameworkPathOverride=' + framework_path_override] - - msbuild_env = os.environ.copy() - - # Needed when running from Developer Command Prompt for VS - if 'PLATFORM' in msbuild_env: - del msbuild_env['PLATFORM'] + if framework_path: + msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] try: subprocess.check_call(msbuild_args, env=msbuild_env) diff --git a/modules/mono/config.py b/modules/mono/config.py index 18d9c67795..ebf8512fb6 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -4,7 +4,7 @@ import os import sys import subprocess -from SCons.Script import BoolVariable, Dir, Environment, PathVariable, Variables +from SCons.Script import BoolVariable, Dir, Environment, File, PathVariable, SCons, Variables monoreg = imp.load_source('mono_reg_utils', 'modules/mono/mono_reg_utils.py') @@ -19,7 +19,7 @@ def find_file_in_dir(directory, files, prefix='', extension=''): return '' -def can_build(platform): +def can_build(env, platform): if platform in ["javascript"]: return False # Not yet supported return True @@ -42,13 +42,24 @@ def copy_file(src_dir, dst_dir, name): copyfile(src_path, dst_path) +def custom_path_is_dir_create(key, val, env): + """Validator to check if Path is a directory, creating it if it does not exist. + Similar to PathIsDirCreate, except it uses SCons.Script.Dir() and + SCons.Script.File() in order to support the '#' top level directory token. + """ + # Dir constructor will throw an error if the path points to a file + fsDir = Dir(val) + if not fsDir.exists: + os.makedirs(fsDir.abspath) + + def configure(env): env.use_ptrcall = True env.add_module_version_string("mono") envvars = Variables() envvars.Add(BoolVariable('mono_static', 'Statically link mono', False)) - envvars.Add(PathVariable('mono_assemblies_output_dir', 'Path to the assemblies output directory', '#bin', PathVariable.PathIsDirCreate)) + envvars.Add(PathVariable('mono_assemblies_output_dir', 'Path to the assemblies output directory', '#bin', custom_path_is_dir_create)) envvars.Update(env) bits = env['bits'] @@ -80,7 +91,11 @@ def configure(env): if mono_static: lib_suffix = Environment()['LIBSUFFIX'] - mono_static_lib_name = 'libmono-static-sgen' + + if env.msvc: + mono_static_lib_name = 'libmono-static-sgen' + else: + mono_static_lib_name = 'libmonosgen-2.0' if not os.path.isfile(os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)): raise RuntimeError('Could not find static mono library in: ' + mono_lib_path) @@ -93,7 +108,10 @@ def configure(env): env.Append(LINKFLAGS='LIBCMT' + lib_suffix) env.Append(LINKFLAGS='Psapi' + lib_suffix) else: - env.Append(LIBS=mono_static_lib_name) + env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)) + + env.Append(LIBS='psapi') + env.Append(LIBS='version') else: mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') @@ -128,6 +146,22 @@ def configure(env): if os.getenv('MONO64_PREFIX'): mono_root = os.getenv('MONO64_PREFIX') + # We can't use pkg-config to link mono statically, + # but we can still use it to find the mono root directory + if not mono_root and mono_static: + def pkgconfig_try_find_mono_root(): + tmpenv = Environment() + tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH')) + tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L') + for hint_dir in tmpenv['LIBPATH']: + name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext) + if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')): + return os.path.join(hint_dir, '..') + return '' + mono_root = pkgconfig_try_find_mono_root() + if not mono_root: + raise RuntimeError('Building with mono_static=yes, but failed to find the mono prefix with pkg-config. Specify one manually') + if mono_root: mono_lib_path = os.path.join(mono_root, 'lib') @@ -168,8 +202,7 @@ def configure(env): copy_file(os.path.join(mono_lib_path, 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll') else: - if mono_static: - raise RuntimeError('mono-static: Not supported with pkg-config. Specify a mono prefix manually') + assert not mono_static env.ParseConfig('pkg-config monosgen-2 --cflags --libs') diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 24292b77ed..2420cdb4af 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -298,22 +298,20 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin "\n" "public class %CLASS_NAME% : %BASE_CLASS_NAME%\n" "{\n" - " // Member variables here, example:\n" + " // Declare member variables here. Examples:\n" " // private int a = 2;\n" - " // private string b = \"textvar\";\n" + " // private string b = \"text\";\n" "\n" + " // Called when the node enters the scene tree for the first time." " public override void _Ready()\n" " {\n" - " // Called every time the node is added to the scene.\n" - " // Initialization here.\n" - " \n" + " " " }\n" "\n" + "// // Called every frame. 'delta' is the elapsed time since the previous frame." "// public override void _Process(float delta)\n" "// {\n" - "// // Called every frame. Delta is time since last frame.\n" - "// // Update game logic here.\n" - "// \n" + "// " "// }\n" "}\n"; @@ -1310,21 +1308,27 @@ bool CSharpInstance::refcount_decremented() { return ref_dying; } -ScriptInstance::RPCMode CSharpInstance::_member_get_rpc_mode(GDMonoClassMember *p_member) const { +MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(GDMonoClassMember *p_member) const { if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) - return RPC_MODE_REMOTE; + return MultiplayerAPI::RPC_MODE_REMOTE; if (p_member->has_attribute(CACHED_CLASS(SyncAttribute))) - return RPC_MODE_SYNC; + return MultiplayerAPI::RPC_MODE_SYNC; if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) - return RPC_MODE_MASTER; + return MultiplayerAPI::RPC_MODE_MASTER; if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute))) - return RPC_MODE_SLAVE; + return MultiplayerAPI::RPC_MODE_SLAVE; + if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) + return MultiplayerAPI::RPC_MODE_REMOTESYNC; + if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) + return MultiplayerAPI::RPC_MODE_MASTERSYNC; + if (p_member->has_attribute(CACHED_CLASS(SlaveSyncAttribute))) + return MultiplayerAPI::RPC_MODE_SLAVESYNC; - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } -ScriptInstance::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { +MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { GDMonoClass *top = script->script_class; @@ -1337,10 +1341,10 @@ ScriptInstance::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) top = top->get_parent_class(); } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } -ScriptInstance::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { +MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { GDMonoClass *top = script->script_class; @@ -1358,7 +1362,7 @@ ScriptInstance::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab top = top->get_parent_class(); } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } void CSharpInstance::notification(int p_notification) { @@ -1726,6 +1730,12 @@ void CSharpScript::_clear() { Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + if (unlikely(GDMono::get_singleton() == NULL)) { + // Probably not the best error but eh. + r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return Variant(); + } + GDMonoClass *top = script_class; while (top && top != native) { @@ -1967,15 +1977,15 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { return NULL; #endif } - + if (!script_class) { if (GDMono::get_singleton()->get_project_assembly() == NULL) { // The project assembly is not loaded ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path()); ERR_FAIL_V(NULL); } - - // The project assembly is loaded, but the class could not found + + // The project assembly is loaded, but the class could not found ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path()); ERR_FAIL_V(NULL); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 8666149111..cae2bbf40a 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -192,7 +192,7 @@ class CSharpInstance : public ScriptInstance { void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount); - RPCMode _member_get_rpc_mode(GDMonoClassMember *p_member) const; + MultiplayerAPI::RPCMode _member_get_rpc_mode(GDMonoClassMember *p_member) const; public: MonoObject *get_mono_object() const; @@ -213,8 +213,8 @@ public: virtual void refcount_incremented(); virtual bool refcount_decremented(); - virtual RPCMode get_rpc_mode(const StringName &p_method) const; - virtual RPCMode get_rset_mode(const StringName &p_variable) const; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; virtual void notification(int p_notification); void call_notification_no_check(MonoObject *p_mono_object, int p_notification); diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs index f3b4b66663..16beacb45c 100644 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs @@ -78,6 +78,8 @@ namespace GodotSharpTools.Build public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) { + bool debugMSBuild = IsDebugMSBuildRequested(); + List<string> customPropertiesList = new List<string>(); if (customProperties != null) @@ -92,9 +94,10 @@ namespace GodotSharpTools.Build ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - // No console output, thanks - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; + bool redirectOutput = !debugMSBuild; + + startInfo.RedirectStandardOutput = redirectOutput; + startInfo.RedirectStandardError = redirectOutput; startInfo.UseShellExecute = false; if (UsingMonoMSBuildOnWindows) @@ -116,8 +119,11 @@ namespace GodotSharpTools.Build process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); + if (redirectOutput) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } process.WaitForExit(); @@ -129,6 +135,8 @@ namespace GodotSharpTools.Build public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) { + bool debugMSBuild = IsDebugMSBuildRequested(); + if (process != null) throw new InvalidOperationException("Already in use"); @@ -146,9 +154,10 @@ namespace GodotSharpTools.Build ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - // No console output, thanks - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; + bool redirectOutput = !debugMSBuild; + + startInfo.RedirectStandardOutput = redirectOutput; + startInfo.RedirectStandardError = redirectOutput; startInfo.UseShellExecute = false; if (UsingMonoMSBuildOnWindows) @@ -171,8 +180,11 @@ namespace GodotSharpTools.Build process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); + if (redirectOutput) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } return true; } @@ -220,6 +232,11 @@ namespace GodotSharpTools.Build Dispose(); } + private static bool IsDebugMSBuildRequested() + { + return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; + } + public void Dispose() { if (process != null) diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index e29384aabf..43d58caad2 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -461,12 +461,12 @@ void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { exit_code = -1; - String logs_dir = GodotSharpDirs::get_build_logs_dir().plus_file(build_info.solution.md5_text() + "_" + build_info.configuration); + String log_dirpath = build_info.get_log_dirpath(); if (build_tab) { build_tab->on_build_start(); } else { - build_tab = memnew(MonoBuildTab(build_info, logs_dir)); + build_tab = memnew(MonoBuildTab(build_info, log_dirpath)); MonoBottomPanel::get_singleton()->add_build_tab(build_tab); } @@ -488,12 +488,12 @@ void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { // Remove old issues file String issues_file = "msbuild_issues.csv"; - DirAccessRef d = DirAccess::create_for_path(logs_dir); + DirAccessRef d = DirAccess::create_for_path(log_dirpath); if (d->file_exists(issues_file)) { Error err = d->remove(issues_file); if (err != OK) { exited = true; - String file_path = ProjectSettings::get_singleton()->localize_path(logs_dir).plus_file(issues_file); + String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file); String message = "Cannot remove issues file: " + file_path; build_tab->on_build_exec_failed(message); ERR_EXPLAIN(message); @@ -527,8 +527,9 @@ void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { // Call Build - Variant logger_assembly = OS::get_singleton()->get_executable_path().get_base_dir().plus_file(EDITOR_TOOLS_ASSEMBLY_NAME) + ".dll"; - Variant logger_output_dir = logs_dir; + String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path(); + Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path); + Variant logger_output_dir = log_dirpath; Variant custom_props = build_info.custom_props; const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props }; diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index 1b5a303835..9317550d28 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -73,7 +73,7 @@ void MonoBottomPanel::_update_build_tabs_list() { if (no_current_tab || current_tab == i) { build_tabs_list->select(i); - _build_tab_item_selected(i); + _build_tabs_item_selected(i); } } } @@ -105,21 +105,27 @@ void MonoBottomPanel::show_build_tab() { ERR_PRINT("Builds tab not found"); } -void MonoBottomPanel::_build_tab_item_selected(int p_idx) { +void MonoBottomPanel::_build_tabs_item_selected(int p_idx) { ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count()); + build_tabs->set_current_tab(p_idx); + if (!build_tabs->is_visible()) + build_tabs->set_visible(true); + + warnings_btn->set_visible(true); + errors_btn->set_visible(true); + view_log_btn->set_visible(true); } -void MonoBottomPanel::_build_tab_changed(int p_idx) { +void MonoBottomPanel::_build_tabs_nothing_selected() { - if (p_idx < 0 || p_idx >= build_tabs->get_tab_count()) { - warnings_btn->set_visible(false); - errors_btn->set_visible(false); - } else { - warnings_btn->set_visible(true); - errors_btn->set_visible(true); - } + if (build_tabs->get_tab_count() != 0) // just in case + build_tabs->set_visible(false); + + warnings_btn->set_visible(false); + errors_btn->set_visible(false); + view_log_btn->set_visible(false); } void MonoBottomPanel::_warnings_toggled(bool p_pressed) { @@ -148,6 +154,22 @@ void MonoBottomPanel::_build_project_pressed() { CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true); } +void MonoBottomPanel::_view_log_pressed() { + + if (build_tabs_list->is_anything_selected()) { + Vector<int> selected_items = build_tabs_list->get_selected_items(); + CRASH_COND(selected_items.size() != 1); + int selected_item = selected_items[0]; + + MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item)); + ERR_FAIL_NULL(build_tab); + + String log_dirpath = build_tab->get_build_info().get_log_dirpath(); + + OS::get_singleton()->shell_open(log_dirpath.plus_file("msbuild_log.txt")); + } +} + void MonoBottomPanel::_notification(int p_what) { switch (p_what) { @@ -163,10 +185,11 @@ void MonoBottomPanel::_notification(int p_what) { void MonoBottomPanel::_bind_methods() { ClassDB::bind_method(D_METHOD("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed); + ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed); ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled); ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled); - ClassDB::bind_method(D_METHOD("_build_tab_item_selected", "idx"), &MonoBottomPanel::_build_tab_item_selected); - ClassDB::bind_method(D_METHOD("_build_tab_changed", "idx"), &MonoBottomPanel::_build_tab_changed); + ClassDB::bind_method(D_METHOD("_build_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected); + ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected); } MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { @@ -223,6 +246,15 @@ MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { errors_btn->connect("toggled", this, "_errors_toggled"); toolbar_hbc->add_child(errors_btn); + toolbar_hbc->add_spacer(); + + view_log_btn = memnew(Button); + view_log_btn->set_text(TTR("View log")); + view_log_btn->set_focus_mode(FOCUS_NONE); + view_log_btn->set_visible(false); + view_log_btn->connect("pressed", this, "_view_log_pressed"); + toolbar_hbc->add_child(view_log_btn); + HSplitContainer *hsc = memnew(HSplitContainer); hsc->set_h_size_flags(SIZE_EXPAND_FILL); hsc->set_v_size_flags(SIZE_EXPAND_FILL); @@ -230,14 +262,14 @@ MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { build_tabs_list = memnew(ItemList); build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs_list->connect("item_selected", this, "_build_tab_item_selected"); + build_tabs_list->connect("item_selected", this, "_build_tabs_item_selected"); + build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_selected"); hsc->add_child(build_tabs_list); build_tabs = memnew(TabContainer); build_tabs->set_tab_align(TabContainer::ALIGN_LEFT); build_tabs->set_h_size_flags(SIZE_EXPAND_FILL); build_tabs->set_tabs_visible(false); - build_tabs->connect("tab_changed", this, "_build_tab_changed"); hsc->add_child(build_tabs); } } diff --git a/modules/mono/editor/mono_bottom_panel.h b/modules/mono/editor/mono_bottom_panel.h index a44d3a9af8..03240e9a13 100644 --- a/modules/mono/editor/mono_bottom_panel.h +++ b/modules/mono/editor/mono_bottom_panel.h @@ -53,16 +53,18 @@ class MonoBottomPanel : public VBoxContainer { Button *warnings_btn; Button *errors_btn; + Button *view_log_btn; void _update_build_tabs_list(); - void _build_tab_item_selected(int p_idx); - void _build_tab_changed(int p_idx); + void _build_tabs_item_selected(int p_idx); + void _build_tabs_nothing_selected(); void _warnings_toggled(bool p_pressed); void _errors_toggled(bool p_pressed); void _build_project_pressed(); + void _view_log_pressed(); static MonoBottomPanel *singleton; diff --git a/modules/mono/editor/mono_build_info.cpp b/modules/mono/editor/mono_build_info.cpp new file mode 100644 index 0000000000..e4af2aac4f --- /dev/null +++ b/modules/mono/editor/mono_build_info.cpp @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* mono_build_info.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "mono_build_info.h" + +#include "../godotsharp_dirs.h" +#include "../mono_gd/gd_mono_utils.h" + +uint32_t MonoBuildInfo::Hasher::hash(const MonoBuildInfo &p_key) { + + uint32_t hash = 0; + + GDMonoUtils::hash_combine(hash, p_key.solution.hash()); + GDMonoUtils::hash_combine(hash, p_key.configuration.hash()); + + return hash; +} + +bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const { + + return p_b.solution == solution && p_b.configuration == configuration; +} + +String MonoBuildInfo::get_log_dirpath() { + + return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration); +} + +MonoBuildInfo::MonoBuildInfo() {} + +MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) { + + solution = p_solution; + configuration = p_config; +} diff --git a/modules/mono/editor/mono_build_info.h b/modules/mono/editor/mono_build_info.h index 4806764a61..64ba0f4037 100644 --- a/modules/mono/editor/mono_build_info.h +++ b/modules/mono/editor/mono_build_info.h @@ -31,35 +31,25 @@ #ifndef MONO_BUILD_INFO_H #define MONO_BUILD_INFO_H -#include "../mono_gd/gd_mono_utils.h" +#include "core/ustring.h" +#include "core/vector.h" struct MonoBuildInfo { struct Hasher { - static _FORCE_INLINE_ uint32_t hash(const MonoBuildInfo &p_key) { - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.solution.hash()); - GDMonoUtils::hash_combine(hash, p_key.configuration.hash()); - - return hash; - } + static uint32_t hash(const MonoBuildInfo &p_key); }; String solution; String configuration; Vector<String> custom_props; - MonoBuildInfo() {} + bool operator==(const MonoBuildInfo &p_b) const; - MonoBuildInfo(const String &p_solution, const String &p_config) { - solution = p_solution; - configuration = p_config; - } + String get_log_dirpath(); - bool operator==(const MonoBuildInfo &p_b) const { - return p_b.solution == solution && p_b.configuration == configuration; - } + MonoBuildInfo(); + MonoBuildInfo(const String &p_solution, const String &p_config); }; #endif // MONO_BUILD_INFO_H diff --git a/modules/mono/glue/cs_files/RPCAttributes.cs b/modules/mono/glue/cs_files/RPCAttributes.cs index 08841ffd76..6bf9560bfa 100644 --- a/modules/mono/glue/cs_files/RPCAttributes.cs +++ b/modules/mono/glue/cs_files/RPCAttributes.cs @@ -13,4 +13,13 @@ namespace Godot [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] public class SlaveAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class RemoteSyncAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class MasterSyncAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class SlaveSyncAttribute : Attribute {} } diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index e6baea3089..c04fcca962 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -606,6 +606,8 @@ MonoArray *Array_to_mono_array(const Array &p_array) { Array mono_array_to_Array(MonoArray *p_array) { Array ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -631,6 +633,8 @@ MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) { PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array) { PoolIntArray ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); for (int i = 0; i < length; i++) { @@ -653,6 +657,8 @@ MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array) { PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array) { PoolByteArray ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -676,6 +682,8 @@ MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array) { PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array) { PoolRealArray ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -700,6 +708,8 @@ MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) { PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array) { PoolStringArray ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -732,6 +742,8 @@ MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array) { PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array) { PoolColorArray ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -763,6 +775,8 @@ MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array) { PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array) { PoolVector2Array ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -795,6 +809,8 @@ MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array) { PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) { PoolVector3Array ret; + if (!p_array) + return ret; int length = mono_array_length(p_array); ret.resize(length); @@ -835,6 +851,9 @@ MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict) { Dictionary mono_object_to_Dictionary(MonoObject *p_dict) { Dictionary ret; + if (!p_dict) + return ret; + GDMonoUtils::MarshalUtils_DictToArrays dict_to_arrays = CACHED_METHOD_THUNK(MarshalUtils, DictionaryToArrays); MonoArray *keys = NULL; diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index db136a1313..fbdbeaa3f7 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -120,6 +120,9 @@ void MonoCache::clear_members() { class_SyncAttribute = NULL; class_MasterAttribute = NULL; class_SlaveAttribute = NULL; + class_RemoteSyncAttribute = NULL; + class_MasterSyncAttribute = NULL; + class_SlaveSyncAttribute = NULL; class_GodotMethodAttribute = NULL; field_GodotMethodAttribute_methodName = NULL; @@ -208,6 +211,9 @@ void update_godot_api_cache() { CACHE_CLASS_AND_CHECK(SyncAttribute, GODOT_API_CLASS(SyncAttribute)); CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute)); CACHE_CLASS_AND_CHECK(SlaveAttribute, GODOT_API_CLASS(SlaveAttribute)); + CACHE_CLASS_AND_CHECK(RemoteSyncAttribute, GODOT_API_CLASS(RemoteSyncAttribute)); + CACHE_CLASS_AND_CHECK(MasterSyncAttribute, GODOT_API_CLASS(MasterSyncAttribute)); + CACHE_CLASS_AND_CHECK(SlaveSyncAttribute, GODOT_API_CLASS(SlaveSyncAttribute)); CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index 1a34180d15..fc13a00e85 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -112,6 +112,9 @@ struct MonoCache { GDMonoClass *class_ToolAttribute; GDMonoClass *class_RemoteAttribute; GDMonoClass *class_SyncAttribute; + GDMonoClass *class_RemoteSyncAttribute; + GDMonoClass *class_MasterSyncAttribute; + GDMonoClass *class_SlaveSyncAttribute; GDMonoClass *class_MasterAttribute; GDMonoClass *class_SlaveAttribute; GDMonoClass *class_GodotMethodAttribute; diff --git a/modules/mono/mono_reg_utils.py b/modules/mono/mono_reg_utils.py index 9c188d07a7..c8ebb54ded 100644 --- a/modules/mono/mono_reg_utils.py +++ b/modules/mono/mono_reg_utils.py @@ -60,10 +60,10 @@ def _find_mono_in_reg_old(subkey, bits): def find_mono_root_dir(bits): root_dir = _find_mono_in_reg(r'SOFTWARE\Mono', bits) if root_dir is not None: - return root_dir + return str(root_dir) root_dir = _find_mono_in_reg_old(r'SOFTWARE\Novell\Mono', bits) if root_dir is not None: - return root_dir + return str(root_dir) return '' diff --git a/modules/ogg/config.py b/modules/ogg/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/ogg/config.py +++ b/modules/ogg/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/opus/config.py b/modules/opus/config.py index 60f8d838d6..a1cde10ea0 100644 --- a/modules/opus/config.py +++ b/modules/opus/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/pvr/config.py b/modules/pvr/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/pvr/config.py +++ b/modules/pvr/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/recast/SCsub b/modules/recast/SCsub index 530df9a37c..f56be72b24 100644 --- a/modules/recast/SCsub +++ b/modules/recast/SCsub @@ -1,8 +1,9 @@ #!/usr/bin/env python Import('env') +Import('env_modules') -# Not building in a separate env as core needs it +env_recast = env_modules.Clone() # Thirdparty source files if env['builtin_recast']: @@ -22,13 +23,10 @@ if env['builtin_recast']: ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - env.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "/Include"]) - - lib = env.add_library("recast_builtin", thirdparty_sources) - env.Append(LIBS=[lib]) + env_recast.add_source_files(env.modules_sources, thirdparty_sources) + env_recast.Append(CPPPATH=[thirdparty_dir + "/Include"]) # Godot source files -env.add_source_files(env.modules_sources, "*.cpp") -env.Append(CCFLAGS=['-DRECAST_ENABLED']) +env_recast.add_source_files(env.modules_sources, "*.cpp") Export('env') diff --git a/modules/recast/config.py b/modules/recast/config.py index fc074cf661..098f1eafa9 100644 --- a/modules/recast/config.py +++ b/modules/recast/config.py @@ -1,5 +1,5 @@ -def can_build(platform): - return platform != "android" +def can_build(env, platform): + return env['tools'] def configure(env): pass diff --git a/editor/plugins/navigation_mesh_editor_plugin.cpp b/modules/recast/navigation_mesh_editor_plugin.cpp index da3c744324..8556b7aa0a 100644 --- a/editor/plugins/navigation_mesh_editor_plugin.cpp +++ b/modules/recast/navigation_mesh_editor_plugin.cpp @@ -29,13 +29,12 @@ /*************************************************************************/ #include "navigation_mesh_editor_plugin.h" + #include "io/marshalls.h" #include "io/resource_saver.h" #include "scene/3d/mesh_instance.h" #include "scene/gui/box_container.h" -#ifdef RECAST_ENABLED - void NavigationMeshEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -162,5 +161,3 @@ NavigationMeshEditorPlugin::NavigationMeshEditorPlugin(EditorNode *p_node) { NavigationMeshEditorPlugin::~NavigationMeshEditorPlugin() { } - -#endif // RECAST_ENABLED diff --git a/editor/plugins/navigation_mesh_editor_plugin.h b/modules/recast/navigation_mesh_editor_plugin.h index 9382467d85..4f3e222002 100644 --- a/editor/plugins/navigation_mesh_editor_plugin.h +++ b/modules/recast/navigation_mesh_editor_plugin.h @@ -31,8 +31,6 @@ #ifndef NAVIGATION_MESH_GENERATOR_PLUGIN_H #define NAVIGATION_MESH_GENERATOR_PLUGIN_H -#ifdef RECAST_ENABLED - #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "navigation_mesh_generator.h" @@ -83,5 +81,4 @@ public: ~NavigationMeshEditorPlugin(); }; -#endif // RECAST_ENABLED #endif // NAVIGATION_MESH_GENERATOR_PLUGIN_H diff --git a/editor/plugins/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp index 0537c5c31f..64c4b85269 100644 --- a/editor/plugins/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -30,8 +30,6 @@ #include "navigation_mesh_generator.h" -#ifdef RECAST_ENABLED - void NavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies) { p_verticies.push_back(p_vec3.x); p_verticies.push_back(p_vec3.y); @@ -304,5 +302,3 @@ void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_nav_mesh) { p_nav_mesh->set_vertices(PoolVector<Vector3>()); } } - -#endif //RECAST_ENABLED diff --git a/editor/plugins/navigation_mesh_generator.h b/modules/recast/navigation_mesh_generator.h index d26f541b8d..3588539ef1 100644 --- a/editor/plugins/navigation_mesh_generator.h +++ b/modules/recast/navigation_mesh_generator.h @@ -31,16 +31,11 @@ #ifndef NAVIGATION_MESH_GENERATOR_H #define NAVIGATION_MESH_GENERATOR_H -#ifdef RECAST_ENABLED - #include "editor/editor_node.h" #include "editor/editor_settings.h" - +#include "os/thread.h" #include "scene/3d/mesh_instance.h" - #include "scene/3d/navigation_mesh.h" - -#include "os/thread.h" #include "scene/resources/shape.h" #include <Recast.h> @@ -61,6 +56,4 @@ public: static void clear(Ref<NavigationMesh> p_nav_mesh); }; -#endif // RECAST_ENABLED - #endif // NAVIGATION_MESH_GENERATOR_H diff --git a/modules/recast/register_types.cpp b/modules/recast/register_types.cpp index 913857c591..f2f18fc86f 100644 --- a/modules/recast/register_types.cpp +++ b/modules/recast/register_types.cpp @@ -30,5 +30,10 @@ #include "register_types.h" -void register_recast_types() {} +#include "navigation_mesh_editor_plugin.h" + +void register_recast_types() { + EditorPlugins::add_by_type<NavigationMeshEditorPlugin>(); +} + void unregister_recast_types() {} diff --git a/modules/regex/config.py b/modules/regex/config.py index cb2da26738..42cfe3b43c 100644 --- a/modules/regex/config.py +++ b/modules/regex/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/squish/config.py b/modules/squish/config.py index 97c95999c8..098f1eafa9 100644 --- a/modules/squish/config.py +++ b/modules/squish/config.py @@ -1,9 +1,5 @@ -def can_build(platform): - return True +def can_build(env, platform): + return env['tools'] def configure(env): - # Tools only, disabled for non-tools - # TODO: Find a cleaner way to achieve that - if not env['tools']: - env['module_squish_enabled'] = False - env.disabled_modules.append("squish") + pass diff --git a/modules/stb_vorbis/config.py b/modules/stb_vorbis/config.py index defe8d0c94..d75e41797a 100644 --- a/modules/stb_vorbis/config.py +++ b/modules/stb_vorbis/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/svg/SCsub b/modules/svg/SCsub index e12abac8c1..a41e0703bd 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -1,7 +1,6 @@ #!/usr/bin/env python Import('env') -from compat import isbasestring # Thirdparty source files thirdparty_dir = "#thirdparty/nanosvg/" @@ -10,23 +9,7 @@ thirdparty_sources = [ ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] -# env.add_source_files(env.modules_sources, thirdparty_sources) - -lib = env.add_library("svg_builtin", thirdparty_sources) - -# Needs to be appended to arrive after libscene in the linker call, -# but we don't want it to arrive *after* system libs, so manual hack -# LIBS contains first SCons Library objects ("SCons.Node.FS.File object") -# and then plain strings for system library. We insert between the two. -inserted = False -for idx, linklib in enumerate(env["LIBS"]): - if isbasestring(linklib): # first system lib such as "X11", otherwise SCons lib object - env["LIBS"].insert(idx, lib) - inserted = True - break -if not inserted: - env.Append(LIBS=[lib]) - +env.add_source_files(env.modules_sources, thirdparty_sources) env.Append(CPPPATH=[thirdparty_dir]) env.Append(CCFLAGS=["-DSVG_ENABLED"]) diff --git a/modules/svg/config.py b/modules/svg/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/svg/config.py +++ b/modules/svg/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/tga/config.py b/modules/tga/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/tga/config.py +++ b/modules/tga/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/thekla_unwrap/config.py b/modules/thekla_unwrap/config.py index b1ce7d4b91..bd092bdc16 100644 --- a/modules/thekla_unwrap/config.py +++ b/modules/thekla_unwrap/config.py @@ -1,7 +1,5 @@ -def can_build(platform): - return platform != "android" and platform != "ios" +def can_build(env, platform): + return (env['tools'] and platform not in ["android", "ios"]) def configure(env): - if not env['tools']: - env['builtin_thekla_atlas'] = False - env.disabled_modules.append("thekla_unwrap") + pass diff --git a/modules/theora/config.py b/modules/theora/config.py index 34d34f8be2..7504166237 100644 --- a/modules/theora/config.py +++ b/modules/theora/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/tinyexr/config.py b/modules/tinyexr/config.py index e12bb398ce..098f1eafa9 100644 --- a/modules/tinyexr/config.py +++ b/modules/tinyexr/config.py @@ -1,9 +1,5 @@ -def can_build(platform): - return True +def can_build(env, platform): + return env['tools'] def configure(env): - # Tools only, disabled for non-tools - # TODO: Find a cleaner way to achieve that - if not env['tools']: - env['module_tinyexr_enabled'] = False - env.disabled_modules.append("tinyexr") + pass diff --git a/modules/upnp/SCsub b/modules/upnp/SCsub new file mode 100644 index 0000000000..cde231867f --- /dev/null +++ b/modules/upnp/SCsub @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_upnp = env_modules.Clone() + +# Thirdparty source files + +if env['builtin_miniupnpc']: + thirdparty_dir = "#thirdparty/miniupnpc/" + thirdparty_sources = [ + "miniupnpc.c", + "upnpcommands.c", + "miniwget.c", + "upnpdev.c", + "igd_desc_parse.c", + "minissdpc.c", + "minisoap.c", + "minixml.c", + "connecthostport.c", + "receivedata.c", + "portlistingparse.c", + "upnpreplyparse.c", + ] + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + + env_upnp.add_source_files(env.modules_sources, thirdparty_sources) + env_upnp.Append(CPPPATH=[thirdparty_dir]) + env_upnp.Append(CPPFLAGS=["-DMINIUPNP_STATICLIB"]) + +env_upnp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/upnp/config.py b/modules/upnp/config.py new file mode 100644 index 0000000000..8724ff1a51 --- /dev/null +++ b/modules/upnp/config.py @@ -0,0 +1,14 @@ +def can_build(env, platform): + return True + +def configure(env): + pass + +def get_doc_classes(): + return [ + "UPNP", + "UPNPDevice" + ] + +def get_doc_path(): + return "doc_classes" diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml new file mode 100644 index 0000000000..30be9c836b --- /dev/null +++ b/modules/upnp/doc_classes/UPNP.xml @@ -0,0 +1,227 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="UPNP" inherits="Reference" category="Core" version="3.1"> + <brief_description> + UPNP network functions. + </brief_description> + <description> + Provides UPNP functionality to discover [UPNPDevice]s on the local network and execute commands on them, like managing port mappings (port forwarding) and querying the local and remote network IP address. Note that methods on this class are synchronous and block the calling thread. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="add_device"> + <return type="void"> + </return> + <argument index="0" name="device" type="UPNPDevice"> + </argument> + <description> + Adds the given [UPNPDevice] to the list of discovered devices. + </description> + </method> + <method name="add_port_mapping" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="port" type="int"> + </argument> + <argument index="1" name="port_internal" type="int" default="0"> + </argument> + <argument index="2" name="desc" type="String" default=""""> + </argument> + <argument index="3" name="proto" type="String" default=""UDP""> + </argument> + <argument index="4" name="duration" type="int" default="0"> + </argument> + <description> + Adds a mapping to forward the external [code]port[/code] (between 1 and 65535) on the default gateway (see [method get_gateway]) to the [code]internal_port[/code] on the local machine for the given protocol [code]proto[/code] (either [code]TCP[/code] or [code]UDP[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any. + If [code]internal_port[/code] is [code]0[/code] (the default), the same port number is used for both the external and the internal port (the [code]port[/code] value). + The description ([code]desc[/code]) is shown in some router UIs and can be used to point out which application added the mapping, and the lifetime of the mapping can be limited by [code]duration[/code]. However, some routers are incompatible with one or both of these, so use with caution and add fallback logic in case of errors to retry without them if in doubt. + See [enum UPNPResult] for possible return values. + </description> + </method> + <method name="clear_devices"> + <return type="void"> + </return> + <description> + Clears the list of discovered devices. + </description> + </method> + <method name="delete_port_mapping" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="port" type="int"> + </argument> + <argument index="1" name="proto" type="String" default=""UDP""> + </argument> + <description> + Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [code]port[/code] must be a valid port between 1 and 65535, [code]proto[/code] can be either [code]TCP[/code] or [code]UDP[/code]. See [enum UPNPResult] for possible return values. + </description> + </method> + <method name="discover"> + <return type="int"> + </return> + <argument index="0" name="timeout" type="int" default="2000"> + </argument> + <argument index="1" name="ttl" type="int" default="2"> + </argument> + <argument index="2" name="device_filter" type="String" default=""InternetGatewayDevice""> + </argument> + <description> + Discovers local [UPNPDevice]s. Clears the list of previously discovered devices. + Filters for IGD (InternetGatewayDevice) type devices by default, as those manage port forwarding. [code]timeout[/code] is the time to wait for responses in miliseconds. [code]ttl[/code] is the time-to-live; only touch this if you know what you're doing. + See [enum UPNPResult] for possible return values. + </description> + </method> + <method name="get_device" qualifiers="const"> + <return type="UPNPDevice"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + Returns the [UPNPDevice] at the given [code]index[/code]. + </description> + </method> + <method name="get_device_count" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the number of discovered [UPNPDevice]s. + </description> + </method> + <method name="get_gateway" qualifiers="const"> + <return type="UPNPDevice"> + </return> + <description> + Returns the default gateway. That is the first discovered [UPNPDevice] that is also a valid IGD (InternetGatewayDevice). + </description> + </method> + <method name="query_external_address" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the external [IP] address of the default gateway (see [method get_gateway]) as string. Returns an empty string on error. + </description> + </method> + <method name="remove_device"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + Removes the device at [code]index[/code] from the list of discovered devices. + </description> + </method> + <method name="set_device"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="device" type="UPNPDevice"> + </argument> + <description> + Sets the device at [code]index[/code] from the list of discovered devices to [code]device[/code]. + </description> + </method> + </methods> + <members> + <member name="discover_ipv6" type="bool" setter="set_discover_ipv6" getter="is_discover_ipv6"> + If [code]true[/code], IPv6 is used for [UPNPDevice] discovery. + </member> + <member name="discover_local_port" type="int" setter="set_discover_local_port" getter="get_discover_local_port"> + If [code]0[/code], the local port to use for discovery is chosen automatically by the system. If [code]1[/code], discovery will be done from the source port 1900 (same as destination port). Otherwise, the value will be used as the port. + </member> + <member name="discover_multicast_if" type="String" setter="set_discover_multicast_if" getter="get_discover_multicast_if"> + Multicast interface to use for discovery. Uses the default multicast interface if empty. + </member> + </members> + <constants> + <constant name="UPNP_RESULT_SUCCESS" value="0" enum="UPNPResult"> + UPNP command or discovery was successful. + </constant> + <constant name="UPNP_RESULT_NOT_AUTHORIZED" value="1" enum="UPNPResult"> + Not authorized to use the command on the [UPNPDevice]. May be returned when the user disabled UPNP on their router. + </constant> + <constant name="UPNP_RESULT_PORT_MAPPING_NOT_FOUND" value="2" enum="UPNPResult"> + No port mapping was found for the given port, protocol combination on the given [UPNPDevice]. + </constant> + <constant name="UPNP_RESULT_INCONSISTENT_PARAMETERS" value="3" enum="UPNPResult"> + Inconsistent parameters. + </constant> + <constant name="UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY" value="4" enum="UPNPResult"> + No such entry in array. May be returned if a given port, protocol combination is not found on an [UPNPDevice]. + </constant> + <constant name="UPNP_RESULT_ACTION_FAILED" value="5" enum="UPNPResult"> + The action failed. + </constant> + <constant name="UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED" value="6" enum="UPNPResult"> + The [UPNPDevice] does not allow wildcard values for the source IP address. + </constant> + <constant name="UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED" value="7" enum="UPNPResult"> + The [UPNPDevice] does not allow wildcard values for the external port. + </constant> + <constant name="UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED" value="8" enum="UPNPResult"> + The [UPNPDevice] does not allow wildcard values for the internal port. + </constant> + <constant name="UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD" value="9" enum="UPNPResult"> + The remote host value must be a wildcard. + </constant> + <constant name="UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD" value="10" enum="UPNPResult"> + The external port value must be a wildcard. + </constant> + <constant name="UPNP_RESULT_NO_PORT_MAPS_AVAILABLE" value="11" enum="UPNPResult"> + No port maps are available. May also be returned if port mapping functionality is not available. + </constant> + <constant name="UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM" value="12" enum="UPNPResult"> + Conflict with other mechanism. May be returned instead of [code]UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING[/code] if a port mapping conflicts with an existing one. + </constant> + <constant name="UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING" value="13" enum="UPNPResult"> + Conflict with an existing port mapping. + </constant> + <constant name="UPNP_RESULT_SAME_PORT_VALUES_REQUIRED" value="14" enum="UPNPResult"> + External and internal port values must be the same. + </constant> + <constant name="UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED" value="15" enum="UPNPResult"> + Only permanent leases are supported. Do not use the [code]duration[/code] parameter when adding port mappings. + </constant> + <constant name="UPNP_RESULT_INVALID_GATEWAY" value="16" enum="UPNPResult"> + Invalid gateway. + </constant> + <constant name="UPNP_RESULT_INVALID_PORT" value="17" enum="UPNPResult"> + Invalid port. + </constant> + <constant name="UPNP_RESULT_INVALID_PROTOCOL" value="18" enum="UPNPResult"> + Invalid protocol. + </constant> + <constant name="UPNP_RESULT_INVALID_DURATION" value="19" enum="UPNPResult"> + Invalid duration. + </constant> + <constant name="UPNP_RESULT_INVALID_ARGS" value="20" enum="UPNPResult"> + Invalid arguments. + </constant> + <constant name="UPNP_RESULT_INVALID_RESPONSE" value="21" enum="UPNPResult"> + Invalid response. + </constant> + <constant name="UPNP_RESULT_INVALID_PARAM" value="22" enum="UPNPResult"> + Invalid parameter. + </constant> + <constant name="UPNP_RESULT_HTTP_ERROR" value="23" enum="UPNPResult"> + HTTP error. + </constant> + <constant name="UPNP_RESULT_SOCKET_ERROR" value="24" enum="UPNPResult"> + Socket error. + </constant> + <constant name="UPNP_RESULT_MEM_ALLOC_ERROR" value="25" enum="UPNPResult"> + Error allocating memory. + </constant> + <constant name="UPNP_RESULT_NO_GATEWAY" value="26" enum="UPNPResult"> + No gateway available. You may need to call [method discover] first, or discovery didn't detect any valid IGDs (InternetGatewayDevices). + </constant> + <constant name="UPNP_RESULT_NO_DEVICES" value="27" enum="UPNPResult"> + No devices available. You may need to call [method discover] first, or discovery didn't detect any valid [UPNPDevice]s. + </constant> + <constant name="UPNP_RESULT_UNKNOWN_ERROR" value="28" enum="UPNPResult"> + Unknown error. + </constant> + </constants> +</class> diff --git a/modules/upnp/doc_classes/UPNPDevice.xml b/modules/upnp/doc_classes/UPNPDevice.xml new file mode 100644 index 0000000000..9de8042daf --- /dev/null +++ b/modules/upnp/doc_classes/UPNPDevice.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="UPNPDevice" inherits="Reference" category="Core" version="3.1"> + <brief_description> + UPNP device. + </brief_description> + <description> + UPNP device. See [UPNP] for UPNP discovery and utility functions. Provides low-level access to UPNP control commands. Allows to manage port mappings (port forwarding) and to query network information of the device (like local and external IP address and status). Note that methods on this class are synchronous and block the calling thread. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="add_port_mapping" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="port" type="int"> + </argument> + <argument index="1" name="port_internal" type="int" default="0"> + </argument> + <argument index="2" name="desc" type="String" default=""""> + </argument> + <argument index="3" name="proto" type="String" default=""UDP""> + </argument> + <argument index="4" name="duration" type="int" default="0"> + </argument> + <description> + Adds a port mapping to forward the given external port on this [UPNPDevice] for the given protocol to the local machine. See [method UPNP.add_port_mapping]. + </description> + </method> + <method name="delete_port_mapping" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="port" type="int"> + </argument> + <argument index="1" name="proto" type="String" default=""UDP""> + </argument> + <description> + Deletes the port mapping identified by the given port and protocol combination on this device. See [method UPNP.delete_port_mapping]. + </description> + </method> + <method name="is_valid_gateway" qualifiers="const"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if this is a valid IGD (InternetGatewayDevice) which potentially supports port forwarding. + </description> + </method> + <method name="query_external_address" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the external IP address of this [UPNPDevice] or an empty string. + </description> + </method> + </methods> + <members> + <member name="description_url" type="String" setter="set_description_url" getter="get_description_url"> + URL to the device description. + </member> + <member name="igd_control_url" type="String" setter="set_igd_control_url" getter="get_igd_control_url"> + IDG control URL. + </member> + <member name="igd_our_addr" type="String" setter="set_igd_our_addr" getter="get_igd_our_addr"> + Address of the local machine in the network connecting it to this [UPNPDevice]. + </member> + <member name="igd_service_type" type="String" setter="set_igd_service_type" getter="get_igd_service_type"> + IGD service type. + </member> + <member name="igd_status" type="int" setter="set_igd_status" getter="get_igd_status" enum="UPNPDevice.IGDStatus"> + IGD status. See [enum IGDStatus]. + </member> + <member name="service_type" type="String" setter="set_service_type" getter="get_service_type"> + Service type. + </member> + </members> + <constants> + <constant name="IGD_STATUS_OK" value="0" enum="IGDStatus"> + OK. + </constant> + <constant name="IGD_STATUS_HTTP_ERROR" value="1" enum="IGDStatus"> + HTTP error. + </constant> + <constant name="IGD_STATUS_HTTP_EMPTY" value="2" enum="IGDStatus"> + Empty HTTP response. + </constant> + <constant name="IGD_STATUS_NO_URLS" value="3" enum="IGDStatus"> + Returned response contained no URLs. + </constant> + <constant name="IGD_STATUS_NO_IGD" value="4" enum="IGDStatus"> + Not a valid IGD. + </constant> + <constant name="IGD_STATUS_DISCONNECTED" value="5" enum="IGDStatus"> + Disconnected. + </constant> + <constant name="IGD_STATUS_UNKNOWN_DEVICE" value="6" enum="IGDStatus"> + Unknown device. + </constant> + <constant name="IGD_STATUS_INVALID_CONTROL" value="7" enum="IGDStatus"> + Invalid control. + </constant> + <constant name="IGD_STATUS_MALLOC_ERROR" value="8" enum="IGDStatus"> + Memory allocation error. + </constant> + <constant name="IGD_STATUS_UNKNOWN_ERROR" value="9" enum="IGDStatus"> + Unknown error. + </constant> + </constants> +</class> diff --git a/modules/upnp/register_types.cpp b/modules/upnp/register_types.cpp new file mode 100644 index 0000000000..c79155c4d2 --- /dev/null +++ b/modules/upnp/register_types.cpp @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "register_types.h" +#include "error_macros.h" +#include "upnp.h" +#include "upnpdevice.h" + +void register_upnp_types() { + + ClassDB::register_class<UPNP>(); + ClassDB::register_class<UPNPDevice>(); +} + +void unregister_upnp_types() { +} diff --git a/modules/upnp/register_types.h b/modules/upnp/register_types.h new file mode 100644 index 0000000000..2aeb06abc7 --- /dev/null +++ b/modules/upnp/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +void register_upnp_types(); +void unregister_upnp_types(); diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp new file mode 100644 index 0000000000..32fdfe22f8 --- /dev/null +++ b/modules/upnp/upnp.cpp @@ -0,0 +1,401 @@ +/*************************************************************************/ +/* upnp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "upnp.h" +#include "miniupnpc/miniwget.h" +#include "upnpcommands.h" +#include <stdlib.h> + +bool UPNP::is_common_device(const String &dev) const { + return dev.empty() || + dev.find("InternetGatewayDevice") >= 0 || + dev.find("WANIPConnection") >= 0 || + dev.find("WANPPPConnection") >= 0 || + dev.find("rootdevice") >= 0; +} + +int UPNP::discover(int timeout, int ttl, const String &device_filter) { + ERR_FAIL_COND_V(timeout < 0, UPNP_RESULT_INVALID_PARAM); + ERR_FAIL_COND_V(ttl < 0, UPNP_RESULT_INVALID_PARAM); + ERR_FAIL_COND_V(ttl > 255, UPNP_RESULT_INVALID_PARAM); + + devices.clear(); + + int error = 0; + struct UPNPDev *devlist; + + if (is_common_device(device_filter)) { + devlist = upnpDiscover(timeout, discover_multicast_if.utf8().get_data(), NULL, discover_local_port, discover_ipv6, ttl, &error); + } else { + devlist = upnpDiscoverAll(timeout, discover_multicast_if.utf8().get_data(), NULL, discover_local_port, discover_ipv6, ttl, &error); + } + + if (error != UPNPDISCOVER_SUCCESS) { + switch (error) { + case UPNPDISCOVER_SOCKET_ERROR: + return UPNP_RESULT_SOCKET_ERROR; + case UPNPDISCOVER_MEMORY_ERROR: + return UPNP_RESULT_MEM_ALLOC_ERROR; + default: + return UPNP_RESULT_UNKNOWN_ERROR; + } + } + + if (!devlist) { + return UPNP_RESULT_NO_DEVICES; + } + + struct UPNPDev *dev = devlist; + + while (dev) { + if (device_filter.empty() || strstr(dev->st, device_filter.utf8().get_data())) { + add_device_to_list(dev, devlist); + } + + dev = dev->pNext; + } + + freeUPNPDevlist(devlist); + + return UPNP_RESULT_SUCCESS; +} + +void UPNP::add_device_to_list(UPNPDev *dev, UPNPDev *devlist) { + Ref<UPNPDevice> new_device; + new_device.instance(); + + new_device->set_description_url(dev->descURL); + new_device->set_service_type(dev->st); + + parse_igd(new_device, devlist); + + devices.push_back(new_device); +} + +char *UPNP::load_description(const String &url, int *size, int *status_code) const { + return (char *)miniwget(url.utf8().get_data(), size, 0, status_code); +} + +void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) { + int size = 0; + int status_code = -1; + char *xml = load_description(dev->get_description_url(), &size, &status_code); + + if (status_code != 200) { + dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_ERROR); + return; + } + + if (!xml || size < 1) { + dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_EMPTY); + return; + } + + struct UPNPUrls *urls = (UPNPUrls *)malloc(sizeof(struct UPNPUrls)); + + if (!urls) { + dev->set_igd_status(UPNPDevice::IGD_STATUS_MALLOC_ERROR); + return; + } + + struct IGDdatas data; + + memset(urls, 0, sizeof(struct UPNPUrls)); + + parserootdesc(xml, size, &data); + free(xml); + xml = 0; + + GetUPNPUrls(urls, &data, dev->get_description_url().utf8().get_data(), 0); + + if (!urls) { + dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_URLS); + return; + } + + char addr[16]; + int i = UPNP_GetValidIGD(devlist, urls, &data, (char *)&addr, 16); + + if (i != 1) { + FreeUPNPUrls(urls); + + switch (i) { + case 0: + dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_IGD); + return; + case 2: + dev->set_igd_status(UPNPDevice::IGD_STATUS_DISCONNECTED); + return; + case 3: + dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_DEVICE); + return; + default: + dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_ERROR); + return; + } + } + + if (urls->controlURL[0] == '\0') { + FreeUPNPUrls(urls); + dev->set_igd_status(UPNPDevice::IGD_STATUS_INVALID_CONTROL); + return; + } + + dev->set_igd_control_url(urls->controlURL); + dev->set_igd_service_type(data.first.servicetype); + dev->set_igd_our_addr(addr); + dev->set_igd_status(UPNPDevice::IGD_STATUS_OK); + + FreeUPNPUrls(urls); +} + +int UPNP::upnp_result(int in) { + switch (in) { + case UPNPCOMMAND_SUCCESS: + return UPNP_RESULT_SUCCESS; + case UPNPCOMMAND_UNKNOWN_ERROR: + return UPNP_RESULT_UNKNOWN_ERROR; + case UPNPCOMMAND_INVALID_ARGS: + return UPNP_RESULT_INVALID_ARGS; + case UPNPCOMMAND_HTTP_ERROR: + return UPNP_RESULT_HTTP_ERROR; + case UPNPCOMMAND_INVALID_RESPONSE: + return UPNP_RESULT_INVALID_RESPONSE; + case UPNPCOMMAND_MEM_ALLOC_ERROR: + return UPNP_RESULT_MEM_ALLOC_ERROR; + + case 402: + return UPNP_RESULT_INVALID_ARGS; + case 403: + return UPNP_RESULT_NOT_AUTHORIZED; + case 501: + return UPNP_RESULT_ACTION_FAILED; + case 606: + return UPNP_RESULT_NOT_AUTHORIZED; + case 714: + return UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY; + case 715: + return UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED; + case 716: + return UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED; + case 718: + return UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING; + case 724: + return UPNP_RESULT_SAME_PORT_VALUES_REQUIRED; + case 725: + return UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED; + case 726: + return UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD; + case 727: + return UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD; + case 728: + return UPNP_RESULT_NO_PORT_MAPS_AVAILABLE; + case 729: + return UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM; + case 732: + return UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED; + case 733: + return UPNP_RESULT_INCONSISTENT_PARAMETERS; + } + + return UPNP_RESULT_UNKNOWN_ERROR; +} + +int UPNP::get_device_count() const { + return devices.size(); +} + +Ref<UPNPDevice> UPNP::get_device(int index) const { + ERR_FAIL_INDEX_V(index, devices.size(), NULL); + + return devices.get(index); +} + +void UPNP::add_device(Ref<UPNPDevice> device) { + ERR_FAIL_COND(device == NULL); + + devices.push_back(device); +} + +void UPNP::set_device(int index, Ref<UPNPDevice> device) { + ERR_FAIL_INDEX(index, devices.size()); + ERR_FAIL_COND(device == NULL); + + devices.set(index, device); +} + +void UPNP::remove_device(int index) { + ERR_FAIL_INDEX(index, devices.size()); + + devices.remove(index); +} + +void UPNP::clear_devices() { + devices.clear(); +} + +Ref<UPNPDevice> UPNP::get_gateway() const { + ERR_FAIL_COND_V(devices.size() < 1, NULL); + + for (int i = 0; i < devices.size(); i++) { + Ref<UPNPDevice> dev = get_device(i); + + if (dev != NULL && dev->is_valid_gateway()) { + return dev; + } + } + + return NULL; +} + +void UPNP::set_discover_multicast_if(const String &m_if) { + discover_multicast_if = m_if; +} + +String UPNP::get_discover_multicast_if() const { + return discover_multicast_if; +} + +void UPNP::set_discover_local_port(int port) { + discover_local_port = port; +} + +int UPNP::get_discover_local_port() const { + return discover_local_port; +} + +void UPNP::set_discover_ipv6(bool ipv6) { + discover_ipv6 = ipv6; +} + +bool UPNP::is_discover_ipv6() const { + return discover_ipv6; +} + +String UPNP::query_external_address() const { + Ref<UPNPDevice> dev = get_gateway(); + + if (dev == NULL) { + return ""; + } + + return dev->query_external_address(); +} + +int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { + Ref<UPNPDevice> dev = get_gateway(); + + if (dev == NULL) { + return UPNP_RESULT_NO_GATEWAY; + } + + dev->delete_port_mapping(port, proto); + + return dev->add_port_mapping(port, port_internal, desc, proto, duration); +} + +int UPNP::delete_port_mapping(int port, String proto) const { + Ref<UPNPDevice> dev = get_gateway(); + + if (dev == NULL) { + return UPNP_RESULT_NO_GATEWAY; + } + + return dev->delete_port_mapping(port, proto); +} + +void UPNP::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_device_count"), &UPNP::get_device_count); + ClassDB::bind_method(D_METHOD("get_device", "index"), &UPNP::get_device); + ClassDB::bind_method(D_METHOD("add_device", "device"), &UPNP::add_device); + ClassDB::bind_method(D_METHOD("set_device", "index", "device"), &UPNP::set_device); + ClassDB::bind_method(D_METHOD("remove_device", "index"), &UPNP::remove_device); + ClassDB::bind_method(D_METHOD("clear_devices"), &UPNP::clear_devices); + + ClassDB::bind_method(D_METHOD("get_gateway"), &UPNP::get_gateway); + + ClassDB::bind_method(D_METHOD("discover", "timeout", "ttl", "device_filter"), &UPNP::discover, DEFVAL(2000), DEFVAL(2), DEFVAL("InternetGatewayDevice")); + + ClassDB::bind_method(D_METHOD("query_external_address"), &UPNP::query_external_address); + + ClassDB::bind_method(D_METHOD("add_port_mapping", "port", "port_internal", "desc", "proto", "duration"), &UPNP::add_port_mapping, DEFVAL(0), DEFVAL(""), DEFVAL("UDP"), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("delete_port_mapping", "port", "proto"), &UPNP::delete_port_mapping, DEFVAL("UDP")); + + ClassDB::bind_method(D_METHOD("set_discover_multicast_if", "m_if"), &UPNP::set_discover_multicast_if); + ClassDB::bind_method(D_METHOD("get_discover_multicast_if"), &UPNP::get_discover_multicast_if); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "discover_multicast_if"), "set_discover_multicast_if", "get_discover_multicast_if"); + + ClassDB::bind_method(D_METHOD("set_discover_local_port", "port"), &UPNP::set_discover_local_port); + ClassDB::bind_method(D_METHOD("get_discover_local_port"), &UPNP::get_discover_local_port); + ADD_PROPERTY(PropertyInfo(Variant::INT, "discover_local_port", PROPERTY_HINT_RANGE, "0,65535"), "set_discover_local_port", "get_discover_local_port"); + + ClassDB::bind_method(D_METHOD("set_discover_ipv6", "ipv6"), &UPNP::set_discover_ipv6); + ClassDB::bind_method(D_METHOD("is_discover_ipv6"), &UPNP::is_discover_ipv6); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "discover_ipv6"), "set_discover_ipv6", "is_discover_ipv6"); + + BIND_ENUM_CONSTANT(UPNP_RESULT_SUCCESS); + BIND_ENUM_CONSTANT(UPNP_RESULT_NOT_AUTHORIZED); + BIND_ENUM_CONSTANT(UPNP_RESULT_PORT_MAPPING_NOT_FOUND); + BIND_ENUM_CONSTANT(UPNP_RESULT_INCONSISTENT_PARAMETERS); + BIND_ENUM_CONSTANT(UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY); + BIND_ENUM_CONSTANT(UPNP_RESULT_ACTION_FAILED); + BIND_ENUM_CONSTANT(UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED); + BIND_ENUM_CONSTANT(UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED); + BIND_ENUM_CONSTANT(UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED); + BIND_ENUM_CONSTANT(UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD); + BIND_ENUM_CONSTANT(UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD); + BIND_ENUM_CONSTANT(UPNP_RESULT_NO_PORT_MAPS_AVAILABLE); + BIND_ENUM_CONSTANT(UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM); + BIND_ENUM_CONSTANT(UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING); + BIND_ENUM_CONSTANT(UPNP_RESULT_SAME_PORT_VALUES_REQUIRED); + BIND_ENUM_CONSTANT(UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_GATEWAY); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PORT); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PROTOCOL); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_DURATION); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_ARGS); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_RESPONSE); + BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PARAM); + BIND_ENUM_CONSTANT(UPNP_RESULT_HTTP_ERROR); + BIND_ENUM_CONSTANT(UPNP_RESULT_SOCKET_ERROR); + BIND_ENUM_CONSTANT(UPNP_RESULT_MEM_ALLOC_ERROR); + BIND_ENUM_CONSTANT(UPNP_RESULT_NO_GATEWAY); + BIND_ENUM_CONSTANT(UPNP_RESULT_NO_DEVICES); + BIND_ENUM_CONSTANT(UPNP_RESULT_UNKNOWN_ERROR); +} + +UPNP::UPNP() { + discover_multicast_if = ""; + discover_local_port = 0; + discover_ipv6 = false; +} + +UPNP::~UPNP() { +} diff --git a/modules/upnp/upnp.h b/modules/upnp/upnp.h new file mode 100644 index 0000000000..fb0c0f30a0 --- /dev/null +++ b/modules/upnp/upnp.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* upnp.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef GODOT_UPNP_H +#define GODOT_UPNP_H + +#include "miniupnpc/miniupnpc.h" +#include "upnpdevice.h" +#include <reference.h> + +class UPNP : public Reference { + + GDCLASS(UPNP, Reference); + +private: + String discover_multicast_if; + int discover_local_port; + bool discover_ipv6; + + Vector<Ref<UPNPDevice> > devices; + + bool is_common_device(const String &dev) const; + void add_device_to_list(UPNPDev *dev, UPNPDev *devlist); + void parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist); + char *load_description(const String &url, int *size, int *status_code) const; + +protected: + static void _bind_methods(); + +public: + enum UPNPResult { + + UPNP_RESULT_SUCCESS, + UPNP_RESULT_NOT_AUTHORIZED, + UPNP_RESULT_PORT_MAPPING_NOT_FOUND, + UPNP_RESULT_INCONSISTENT_PARAMETERS, + UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY, + UPNP_RESULT_ACTION_FAILED, + UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED, + UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED, + UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED, + UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD, + UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD, + UPNP_RESULT_NO_PORT_MAPS_AVAILABLE, + UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM, + UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING, + UPNP_RESULT_SAME_PORT_VALUES_REQUIRED, + UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED, + UPNP_RESULT_INVALID_GATEWAY, + UPNP_RESULT_INVALID_PORT, + UPNP_RESULT_INVALID_PROTOCOL, + UPNP_RESULT_INVALID_DURATION, + UPNP_RESULT_INVALID_ARGS, + UPNP_RESULT_INVALID_RESPONSE, + UPNP_RESULT_INVALID_PARAM, + UPNP_RESULT_HTTP_ERROR, + UPNP_RESULT_SOCKET_ERROR, + UPNP_RESULT_MEM_ALLOC_ERROR, + UPNP_RESULT_NO_GATEWAY, + UPNP_RESULT_NO_DEVICES, + UPNP_RESULT_UNKNOWN_ERROR, + }; + + static int upnp_result(int in); + + int get_device_count() const; + Ref<UPNPDevice> get_device(int index) const; + void add_device(Ref<UPNPDevice> device); + void set_device(int index, Ref<UPNPDevice> device); + void remove_device(int index); + void clear_devices(); + + Ref<UPNPDevice> get_gateway() const; + + int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice"); + + String query_external_address() const; + + int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const; + int delete_port_mapping(int port, String proto = "UDP") const; + + void set_discover_multicast_if(const String &m_if); + String get_discover_multicast_if() const; + + void set_discover_local_port(int port); + int get_discover_local_port() const; + + void set_discover_ipv6(bool ipv6); + bool is_discover_ipv6() const; + + UPNP(); + ~UPNP(); +}; + +VARIANT_ENUM_CAST(UPNP::UPNPResult) + +#endif // GODOT_UPNP_H diff --git a/modules/upnp/upnpdevice.cpp b/modules/upnp/upnpdevice.cpp new file mode 100644 index 0000000000..a5959cf649 --- /dev/null +++ b/modules/upnp/upnpdevice.cpp @@ -0,0 +1,197 @@ +/*************************************************************************/ +/* upnpdevice.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "upnpdevice.h" +#include "upnp.h" +#include "upnpcommands.h" + +String UPNPDevice::query_external_address() const { + ERR_FAIL_COND_V(!is_valid_gateway(), ""); + + char addr[16]; + int i = UPNP_GetExternalIPAddress( + igd_control_url.utf8().get_data(), + igd_service_type.utf8().get_data(), + (char *)&addr); + + ERR_FAIL_COND_V(i != UPNPCOMMAND_SUCCESS, ""); + + return String(addr); +} + +int UPNPDevice::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { + ERR_FAIL_COND_V(!is_valid_gateway(), UPNP::UPNP_RESULT_INVALID_GATEWAY); + ERR_FAIL_COND_V(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT); + ERR_FAIL_COND_V(port_internal < 0 || port_internal > 65535, UPNP::UPNP_RESULT_INVALID_PORT); // Needs to allow 0 because 0 signifies "use external port as internal port" + ERR_FAIL_COND_V(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL); + ERR_FAIL_COND_V(duration < 0, UPNP::UPNP_RESULT_INVALID_DURATION); + + if (port_internal < 1) { + port_internal = port; + } + + int i = UPNP_AddPortMapping( + igd_control_url.utf8().get_data(), + igd_service_type.utf8().get_data(), + itos(port).utf8().get_data(), + itos(port_internal).utf8().get_data(), + igd_our_addr.utf8().get_data(), + desc.empty() ? 0 : desc.utf8().get_data(), + proto.utf8().get_data(), + NULL, // Remote host, always NULL as IGDs don't support it + duration > 0 ? itos(duration).utf8().get_data() : 0); + + ERR_FAIL_COND_V(i != UPNPCOMMAND_SUCCESS, UPNP::upnp_result(i)); + + return UPNP::UPNP_RESULT_SUCCESS; +} + +int UPNPDevice::delete_port_mapping(int port, String proto) const { + ERR_FAIL_COND_V(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT); + ERR_FAIL_COND_V(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL); + + int i = UPNP_DeletePortMapping( + igd_control_url.utf8().get_data(), + igd_service_type.utf8().get_data(), + itos(port).utf8().get_data(), + proto.utf8().get_data(), + NULL // Remote host, always NULL as IGDs don't support it + ); + + ERR_FAIL_COND_V(i != UPNPCOMMAND_SUCCESS, UPNP::upnp_result(i)); + + return UPNP::UPNP_RESULT_SUCCESS; +} + +void UPNPDevice::set_description_url(const String &url) { + description_url = url; +} + +String UPNPDevice::get_description_url() const { + return description_url; +} + +void UPNPDevice::set_service_type(const String &type) { + service_type = type; +} + +String UPNPDevice::get_service_type() const { + return service_type; +} + +void UPNPDevice::set_igd_control_url(const String &url) { + igd_control_url = url; +} + +String UPNPDevice::get_igd_control_url() const { + return igd_control_url; +} + +void UPNPDevice::set_igd_service_type(const String &type) { + igd_service_type = type; +} + +String UPNPDevice::get_igd_service_type() const { + return igd_service_type; +} + +void UPNPDevice::set_igd_our_addr(const String &addr) { + igd_our_addr = addr; +} + +String UPNPDevice::get_igd_our_addr() const { + return igd_our_addr; +} + +void UPNPDevice::set_igd_status(IGDStatus status) { + igd_status = status; +} + +UPNPDevice::IGDStatus UPNPDevice::get_igd_status() const { + return igd_status; +} + +bool UPNPDevice::is_valid_gateway() const { + return igd_status == IGD_STATUS_OK; +} + +void UPNPDevice::_bind_methods() { + ClassDB::bind_method(D_METHOD("is_valid_gateway"), &UPNPDevice::is_valid_gateway); + ClassDB::bind_method(D_METHOD("query_external_address"), &UPNPDevice::query_external_address); + ClassDB::bind_method(D_METHOD("add_port_mapping", "port", "port_internal", "desc", "proto", "duration"), &UPNPDevice::add_port_mapping, DEFVAL(0), DEFVAL(""), DEFVAL("UDP"), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("delete_port_mapping", "port", "proto"), &UPNPDevice::delete_port_mapping, DEFVAL("UDP")); + + ClassDB::bind_method(D_METHOD("set_description_url", "url"), &UPNPDevice::set_description_url); + ClassDB::bind_method(D_METHOD("get_description_url"), &UPNPDevice::get_description_url); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description_url"), "set_description_url", "get_description_url"); + + ClassDB::bind_method(D_METHOD("set_service_type", "type"), &UPNPDevice::set_service_type); + ClassDB::bind_method(D_METHOD("get_service_type"), &UPNPDevice::get_service_type); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "service_type"), "set_service_type", "get_service_type"); + + ClassDB::bind_method(D_METHOD("set_igd_control_url", "url"), &UPNPDevice::set_igd_control_url); + ClassDB::bind_method(D_METHOD("get_igd_control_url"), &UPNPDevice::get_igd_control_url); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "igd_control_url"), "set_igd_control_url", "get_igd_control_url"); + + ClassDB::bind_method(D_METHOD("set_igd_service_type", "type"), &UPNPDevice::set_igd_service_type); + ClassDB::bind_method(D_METHOD("get_igd_service_type"), &UPNPDevice::get_igd_service_type); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "igd_service_type"), "set_igd_service_type", "get_igd_service_type"); + + ClassDB::bind_method(D_METHOD("set_igd_our_addr", "addr"), &UPNPDevice::set_igd_our_addr); + ClassDB::bind_method(D_METHOD("get_igd_our_addr"), &UPNPDevice::get_igd_our_addr); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "igd_our_addr"), "set_igd_our_addr", "get_igd_our_addr"); + + ClassDB::bind_method(D_METHOD("set_igd_status", "status"), &UPNPDevice::set_igd_status); + ClassDB::bind_method(D_METHOD("get_igd_status"), &UPNPDevice::get_igd_status); + ADD_PROPERTY(PropertyInfo(Variant::INT, "igd_status", PROPERTY_HINT_ENUM), "set_igd_status", "get_igd_status"); + + BIND_ENUM_CONSTANT(IGD_STATUS_OK); + BIND_ENUM_CONSTANT(IGD_STATUS_HTTP_ERROR); + BIND_ENUM_CONSTANT(IGD_STATUS_HTTP_EMPTY); + BIND_ENUM_CONSTANT(IGD_STATUS_NO_URLS); + BIND_ENUM_CONSTANT(IGD_STATUS_NO_IGD); + BIND_ENUM_CONSTANT(IGD_STATUS_DISCONNECTED); + BIND_ENUM_CONSTANT(IGD_STATUS_UNKNOWN_DEVICE); + BIND_ENUM_CONSTANT(IGD_STATUS_INVALID_CONTROL); + BIND_ENUM_CONSTANT(IGD_STATUS_MALLOC_ERROR); + BIND_ENUM_CONSTANT(IGD_STATUS_UNKNOWN_ERROR); +} + +UPNPDevice::UPNPDevice() { + description_url = ""; + service_type = ""; + igd_control_url = ""; + igd_service_type = ""; + igd_our_addr = ""; + igd_status = IGD_STATUS_UNKNOWN_ERROR; +} + +UPNPDevice::~UPNPDevice() { +} diff --git a/modules/upnp/upnpdevice.h b/modules/upnp/upnpdevice.h new file mode 100644 index 0000000000..25c9fe1ba3 --- /dev/null +++ b/modules/upnp/upnpdevice.h @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* upnpdevice.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef GODOT_UPNPDEVICE_H +#define GODOT_UPNPDEVICE_H + +#include <reference.h> + +class UPNPDevice : public Reference { + + GDCLASS(UPNPDevice, Reference); + +public: + enum IGDStatus { + + IGD_STATUS_OK, + IGD_STATUS_HTTP_ERROR, + IGD_STATUS_HTTP_EMPTY, + IGD_STATUS_NO_URLS, + IGD_STATUS_NO_IGD, + IGD_STATUS_DISCONNECTED, + IGD_STATUS_UNKNOWN_DEVICE, + IGD_STATUS_INVALID_CONTROL, + IGD_STATUS_MALLOC_ERROR, + IGD_STATUS_UNKNOWN_ERROR, + }; + + void set_description_url(const String &url); + String get_description_url() const; + + void set_service_type(const String &type); + String get_service_type() const; + + void set_igd_control_url(const String &url); + String get_igd_control_url() const; + + void set_igd_service_type(const String &type); + String get_igd_service_type() const; + + void set_igd_our_addr(const String &addr); + String get_igd_our_addr() const; + + void set_igd_status(IGDStatus status); + IGDStatus get_igd_status() const; + + bool is_valid_gateway() const; + String query_external_address() const; + int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const; + int delete_port_mapping(int port, String proto = "UDP") const; + + UPNPDevice(); + ~UPNPDevice(); + +protected: + static void _bind_methods(); + +private: + String description_url; + String service_type; + String igd_control_url; + String igd_service_type; + String igd_our_addr; + IGDStatus igd_status; +}; + +VARIANT_ENUM_CAST(UPNPDevice::IGDStatus) + +#endif // GODOT_UPNPDEVICE_H diff --git a/modules/visual_script/config.py b/modules/visual_script/config.py index 6b1ce41014..07a0450734 100644 --- a/modules/visual_script/config.py +++ b/modules/visual_script/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/visual_script/doc_classes/VisualScript.xml b/modules/visual_script/doc_classes/VisualScript.xml index dab186fb57..28764aca40 100644 --- a/modules/visual_script/doc_classes/VisualScript.xml +++ b/modules/visual_script/doc_classes/VisualScript.xml @@ -9,7 +9,7 @@ You are most likely to use this class via the Visual Script editor or when writing plugins for it. </description> <tutorials> - http://docs.godotengine.org/en/3.0/getting_started/scripting/visual_script/index.html + <link>http://docs.godotengine.org/en/3.0/getting_started/scripting/visual_script/index.html</link> </tutorials> <demos> </demos> diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 318fb7fb9c..9331092171 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1971,11 +1971,11 @@ Ref<Script> VisualScriptInstance::get_script() const { return script; } -ScriptInstance::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_method) const { +MultiplayerAPI::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_method) const { const Map<StringName, VisualScript::Function>::Element *E = script->functions.find(p_method); if (!E) { - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } if (E->get().function_id >= 0 && E->get().nodes.has(E->get().function_id)) { @@ -1987,12 +1987,12 @@ ScriptInstance::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_m } } - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } -ScriptInstance::RPCMode VisualScriptInstance::get_rset_mode(const StringName &p_variable) const { +MultiplayerAPI::RPCMode VisualScriptInstance::get_rset_mode(const StringName &p_variable) const { - return RPC_MODE_DISABLED; + return MultiplayerAPI::RPC_MODE_DISABLED; } void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_owner) { diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index a15360ad39..aaa6dfea11 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -433,8 +433,8 @@ public: virtual ScriptLanguage *get_language(); - virtual RPCMode get_rpc_mode(const StringName &p_method) const; - virtual RPCMode get_rset_mode(const StringName &p_variable) const; + virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; + virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; VisualScriptInstance(); ~VisualScriptInstance(); diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 72b5e09222..0bd64d6a1d 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -137,7 +137,7 @@ class VisualScriptEditor : public ScriptEditorBase { Vector<Pair<Variant::Type, String> > args; }; - HashMap<StringName, Ref<StyleBox>, StringNameHasher> node_styles; + HashMap<StringName, Ref<StyleBox> > node_styles; StringName edited_func; void _update_graph_connections(); diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 4803f29519..8b7b809ec0 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -93,7 +93,7 @@ bool VisualScriptFunction::_set(const StringName &p_name, const Variant &p_value } if (p_name == "rpc/mode") { - rpc_mode = ScriptInstance::RPCMode(int(p_value)); + rpc_mode = MultiplayerAPI::RPCMode(int(p_value)); return true; } @@ -267,11 +267,11 @@ int VisualScriptFunction::get_argument_count() const { return arguments.size(); } -void VisualScriptFunction::set_rpc_mode(ScriptInstance::RPCMode p_mode) { +void VisualScriptFunction::set_rpc_mode(MultiplayerAPI::RPCMode p_mode) { rpc_mode = p_mode; } -ScriptInstance::RPCMode VisualScriptFunction::get_rpc_mode() const { +MultiplayerAPI::RPCMode VisualScriptFunction::get_rpc_mode() const { return rpc_mode; } @@ -319,7 +319,7 @@ VisualScriptFunction::VisualScriptFunction() { stack_size = 256; stack_less = false; sequenced = true; - rpc_mode = ScriptInstance::RPC_MODE_DISABLED; + rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; } void VisualScriptFunction::set_stack_less(bool p_enable) { diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index a0bc35dd92..9bfbd46e47 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -46,7 +46,7 @@ class VisualScriptFunction : public VisualScriptNode { bool stack_less; int stack_size; - ScriptInstance::RPCMode rpc_mode; + MultiplayerAPI::RPCMode rpc_mode; bool sequenced; protected: @@ -93,8 +93,8 @@ public: void set_return_type(Variant::Type p_type); Variant::Type get_return_type() const; - void set_rpc_mode(ScriptInstance::RPCMode p_mode); - ScriptInstance::RPCMode get_rpc_mode() const; + void set_rpc_mode(MultiplayerAPI::RPCMode p_mode); + MultiplayerAPI::RPCMode get_rpc_mode() const; virtual VisualScriptNodeInstance *instance(VisualScriptInstance *p_instance); diff --git a/modules/vorbis/config.py b/modules/vorbis/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/vorbis/config.py +++ b/modules/vorbis/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/webm/config.py b/modules/webm/config.py index dcae4447d5..72a4073423 100644 --- a/modules/webm/config.py +++ b/modules/webm/config.py @@ -1,5 +1,5 @@ -def can_build(platform): - return platform != 'iphone' +def can_build(env, platform): + return platform not in ['iphone'] def configure(env): pass diff --git a/modules/webp/config.py b/modules/webp/config.py index 5f133eba90..1c8cd12a2d 100644 --- a/modules/webp/config.py +++ b/modules/webp/config.py @@ -1,4 +1,4 @@ -def can_build(platform): +def can_build(env, platform): return True def configure(env): diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index b36f1beacd..15a88773e7 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -7,47 +7,57 @@ Import('env_modules') env_lws = env_modules.Clone() -thirdparty_dir = "#thirdparty/lws/" +thirdparty_dir = "#thirdparty/libwebsockets/" helper_dir = "win32helpers/" thirdparty_sources = [ - "client/client.c", - "client/client-handshake.c", - "client/client-parser.c", - "client/ssl-client.c", - - "ext/extension.c", - "ext/extension-permessage-deflate.c", - - "server/fops-zip.c", - "server/lejp-conf.c", - "server/parsers.c", - "server/ranges.c", - "server/server.c", - "server/server-handshake.c", - "server/ssl-server.c", + + "core/alloc.c", + "core/context.c", + "core/libwebsockets.c", + "core/output.c", + "core/pollfd.c", + "core/service.c", + + "event-libs/poll/poll.c", "misc/base64-decode.c", "misc/lejp.c", "misc/sha-1.c", - "alloc.c", - "context.c", - "handshake.c", - "header.c", - "libwebsockets.c", - "output.c", - "pollfd.c", - "service.c", - "ssl.c", - - "mbedtls_wrapper/library/ssl_cert.c", - "mbedtls_wrapper/library/ssl_pkey.c", - "mbedtls_wrapper/library/ssl_stack.c", - "mbedtls_wrapper/library/ssl_methods.c", - "mbedtls_wrapper/library/ssl_lib.c", - "mbedtls_wrapper/library/ssl_x509.c", - "mbedtls_wrapper/platform/ssl_port.c", - "mbedtls_wrapper/platform/ssl_pm.c", + "roles/h1/ops-h1.c", + "roles/http/header.c", + "roles/http/client/client.c", + "roles/http/client/client-handshake.c", + "roles/http/server/fops-zip.c", + "roles/http/server/lejp-conf.c", + "roles/http/server/parsers.c", + "roles/http/server/server.c", + "roles/listen/ops-listen.c", + "roles/pipe/ops-pipe.c", + "roles/raw/ops-raw.c", + + "roles/ws/client-ws.c", + "roles/ws/client-parser-ws.c", + "roles/ws/ops-ws.c", + "roles/ws/server-ws.c", + + "tls/tls.c", + "tls/tls-client.c", + "tls/tls-server.c", + + "tls/mbedtls/wrapper/library/ssl_cert.c", + "tls/mbedtls/wrapper/library/ssl_pkey.c", + "tls/mbedtls/wrapper/library/ssl_stack.c", + "tls/mbedtls/wrapper/library/ssl_methods.c", + "tls/mbedtls/wrapper/library/ssl_lib.c", + "tls/mbedtls/wrapper/library/ssl_x509.c", + "tls/mbedtls/wrapper/platform/ssl_port.c", + "tls/mbedtls/wrapper/platform/ssl_pm.c", + "tls/mbedtls/lws-genhash.c", + "tls/mbedtls/mbedtls-client.c", + "tls/mbedtls/lws-genrsa.c", + "tls/mbedtls/ssl.c", + "tls/mbedtls/mbedtls-server.c" ] if env_lws["platform"] == "android": # Builtin getifaddrs @@ -67,7 +77,7 @@ else: env_lws.add_source_files(env.modules_sources, thirdparty_sources) env_lws.Append(CPPPATH=[thirdparty_dir]) - wrapper_includes = ["#thirdparty/lws/mbedtls_wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] + wrapper_includes = ["#thirdparty/libwebsockets/tls/mbedtls/wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] env_lws.Prepend(CPPPATH=wrapper_includes) if env['builtin_mbedtls']: diff --git a/modules/websocket/config.py b/modules/websocket/config.py index 399ca88fc1..f59ef432b4 100644 --- a/modules/websocket/config.py +++ b/modules/websocket/config.py @@ -1,8 +1,6 @@ - -def can_build(platform): +def can_build(env, platform): return True - def configure(env): pass diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp index 2220c9adf2..06f97aaf05 100644 --- a/modules/websocket/lws_client.cpp +++ b/modules/websocket/lws_client.cpp @@ -32,6 +32,7 @@ #include "lws_client.h" #include "core/io/ip.h" #include "core/io/stream_peer_ssl.h" +#include "tls/mbedtls/wrapper/include/openssl/ssl.h" Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { @@ -140,7 +141,7 @@ int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, voi destroy_context(); return -1; // we should close the connection (would probably happen anyway) - case LWS_CALLBACK_CLOSED: + case LWS_CALLBACK_CLIENT_CLOSED: peer_data->in_count = 0; peer_data->out_count = 0; peer_data->rbw.resize(0); diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h index a850a545d3..a4920c3d54 100644 --- a/modules/websocket/lws_helper.h +++ b/modules/websocket/lws_helper.h @@ -30,6 +30,9 @@ #ifndef LWS_HELPER_H #define LWS_HELPER_H +#define LWS_BUF_SIZE 65536 +#define LWS_PACKET_SIZE LWS_BUF_SIZE + #include "core/io/stream_peer.h" #include "core/os/os.h" #include "core/reference.h" @@ -124,6 +127,7 @@ static void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, /* LWS protocol structs */ ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2)); + memset(ref->lws_structs, 0, sizeof(struct lws_protocols) * (len + 2)); CharString strings = p_names.join(",").ascii(); int str_len = strings.length(); @@ -145,13 +149,15 @@ static void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, structs_ptr[0].name = "http-only"; structs_ptr[0].callback = p_callback; structs_ptr[0].per_session_data_size = data_size; - structs_ptr[0].rx_buffer_size = 0; + structs_ptr[0].rx_buffer_size = LWS_BUF_SIZE; + structs_ptr[0].tx_packet_size = LWS_PACKET_SIZE; /* add user defined protocols */ for (i = 0; i < len; i++) { structs_ptr[i + 1].name = (const char *)&names_ptr[pos]; structs_ptr[i + 1].callback = p_callback; structs_ptr[i + 1].per_session_data_size = data_size; - structs_ptr[i + 1].rx_buffer_size = 0; + structs_ptr[i + 1].rx_buffer_size = LWS_BUF_SIZE; + structs_ptr[i + 1].tx_packet_size = LWS_PACKET_SIZE; pos += pnr[i].ascii().length() + 1; names_ptr[pos - 1] = '\0'; } diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp index 3855a39aef..96acb99cc4 100644 --- a/modules/websocket/lws_peer.cpp +++ b/modules/websocket/lws_peer.cpp @@ -191,8 +191,8 @@ IP_Address LWSPeer::get_connected_host() const { IP_Address ip; int port = 0; - socklen_t len = 0; struct sockaddr_storage addr; + socklen_t len = sizeof(addr); int fd = lws_get_socket_fd(wsi); ERR_FAIL_COND_V(fd == -1, IP_Address()); @@ -212,8 +212,8 @@ uint16_t LWSPeer::get_connected_port() const { IP_Address ip; int port = 0; - socklen_t len = 0; struct sockaddr_storage addr; + socklen_t len = sizeof(addr); int fd = lws_get_socket_fd(wsi); ERR_FAIL_COND_V(fd == -1, 0); diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 6fe137a386..9e6377f4fe 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -1023,7 +1023,7 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,65535,1"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name"), "org.godotengine.$genname")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/name"), "")); diff --git a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png Binary files differindex 94bc406416..372b763ec5 100644 --- a/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/res/drawable-hdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png Binary files differindex ef6fe4e836..c61c440636 100644 --- a/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/res/drawable-mdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/res/drawable/icon.png b/platform/android/java/res/drawable/icon.png Binary files differindex 29c4a7b8fc..6ad9b43117 100644 --- a/platform/android/java/res/drawable/icon.png +++ b/platform/android/java/res/drawable/icon.png diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java index 90848e6a90..8a2d789dc5 100644 --- a/platform/android/java/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/src/org/godotengine/godot/Godot.java @@ -32,6 +32,7 @@ package org.godotengine.godot; import android.R; import android.app.Activity; +import android.content.pm.ConfigurationInfo; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; @@ -246,9 +247,11 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC } }; - public void onVideoInit(boolean use_gl2) { + public void onVideoInit() { - //mView = new GodotView(getApplication(),io,use_gl2); + boolean use_gl3 = getGLESVersionCode() >= 0x00030000; + + //mView = new GodotView(getApplication(),io,use_gl3); //setContentView(mView); layout = new FrameLayout(this); @@ -261,7 +264,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC // ...add to FrameLayout layout.addView(edittext); - mView = new GodotView(getApplication(), io, use_gl2, use_32_bits, this); + mView = new GodotView(getApplication(), io, use_gl3, use_32_bits, this); layout.addView(mView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); edittext.setView(mView); io.setEdit(edittext); @@ -338,6 +341,12 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC return Godot._self; } + public int getGLESVersionCode() { + ActivityManager am = (ActivityManager)Godot.getInstance().getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo(); + return deviceInfo.reqGlEsVersion; + } + private String[] getCommandLine() { InputStream is; try { diff --git a/platform/android/java_glue.cpp b/platform/android/java_glue.cpp index 579c06f76b..e6240ad9e9 100644 --- a/platform/android/java_glue.cpp +++ b/platform/android/java_glue.cpp @@ -614,6 +614,7 @@ static jmethodID _hideKeyboard = 0; static jmethodID _setScreenOrientation = 0; static jmethodID _getUniqueID = 0; static jmethodID _getSystemDir = 0; +static jmethodID _getGLESVersionCode = 0; static jmethodID _playVideo = 0; static jmethodID _isVideoPlaying = 0; static jmethodID _pauseVideo = 0; @@ -685,6 +686,11 @@ static String _get_system_dir(int p_dir) { return String(env->GetStringUTFChars(s, NULL)); } +static int _get_gles_version_code() { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallIntMethod(_godot_instance, _getGLESVersionCode); +} + static void _hide_vk() { JNIEnv *env = ThreadAndroid::get_env(); @@ -764,9 +770,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en godot_io = gob; - _on_video_init = env->GetMethodID(cls, "onVideoInit", "(Z)V"); + _on_video_init = env->GetMethodID(cls, "onVideoInit", "()V"); _setKeepScreenOn = env->GetMethodID(cls, "setKeepScreenOn", "(Z)V"); _alertDialog = env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); + _getGLESVersionCode = env->GetMethodID(cls, "getGLESVersionCode", "()I"); jclass clsio = env->FindClass("org/godotengine/godot/Godot"); if (cls) { @@ -800,16 +807,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en AudioDriverAndroid::setup(gob); } - os_android = new OS_Android(_gfx_init_func, env, _open_uri, _get_user_data_dir, _get_locale, _get_model, _get_screen_dpi, _show_vk, _hide_vk, _get_vk_height, _set_screen_orient, _get_unique_id, _get_system_dir, _play_video, _is_video_playing, _pause_video, _stop_video, _set_keep_screen_on, _alert, p_use_apk_expansion); + os_android = new OS_Android(_gfx_init_func, env, _open_uri, _get_user_data_dir, _get_locale, _get_model, _get_screen_dpi, _show_vk, _hide_vk, _get_vk_height, _set_screen_orient, _get_unique_id, _get_system_dir, _get_gles_version_code, _play_video, _is_video_playing, _pause_video, _stop_video, _set_keep_screen_on, _alert, p_use_apk_expansion); os_android->set_need_reload_hooks(p_need_reload_hook); char wd[500]; getcwd(wd, 500); - //video driver is determined here, because once initialized, it can't be changed - // String vd = ProjectSettings::get_singleton()->get("display/driver"); - - env->CallVoidMethod(_godot_instance, _on_video_init, (jboolean) true); + env->CallVoidMethod(_godot_instance, _on_video_init); } static void _initialize_java_modules() { diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex fcf684c026..ba2a0e366a 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index fc41adeb76..9188f09f21 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -32,6 +32,7 @@ #include "core/io/file_access_buffered_fa.h" #include "core/project_settings.h" +#include "drivers/gles2/rasterizer_gles2.h" #include "drivers/gles3/rasterizer_gles3.h" #include "drivers/unix/dir_access_unix.h" #include "drivers/unix/file_access_unix.h" @@ -125,13 +126,20 @@ void OS_Android::set_opengl_extensions(const char *p_gl_extensions) { Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { - use_gl2 = p_video_driver != 1; + bool use_gl3 = get_gl_version_code_func() >= 0x00030000; + use_gl3 = use_gl3 && (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3"); + use_gl2 = !use_gl3; if (gfx_init_func) gfx_init_func(gfx_init_ud, use_gl2); - RasterizerGLES3::register_config(); - RasterizerGLES3::make_current(); + if (use_gl2) { + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + } else { + RasterizerGLES3::register_config(); + RasterizerGLES3::make_current(); + } visual_server = memnew(VisualServerRaster); /* if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { @@ -684,7 +692,7 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) { return false; } -OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion) { +OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, GetGLVersionCodeFunc p_get_gl_version_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion) { use_apk_expansion = p_use_apk_expansion; default_videomode.width = 800; @@ -706,6 +714,7 @@ OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURI get_screen_dpi_func = p_get_screen_dpi_func; get_unique_id_func = p_get_unique_id; get_system_dir_func = p_get_sdir_func; + get_gl_version_code_func = p_get_gl_version_func; video_play_func = p_video_play_func; video_is_playing_func = p_video_is_playing_func; diff --git a/platform/android/os_android.h b/platform/android/os_android.h index d2457e538d..ac901d4832 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -58,6 +58,7 @@ typedef void (*ShowVirtualKeyboardFunc)(const String &); typedef void (*HideVirtualKeyboardFunc)(); typedef void (*SetScreenOrientationFunc)(int); typedef String (*GetSystemDirFunc)(int); +typedef int (*GetGLVersionCodeFunc)(); typedef void (*VideoPlayFunc)(const String &); typedef bool (*VideoIsPlayingFunc)(); @@ -126,6 +127,7 @@ private: SetScreenOrientationFunc set_screen_orientation_func; GetUniqueIDFunc get_unique_id_func; GetSystemDirFunc get_system_dir_func; + GetGLVersionCodeFunc get_gl_version_code_func; VideoPlayFunc video_play_func; VideoIsPlayingFunc video_is_playing_func; @@ -239,7 +241,7 @@ public: void joy_connection_changed(int p_device, bool p_connected, String p_name); virtual bool _check_internal_feature_support(const String &p_feature); - OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion); + OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, GetGLVersionCodeFunc p_get_gl_version_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion); ~OS_Android(); }; diff --git a/platform/android/run_icon.png b/platform/android/run_icon.png Binary files differindex e53f8e9da5..b687c9ac31 100644 --- a/platform/android/run_icon.png +++ b/platform/android/run_icon.png diff --git a/platform/haiku/detect.py b/platform/haiku/detect.py index 7c62654ef6..2959023204 100644 --- a/platform/haiku/detect.py +++ b/platform/haiku/detect.py @@ -22,7 +22,7 @@ def get_opts(): from SCons.Variables import EnumVariable return [ - EnumVariable('debug_symbols', 'Add debug symbols to release version', 'yes', ('yes', 'no', 'full')), + EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), ] diff --git a/platform/haiku/logo.png b/platform/haiku/logo.png Binary files differindex d5d98e4cc6..a2d8e242a6 100644 --- a/platform/haiku/logo.png +++ b/platform/haiku/logo.png diff --git a/platform/iphone/logo.png b/platform/iphone/logo.png Binary files differindex 8dd718524c..405b6f93ca 100644 --- a/platform/iphone/logo.png +++ b/platform/iphone/logo.png diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 5bf345e6cd..7a6613bb32 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -32,113 +32,134 @@ #include <emscripten.h> -AudioDriverJavaScript *AudioDriverJavaScript::singleton_js = NULL; +AudioDriverJavaScript *AudioDriverJavaScript::singleton = NULL; const char *AudioDriverJavaScript::get_name() const { return "JavaScript"; } -extern "C" EMSCRIPTEN_KEEPALIVE void js_audio_driver_mix_function(int p_frames) { +extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() { - //print_line("MIXI! "+itos(p_frames)); - AudioDriverJavaScript::singleton_js->mix_to_js(p_frames); + AudioDriverJavaScript::singleton->mix_to_js(); } -void AudioDriverJavaScript::mix_to_js(int p_frames) { +void AudioDriverJavaScript::mix_to_js() { - int todo = p_frames; - int offset = 0; + int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode()); + int sample_count = memarr_len(internal_buffer) / channel_count; + int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer); + audio_server_process(sample_count, stream_buffer); + for (int i = 0; i < sample_count * channel_count; i++) { + internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.0; + } +} - while (todo) { +Error AudioDriverJavaScript::init() { - int tomix = MIN(todo, INTERNAL_BUFFER_SIZE); + /* clang-format off */ + EM_ASM({ + _audioDriver_audioContext = new (window.AudioContext || window.webkitAudioContext); + _audioDriver_scriptNode = null; + }); + /* clang-format on */ - audio_server_process(p_frames, stream_buffer); - for (int i = 0; i < tomix * internal_buffer_channels; i++) { - internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.0; + int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode()); + /* clang-format off */ + int buffer_length = EM_ASM_INT({ + var CHANNEL_COUNT = $0; + + var channelCount = _audioDriver_audioContext.destination.channelCount; + try { + // Try letting the browser recommend a buffer length. + _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(0, 0, channelCount); + } catch (e) { + // ...otherwise, default to 4096. + _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(4096, 0, channelCount); } + _audioDriver_scriptNode.connect(_audioDriver_audioContext.destination); - /* clang-format off */ - EM_ASM_ARGS({ - var data = HEAPF32.subarray($0 / 4, $0 / 4 + $2 * 2); - - for (var channel = 0; channel < _as_output_buffer.numberOfChannels; channel++) { - var outputData = _as_output_buffer.getChannelData(channel); - // Loop through samples - for (var sample = 0; sample < $2; sample++) { - // make output equal to the same as the input - outputData[sample + $1] = data[sample * 2 + channel]; - } - } - }, internal_buffer, offset, tomix); - /* clang-format on */ - - todo -= tomix; - offset += tomix; + return _audioDriver_scriptNode.bufferSize; + }, channel_count); + /* clang-format on */ + if (!buffer_length) { + return FAILED; } -} - -Error AudioDriverJavaScript::init() { - return OK; + if (!internal_buffer || memarr_len(internal_buffer) != buffer_length * channel_count) { + if (internal_buffer) + memdelete_arr(internal_buffer); + internal_buffer = memnew_arr(float, buffer_length *channel_count); + } + return internal_buffer ? OK : ERR_OUT_OF_MEMORY; } void AudioDriverJavaScript::start() { - internal_buffer = memnew_arr(float, INTERNAL_BUFFER_SIZE *internal_buffer_channels); - stream_buffer = memnew_arr(int32_t, INTERNAL_BUFFER_SIZE * 4); //max 4 channels - /* clang-format off */ - mix_rate = EM_ASM_INT({ - _as_audioctx = new (window.AudioContext || window.webkitAudioContext); - _as_script_node = _as_audioctx.createScriptProcessor($0, 0, $1); - _as_script_node.connect(_as_audioctx.destination); - console.log(_as_script_node.bufferSize); - var jsAudioDriverMixFunction = cwrap('js_audio_driver_mix_function', null, ['number']); - - _as_script_node.onaudioprocess = function(audioProcessingEvent) { - // The output buffer contains the samples that will be modified and played - _as_output_buffer = audioProcessingEvent.outputBuffer; - jsAudioDriverMixFunction([_as_output_buffer.getChannelData(0).length]); + EM_ASM({ + var INTERNAL_BUFFER_PTR = $0; + + var audioDriverMixFunction = cwrap('audio_driver_js_mix'); + _audioDriver_scriptNode.onaudioprocess = function(audioProcessingEvent) { + audioDriverMixFunction(); + // The output buffer contains the samples that will be modified and played. + var output = audioProcessingEvent.outputBuffer; + var input = HEAPF32.subarray( + INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT, + INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels); + + for (var channel = 0; channel < output.numberOfChannels; channel++) { + var outputData = output.getChannelData(channel); + // Loop through samples. + for (var sample = 0; sample < outputData.length; sample++) { + // Set output equal to input. + outputData[sample] = input[sample * output.numberOfChannels + channel]; + } + } }; - return _as_audioctx.sampleRate; - }, INTERNAL_BUFFER_SIZE, internal_buffer_channels); + }, internal_buffer); /* clang-format on */ } int AudioDriverJavaScript::get_mix_rate() const { - return mix_rate; + /* clang-format off */ + return EM_ASM_INT_V({ + return _audioDriver_audioContext.sampleRate; + }); + /* clang-format on */ } AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { - return SPEAKER_MODE_STEREO; + /* clang-format off */ + return get_speaker_mode_by_total_channels(EM_ASM_INT_V({ + return _audioDriver_audioContext.destination.channelCount; + })); + /* clang-format on */ } +// No locking, as threads are not supported. void AudioDriverJavaScript::lock() { - - /*no locking, as threads are not supported - if (active && mutex) - mutex->lock(); - */ } void AudioDriverJavaScript::unlock() { - - /*no locking, as threads are not supported - if (active && mutex) - mutex->unlock(); - */ } void AudioDriverJavaScript::finish() { + + /* clang-format off */ + EM_ASM({ + _audioDriver_audioContext = null; + _audioDriver_scriptNode = null; + }); + /* clang-format on */ + memdelete_arr(internal_buffer); + internal_buffer = NULL; } AudioDriverJavaScript::AudioDriverJavaScript() { - internal_buffer_channels = 2; - mix_rate = DEFAULT_MIX_RATE; - singleton_js = this; + singleton = this; } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index d78ab8eea4..a65a8ec29f 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -35,18 +35,11 @@ class AudioDriverJavaScript : public AudioDriver { - enum { - INTERNAL_BUFFER_SIZE = 4096, - }; - - int mix_rate; float *internal_buffer; - int internal_buffer_channels; - int32_t *stream_buffer; public: - void mix_to_js(int p_frames); - static AudioDriverJavaScript *singleton_js; + void mix_to_js(); + static AudioDriverJavaScript *singleton; virtual const char *get_name() const; diff --git a/platform/javascript/logo.png b/platform/javascript/logo.png Binary files differindex ce911180ac..36832d93ba 100644 --- a/platform/javascript/logo.png +++ b/platform/javascript/logo.png diff --git a/platform/javascript/run_icon.png b/platform/javascript/run_icon.png Binary files differindex dedee6f479..574abb0150 100644 --- a/platform/javascript/run_icon.png +++ b/platform/javascript/run_icon.png diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 1e9631fae0..72b8aa99f8 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -23,8 +23,8 @@ def get_opts(): return [ ('osxcross_sdk', 'OSXCross SDK version', 'darwin14'), - EnumVariable('debug_symbols', 'Add debug symbols to release version', 'yes', ('yes', 'no', 'full')), - BoolVariable('separate_debug_symbols', 'Create a separate file with the debug symbols', False), + EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), + BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), ] diff --git a/platform/osx/logo.png b/platform/osx/logo.png Binary files differindex 93c6890e85..62086fc415 100644 --- a/platform/osx/logo.png +++ b/platform/osx/logo.png diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 2b2d21553b..7bd5b16f36 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -117,6 +117,7 @@ public: String open_with_filename; Point2 im_position; + bool im_active; ImeCallback im_callback; void *im_target; @@ -233,6 +234,7 @@ public: virtual bool get_window_per_pixel_transparency_enabled() const; virtual void set_window_per_pixel_transparency_enabled(bool p_enabled); + virtual void set_ime_active(const bool p_active); virtual void set_ime_position(const Point2 &p_pos); virtual void set_ime_intermediate_text_callback(ImeCallback p_callback, void *p_inp); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 5589f93a5d..4ece1e0325 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -959,7 +959,7 @@ static int remapKey(unsigned int key) { push_to_key_event_buffer(ke); } - if ((OS_OSX::singleton->im_position.x != 0) && (OS_OSX::singleton->im_position.y != 0)) + if (OS_OSX::singleton->im_active == true) [self interpretKeyEvents:[NSArray arrayWithObject:event]]; } @@ -1129,6 +1129,10 @@ String OS_OSX::get_unique_id() const { return serial_number; } +void OS_OSX::set_ime_active(const bool p_active) { + im_active = p_active; +} + void OS_OSX::set_ime_position(const Point2 &p_pos) { im_position = p_pos; } @@ -1546,7 +1550,9 @@ void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c image = texture->get_data(); - NSBitmapImageRep *imgrep = [[[NSBitmapImageRep alloc] + ERR_FAIL_COND(!image.is_valid()); + + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:int(texture_size.width) pixelsHigh:int(texture_size.height) @@ -1556,7 +1562,7 @@ void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:int(texture_size.width) * 4 - bitsPerPixel:32] autorelease]; + bitsPerPixel:32]; ERR_FAIL_COND(imgrep == nil); uint8_t *pixels = [imgrep bitmapData]; @@ -1588,16 +1594,20 @@ void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c image->unlock(); - NSImage *nsimage = [[[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)] autorelease]; + NSImage *nsimage = [[NSImage alloc] initWithSize:NSMakeSize(texture_size.width, texture_size.height)]; [nsimage addRepresentation:imgrep]; NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)]; + [cursors[p_shape] release]; cursors[p_shape] = cursor; if (p_shape == CURSOR_ARROW) { [cursor set]; } + + [imgrep release]; + [nsimage release]; } else { // Reset to default system cursor cursors[p_shape] = NULL; @@ -2536,6 +2546,7 @@ OS_OSX::OS_OSX() { mouse_mode = OS::MOUSE_MODE_VISIBLE; main_loop = NULL; singleton = this; + im_active = false; im_position = Point2(); im_callback = NULL; im_target = NULL; diff --git a/platform/server/detect.py b/platform/server/detect.py index 7bf445b43f..266b0c5cc9 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -67,9 +67,6 @@ def configure(env): # FIXME: Check for existence of the libs before parsing their flags with pkg-config - if not env['builtin_libwebp']: - env.ParseConfig('pkg-config libwebp --cflags --libs') - # freetype depends on libpng and zlib, so bundling one of them while keeping others # as shared libraries leads to weird issues if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']: @@ -124,6 +121,21 @@ def configure(env): if not env['builtin_libogg']: env.ParseConfig('pkg-config ogg --cflags --libs') + if not env['builtin_libwebp']: + env.ParseConfig('pkg-config libwebp --cflags --libs') + + if not env['builtin_mbedtls']: + # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228 + env.Append(LIBS=['mbedtls', 'mbedcrypto', 'mbedx509']) + + if not env['builtin_libwebsockets']: + env.ParseConfig('pkg-config libwebsockets --cflags --libs') + + if not env['builtin_miniupnpc']: + # No pkgconfig file so far, hardcode default paths. + env.Append(CPPPATH=["/usr/include/miniupnpc"]) + env.Append(LIBS=["miniupnpc"]) + # On Linux wchar_t should be 32-bits # 16-bit library shouldn't be required due to compiler optimisations if not env['builtin_pcre2']: diff --git a/platform/server/logo.png b/platform/server/logo.png Binary files differindex 5e98ac26ec..8666ada9ca 100644 --- a/platform/server/logo.png +++ b/platform/server/logo.png diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index 0e7b125dc5..559f23ca5b 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -28,8 +28,8 @@ def get_opts(): from SCons.Variables import BoolVariable return [ - ('msvc_version', 'MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.', None), - BoolVariable('use_mingw', 'Use the Mingw compiler, even if MSVC is installed. Only used on Windows.', False), + ('msvc_version', 'MSVC version to use (ignored if the VCINSTALLDIR environment variable is set)', None), + BoolVariable('use_mingw', 'Use the MinGW compiler even if MSVC is installed (only used on Windows)', False), ] @@ -112,7 +112,7 @@ def configure(env): env["bits"] = "32" print("Compiled program architecture will be a x86 executable. (forcing bits=32).") else: - print("Failed to detect MSVC compiler architecture version... Defaulting to 32bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup.") + print("Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup.") env["bits"] = "32" if (env["bits"] == "32"): diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 6d559520d7..05806d2fe8 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -62,8 +62,8 @@ def get_opts(): # XP support dropped after EOL due to missing API for IPv6 and other issues # Vista support dropped after EOL due to GH-10243 ('target_win_version', 'Targeted Windows version, >= 0x0601 (Windows 7)', '0x0601'), - EnumVariable('debug_symbols', 'Add debug symbols to release version', 'yes', ('yes', 'no', 'full')), - BoolVariable('separate_debug_symbols', 'Create a separate file with the debug symbols', False), + EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), + BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), ('msvc_version', 'MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.', None), BoolVariable('use_mingw', 'Use the Mingw compiler, even if MSVC is installed. Only used on Windows.', False), ] diff --git a/platform/windows/logo.png b/platform/windows/logo.png Binary files differindex 4376abd563..f06b463850 100644 --- a/platform/windows/logo.png +++ b/platform/windows/logo.png diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 8d664b5832..f52c8881d4 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -455,6 +455,13 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } break; case WM_LBUTTONDOWN: case WM_LBUTTONUP: + if (input->is_emulating_mouse_from_touch()) { + // Universal translation enabled; ignore OS translations for left button + LPARAM extra = GetMessageExtraInfo(); + if (IsPenEvent(extra)) { + break; + } + } case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: @@ -467,14 +474,6 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) /*case WM_XBUTTONDOWN: case WM_XBUTTONUP: */ { - if (input->is_emulating_mouse_from_touch()) { - // Universal translation enabled; ignore OS translation - LPARAM extra = GetMessageExtraInfo(); - if (IsPenEvent(extra)) { - break; - } - } - Ref<InputEventMouseButton> mb; mb.instance(); @@ -742,13 +741,18 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) { for (UINT i = 0; i < cInputs; i++) { TOUCHINPUT ti = pInputs[i]; + POINT touch_pos = { + TOUCH_COORD_TO_PIXEL(ti.x), + TOUCH_COORD_TO_PIXEL(ti.y), + }; + ScreenToClient(hWnd, &touch_pos); //do something with each touch input entry if (ti.dwFlags & TOUCHEVENTF_MOVE) { - _drag_event(ti.x / 100.0f, ti.y / 100.0f, ti.dwID); + _drag_event(touch_pos.x, touch_pos.y, ti.dwID); } else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) { - _touch_event(ti.dwFlags & TOUCHEVENTF_DOWN, ti.x / 100.0f, ti.y / 100.0f, ti.dwID); + _touch_event(ti.dwFlags & TOUCHEVENTF_DOWN, touch_pos.x, touch_pos.y, ti.dwID); }; } bHandled = TRUE; @@ -1181,6 +1185,15 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int if (p_desired.layered_splash) { set_window_per_pixel_transparency_enabled(true); } + + // IME + im_himc = ImmGetContext(hWnd); + ImmReleaseContext(hWnd, im_himc); + + im_position = Vector2(); + + set_ime_active(false); + return OK; } @@ -2077,11 +2090,13 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap image = texture->get_data(); + ERR_FAIL_COND(!image.is_valid()); + UINT image_size = texture_size.width * texture_size.height; UINT size = sizeof(UINT) * image_size; // Create the BITMAP with alpha channel - COLORREF *buffer = (COLORREF *)malloc(sizeof(COLORREF) * image_size); + COLORREF *buffer = (COLORREF *)memalloc(sizeof(COLORREF) * image_size); image->lock(); for (UINT index = 0; index < image_size; index++) { @@ -2108,6 +2123,8 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap GetMaskBitmaps(bitmap, clrTransparent, hAndMask, hXorMask); if (NULL == hAndMask || NULL == hXorMask) { + memfree(buffer); + DeleteObject(bitmap); return; } @@ -2132,6 +2149,9 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap if (hXorMask != NULL) { DeleteObject(hXorMask); } + + memfree(buffer); + DeleteObject(bitmap); } else { // Reset to default system cursor cursors[p_shape] = NULL; @@ -2202,10 +2222,6 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, argss += String(" \"") + E->get() + "\""; } - //print_line("ARGS: "+argss); - //argss+"\""; - //argss+=" 2>nul"; - FILE *f = _wpopen(argss.c_str(), L"r"); ERR_FAIL_COND_V(!f, ERR_CANT_OPEN); @@ -2232,15 +2248,12 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, I = I->next(); }; - //cmdline+="\""; - ProcessInfo pi; ZeroMemory(&pi.si, sizeof(pi.si)); pi.si.cb = sizeof(pi.si); ZeroMemory(&pi.pi, sizeof(pi.pi)); LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; - print_line("running cmdline: " + cmdline); Vector<CharType> modstr; //windows wants to change this no idea why modstr.resize(cmdline.size()); for (int i = 0; i < cmdline.size(); i++) @@ -2659,13 +2672,29 @@ String OS_Windows::get_unique_id() const { return String(HwProfInfo.szHwProfileGuid); } +void OS_Windows::set_ime_active(const bool p_active) { + + if (p_active) { + ImmAssociateContext(hWnd, im_himc); + + set_ime_position(im_position); + } else { + ImmAssociateContext(hWnd, (HIMC)0); + } +} + void OS_Windows::set_ime_position(const Point2 &p_pos) { + im_position = p_pos; + HIMC himc = ImmGetContext(hWnd); + if (himc == (HIMC)0) + return; + COMPOSITIONFORM cps; cps.dwStyle = CFS_FORCE_POSITION; - cps.ptCurrentPos.x = p_pos.x; - cps.ptCurrentPos.y = p_pos.y; + cps.ptCurrentPos.x = im_position.x; + cps.ptCurrentPos.y = im_position.y; ImmSetCompositionWindow(himc, &cps); ImmReleaseContext(hWnd, himc); } diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 81849497ee..19af63bae0 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -111,6 +111,10 @@ class OS_Windows : public OS { WNDPROC user_proc; + // IME + HIMC im_himc; + Vector2 im_position; + MouseMode mouse_mode; bool alt_mem; bool gr_mem; @@ -282,6 +286,7 @@ public: virtual String get_unique_id() const; + virtual void set_ime_active(const bool p_active); virtual void set_ime_position(const Point2 &p_pos); virtual void release_rendering_thread(); diff --git a/platform/x11/detect.py b/platform/x11/detect.py index ad2620c9f5..09e16ad078 100644 --- a/platform/x11/detect.py +++ b/platform/x11/detect.py @@ -59,8 +59,8 @@ def get_opts(): BoolVariable('use_leak_sanitizer', 'Use LLVM compiler memory leaks sanitizer (implies use_sanitizer)', False), BoolVariable('pulseaudio', 'Detect & use pulseaudio', True), BoolVariable('udev', 'Use udev for gamepad connection callbacks', False), - EnumVariable('debug_symbols', 'Add debug symbols to release version', 'yes', ('yes', 'no', 'full')), - BoolVariable('separate_debug_symbols', 'Create a separate file with the debug symbols', False), + EnumVariable('debug_symbols', 'Add debugging symbols to release builds', 'yes', ('yes', 'no', 'full')), + BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), BoolVariable('touch', 'Enable touch events', True), ] @@ -158,14 +158,6 @@ def configure(env): # FIXME: Check for existence of the libs before parsing their flags with pkg-config - if not env['builtin_mbedtls']: - # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228 - env.Append(LIBS=['mbedtls', 'mbedcrypto', 'mbedx509']) - - if not env['builtin_libwebp']: - env.ParseConfig('pkg-config libwebp --cflags --libs') - - # freetype depends on libpng and zlib, so bundling one of them while keeping others # as shared libraries leads to weird issues if env['builtin_freetype'] or env['builtin_libpng'] or env['builtin_zlib']: @@ -205,6 +197,10 @@ def configure(env): env['builtin_libogg'] = False # Needed to link against system libtheora env['builtin_libvorbis'] = False # Needed to link against system libtheora env.ParseConfig('pkg-config theora theoradec --cflags --libs') + else: + list_of_x86 = ['x86_64', 'x86', 'i386', 'i586'] + if any(platform.machine() in s for s in list_of_x86): + env["x86_libtheora_opt_gcc"] = True if not env['builtin_libvpx']: env.ParseConfig('pkg-config vpx --cflags --libs') @@ -220,10 +216,20 @@ def configure(env): if not env['builtin_libogg']: env.ParseConfig('pkg-config ogg --cflags --libs') - if env['builtin_libtheora']: - list_of_x86 = ['x86_64', 'x86', 'i386', 'i586'] - if any(platform.machine() in s for s in list_of_x86): - env["x86_libtheora_opt_gcc"] = True + if not env['builtin_libwebp']: + env.ParseConfig('pkg-config libwebp --cflags --libs') + + if not env['builtin_mbedtls']: + # mbedTLS does not provide a pkgconfig config yet. See https://github.com/ARMmbed/mbedtls/issues/228 + env.Append(LIBS=['mbedtls', 'mbedcrypto', 'mbedx509']) + + if not env['builtin_libwebsockets']: + env.ParseConfig('pkg-config libwebsockets --cflags --libs') + + if not env['builtin_miniupnpc']: + # No pkgconfig file so far, hardcode default paths. + env.Append(CPPPATH=["/usr/include/miniupnpc"]) + env.Append(LIBS=["miniupnpc"]) # On Linux wchar_t should be 32-bits # 16-bit library shouldn't be required due to compiler optimisations diff --git a/platform/x11/logo.png b/platform/x11/logo.png Binary files differindex 1cc93b46ac..078654b757 100644 --- a/platform/x11/logo.png +++ b/platform/x11/logo.png diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 7b514d0f90..2bc85f76c9 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -391,6 +391,9 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true); XSetWMProtocols(x11_display, x11_window, &wm_delete, 1); + im_active = false; + im_position = Vector2(); + if (xim && xim_style) { xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, x11_window, XNFocusWindow, x11_window, (char *)NULL); @@ -400,7 +403,7 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a xic = NULL; } if (xic) { - XSetICFocus(xic); + XUnsetICFocus(xic); } else { WARN_PRINT("XCreateIC couldn't create xic"); } @@ -541,8 +544,25 @@ void OS_X11::xim_destroy_callback(::XIM im, ::XPointer client_data, os->xic = NULL; } +void OS_X11::set_ime_active(const bool p_active) { + + im_active = p_active; + + if (!xic) + return; + + if (p_active) { + XSetICFocus(xic); + set_ime_position(im_position); + } else { + XUnsetICFocus(xic); + } +} + void OS_X11::set_ime_position(const Point2 &p_pos) { + im_position = p_pos; + if (!xic) return; @@ -1934,6 +1954,7 @@ void OS_X11::process_xevents() { // to be able to send relative motion events. Point2i pos(event.xmotion.x, event.xmotion.y); +#ifdef TOUCH_ENABLED // Avoidance of spurious mouse motion (see handling of touch) bool filter = false; // Adding some tolerance to match better Point2i to Vector2 @@ -1945,6 +1966,7 @@ void OS_X11::process_xevents() { if (filter) { break; } +#endif if (mouse_mode == MOUSE_MODE_CAPTURED) { @@ -2452,6 +2474,8 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c image = texture->get_data(); + ERR_FAIL_COND(!image.is_valid()); + // Create the cursor structure XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); XcursorUInt image_size = texture_size.width * texture_size.height; @@ -2463,7 +2487,7 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c cursor_image->yhot = p_hotspot.y; // allocate memory to contain the whole file - cursor_image->pixels = (XcursorPixel *)malloc(size); + cursor_image->pixels = (XcursorPixel *)memalloc(size); image->lock(); @@ -2489,6 +2513,9 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c if (p_shape == CURSOR_ARROW) { XDefineCursor(x11_display, x11_window, cursors[p_shape]); } + + memfree(cursor_image->pixels); + XcursorImageDestroy(cursor_image); } else { // Reset to default system cursor if (img[p_shape]) { @@ -2502,17 +2529,23 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c void OS_X11::release_rendering_thread() { +#if defined(OPENGL_ENABLED) context_gl->release_current(); +#endif } void OS_X11::make_rendering_thread() { +#if defined(OPENGL_ENABLED) context_gl->make_current(); +#endif } void OS_X11::swap_buffers() { +#if defined(OPENGL_ENABLED) context_gl->swap_buffers(); +#endif } void OS_X11::alert(const String &p_alert, const String &p_title) { @@ -2606,8 +2639,10 @@ String OS_X11::get_joy_guid(int p_device) const { } void OS_X11::_set_use_vsync(bool p_enable) { +#if defined(OPENGL_ENABLED) if (context_gl) - return context_gl->set_use_vsync(p_enable); + context_gl->set_use_vsync(p_enable); +#endif } /* bool OS_X11::is_vsync_enabled() const { diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 09ed9588c4..8cab23fe63 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -116,6 +116,10 @@ class OS_X11 : public OS_Unix { static void xim_destroy_callback(::XIM im, ::XPointer client_data, ::XPointer call_data); + // IME + bool im_active; + Vector2 im_position; + Point2i last_mouse_pos; bool last_mouse_pos_valid; Point2i last_click_pos; @@ -269,6 +273,7 @@ public: virtual bool get_window_per_pixel_transparency_enabled() const; virtual void set_window_per_pixel_transparency_enabled(bool p_enabled); + virtual void set_ime_active(const bool p_active); virtual void set_ime_position(const Point2 &p_pos); virtual String get_unique_id() const; diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index 27bdeda4a8..f1c09594da 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -272,8 +272,7 @@ bool CanvasItem::is_visible_in_tree() const { void CanvasItem::_propagate_visibility_changed(bool p_visible) { - if (!first_draw) - notification(NOTIFICATION_VISIBILITY_CHANGED); + notification(NOTIFICATION_VISIBILITY_CHANGED); if (p_visible) update(); //todo optimize diff --git a/scene/2d/joints_2d.cpp b/scene/2d/joints_2d.cpp index 329382c034..7d5360c0e4 100644 --- a/scene/2d/joints_2d.cpp +++ b/scene/2d/joints_2d.cpp @@ -158,8 +158,8 @@ void Joint2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint2D::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint2D::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject2D"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0,0.9,0.001"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_collision"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); } diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index 4d6ebc81c3..81ed3c63c3 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -649,7 +649,7 @@ void Polygon2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation_degrees", PROPERTY_HINT_RANGE, "-1440,1440,0.1"), "set_texture_rotation_degrees", "get_texture_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "texture_rotation", PROPERTY_HINT_NONE, "", 0), "set_texture_rotation", "get_texture_rotation"); ADD_GROUP("Skeleton", ""); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton"), "set_skeleton", "get_skeleton"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton2D"), "set_skeleton", "get_skeleton"); ADD_GROUP("Invert", "invert_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_enable"), "set_invert", "get_invert"); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index da764e032b..63c3d78dfd 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -201,7 +201,7 @@ void RemoteTransform2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform2D::set_update_scale); ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform2D::get_update_scale); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path"), "set_remote_node", "get_remote_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_remote_node", "get_remote_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates"); ADD_GROUP("Update", "update_"); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index 0595cc43b8..8ceffb3c27 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -89,8 +89,8 @@ void Bone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length); ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length); - ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D,"rest"),"set_rest","get_rest"); - ADD_PROPERTY(PropertyInfo(Variant::REAL,"default_length",PROPERTY_HINT_RANGE,"1,1024,1"),"set_default_length","get_default_length"); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_length", PROPERTY_HINT_RANGE, "1,1024,1"), "set_default_length", "get_default_length"); } void Bone2D::set_rest(const Transform2D &p_rest) { @@ -120,8 +120,7 @@ void Bone2D::apply_rest() { void Bone2D::set_default_length(float p_length) { - default_length=p_length; - + default_length = p_length; } float Bone2D::get_default_length() const { @@ -129,7 +128,7 @@ float Bone2D::get_default_length() const { } int Bone2D::get_index_in_skeleton() const { - ERR_FAIL_COND_V(!skeleton,-1); + ERR_FAIL_COND_V(!skeleton, -1); skeleton->_update_bone_setup(); return skeleton_index; } @@ -137,22 +136,21 @@ String Bone2D::get_configuration_warning() const { String warning = Node2D::get_configuration_warning(); if (!skeleton) { - if (warning!=String()) { - warning+="\n"; + if (warning != String()) { + warning += "\n"; } if (parent_bone) { - warning+=TTR("This Bone2D chain should end at a Skeleton2D node."); + warning += TTR("This Bone2D chain should end at a Skeleton2D node."); } else { - warning+=TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."); + warning += TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node."); } } - if (rest==Transform2D(0,0,0,0,0,0)) { - if (warning!=String()) { - warning+="\n"; + if (rest == Transform2D(0, 0, 0, 0, 0, 0)) { + if (warning != String()) { + warning += "\n"; } - warning+=TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); - + warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one."); } return warning; @@ -161,12 +159,12 @@ String Bone2D::get_configuration_warning() const { Bone2D::Bone2D() { skeleton = NULL; parent_bone = NULL; - skeleton_index=-1; - default_length=16; + skeleton_index = -1; + default_length = 16; set_notify_local_transform(true); //this is a clever hack so the bone knows no rest has been set yet, allowing to show an error. - for(int i=0;i<3;i++) { - rest[i]=Vector2(0,0); + for (int i = 0; i < 3; i++) { + rest[i] = Vector2(0, 0); } } @@ -194,12 +192,12 @@ void Skeleton2D::_update_bone_setup() { for (int i = 0; i < bones.size(); i++) { bones[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose - bones[i].bone->skeleton_index=i; + bones[i].bone->skeleton_index = i; Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent()); if (parent_bone) { - bones[i].parent_index=parent_bone->skeleton_index; + bones[i].parent_index = parent_bone->skeleton_index; } else { - bones[i].parent_index=-1; + bones[i].parent_index = -1; } } @@ -230,8 +228,8 @@ void Skeleton2D::_update_transform() { for (int i = 0; i < bones.size(); i++) { - ERR_CONTINUE(bones[i].parent_index>=i); - if (bones[i].parent_index>=0) { + ERR_CONTINUE(bones[i].parent_index >= i); + if (bones[i].parent_index >= 0) { bones[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform(); } else { bones[i].accum_transform = bones[i].bone->get_transform(); @@ -277,7 +275,7 @@ void Skeleton2D::_notification(int p_what) { } if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - VS::get_singleton()->skeleton_set_base_transform_2d(skeleton,get_global_transform()); + VS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform()); } } diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index b86cf3be81..9d0a061457 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -38,12 +38,13 @@ class Skeleton2D; class Bone2D : public Node2D { GDCLASS(Bone2D, Node2D) + friend class Skeleton2D; + Bone2D *parent_bone; Skeleton2D *skeleton; Transform2D rest; float default_length; -friend class Skeleton2D; int skeleton_index; protected: diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index d88e148b2c..1d60037287 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -466,10 +466,12 @@ void TileMap::_update_dirty_quadrants() { Transform2D xform; xform.set_origin(offset.floor()); - Vector2 shape_ofs = tile_set->tile_get_shape_offset(c.id, i); + Vector2 shape_ofs = shapes[i].shape_transform.get_origin(); _fix_cell_transform(xform, c, shape_ofs + center_ofs, s); + xform *= shapes[i].shape_transform.untranslated(); + if (debug_canvas_item.is_valid()) { vs->canvas_item_add_set_transform(debug_canvas_item, xform); shape->draw(debug_canvas_item, debug_collision_color); @@ -706,7 +708,7 @@ void TileMap::_erase_quadrant(Map<PosKey, Quadrant>::Element *Q) { rect_cache_dirty = true; } -void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q) { +void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update) { Quadrant &q = Q->get(); if (!q.dirty_list.in_list()) @@ -717,7 +719,10 @@ void TileMap::_make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q) { pending_update = true; if (!is_inside_tree()) return; - _update_dirty_quadrants(); + + if (update) { + _update_dirty_quadrants(); + } } void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose) { @@ -725,6 +730,11 @@ void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_ set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose); } +void TileMap::set_celld(const Vector2 &p_pos, const Dictionary &p_data) { + + set_cell(p_pos.x, p_pos.y, p_data["id"], p_data["flip_h"], p_data["flip_y"], p_data["transpose"], p_data["auto_coord"]); +} + void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) { PosKey pk(p_x, p_y); @@ -977,6 +987,14 @@ void TileMap::set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord) c.autotile_coord_x = p_coord.x; c.autotile_coord_y = p_coord.y; tile_map[pk] = c; + + PosKey qk(p_x / _get_quadrant_size(), p_y / _get_quadrant_size()); + Map<PosKey, Quadrant>::Element *Q = quadrant_map.find(qk); + + if (!Q) + return; + + _make_quadrant_dirty(Q); } Vector2 TileMap::get_cell_autotile_coord(int p_x, int p_y) const { @@ -1006,8 +1024,9 @@ void TileMap::_recreate_quadrants() { } Q->get().cells.insert(E->key()); - _make_quadrant_dirty(Q); + _make_quadrant_dirty(Q, false); } + _update_dirty_quadrants(); } void TileMap::_clear_quadrants() { @@ -1602,6 +1621,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &TileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2())); ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &TileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_celld", "data"), &TileMap::set_celld); ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &TileMap::get_cell); ClassDB::bind_method(D_METHOD("get_cellv", "position"), &TileMap::get_cellv); ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &TileMap::is_cell_x_flipped); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 07947004b3..3ddb143f4a 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -188,7 +188,7 @@ private: Map<PosKey, Quadrant>::Element *_create_quadrant(const PosKey &p_qk); void _erase_quadrant(Map<PosKey, Quadrant>::Element *Q); - void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q); + void _make_quadrant_dirty(Map<PosKey, Quadrant>::Element *Q, bool update = true); void _recreate_quadrants(); void _clear_quadrants(); void _update_dirty_quadrants(); @@ -241,6 +241,7 @@ public: void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord); Vector2 get_cell_autotile_coord(int p_x, int p_y) const; + void set_celld(const Vector2 &p_pos, const Dictionary &p_data); void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false); int get_cellv(const Vector2 &p_pos) const; diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp index 001c58ea76..4bff26a200 100644 --- a/scene/3d/arvr_nodes.cpp +++ b/scene/3d/arvr_nodes.cpp @@ -73,7 +73,10 @@ Vector3 ARVRCamera::project_local_ray_normal(const Point2 &p_pos) const { ERR_FAIL_NULL_V(arvr_server, Vector3()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector3()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::project_local_ray_normal(p_pos); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -98,7 +101,10 @@ Point2 ARVRCamera::unproject_position(const Vector3 &p_pos) const { ERR_FAIL_NULL_V(arvr_server, Vector2()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector2()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::unproject_position(p_pos); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -127,7 +133,10 @@ Vector3 ARVRCamera::project_position(const Point2 &p_point) const { ERR_FAIL_NULL_V(arvr_server, Vector3()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector3()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::project_position(p_point); + } if (!is_inside_tree()) { ERR_EXPLAIN("Camera is not inside scene."); @@ -157,7 +166,10 @@ Vector<Plane> ARVRCamera::get_frustum() const { ERR_FAIL_NULL_V(arvr_server, Vector<Plane>()); Ref<ARVRInterface> arvr_interface = arvr_server->get_primary_interface(); - ERR_FAIL_COND_V(arvr_interface.is_null(), Vector<Plane>()); + if (arvr_interface.is_null()) { + // we might be in the editor or have VR turned off, just call superclass + return Camera::get_frustum(); + } ERR_FAIL_COND_V(!is_inside_world(), Vector<Plane>()); diff --git a/scene/3d/mesh_instance.cpp b/scene/3d/mesh_instance.cpp index 80bae911d4..e836a6154a 100644 --- a/scene/3d/mesh_instance.cpp +++ b/scene/3d/mesh_instance.cpp @@ -371,7 +371,7 @@ void MeshInstance::_bind_methods() { ClassDB::set_method_flags("MeshInstance", "create_debug_tangents", METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton"), "set_skeleton_path", "get_skeleton_path"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton"), "set_skeleton_path", "get_skeleton_path"); } MeshInstance::MeshInstance() { diff --git a/scene/3d/particles.cpp b/scene/3d/particles.cpp index 7b5eb8ebc3..2b3a62fcdc 100644 --- a/scene/3d/particles.cpp +++ b/scene/3d/particles.cpp @@ -1444,7 +1444,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trail_color_modifier", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture"), "set_trail_color_modifier", "get_trail_color_modifier"); ADD_GROUP("Emission Shape", "emission_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Box,Points,Directed Points"), "set_emission_shape", "get_emission_shape"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01"), "set_emission_sphere_radius", "get_emission_sphere_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_point_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_point_texture", "get_emission_point_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_normal_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_emission_normal_texture", "get_emission_normal_texture"); @@ -1483,7 +1483,7 @@ void ParticlesMaterial::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "tangential_accel_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_TANGENTIAL_ACCEL); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL); ADD_GROUP("Damping", ""); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01"), "set_param", "get_param", PARAM_DAMPING); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0,100,0.01,or_greater"), "set_param", "get_param", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "damping_random", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param_randomness", "get_param_randomness", PARAM_DAMPING); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING); ADD_GROUP("Angle", ""); diff --git a/scene/3d/physics_body.cpp b/scene/3d/physics_body.cpp index 5056fb2fe4..e851c8d643 100644 --- a/scene/3d/physics_body.cpp +++ b/scene/3d/physics_body.cpp @@ -979,7 +979,7 @@ bool KinematicBody::move_and_collide(const Vector3 &p_motion, bool p_infinite_in return colliding; } -Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction, bool p_infinite_inertia, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle) { +Vector3 KinematicBody::move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction, float p_slope_stop_min_velocity, int p_max_slides, float p_floor_max_angle, bool p_infinite_inertia) { Vector3 lv = p_linear_velocity; @@ -1128,7 +1128,7 @@ Ref<KinematicCollision> KinematicBody::_get_slide_collision(int p_bounce) { void KinematicBody::_bind_methods() { ClassDB::bind_method(D_METHOD("move_and_collide", "rel_vec", "infinite_inertia"), &KinematicBody::_move, DEFVAL(true)); - ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "infinite_inertia", "slope_stop_min_velocity", "max_slides", "floor_max_angle"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(true), DEFVAL(0.05), DEFVAL(4), DEFVAL(Math::deg2rad((float)45))); + ClassDB::bind_method(D_METHOD("move_and_slide", "linear_velocity", "floor_normal", "slope_stop_min_velocity", "max_slides", "floor_max_angle", "infinite_inertia"), &KinematicBody::move_and_slide, DEFVAL(Vector3(0, 0, 0)), DEFVAL(0.05), DEFVAL(4), DEFVAL(Math::deg2rad((float)45)), DEFVAL(true)); ClassDB::bind_method(D_METHOD("test_move", "from", "rel_vec", "infinite_inertia"), &KinematicBody::test_move); diff --git a/scene/3d/physics_body.h b/scene/3d/physics_body.h index 17d2769c79..0190dcbfc3 100644 --- a/scene/3d/physics_body.h +++ b/scene/3d/physics_body.h @@ -303,7 +303,7 @@ public: void set_safe_margin(float p_margin); float get_safe_margin() const; - Vector3 move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction = Vector3(0, 0, 0), bool p_infinite_inertia = true, float p_slope_stop_min_velocity = 0.05, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45)); + Vector3 move_and_slide(const Vector3 &p_linear_velocity, const Vector3 &p_floor_direction = Vector3(0, 0, 0), float p_slope_stop_min_velocity = 0.05, int p_max_slides = 4, float p_floor_max_angle = Math::deg2rad((float)45), bool p_infinite_inertia = true); bool is_on_floor() const; bool is_on_wall() const; bool is_on_ceiling() const; diff --git a/scene/3d/physics_joint.cpp b/scene/3d/physics_joint.cpp index b2d10006f7..7988c43eab 100644 --- a/scene/3d/physics_joint.cpp +++ b/scene/3d/physics_joint.cpp @@ -154,8 +154,8 @@ void Joint::_bind_methods() { ClassDB::bind_method(D_METHOD("set_exclude_nodes_from_collision", "enable"), &Joint::set_exclude_nodes_from_collision); ClassDB::bind_method(D_METHOD("get_exclude_nodes_from_collision"), &Joint::get_exclude_nodes_from_collision); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_a"), "set_node_a", "get_node_a"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_b"), "set_node_b", "get_node_b"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_a", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject"), "set_node_a", "get_node_a"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "nodes/node_b", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CollisionObject"), "set_node_b", "get_node_b"); ADD_PROPERTY(PropertyInfo(Variant::INT, "solver/priority", PROPERTY_HINT_RANGE, "1,8,1"), "set_solver_priority", "get_solver_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision/exclude_nodes"), "set_exclude_nodes_from_collision", "get_exclude_nodes_from_collision"); @@ -260,7 +260,7 @@ void HingeJoint::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_lower_limit", "lower_limit"), &HingeJoint::_set_lower_limit); ClassDB::bind_method(D_METHOD("_get_lower_limit"), &HingeJoint::_get_lower_limit); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "params/bias", PROPERTY_HINT_RANGE, "0.01,0.99,0.01"), "set_param", "get_param", PARAM_BIAS); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "params/bias", PROPERTY_HINT_RANGE, "0.00,0.99,0.01"), "set_param", "get_param", PARAM_BIAS); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "angular_limit/enable"), "set_flag", "get_flag", FLAG_USE_LIMIT); ADD_PROPERTY(PropertyInfo(Variant::REAL, "angular_limit/upper", PROPERTY_HINT_RANGE, "-180,180,0.1"), "_set_upper_limit", "_get_upper_limit"); @@ -270,7 +270,7 @@ void HingeJoint::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::REAL, "angular_limit/relaxation", PROPERTY_HINT_RANGE, "0.01,16,0.01"), "set_param", "get_param", PARAM_LIMIT_RELAXATION); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "motor/enable"), "set_flag", "get_flag", FLAG_ENABLE_MOTOR); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/target_velocity", PROPERTY_HINT_RANGE, "0.01,4096,0.01"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/target_velocity", PROPERTY_HINT_RANGE, "-200,200,0.01,or_greater,or_lesser"), "set_param", "get_param", PARAM_MOTOR_TARGET_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::REAL, "motor/max_impulse", PROPERTY_HINT_RANGE, "0.01,1024,0.01"), "set_param", "get_param", PARAM_MOTOR_MAX_IMPULSE); BIND_ENUM_CONSTANT(PARAM_BIAS); diff --git a/scene/3d/reflection_probe.cpp b/scene/3d/reflection_probe.cpp index 7e3a87cbd4..4d50945062 100644 --- a/scene/3d/reflection_probe.cpp +++ b/scene/3d/reflection_probe.cpp @@ -241,8 +241,8 @@ void ReflectionProbe::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Once,Always"), "set_update_mode", "get_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "intensity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_intensity", "get_intensity"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_distance", PROPERTY_HINT_EXP_RANGE, "0,16384,0.1,or_greater"), "set_max_distance", "get_max_distance"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "extents"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "origin_offset"), "set_origin_offset", "get_origin_offset"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "origin_offset"), "set_origin_offset", "get_origin_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "box_projection"), "set_enable_box_projection", "is_box_projection_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_shadows"), "set_enable_shadows", "are_shadows_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cull_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_cull_mask", "get_cull_mask"); diff --git a/scene/3d/remote_transform.cpp b/scene/3d/remote_transform.cpp index afb85f7314..2156e24cd0 100644 --- a/scene/3d/remote_transform.cpp +++ b/scene/3d/remote_transform.cpp @@ -194,7 +194,7 @@ void RemoteTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("set_update_scale", "update_remote_scale"), &RemoteTransform::set_update_scale); ClassDB::bind_method(D_METHOD("get_update_scale"), &RemoteTransform::get_update_scale); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path"), "set_remote_node", "get_remote_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "remote_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Spatial"), "set_remote_node", "get_remote_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_global_coordinates"), "set_use_global_coordinates", "get_use_global_coordinates"); ADD_GROUP("Update", "update_"); diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index 76d90dc6ff..8d91b6f09f 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -547,6 +547,8 @@ void Skeleton::localize_rests() { } } +#ifndef _3D_DISABLED + void Skeleton::bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone) { ERR_FAIL_INDEX(p_bone, bones.size()); ERR_FAIL_COND(bones[p_bone].physical_bone); @@ -691,6 +693,8 @@ void Skeleton::physical_bones_remove_collision_exception(RID p_exception) { _physical_bones_add_remove_collision_exception(false, this, p_exception); } +#endif // _3D_DISABLED + void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); @@ -727,11 +731,15 @@ void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_transform", "bone_idx"), &Skeleton::get_bone_transform); +#ifndef _3D_DISABLED + ClassDB::bind_method(D_METHOD("physical_bones_stop_simulation"), &Skeleton::physical_bones_stop_simulation); ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton::physical_bones_start_simulation_on, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton::physical_bones_remove_collision_exception); +#endif // _3D_DISABLED + BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index dad11960a5..9672acb57a 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -38,7 +38,10 @@ @author Juan Linietsky <reduzio@gmail.com> */ +#ifndef _3D_DISABLED class PhysicalBone; +#endif // _3D_DISABLED + class Skeleton : public Spatial { GDCLASS(Skeleton, Spatial); @@ -64,8 +67,10 @@ class Skeleton : public Spatial { Transform transform_final; +#ifndef _3D_DISABLED PhysicalBone *physical_bone; PhysicalBone *cache_parent_physical_bone; +#endif // _3D_DISABLED List<uint32_t> nodes_bound; @@ -75,8 +80,10 @@ class Skeleton : public Spatial { ignore_animation = false; custom_pose_enable = false; disable_rest = false; +#ifndef _3D_DISABLED physical_bone = NULL; cache_parent_physical_bone = NULL; +#endif // _3D_DISABLED } }; @@ -164,6 +171,7 @@ public: void localize_rests(); // used for loaders and tools +#ifndef _3D_DISABLED // Physical bone API void bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone); @@ -182,6 +190,7 @@ public: void physical_bones_start_simulation_on(const Array &p_bones); void physical_bones_add_collision_exception(RID p_exception); void physical_bones_remove_collision_exception(RID p_exception); +#endif // _3D_DISABLED public: Skeleton(); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index d833e6a8bb..036a748c83 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -185,6 +185,9 @@ void SpriteBase3D::_queue_update() { if (pending_update) return; + triangle_mesh.unref(); + update_gizmo(); + pending_update = true; call_deferred(SceneStringNames::get_singleton()->_im_update); } @@ -198,6 +201,66 @@ PoolVector<Face3> SpriteBase3D::get_faces(uint32_t p_usage_flags) const { return PoolVector<Face3>(); } +Ref<TriangleMesh> SpriteBase3D::generate_triangle_mesh() const { + if (triangle_mesh.is_valid()) + return triangle_mesh; + + PoolVector<Vector3> faces; + faces.resize(6); + PoolVector<Vector3>::Write facesw = faces.write(); + + Rect2 final_rect = get_item_rect(); + + if (final_rect.size.x == 0 || final_rect.size.y == 0) + return Ref<TriangleMesh>(); + + float pixel_size = get_pixel_size(); + + Vector2 vertices[4] = { + + (final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size, + (final_rect.position + final_rect.size) * pixel_size, + (final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size, + final_rect.position * pixel_size, + + }; + + int x_axis = ((axis + 1) % 3); + int y_axis = ((axis + 2) % 3); + + if (axis != Vector3::AXIS_Z) { + SWAP(x_axis, y_axis); + + for (int i = 0; i < 4; i++) { + if (axis == Vector3::AXIS_Y) { + vertices[i].y = -vertices[i].y; + } else if (axis == Vector3::AXIS_X) { + vertices[i].x = -vertices[i].x; + } + } + } + + static const int indices[6] = { + 0, 1, 2, + 0, 2, 3 + }; + + for (int j = 0; j < 6; j++) { + int i = indices[j]; + Vector3 vtx; + vtx[x_axis] = vertices[i][0]; + vtx[y_axis] = vertices[i][1]; + facesw[j] = vtx; + } + + facesw = PoolVector<Vector3>::Write(); + + triangle_mesh = Ref<TriangleMesh>(memnew(TriangleMesh)); + triangle_mesh->create(faces); + + return triangle_mesh; +} + void SpriteBase3D::set_draw_flag(DrawFlags p_flag, bool p_enable) { ERR_FAIL_INDEX(p_flag, FLAG_MAX); @@ -255,6 +318,7 @@ void SpriteBase3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_alpha_cut_mode"), &SpriteBase3D::get_alpha_cut_mode); ClassDB::bind_method(D_METHOD("get_item_rect"), &SpriteBase3D::get_item_rect); + ClassDB::bind_method(D_METHOD("generate_triangle_mesh"), &SpriteBase3D::generate_triangle_mesh); ClassDB::bind_method(D_METHOD("_queue_update"), &SpriteBase3D::_queue_update); ClassDB::bind_method(D_METHOD("_im_update"), &SpriteBase3D::_im_update); @@ -901,7 +965,7 @@ Rect2 AnimatedSprite3D::get_item_rect() const { return Rect2(0, 0, 1, 1); Size2i s = t->get_size(); - Point2 ofs = offset; + Point2 ofs = get_offset(); if (centered) ofs -= s / 2; diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h index 23e1d96b4b..a4705a8970 100644 --- a/scene/3d/sprite_3d.h +++ b/scene/3d/sprite_3d.h @@ -38,6 +38,8 @@ class SpriteBase3D : public GeometryInstance { GDCLASS(SpriteBase3D, GeometryInstance); + mutable Ref<TriangleMesh> triangle_mesh; //cached + public: enum DrawFlags { FLAG_TRANSPARENT, @@ -133,6 +135,7 @@ public: virtual AABB get_aabb() const; virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + Ref<TriangleMesh> generate_triangle_mesh() const; SpriteBase3D(); ~SpriteBase3D(); @@ -192,7 +195,6 @@ class AnimatedSprite3D : public SpriteBase3D { int frame; bool centered; - Point2 offset; float timeout; diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp new file mode 100644 index 0000000000..d3d2870c3f --- /dev/null +++ b/scene/animation/animation_blend_space_1d.cpp @@ -0,0 +1,294 @@ +#include "animation_blend_space_1d.h" + +void AnimationNodeBlendSpace1D::set_tree(AnimationTree *p_player) { + + AnimationRootNode::set_tree(p_player); + + for(int i=0;i<blend_points_used;i++) { + blend_points[i].node->set_tree(p_player); + } + +} + +void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("blend_point_")) { + String left = property.name.get_slicec('/', 0); + int idx = left.get_slicec('_', 2).to_int(); + if (idx >= blend_points_used) { + property.usage = 0; + } + } + AnimationRootNode::_validate_property(property); +} + +void AnimationNodeBlendSpace1D::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position); + ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace1D::get_blend_point_position); + ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace1D::set_blend_point_node); + ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace1D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace1D::remove_blend_point); + ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace1D::get_blend_point_count); + + ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace1D::set_min_space); + ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace1D::get_min_space); + + ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace1D::set_max_space); + ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace1D::get_max_space); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace1D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace1D::get_snap); + + ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace1D::set_blend_pos); + ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace1D::get_blend_pos); + + ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label); + ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label); + + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point); + + for (int i = 0; i < MAX_BLEND_POINTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i); + } + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label"); +} + +void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index) { + ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); + + if (p_at_index == -1 || p_at_index == blend_points_used) { + p_at_index = blend_points_used; + } else { + for (int i = blend_points_used - 1; i > p_at_index; i++) { + blend_points[i] = blend_points[i - 1]; + } + } + + blend_points[p_at_index].node = p_node; + blend_points[p_at_index].position = p_position; + + blend_points[p_at_index].node->set_parent(this); + blend_points[p_at_index].node->set_tree(get_tree()); + + blend_points_used++; +} + +void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].position = p_position; +} + +void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) { + ERR_FAIL_INDEX(p_point, blend_points_used); + ERR_FAIL_COND(p_node.is_null()); + + if (blend_points[p_point].node.is_valid()) { + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + } + + blend_points[p_point].node = p_node; + blend_points[p_point].node->set_parent(this); + blend_points[p_point].node->set_tree(get_tree()); +} + +float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, 0); + return blend_points[p_point].position; +} + +Ref<AnimationRootNode> AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>()); + return blend_points[p_point].node; +} + +void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + + for (int i = p_point; i < blend_points_used - 1; i++) { + blend_points[i] = blend_points[i + 1]; + } + + blend_points_used--; +} + +int AnimationNodeBlendSpace1D::get_blend_point_count() const { + + return blend_points_used; +} + +void AnimationNodeBlendSpace1D::set_min_space(float p_min) { + min_space = p_min; + + if (min_space >= max_space) { + min_space = max_space - 1; + } +} + +float AnimationNodeBlendSpace1D::get_min_space() const { + return min_space; +} + +void AnimationNodeBlendSpace1D::set_max_space(float p_max) { + max_space = p_max; + + if (max_space <= min_space) { + max_space = min_space + 1; + } +} + +float AnimationNodeBlendSpace1D::get_max_space() const { + return max_space; +} + +void AnimationNodeBlendSpace1D::set_snap(float p_snap) { + snap = p_snap; +} + +float AnimationNodeBlendSpace1D::get_snap() const { + return snap; +} + +void AnimationNodeBlendSpace1D::set_blend_pos(float p_pos) { + blend_pos = p_pos; +} + +float AnimationNodeBlendSpace1D::get_blend_pos() const { + return blend_pos; +} + +void AnimationNodeBlendSpace1D::set_value_label(const String &p_label) { + value_label = p_label; +} + +String AnimationNodeBlendSpace1D::get_value_label() const { + return value_label; +} + +void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { + if (p_index == blend_points_used) { + add_blend_point(p_node, 0); + } else { + set_blend_point_node(p_index, p_node); + } +} + +float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) { + + if (blend_points_used == 0) { + return 0.0; + } + + if (blend_points_used == 1) { + // only one point available, just play that animation + return blend_node(blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false); + } + + float weights[MAX_BLEND_POINTS] = {}; + + int point_lower = -1; + float pos_lower = 0.0; + int point_higher = -1; + float pos_higher = 0.0; + + // find the closest two points to blend between + for (int i = 0; i < blend_points_used; i++) { + + float pos = blend_points[i].position; + + if (pos <= blend_pos) { + if (point_lower == -1) { + point_lower = i; + pos_lower = pos; + } else if ((blend_pos - pos) < (blend_pos - pos_lower)) { + point_lower = i; + pos_lower = pos; + } + } else { + if (point_higher == -1) { + point_higher = i; + pos_higher = pos; + } else if ((pos - blend_pos) < (pos_higher - blend_pos)) { + point_higher = i; + pos_higher = pos; + } + } + } + + // fill in weights + + if (point_lower == -1) { + // we are on the left side, no other point to the left + // we just play the next point. + + weights[point_higher] = 1.0; + } else if (point_higher == -1) { + // we are on the right side, no other point to the right + // we just play the previous point + + weights[point_lower] = 1.0; + } else { + + // we are between two points. + // figure out weights, then blend the animations + + float distance_between_points = pos_higher - pos_lower; + + float current_pos_inbetween = blend_pos - pos_lower; + + float blend_percentage = current_pos_inbetween / distance_between_points; + + float blend_lower = 1.0 - blend_percentage; + float blend_higher = blend_percentage; + + weights[point_lower] = blend_lower; + weights[point_higher] = blend_higher; + } + + // actually blend the animations now + + float max_time_remaining = 0.0; + + for (int i = 0; i < blend_points_used; i++) { + float remaining = blend_node(blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false); + + max_time_remaining = MAX(max_time_remaining, remaining); + } + + return max_time_remaining; +} + +String AnimationNodeBlendSpace1D::get_caption() const { + return "BlendSpace1D"; +} + +AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() { + + blend_points_used = 0; + max_space = 1; + min_space = -1; + + snap = 0.1; + value_label = "value"; +} + +AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() { + + for (int i = 0; i < blend_points_used; i++) { + blend_points[i].node->set_parent(this); + blend_points[i].node->set_tree(get_tree()); + } +} diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h new file mode 100644 index 0000000000..774894ef4b --- /dev/null +++ b/scene/animation/animation_blend_space_1d.h @@ -0,0 +1,71 @@ +#ifndef ANIMATION_BLEND_SPACE_1D_H +#define ANIMATION_BLEND_SPACE_1D_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeBlendSpace1D : public AnimationRootNode { + GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode) + + enum { + MAX_BLEND_POINTS = 64 + }; + + struct BlendPoint { + Ref<AnimationRootNode> node; + float position; + }; + + BlendPoint blend_points[MAX_BLEND_POINTS]; + int blend_points_used; + + float blend_pos; + + float max_space; + float min_space; + + float snap; + + String value_label; + + void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node); + +protected: + virtual void _validate_property(PropertyInfo &property) const; + static void _bind_methods(); + +public: + + virtual void set_tree(AnimationTree *p_player); + + void add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1); + void set_blend_point_position(int p_point, float p_position); + void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node); + + float get_blend_point_position(int p_point) const; + Ref<AnimationRootNode> get_blend_point_node(int p_point) const; + void remove_blend_point(int p_point); + int get_blend_point_count() const; + + void set_min_space(float p_min); + float get_min_space() const; + + void set_max_space(float p_max); + float get_max_space() const; + + void set_snap(float p_snap); + float get_snap() const; + + void set_blend_pos(float p_pos); + float get_blend_pos() const; + + void set_value_label(const String &p_label); + String get_value_label() const; + + float process(float p_time, bool p_seek); + String get_caption() const; + + AnimationNodeBlendSpace1D(); + ~AnimationNodeBlendSpace1D(); +}; + +#endif // ANIMATION_BLEND_SPACE_1D_H diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp new file mode 100644 index 0000000000..82db647124 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.cpp @@ -0,0 +1,567 @@ +#include "animation_blend_space_2d.h" +#include "math/delaunay.h" + +void AnimationNodeBlendSpace2D::set_tree(AnimationTree *p_player) { + AnimationRootNode::set_tree(p_player); + + for(int i=0;i<blend_points_used;i++) { + blend_points[i].node->set_tree(p_player); + } +} + + +void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) { + ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); + + if (p_at_index == -1 || p_at_index == blend_points_used) { + p_at_index = blend_points_used; + } else { + for (int i = blend_points_used - 1; i > p_at_index; i--) { + blend_points[i] = blend_points[i - 1]; + } + for (int i = 0; i < triangles.size(); i++) { + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] >= p_at_index) { + triangles[i].points[j]++; + } + } + } + } + blend_points[p_at_index].node = p_node; + blend_points[p_at_index].position = p_position; + + blend_points[p_at_index].node->set_parent(this); + blend_points[p_at_index].node->set_tree(get_tree()); + blend_points_used++; + + if (auto_triangles) { + trianges_dirty = true; + } +} + +void AnimationNodeBlendSpace2D::set_blend_point_position(int p_point, const Vector2 &p_position) { + ERR_FAIL_INDEX(p_point, blend_points_used); + blend_points[p_point].position = p_position; + if (auto_triangles) { + trianges_dirty = true; + } +} +void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) { + ERR_FAIL_INDEX(p_point, blend_points_used); + ERR_FAIL_COND(p_node.is_null()); + + if (blend_points[p_point].node.is_valid()) { + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + } + blend_points[p_point].node = p_node; + blend_points[p_point].node->set_parent(this); + blend_points[p_point].node->set_tree(get_tree()); +} +Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, Vector2()); + return blend_points[p_point].position; +} +Ref<AnimationRootNode> AnimationNodeBlendSpace2D::get_blend_point_node(int p_point) const { + ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>()); + return blend_points[p_point].node; +} +void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) { + ERR_FAIL_INDEX(p_point, blend_points_used); + + blend_points[p_point].node->set_parent(NULL); + blend_points[p_point].node->set_tree(NULL); + + for (int i = 0; i < triangles.size(); i++) { + bool erase = false; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] == p_point) { + erase = true; + break; + } else if (triangles[i].points[j] > p_point) { + triangles[i].points[j]--; + } + } + if (erase) { + triangles.remove(i); + + i--; + } + } + + for (int i = p_point; i < blend_points_used - 1; i++) { + blend_points[i] = blend_points[i + 1]; + } + blend_points_used--; +} + +int AnimationNodeBlendSpace2D::get_blend_point_count() const { + + return blend_points_used; +} + +bool AnimationNodeBlendSpace2D::has_triangle(int p_x, int p_y, int p_z) const { + + ERR_FAIL_INDEX_V(p_x, blend_points_used, false); + ERR_FAIL_INDEX_V(p_y, blend_points_used, false); + ERR_FAIL_INDEX_V(p_z, blend_points_used, false); + + BlendTriangle t; + t.points[0] = p_x; + t.points[1] = p_y; + t.points[2] = p_z; + + SortArray<int> sort; + sort.sort(t.points, 3); + + for (int i = 0; i < triangles.size(); i++) { + bool all_equal = true; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] != t.points[j]) { + all_equal = false; + break; + } + } + if (all_equal) + return true; + } + + return false; +} + +void AnimationNodeBlendSpace2D::add_triangle(int p_x, int p_y, int p_z, int p_at_index) { + + ERR_FAIL_INDEX(p_x, blend_points_used); + ERR_FAIL_INDEX(p_y, blend_points_used); + ERR_FAIL_INDEX(p_z, blend_points_used); + + _update_triangles(); + + BlendTriangle t; + t.points[0] = p_x; + t.points[1] = p_y; + t.points[2] = p_z; + + SortArray<int> sort; + sort.sort(t.points, 3); + + for (int i = 0; i < triangles.size(); i++) { + bool all_equal = true; + for (int j = 0; j < 3; j++) { + if (triangles[i].points[j] != t.points[j]) { + all_equal = false; + break; + } + } + ERR_FAIL_COND(all_equal); + } + + if (p_at_index == -1 || p_at_index == triangles.size()) { + triangles.push_back(t); + } else { + triangles.insert(p_at_index, t); + } +} +int AnimationNodeBlendSpace2D::get_triangle_point(int p_triangle, int p_point) { + + _update_triangles(); + + ERR_FAIL_INDEX_V(p_point, 3, -1); + ERR_FAIL_INDEX_V(p_triangle, triangles.size(), -1); + return triangles[p_triangle].points[p_point]; +} +void AnimationNodeBlendSpace2D::remove_triangle(int p_triangle) { + ERR_FAIL_INDEX(p_triangle, triangles.size()); + + triangles.remove(p_triangle); +} + +int AnimationNodeBlendSpace2D::get_triangle_count() const { + return triangles.size(); +} + +void AnimationNodeBlendSpace2D::set_min_space(const Vector2 &p_min) { + + min_space = p_min; + if (min_space.x >= max_space.x) { + min_space.x = max_space.x - 1; + } + if (min_space.y >= max_space.y) { + min_space.y = max_space.y - 1; + } +} +Vector2 AnimationNodeBlendSpace2D::get_min_space() const { + return min_space; +} + +void AnimationNodeBlendSpace2D::set_max_space(const Vector2 &p_max) { + + max_space = p_max; + if (max_space.x <= min_space.x) { + max_space.x = min_space.x + 1; + } + if (max_space.y <= min_space.y) { + max_space.y = min_space.y + 1; + } +} +Vector2 AnimationNodeBlendSpace2D::get_max_space() const { + return max_space; +} + +void AnimationNodeBlendSpace2D::set_snap(const Vector2 &p_snap) { + snap = p_snap; +} +Vector2 AnimationNodeBlendSpace2D::get_snap() const { + return snap; +} + +void AnimationNodeBlendSpace2D::set_blend_position(const Vector2 &p_pos) { + blend_pos = p_pos; +} +Vector2 AnimationNodeBlendSpace2D::get_blend_position() const { + return blend_pos; +} + +void AnimationNodeBlendSpace2D::set_x_label(const String &p_label) { + x_label = p_label; +} +String AnimationNodeBlendSpace2D::get_x_label() const { + return x_label; +} + +void AnimationNodeBlendSpace2D::set_y_label(const String &p_label) { + y_label = p_label; +} +String AnimationNodeBlendSpace2D::get_y_label() const { + return y_label; +} + +void AnimationNodeBlendSpace2D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) { + if (p_index == blend_points_used) { + add_blend_point(p_node, Vector2()); + } else { + set_blend_point_node(p_index, p_node); + } +} + +void AnimationNodeBlendSpace2D::_set_triangles(const Vector<int> &p_triangles) { + + if (auto_triangles) + return; + ERR_FAIL_COND(p_triangles.size() % 3 != 0); + for (int i = 0; i < p_triangles.size(); i += 3) { + add_triangle(p_triangles[i + 0], p_triangles[i + 1], p_triangles[i + 2]); + } +} + +Vector<int> AnimationNodeBlendSpace2D::_get_triangles() const { + + Vector<int> t; + if (auto_triangles && trianges_dirty) + return t; + + t.resize(triangles.size() * 3); + for (int i = 0; i < triangles.size(); i++) { + t[i * 3 + 0] = triangles[i].points[0]; + t[i * 3 + 1] = triangles[i].points[1]; + t[i * 3 + 2] = triangles[i].points[2]; + } + return t; +} + +void AnimationNodeBlendSpace2D::_update_triangles() { + + if (!auto_triangles || !trianges_dirty) + return; + + trianges_dirty = false; + triangles.clear(); + if (blend_points_used < 3) + return; + + Vector<Vector2> points; + points.resize(blend_points_used); + for (int i = 0; i < blend_points_used; i++) { + points[i] = blend_points[i].position; + } + + Vector<Delaunay2D::Triangle> triangles = Delaunay2D::triangulate(points); + + for (int i = 0; i < triangles.size(); i++) { + add_triangle(triangles[i].points[0], triangles[i].points[1], triangles[i].points[2]); + } +} + +Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) { + + _update_triangles(); + + if (triangles.size() == 0) + return Vector2(); + + Vector2 best_point; + bool first = true; + + for (int i = 0; i < triangles.size(); i++) { + Vector2 points[3]; + for (int j = 0; j < 3; j++) { + points[j] = get_blend_point_position(get_triangle_point(i, j)); + } + + if (Geometry::is_point_in_triangle(p_point, points[0], points[1], points[2])) { + + return p_point; + } + + for (int j = 0; j < 3; j++) { + Vector2 s[2] = { + points[j], + points[(j + 1) % 3] + }; + Vector2 closest = Geometry::get_closest_point_to_segment_2d(p_point, s); + if (first || closest.distance_to(p_point) < best_point.distance_to(p_point)) { + best_point = closest; + first = false; + } + } + } + + return best_point; +} + +void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) { + + if (p_pos.distance_squared_to(p_points[0]) < CMP_EPSILON2) { + r_weights[0] = 1; + r_weights[1] = 0; + r_weights[2] = 0; + return; + } + if (p_pos.distance_squared_to(p_points[1]) < CMP_EPSILON2) { + r_weights[0] = 0; + r_weights[1] = 1; + r_weights[2] = 0; + return; + } + if (p_pos.distance_squared_to(p_points[2]) < CMP_EPSILON2) { + r_weights[0] = 0; + r_weights[1] = 0; + r_weights[2] = 1; + return; + } + + Vector2 v0 = p_points[1] - p_points[0]; + Vector2 v1 = p_points[2] - p_points[0]; + Vector2 v2 = p_pos - p_points[0]; + + float d00 = v0.dot(v0); + float d01 = v0.dot(v1); + float d11 = v1.dot(v1); + float d20 = v2.dot(v0); + float d21 = v2.dot(v1); + float denom = (d00 * d11 - d01 * d01); + if (denom == 0) { + r_weights[0] = 1; + r_weights[1] = 0; + r_weights[2] = 0; + return; + } + float v = (d11 * d20 - d01 * d21) / denom; + float w = (d00 * d21 - d01 * d20) / denom; + float u = 1.0f - v - w; + + r_weights[0] = u; + r_weights[1] = v; + r_weights[2] = w; +} + +float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) { + + _update_triangles(); + + if (triangles.size() == 0) + return 0; + + Vector2 best_point; + bool first = true; + int blend_triangle = -1; + float blend_weights[3] = { 0, 0, 0 }; + + for (int i = 0; i < triangles.size(); i++) { + Vector2 points[3]; + for (int j = 0; j < 3; j++) { + points[j] = get_blend_point_position(get_triangle_point(i, j)); + } + + if (Geometry::is_point_in_triangle(blend_pos, points[0], points[1], points[2])) { + + blend_triangle = i; + _blend_triangle(blend_pos, points, blend_weights); + break; + } + + for (int j = 0; j < 3; j++) { + Vector2 s[2] = { + points[j], + points[(j + 1) % 3] + }; + Vector2 closest = Geometry::get_closest_point_to_segment_2d(blend_pos, s); + if (first || closest.distance_to(blend_pos) < best_point.distance_to(blend_pos)) { + best_point = closest; + blend_triangle = i; + first = false; + float d = s[0].distance_to(s[1]); + if (d == 0.0) { + blend_weights[j] = 1.0; + blend_weights[(j + 1) % 3] = 0.0; + blend_weights[(j + 2) % 3] = 0.0; + } else { + float c = s[0].distance_to(closest) / d; + + blend_weights[j] = 1.0 - c; + blend_weights[(j + 1) % 3] = c; + blend_weights[(j + 2) % 3] = 0.0; + } + } + } + } + + ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here + + int triangle_points[3]; + for (int j = 0; j < 3; j++) { + triangle_points[j] = get_triangle_point(blend_triangle, j); + } + + first = true; + float mind; + for (int i = 0; i < blend_points_used; i++) { + + bool found = false; + for (int j = 0; j < 3; j++) { + if (i == triangle_points[j]) { + //blend with the given weight + float t = blend_node(blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false); + if (first || t < mind) { + mind = t; + first = false; + } + found = true; + break; + } + } + + if (!found) { + //ignore + blend_node(blend_points[i].node, p_time, p_seek, 0, FILTER_IGNORE, false); + } + } + return mind; +} + +String AnimationNodeBlendSpace2D::get_caption() const { + return "BlendSpace2D"; +} + +void AnimationNodeBlendSpace2D::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("blend_point_")) { + String left = property.name.get_slicec('/', 0); + int idx = left.get_slicec('_', 2).to_int(); + if (idx >= blend_points_used) { + property.usage = 0; + } + } + AnimationRootNode::_validate_property(property); +} + +void AnimationNodeBlendSpace2D::set_auto_triangles(bool p_enable) { + auto_triangles = p_enable; + if (auto_triangles) { + trianges_dirty = true; + } +} + +bool AnimationNodeBlendSpace2D::get_auto_triangles() const { + return auto_triangles; +} + +void AnimationNodeBlendSpace2D::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position); + ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace2D::get_blend_point_position); + ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace2D::set_blend_point_node); + ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace2D::get_blend_point_node); + ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace2D::remove_blend_point); + ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace2D::get_blend_point_count); + + ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace2D::add_triangle, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace2D::get_triangle_point); + ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace2D::remove_triangle); + ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace2D::get_triangle_count); + + ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace2D::set_min_space); + ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace2D::get_min_space); + + ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace2D::set_max_space); + ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace2D::get_max_space); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace2D::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace2D::get_snap); + + ClassDB::bind_method(D_METHOD("set_blend_position", "pos"), &AnimationNodeBlendSpace2D::set_blend_position); + ClassDB::bind_method(D_METHOD("get_blend_position"), &AnimationNodeBlendSpace2D::get_blend_position); + + ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace2D::set_x_label); + ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace2D::get_x_label); + + ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace2D::set_y_label); + ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace2D::get_y_label); + + ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace2D::_add_blend_point); + + ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace2D::_set_triangles); + ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace2D::_get_triangles); + + ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace2D::set_auto_triangles); + ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace2D::get_auto_triangles); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_auto_triangles", "get_auto_triangles"); + + for (int i = 0; i < MAX_BLEND_POINTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i); + ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i); + } + + ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles"); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_position", "get_blend_position"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label"); +} + +AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() { + + auto_triangles = true; + blend_points_used = 0; + max_space = Vector2(1, 1); + min_space = Vector2(-1, -1); + snap = Vector2(0.1, 0.1); + x_label = "x"; + y_label = "y"; + trianges_dirty = false; +} + +AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() { + + for (int i = 0; i < blend_points_used; i++) { + blend_points[i].node->set_parent(this); + blend_points[i].node->set_tree(get_tree()); + } +} diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h new file mode 100644 index 0000000000..4778299df1 --- /dev/null +++ b/scene/animation/animation_blend_space_2d.h @@ -0,0 +1,97 @@ +#ifndef ANIMATION_BLEND_SPACE_2D_H +#define ANIMATION_BLEND_SPACE_2D_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeBlendSpace2D : public AnimationRootNode { + GDCLASS(AnimationNodeBlendSpace2D, AnimationRootNode) + + enum { + MAX_BLEND_POINTS = 64 + }; + + struct BlendPoint { + Ref<AnimationRootNode> node; + Vector2 position; + }; + + BlendPoint blend_points[MAX_BLEND_POINTS]; + int blend_points_used; + + struct BlendTriangle { + int points[3]; + }; + + Vector<BlendTriangle> triangles; + + Vector2 blend_pos; + Vector2 max_space; + Vector2 min_space; + Vector2 snap; + String x_label; + String y_label; + + void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node); + void _set_triangles(const Vector<int> &p_triangles); + Vector<int> _get_triangles() const; + + void _blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights); + + bool auto_triangles; + bool trianges_dirty; + + void _update_triangles(); + +protected: + virtual void _validate_property(PropertyInfo &property) const; + static void _bind_methods(); + +public: + + virtual void set_tree(AnimationTree *p_player); + + void add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index = -1); + void set_blend_point_position(int p_point, const Vector2 &p_position); + void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node); + Vector2 get_blend_point_position(int p_point) const; + Ref<AnimationRootNode> get_blend_point_node(int p_point) const; + void remove_blend_point(int p_point); + int get_blend_point_count() const; + + bool has_triangle(int p_x, int p_y, int p_z) const; + void add_triangle(int p_x, int p_y, int p_z, int p_at_index = -1); + int get_triangle_point(int p_triangle, int p_point); + void remove_triangle(int p_triangle); + int get_triangle_count() const; + + void set_min_space(const Vector2 &p_min); + Vector2 get_min_space() const; + + void set_max_space(const Vector2 &p_max); + Vector2 get_max_space() const; + + void set_snap(const Vector2 &p_snap); + Vector2 get_snap() const; + + void set_blend_position(const Vector2 &p_pos); + Vector2 get_blend_position() const; + + void set_x_label(const String &p_label); + String get_x_label() const; + + void set_y_label(const String &p_label); + String get_y_label() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + Vector2 get_closest_point(const Vector2 &p_point); + + void set_auto_triangles(bool p_enable); + bool get_auto_triangles() const; + + AnimationNodeBlendSpace2D(); + ~AnimationNodeBlendSpace2D(); +}; + +#endif // ANIMATION_BLEND_SPACE_2D_H diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp new file mode 100644 index 0000000000..6dcd5ca8ea --- /dev/null +++ b/scene/animation/animation_blend_tree.cpp @@ -0,0 +1,1170 @@ +#include "animation_blend_tree.h" +#include "scene/scene_string_names.h" + +void AnimationNodeAnimation::set_animation(const StringName &p_name) { + animation = p_name; +} + +StringName AnimationNodeAnimation::get_animation() const { + return animation; +} + +float AnimationNodeAnimation::get_playback_time() const { + return time; +} + +void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const { + + if (property.name == "animation") { + AnimationTree *gp = get_tree(); + if (gp && gp->has_node(gp->get_animation_player())) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player())); + if (ap) { + List<StringName> names; + ap->get_animation_list(&names); + String anims; + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (E != names.front()) { + anims += ","; + } + anims += String(E->get()); + } + if (anims != String()) { + property.hint = PROPERTY_HINT_ENUM; + property.hint_string = anims; + } + } + } + } + + AnimationRootNode::_validate_property(property); +} + +float AnimationNodeAnimation::process(float p_time, bool p_seek) { + + AnimationPlayer *ap = get_player(); + ERR_FAIL_COND_V(!ap, 0); + + Ref<Animation> anim = ap->get_animation(animation); + if (!anim.is_valid()) { + + Ref<AnimationNodeBlendTree> tree = get_parent(); + if (tree.is_valid()) { + String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), name, animation)); + + } else { + make_invalid(vformat(RTR("Animation not found: '%s'"), animation)); + } + + return 0; + } + + if (p_seek) { + time = p_time; + step = 0; + } else { + time = MAX(0, time + p_time); + step = p_time; + } + + float anim_size = anim->get_length(); + + if (anim->has_loop()) { + + if (anim_size) { + time = Math::fposmod(time, anim_size); + } + + } else if (time > anim_size) { + + time = anim_size; + } + + blend_animation(animation, time, step, p_seek, 1.0); + + return anim_size - time; +} + +String AnimationNodeAnimation::get_caption() const { + return "Animation"; +} + +void AnimationNodeAnimation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation); + ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation); + + ClassDB::bind_method(D_METHOD("get_playback_time"), &AnimationNodeAnimation::get_playback_time); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation"); +} + +AnimationNodeAnimation::AnimationNodeAnimation() { + last_version = 0; + skip = false; + time = 0; + step = 0; +} + +//////////////////////////////////////////////////////// + +void AnimationNodeOneShot::set_fadein_time(float p_time) { + + fade_in = p_time; +} + +void AnimationNodeOneShot::set_fadeout_time(float p_time) { + + fade_out = p_time; +} + +float AnimationNodeOneShot::get_fadein_time() const { + + return fade_in; +} +float AnimationNodeOneShot::get_fadeout_time() const { + + return fade_out; +} + +void AnimationNodeOneShot::set_autorestart(bool p_active) { + + autorestart = p_active; +} +void AnimationNodeOneShot::set_autorestart_delay(float p_time) { + + autorestart_delay = p_time; +} +void AnimationNodeOneShot::set_autorestart_random_delay(float p_time) { + + autorestart_random_delay = p_time; +} + +bool AnimationNodeOneShot::has_autorestart() const { + + return autorestart; +} +float AnimationNodeOneShot::get_autorestart_delay() const { + + return autorestart_delay; +} +float AnimationNodeOneShot::get_autorestart_random_delay() const { + + return autorestart_random_delay; +} + +void AnimationNodeOneShot::set_mix_mode(MixMode p_mix) { + + mix = p_mix; +} +AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const { + + return mix; +} + +void AnimationNodeOneShot::start() { + active = true; + do_start = true; +} +void AnimationNodeOneShot::stop() { + active = false; +} +bool AnimationNodeOneShot::is_active() const { + + return active; +} + +String AnimationNodeOneShot::get_caption() const { + return "OneShot"; +} + +bool AnimationNodeOneShot::has_filter() const { + return true; +} + +float AnimationNodeOneShot::process(float p_time, bool p_seek) { + + if (!active) { + //make it as if this node doesn't exist, pass input 0 by. + return blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + } + + bool os_seek = p_seek; + + if (p_seek) + time = p_time; + if (do_start) { + time = 0; + os_seek = true; + } + + float blend; + + if (time < fade_in) { + + if (fade_in > 0) + blend = time / fade_in; + else + blend = 0; //wtf + + } else if (!do_start && remaining < fade_out) { + + if (fade_out) + blend = (remaining / fade_out); + else + blend = 1.0; + } else + blend = 1.0; + + float main_rem; + if (mix == MIX_MODE_ADD) { + main_rem = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + } else { + main_rem = blend_input(0, p_time, p_seek, 1.0 - blend, FILTER_BLEND, !sync); + } + + float os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false); + + if (do_start) { + remaining = os_rem; + do_start = false; + } + + if (!p_seek) { + time += p_time; + remaining = os_rem; + if (remaining <= 0) + active = false; + } + + return MAX(main_rem, remaining); +} +void AnimationNodeOneShot::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeOneShot::is_using_sync() const { + + return sync; +} + +void AnimationNodeOneShot::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_fadein_time", "time"), &AnimationNodeOneShot::set_fadein_time); + ClassDB::bind_method(D_METHOD("get_fadein_time"), &AnimationNodeOneShot::get_fadein_time); + + ClassDB::bind_method(D_METHOD("set_fadeout_time", "time"), &AnimationNodeOneShot::set_fadeout_time); + ClassDB::bind_method(D_METHOD("get_fadeout_time"), &AnimationNodeOneShot::get_fadeout_time); + + ClassDB::bind_method(D_METHOD("set_autorestart", "enable"), &AnimationNodeOneShot::set_autorestart); + ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::has_autorestart); + + ClassDB::bind_method(D_METHOD("set_autorestart_delay", "enable"), &AnimationNodeOneShot::set_autorestart_delay); + ClassDB::bind_method(D_METHOD("get_autorestart_delay"), &AnimationNodeOneShot::get_autorestart_delay); + + ClassDB::bind_method(D_METHOD("set_autorestart_random_delay", "enable"), &AnimationNodeOneShot::set_autorestart_random_delay); + ClassDB::bind_method(D_METHOD("get_autorestart_random_delay"), &AnimationNodeOneShot::get_autorestart_random_delay); + + ClassDB::bind_method(D_METHOD("set_mix_mode", "mode"), &AnimationNodeOneShot::set_mix_mode); + ClassDB::bind_method(D_METHOD("get_mix_mode"), &AnimationNodeOneShot::get_mix_mode); + + ClassDB::bind_method(D_METHOD("start"), &AnimationNodeOneShot::start); + ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeOneShot::stop); + ClassDB::bind_method(D_METHOD("is_active"), &AnimationNodeOneShot::is_active); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeOneShot::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeOneShot::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadein_time", "get_fadein_time"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadeout_time", "get_fadeout_time"); + + ADD_GROUP("autorestart_", "Auto Restart"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart"); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_delay", "get_autorestart_delay"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_random_delay", "get_autorestart_random_delay"); + + ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); + + BIND_CONSTANT(MIX_MODE_BLEND) + BIND_CONSTANT(MIX_MODE_ADD) +} + +AnimationNodeOneShot::AnimationNodeOneShot() { + + add_input("in"); + add_input("shot"); + + time = 0; + fade_in = 0.1; + fade_out = 0.1; + autorestart = false; + autorestart_delay = 1; + autorestart_remaining = 0; + mix = MIX_MODE_BLEND; + active = false; + do_start = false; + sync = false; +} + +//////////////////////////////////////////////// + +void AnimationNodeAdd2::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeAdd2::get_amount() const { + return amount; +} + +String AnimationNodeAdd2::get_caption() const { + return "Add2"; +} +void AnimationNodeAdd2::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeAdd2::is_using_sync() const { + + return sync; +} + +bool AnimationNodeAdd2::has_filter() const { + + return true; +} + +float AnimationNodeAdd2::process(float p_time, bool p_seek) { + + float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + + return rem0; +} + +void AnimationNodeAdd2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd2::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd2::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd2::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd2::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} + +AnimationNodeAdd2::AnimationNodeAdd2() { + + add_input("in"); + add_input("add"); + amount = 0; + sync = false; +} + +//////////////////////////////////////////////// + +void AnimationNodeAdd3::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeAdd3::get_amount() const { + return amount; +} + +String AnimationNodeAdd3::get_caption() const { + return "Add3"; +} +void AnimationNodeAdd3::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeAdd3::is_using_sync() const { + + return sync; +} + +bool AnimationNodeAdd3::has_filter() const { + + return true; +} + +float AnimationNodeAdd3::process(float p_time, bool p_seek) { + + blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_PASS, !sync); + float rem0 = blend_input(1, p_time, p_seek, 1.0, FILTER_IGNORE, !sync); + blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_PASS, !sync); + + return rem0; +} + +void AnimationNodeAdd3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd3::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd3::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd3::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd3::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} + +AnimationNodeAdd3::AnimationNodeAdd3() { + + add_input("-add"); + add_input("in"); + add_input("+add"); + amount = 0; + sync = false; +} +///////////////////////////////////////////// + +void AnimationNodeBlend2::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeBlend2::get_amount() const { + return amount; +} +String AnimationNodeBlend2::get_caption() const { + return "Blend2"; +} + +float AnimationNodeBlend2::process(float p_time, bool p_seek) { + + float rem0 = blend_input(0, p_time, p_seek, 1.0 - amount, FILTER_BLEND, !sync); + float rem1 = blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync); + + return amount > 0.5 ? rem1 : rem0; //hacky but good enough +} + +void AnimationNodeBlend2::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeBlend2::is_using_sync() const { + + return sync; +} + +bool AnimationNodeBlend2::has_filter() const { + + return true; +} +void AnimationNodeBlend2::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeBlend2::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend2::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend2::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend2::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} +AnimationNodeBlend2::AnimationNodeBlend2() { + add_input("in"); + add_input("blend"); + sync = false; + + amount = 0; +} + +////////////////////////////////////// + +void AnimationNodeBlend3::set_amount(float p_amount) { + amount = p_amount; +} + +float AnimationNodeBlend3::get_amount() const { + return amount; +} + +String AnimationNodeBlend3::get_caption() const { + return "Blend3"; +} + +void AnimationNodeBlend3::set_use_sync(bool p_sync) { + + sync = p_sync; +} + +bool AnimationNodeBlend3::is_using_sync() const { + + return sync; +} + +float AnimationNodeBlend3::process(float p_time, bool p_seek) { + + float rem0 = blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_IGNORE, !sync); + float rem1 = blend_input(1, p_time, p_seek, 1.0 - ABS(amount), FILTER_IGNORE, !sync); + float rem2 = blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_IGNORE, !sync); + + return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough +} + +void AnimationNodeBlend3::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeBlend3::set_amount); + ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend3::get_amount); + + ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend3::set_use_sync); + ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend3::is_using_sync); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_amount", "get_amount"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync"); +} +AnimationNodeBlend3::AnimationNodeBlend3() { + add_input("-blend"); + add_input("in"); + add_input("+blend"); + sync = false; + amount = 0; +} + +///////////////////////////////// + +void AnimationNodeTimeScale::set_scale(float p_scale) { + scale = p_scale; +} + +float AnimationNodeTimeScale::get_scale() const { + return scale; +} + +String AnimationNodeTimeScale::get_caption() const { + return "TimeScale"; +} + +float AnimationNodeTimeScale::process(float p_time, bool p_seek) { + + if (p_seek) { + return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); + } else { + return blend_input(0, p_time * scale, false, 1.0, FILTER_IGNORE, false); + } +} + +void AnimationNodeTimeScale::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_scale", "scale"), &AnimationNodeTimeScale::set_scale); + ClassDB::bind_method(D_METHOD("get_scale"), &AnimationNodeTimeScale::get_scale); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"), "set_scale", "get_scale"); +} +AnimationNodeTimeScale::AnimationNodeTimeScale() { + add_input("in"); + scale = 1.0; +} + +//////////////////////////////////// + +void AnimationNodeTimeSeek::set_seek_pos(float p_seek_pos) { + seek_pos = p_seek_pos; +} + +float AnimationNodeTimeSeek::get_seek_pos() const { + return seek_pos; +} + +String AnimationNodeTimeSeek::get_caption() const { + return "Seek"; +} + +float AnimationNodeTimeSeek::process(float p_time, bool p_seek) { + + if (p_seek) { + return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false); + } else if (seek_pos >= 0) { + float ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false); + seek_pos = -1; + _change_notify("seek_pos"); + return ret; + } else { + return blend_input(0, p_time, false, 1.0, FILTER_IGNORE, false); + } +} + +void AnimationNodeTimeSeek::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_seek_pos", "seek_pos"), &AnimationNodeTimeSeek::set_seek_pos); + ClassDB::bind_method(D_METHOD("get_seek_pos"), &AnimationNodeTimeSeek::get_seek_pos); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "seek_pos", PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater"), "set_seek_pos", "get_seek_pos"); +} +AnimationNodeTimeSeek::AnimationNodeTimeSeek() { + add_input("in"); + seek_pos = -1; +} + +///////////////////////////////////////////////// + +String AnimationNodeTransition::get_caption() const { + return "Transition"; +} + +void AnimationNodeTransition::_update_inputs() { + while (get_input_count() < enabled_inputs) { + add_input(inputs[get_input_count()].name); + } + + while (get_input_count() > enabled_inputs) { + remove_input(get_input_count() - 1); + } +} + +void AnimationNodeTransition::set_enabled_inputs(int p_inputs) { + ERR_FAIL_INDEX(p_inputs, MAX_INPUTS); + enabled_inputs = p_inputs; + _update_inputs(); +} + +int AnimationNodeTransition::get_enabled_inputs() { + return enabled_inputs; +} + +void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) { + ERR_FAIL_INDEX(p_input, MAX_INPUTS); + inputs[p_input].auto_advance = p_enable; +} + +bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const { + ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, false); + return inputs[p_input].auto_advance; +} + +void AnimationNodeTransition::set_input_caption(int p_input, const String &p_name) { + ERR_FAIL_INDEX(p_input, MAX_INPUTS); + inputs[p_input].name = p_name; + set_input_name(p_input, p_name); +} + +String AnimationNodeTransition::get_input_caption(int p_input) const { + ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, String()); + return inputs[p_input].name; +} + +void AnimationNodeTransition::set_current(int p_current) { + + if (current == p_current) + return; + ERR_FAIL_INDEX(p_current, enabled_inputs); + + Ref<AnimationNodeBlendTree> tree = get_parent(); + + if (tree.is_valid() && current >= 0) { + prev = current; + prev_xfading = xfade; + prev_time = time; + time = 0; + current = p_current; + switched = true; + _change_notify("current"); + } else { + current = p_current; + } +} + +int AnimationNodeTransition::get_current() const { + return current; +} +void AnimationNodeTransition::set_cross_fade_time(float p_fade) { + xfade = p_fade; +} + +float AnimationNodeTransition::get_cross_fade_time() const { + return xfade; +} + +float AnimationNodeTransition::process(float p_time, bool p_seek) { + + if (prev < 0) { // process current animation, check for transition + + float rem = blend_input(current, p_time, p_seek, 1.0, FILTER_IGNORE, false); + + if (p_seek) + time = p_time; + else + time += p_time; + + if (inputs[current].auto_advance && rem <= xfade) { + + set_current((current + 1) % enabled_inputs); + } + + return rem; + } else { // cross-fading from prev to current + + float blend = xfade ? (prev_xfading / xfade) : 1; + + float rem; + + if (!p_seek && switched) { //just switched, seek to start of current + + rem = blend_input(current, 0, true, 1.0 - blend, FILTER_IGNORE, false); + } else { + + rem = blend_input(current, p_time, p_seek, 1.0 - blend, FILTER_IGNORE, false); + } + + switched = false; + + if (p_seek) { // don't seek prev animation + blend_input(prev, 0, false, blend, FILTER_IGNORE, false); + time = p_time; + } else { + blend_input(prev, p_time, false, blend, FILTER_IGNORE, false); + time += p_time; + prev_xfading -= p_time; + if (prev_xfading < 0) { + prev = -1; + } + } + + return rem; + } +} + +void AnimationNodeTransition::_validate_property(PropertyInfo &property) const { + + if (property.name == "current" && enabled_inputs > 0) { + property.hint = PROPERTY_HINT_ENUM; + String anims; + for (int i = 0; i < enabled_inputs; i++) { + if (i > 0) { + anims += ","; + } + anims += inputs[i].name; + } + property.hint_string = anims; + } + + if (property.name.begins_with("input_")) { + String n = property.name.get_slicec('/', 0).get_slicec('_', 1); + if (n != "count") { + int idx = n.to_int(); + if (idx >= enabled_inputs) { + property.usage = 0; + } + } + } + + AnimationNode::_validate_property(property); +} + +void AnimationNodeTransition::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_enabled_inputs", "amount"), &AnimationNodeTransition::set_enabled_inputs); + ClassDB::bind_method(D_METHOD("get_enabled_inputs"), &AnimationNodeTransition::get_enabled_inputs); + + ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance); + ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance); + + ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption); + ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption); + + ClassDB::bind_method(D_METHOD("set_current", "index"), &AnimationNodeTransition::set_current); + ClassDB::bind_method(D_METHOD("get_current"), &AnimationNodeTransition::get_current); + + ClassDB::bind_method(D_METHOD("set_cross_fade_time", "time"), &AnimationNodeTransition::set_cross_fade_time); + ClassDB::bind_method(D_METHOD("get_cross_fade_time"), &AnimationNodeTransition::get_cross_fade_time); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "current", PROPERTY_HINT_RANGE, "0,64,1"), "set_current", "get_current"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01"), "set_cross_fade_time", "get_cross_fade_time"); + + for (int i = 0; i < MAX_INPUTS; i++) { + ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name"), "set_input_caption", "get_input_caption", i); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance"), "set_input_as_auto_advance", "is_input_set_as_auto_advance", i); + } +} + +AnimationNodeTransition::AnimationNodeTransition() { + enabled_inputs = 0; + xfade = 0; + current = -1; + prev = -1; + prev_time = 0; + prev_xfading = 0; + switched = false; + for (int i = 0; i < MAX_INPUTS; i++) { + inputs[i].auto_advance = false; + inputs[i].name = itos(i + 1); + } +} + +///////////////////// + +String AnimationNodeOutput::get_caption() const { + return "Output"; +} + +float AnimationNodeOutput::process(float p_time, bool p_seek) { + return blend_input(0, p_time, p_seek, 1.0); +} + +AnimationNodeOutput::AnimationNodeOutput() { + add_input("output"); +} + +/////////////////////////////////////////////////////// +void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node) { + + ERR_FAIL_COND(nodes.has(p_name)); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_node->get_tree() != NULL); + ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(String(p_name).find("/") != -1); + nodes[p_name] = p_node; + + p_node->set_parent(this); + p_node->set_tree(get_tree()); + + emit_changed(); +} + +Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) const { + + ERR_FAIL_COND_V(!nodes.has(p_name), Ref<AnimationNode>()); + + return nodes[p_name]; +} + +StringName AnimationNodeBlendTree::get_node_name(const Ref<AnimationNode> &p_node) const { + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + if (E->get() == p_node) { + return E->key(); + } + } + + ERR_FAIL_V(StringName()); +} +bool AnimationNodeBlendTree::has_node(const StringName &p_name) const { + return nodes.has(p_name); +} +void AnimationNodeBlendTree::remove_node(const StringName &p_name) { + + ERR_FAIL_COND(!nodes.has(p_name)); + ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); //can't delete output + + { + //erase node connections + Ref<AnimationNode> node = nodes[p_name]; + for (int i = 0; i < node->get_input_count(); i++) { + node->set_input_connection(i, StringName()); + } + node->set_parent(NULL); + node->set_tree(NULL); + } + + nodes.erase(p_name); + + //erase connections to name + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + if (node->get_input_connection(i) == p_name) { + node->set_input_connection(i, StringName()); + } + } + } + + emit_changed(); +} + +void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) { + + ERR_FAIL_COND(!nodes.has(p_name)); + ERR_FAIL_COND(nodes.has(p_new_name)); + ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output); + + nodes[p_new_name] = nodes[p_name]; + nodes.erase(p_name); + + //rename connections + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + if (node->get_input_connection(i) == p_name) { + node->set_input_connection(i, p_new_name); + } + } + } +} + +void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) { + + ERR_FAIL_COND(!nodes.has(p_output_node)); + ERR_FAIL_COND(!nodes.has(p_input_node)); + ERR_FAIL_COND(p_output_node == SceneStringNames::get_singleton()->output); + ERR_FAIL_COND(p_input_node == p_output_node); + + Ref<AnimationNode> input = nodes[p_input_node]; + ERR_FAIL_INDEX(p_input_index, input->get_input_count()); + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + StringName output = node->get_input_connection(i); + ERR_FAIL_COND(output == p_output_node); + } + } + + input->set_input_connection(p_input_index, p_output_node); + emit_changed(); +} + +void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_input_index) { + + ERR_FAIL_COND(!nodes.has(p_node)); + + Ref<AnimationNode> input = nodes[p_node]; + ERR_FAIL_INDEX(p_input_index, input->get_input_count()); + + input->set_input_connection(p_input_index, StringName()); +} + +float AnimationNodeBlendTree::get_connection_activity(const StringName &p_input_node, int p_input_index) const { + + ERR_FAIL_COND_V(!nodes.has(p_input_node), 0); + + Ref<AnimationNode> input = nodes[p_input_node]; + ERR_FAIL_INDEX_V(p_input_index, input->get_input_count(), 0); + + return input->get_input_activity(p_input_index); +} + +AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const { + + if (!nodes.has(p_output_node) || p_output_node == SceneStringNames::get_singleton()->output) { + return CONNECTION_ERROR_NO_OUTPUT; + } + + if (!nodes.has(p_input_node)) { + return CONNECTION_ERROR_NO_INPUT; + } + + if (!nodes.has(p_input_node)) { + return CONNECTION_ERROR_SAME_NODE; + } + + Ref<AnimationNode> input = nodes[p_input_node]; + + if (p_input_index < 0 || p_input_index >= input->get_input_count()) { + return CONNECTION_ERROR_NO_INPUT_INDEX; + } + + if (input->get_input_connection(p_input_index) != StringName()) { + return CONNECTION_ERROR_CONNECTION_EXISTS; + } + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + StringName output = node->get_input_connection(i); + if (output == p_output_node) { + return CONNECTION_ERROR_CONNECTION_EXISTS; + } + } + } + return CONNECTION_OK; +} + +void AnimationNodeBlendTree::get_node_connections(List<NodeConnection> *r_connections) const { + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + for (int i = 0; i < node->get_input_count(); i++) { + StringName output = node->get_input_connection(i); + if (output != StringName()) { + NodeConnection nc; + nc.input_node = E->key(); + nc.input_index = i; + nc.output_node = output; + r_connections->push_back(nc); + } + } + } +} + +String AnimationNodeBlendTree::get_caption() const { + return "BlendTree"; +} + +float AnimationNodeBlendTree::process(float p_time, bool p_seek) { + + Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output]; + return blend_node(output, p_time, p_seek, 1.0); +} + +void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) { + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + r_list->push_back(E->key()); + } +} + +void AnimationNodeBlendTree::set_graph_offset(const Vector2 &p_graph_offset) { + + graph_offset = p_graph_offset; +} + +Vector2 AnimationNodeBlendTree::get_graph_offset() const { + + return graph_offset; +} + +void AnimationNodeBlendTree::set_tree(AnimationTree *p_player) { + + AnimationNode::set_tree(p_player); + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<AnimationNode> node = E->get(); + node->set_tree(p_player); + } +} + +bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) { + + String name = p_name; + if (name.begins_with("nodes/")) { + + String node_name = name.get_slicec('/', 1); + String what = name.get_slicec('/', 2); + + if (what == "node") { + Ref<AnimationNode> anode = p_value; + if (anode.is_valid()) { + add_node(node_name, p_value); + } + return true; + } + + if (what == "position") { + + if (nodes.has(node_name)) { + nodes[node_name]->set_position(p_value); + } + return true; + } + } else if (name == "node_connections") { + + Array conns = p_value; + ERR_FAIL_COND_V(conns.size() % 3 != 0, false); + + for (int i = 0; i < conns.size(); i += 3) { + connect_node(conns[i], conns[i + 1], conns[i + 2]); + } + return true; + } + + return false; +} + +bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) const { + + String name = p_name; + if (name.begins_with("nodes/")) { + String node_name = name.get_slicec('/', 1); + String what = name.get_slicec('/', 2); + + if (what == "node") { + if (nodes.has(node_name)) { + r_ret = nodes[node_name]; + return true; + } + } + + if (what == "position") { + + if (nodes.has(node_name)) { + r_ret = nodes[node_name]->get_position(); + return true; + } + } + } else if (name == "node_connections") { + List<NodeConnection> nc; + get_node_connections(&nc); + Array conns; + conns.resize(nc.size() * 3); + + int idx = 0; + for (List<NodeConnection>::Element *E = nc.front(); E; E = E->next()) { + conns[idx * 3 + 0] = E->get().input_node; + conns[idx * 3 + 1] = E->get().input_index; + conns[idx * 3 + 2] = E->get().output_node; + idx++; + } + + r_ret = conns; + return true; + } + + return false; +} +void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> names; + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + names.push_back(E->key()); + } + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + String name = E->get(); + if (name != "output") { + p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + } + p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + + p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); +} + +void AnimationNodeBlendTree::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeBlendTree::add_node); + ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeBlendTree::get_node); + ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeBlendTree::remove_node); + ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeBlendTree::rename_node); + ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeBlendTree::has_node); + ClassDB::bind_method(D_METHOD("connect_node", "input_node", "input_index", "output_node"), &AnimationNodeBlendTree::connect_node); + ClassDB::bind_method(D_METHOD("disconnect_node", "input_node", "input_index"), &AnimationNodeBlendTree::disconnect_node); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeBlendTree::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeBlendTree::get_graph_offset); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset"); + + BIND_CONSTANT(CONNECTION_OK); + BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT); + BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT_INDEX); + BIND_CONSTANT(CONNECTION_ERROR_NO_OUTPUT); + BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE); + BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); +} + +AnimationNodeBlendTree::AnimationNodeBlendTree() { + + Ref<AnimationNodeOutput> output; + output.instance(); + output->set_position(Vector2(300, 150)); + output->set_parent(this); + nodes["output"] = output; +} + +AnimationNodeBlendTree::~AnimationNodeBlendTree() { + + for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) { + E->get()->set_parent(NULL); + E->get()->set_tree(NULL); + } +} diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h new file mode 100644 index 0000000000..e86cc2e823 --- /dev/null +++ b/scene/animation/animation_blend_tree.h @@ -0,0 +1,352 @@ +#ifndef ANIMATION_BLEND_TREE_H +#define ANIMATION_BLEND_TREE_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeAnimation : public AnimationRootNode { + + GDCLASS(AnimationNodeAnimation, AnimationRootNode); + + StringName animation; + + uint64_t last_version; + float time; + float step; + bool skip; + +protected: + void _validate_property(PropertyInfo &property) const; + + static void _bind_methods(); + +public: + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + + void set_animation(const StringName &p_name); + StringName get_animation() const; + + float get_playback_time() const; + + AnimationNodeAnimation(); +}; + +class AnimationNodeOneShot : public AnimationNode { + GDCLASS(AnimationNodeOneShot, AnimationNode); + +public: + enum MixMode { + MIX_MODE_BLEND, + MIX_MODE_ADD + }; + +private: + bool active; + bool do_start; + float fade_in; + float fade_out; + + bool autorestart; + float autorestart_delay; + float autorestart_random_delay; + MixMode mix; + + float time; + float remaining; + float autorestart_remaining; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_fadein_time(float p_time); + void set_fadeout_time(float p_time); + + float get_fadein_time() const; + float get_fadeout_time() const; + + void set_autorestart(bool p_active); + void set_autorestart_delay(float p_time); + void set_autorestart_random_delay(float p_time); + + bool has_autorestart() const; + float get_autorestart_delay() const; + float get_autorestart_random_delay() const; + + void set_mix_mode(MixMode p_mix); + MixMode get_mix_mode() const; + + void start(); + void stop(); + bool is_active() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + virtual float process(float p_time, bool p_seek); + + AnimationNodeOneShot(); +}; + +VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode) + +class AnimationNodeAdd2 : public AnimationNode { + GDCLASS(AnimationNodeAdd2, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + virtual float process(float p_time, bool p_seek); + + AnimationNodeAdd2(); +}; + +class AnimationNodeAdd3 : public AnimationNode { + GDCLASS(AnimationNodeAdd3, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + virtual float process(float p_time, bool p_seek); + + AnimationNodeAdd3(); +}; + +class AnimationNodeBlend2 : public AnimationNode { + GDCLASS(AnimationNodeBlend2, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + virtual bool has_filter() const; + AnimationNodeBlend2(); +}; + +class AnimationNodeBlend3 : public AnimationNode { + GDCLASS(AnimationNodeBlend3, AnimationNode); + + float amount; + bool sync; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_amount(float p_amount); + float get_amount() const; + + void set_use_sync(bool p_sync); + bool is_using_sync() const; + + float process(float p_time, bool p_seek); + AnimationNodeBlend3(); +}; + +class AnimationNodeTimeScale : public AnimationNode { + GDCLASS(AnimationNodeTimeScale, AnimationNode); + + float scale; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_scale(float p_scale); + float get_scale() const; + + float process(float p_time, bool p_seek); + + AnimationNodeTimeScale(); +}; + +class AnimationNodeTimeSeek : public AnimationNode { + GDCLASS(AnimationNodeTimeSeek, AnimationNode); + + float seek_pos; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const; + + void set_seek_pos(float p_sec); + float get_seek_pos() const; + + float process(float p_time, bool p_seek); + + AnimationNodeTimeSeek(); +}; + +class AnimationNodeTransition : public AnimationNode { + GDCLASS(AnimationNodeTransition, AnimationNode); + + enum { + MAX_INPUTS = 32 + }; + struct InputData { + + String name; + bool auto_advance; + InputData() { auto_advance = false; } + }; + + InputData inputs[MAX_INPUTS]; + int enabled_inputs; + + float prev_time; + float prev_xfading; + int prev; + bool switched; + + float time; + int current; + + float xfade; + + void _update_inputs(); + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + virtual String get_caption() const; + + void set_enabled_inputs(int p_inputs); + int get_enabled_inputs(); + + void set_input_as_auto_advance(int p_input, bool p_enable); + bool is_input_set_as_auto_advance(int p_input) const; + + void set_input_caption(int p_input, const String &p_name); + String get_input_caption(int p_input) const; + + void set_current(int p_current); + int get_current() const; + + void set_cross_fade_time(float p_fade); + float get_cross_fade_time() const; + + float process(float p_time, bool p_seek); + + AnimationNodeTransition(); +}; + +class AnimationNodeOutput : public AnimationNode { + GDCLASS(AnimationNodeOutput, AnimationNode) +public: + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + AnimationNodeOutput(); +}; + +///// + +class AnimationNodeBlendTree : public AnimationRootNode { + GDCLASS(AnimationNodeBlendTree, AnimationRootNode) + + Map<StringName, Ref<AnimationNode> > nodes; + + Vector2 graph_offset; + +protected: + static void _bind_methods(); + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + enum ConnectionError { + CONNECTION_OK, + CONNECTION_ERROR_NO_INPUT, + CONNECTION_ERROR_NO_INPUT_INDEX, + CONNECTION_ERROR_NO_OUTPUT, + CONNECTION_ERROR_SAME_NODE, + CONNECTION_ERROR_CONNECTION_EXISTS, + //no need to check for cycles due to tree topology + }; + + void add_node(const StringName &p_name, Ref<AnimationNode> p_node); + Ref<AnimationNode> get_node(const StringName &p_name) const; + void remove_node(const StringName &p_name); + void rename_node(const StringName &p_name, const StringName &p_new_name); + bool has_node(const StringName &p_name) const; + StringName get_node_name(const Ref<AnimationNode> &p_node) const; + + void connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node); + void disconnect_node(const StringName &p_node, int p_input_index); + float get_connection_activity(const StringName &p_input_node, int p_input_index) const; + + struct NodeConnection { + StringName input_node; + int input_index; + StringName output_node; + }; + + ConnectionError can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const; + void get_node_connections(List<NodeConnection> *r_connections) const; + + virtual String get_caption() const; + virtual float process(float p_time, bool p_seek); + + void get_node_list(List<StringName> *r_list); + + void set_graph_offset(const Vector2 &p_graph_offset); + Vector2 get_graph_offset() const; + + virtual void set_tree(AnimationTree *p_player); + AnimationNodeBlendTree(); + ~AnimationNodeBlendTree(); +}; + +VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError) + +#endif // ANIMATION_BLEND_TREE_H diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp new file mode 100644 index 0000000000..c5ad980806 --- /dev/null +++ b/scene/animation/animation_node_state_machine.cpp @@ -0,0 +1,790 @@ +#include "animation_node_state_machine.h" + +///////////////////////////////////////////////// + +void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) { + + switch_mode = p_mode; +} + +AnimationNodeStateMachineTransition::SwitchMode AnimationNodeStateMachineTransition::get_switch_mode() const { + + return switch_mode; +} + +void AnimationNodeStateMachineTransition::set_auto_advance(bool p_enable) { + auto_advance = p_enable; +} + +bool AnimationNodeStateMachineTransition::has_auto_advance() const { + return auto_advance; +} + +void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) { + + ERR_FAIL_COND(p_xfade < 0); + xfade = p_xfade; + emit_changed(); +} + +float AnimationNodeStateMachineTransition::get_xfade_time() const { + return xfade; +} + +void AnimationNodeStateMachineTransition::set_disabled(bool p_disabled) { + disabled = p_disabled; + emit_changed(); +} + +bool AnimationNodeStateMachineTransition::is_disabled() const { + return disabled; +} + +void AnimationNodeStateMachineTransition::set_priority(int p_priority) { + priority = p_priority; + emit_changed(); +} + +int AnimationNodeStateMachineTransition::get_priority() const { + return priority; +} + +void AnimationNodeStateMachineTransition::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode); + ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode); + + ClassDB::bind_method(D_METHOD("set_auto_advance", "auto_advance"), &AnimationNodeStateMachineTransition::set_auto_advance); + ClassDB::bind_method(D_METHOD("has_auto_advance"), &AnimationNodeStateMachineTransition::has_auto_advance); + + ClassDB::bind_method(D_METHOD("set_xfade_time", "secs"), &AnimationNodeStateMachineTransition::set_xfade_time); + ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeStateMachineTransition::get_xfade_time); + + ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &AnimationNodeStateMachineTransition::set_disabled); + ClassDB::bind_method(D_METHOD("is_disabled"), &AnimationNodeStateMachineTransition::is_disabled); + + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority); + ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,AtEnd"), "set_switch_mode", "get_switch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); + + BIND_CONSTANT(SWITCH_MODE_IMMEDIATE); + BIND_CONSTANT(SWITCH_MODE_SYNC); + BIND_CONSTANT(SWITCH_MODE_AT_END); +} + +AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() { + + switch_mode = SWITCH_MODE_IMMEDIATE; + auto_advance = false; + xfade = 0; + disabled = false; + priority = 1; +} + +/////////////////////////////////////////////////////// +void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node) { + + ERR_FAIL_COND(states.has(p_name)); + ERR_FAIL_COND(p_node.is_null()); + ERR_FAIL_COND(p_node->get_parent().is_valid()); + ERR_FAIL_COND(p_node->get_tree() != NULL); + ERR_FAIL_COND(String(p_name).find("/") != -1); + states[p_name] = p_node; + + p_node->set_parent(this); + p_node->set_tree(get_tree()); + + emit_changed(); +} + +Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const { + + ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>()); + + return states[p_name]; +} + +StringName AnimationNodeStateMachine::get_node_name(const Ref<AnimationNode> &p_node) const { + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + if (E->get() == p_node) { + return E->key(); + } + } + + ERR_FAIL_V(StringName()); +} + +bool AnimationNodeStateMachine::has_node(const StringName &p_name) const { + return states.has(p_name); +} +void AnimationNodeStateMachine::remove_node(const StringName &p_name) { + + ERR_FAIL_COND(!states.has(p_name)); + + { + //erase node connections + Ref<AnimationNode> node = states[p_name]; + for (int i = 0; i < node->get_input_count(); i++) { + node->set_input_connection(i, StringName()); + } + node->set_parent(NULL); + node->set_tree(NULL); + } + + states.erase(p_name); + path.erase(p_name); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name || transitions[i].to == p_name) { + transitions.remove(i); + i--; + } + } + + if (start_node == p_name) { + start_node = StringName(); + } + + if (end_node == p_name) { + end_node = StringName(); + } + + if (playing && current == p_name) { + stop(); + } + emit_changed(); +} + +void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) { + + ERR_FAIL_COND(!states.has(p_name)); + ERR_FAIL_COND(states.has(p_new_name)); + + states[p_new_name] = states[p_name]; + states.erase(p_name); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name) { + transitions[i].from = p_new_name; + } + + if (transitions[i].to == p_name) { + transitions[i].to = p_new_name; + } + } + + if (start_node == p_name) { + start_node = p_new_name; + } + + if (end_node == p_name) { + end_node = p_new_name; + } + + if (playing && current == p_name) { + current = p_new_name; + } + + path.clear(); //clear path +} + +void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { + + List<StringName> nodes; + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + nodes.push_back(E->key()); + } + nodes.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) { + r_nodes->push_back(E->get()); + } +} + +bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) + return true; + } + return false; +} + +int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) + return i; + } + return -1; +} + +void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) { + + ERR_FAIL_COND(p_from == p_to); + ERR_FAIL_COND(!states.has(p_from)); + ERR_FAIL_COND(!states.has(p_to)); + ERR_FAIL_COND(p_transition.is_null()); + + for (int i = 0; i < transitions.size(); i++) { + ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to); + } + + Transition tr; + tr.from = p_from; + tr.to = p_to; + tr.transition = p_transition; + + transitions.push_back(tr); +} + +Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const { + ERR_FAIL_INDEX_V(p_transition, transitions.size(), Ref<AnimationNodeStateMachineTransition>()); + return transitions[p_transition].transition; +} +StringName AnimationNodeStateMachine::get_transition_from(int p_transition) const { + + ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName()); + return transitions[p_transition].from; +} +StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const { + + ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName()); + return transitions[p_transition].to; +} + +int AnimationNodeStateMachine::get_transition_count() const { + + return transitions.size(); +} +void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_from && transitions[i].to == p_to) { + transitions.remove(i); + return; + } + } + + if (playing) { + path.clear(); + } +} + +void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) { + + transitions.remove(p_transition); + if (playing) { + path.clear(); + } +} + +void AnimationNodeStateMachine::set_start_node(const StringName &p_node) { + + ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); + start_node = p_node; +} + +String AnimationNodeStateMachine::get_start_node() const { + + return start_node; +} + +void AnimationNodeStateMachine::set_end_node(const StringName &p_node) { + + ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); + end_node = p_node; +} + +String AnimationNodeStateMachine::get_end_node() const { + + return end_node; +} + +void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) { + graph_offset = p_offset; +} + +Vector2 AnimationNodeStateMachine::get_graph_offset() const { + return graph_offset; +} + +float AnimationNodeStateMachine::process(float p_time, bool p_seek) { + + //if not playing and it can restart, then restart + if (!playing) { + if (start_node) { + start(start_node); + } else { + return 0; + } + } + + bool do_start = (p_seek && p_time == 0) || play_start || current == StringName(); + + if (do_start) { + + if (start_node != StringName() && p_seek && p_time == 0) { + current = start_node; + } + + len_current = blend_node(states[current], 0, true, 1.0, FILTER_IGNORE, false); + pos_current = 0; + loops_current = 0; + play_start = false; + } + + float fade_blend = 1.0; + + if (fading_from != StringName()) { + + if (!p_seek) { + fading_pos += p_time; + } + fade_blend = MIN(1.0, fading_pos / fading_time); + if (fade_blend >= 1.0) { + fading_from = StringName(); + } + } + + float rem = blend_node(states[current], p_time, p_seek, fade_blend, FILTER_IGNORE, false); + + if (fading_from != StringName()) { + + blend_node(states[fading_from], p_time, p_seek, 1.0 - fade_blend, FILTER_IGNORE, false); + } + + //guess playback position + if (rem > len_current) { // weird but ok + len_current = rem; + } + + { //advance and loop check + + float next_pos = len_current - rem; + + if (next_pos < pos_current) { + loops_current++; + } + pos_current = next_pos; //looped + } + + //find next + StringName next; + float next_xfade = 0; + AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE; + + if (path.size()) { + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current && transitions[i].to == path[0]) { + next_xfade = transitions[i].transition->get_xfade_time(); + switch_mode = transitions[i].transition->get_switch_mode(); + next = path[0]; + } + } + } else { + float priority_best = 1e20; + int auto_advance_to = -1; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current && transitions[i].transition->has_auto_advance()) { + + if (transitions[i].transition->get_priority() < priority_best) { + auto_advance_to = i; + } + } + } + + if (auto_advance_to != -1) { + next = transitions[auto_advance_to].to; + next_xfade = transitions[auto_advance_to].transition->get_xfade_time(); + switch_mode = transitions[auto_advance_to].transition->get_switch_mode(); + } + } + + //if next, see when to transition + if (next != StringName()) { + + bool goto_next = false; + + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) { + goto_next = fading_from == StringName(); + } else { + goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0; + if (loops_current > 0) { + next_xfade = 0; + } + } + + if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped + + if (next_xfade) { + //time to fade, baby + fading_from = current; + fading_time = next_xfade; + fading_pos = 0; + } else { + fading_from = StringName(); + fading_pos = 0; + } + + if (path.size()) { //if it came from path, remove path + path.remove(0); + } + current = next; + if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { + len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false); + pos_current = MIN(pos_current, len_current); + blend_node(states[current], pos_current, true, 0, FILTER_IGNORE, false); + + } else { + len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false); + pos_current = 0; + } + + rem = len_current; //so it does not show 0 on transition + loops_current = 0; + } + } + + //compute time left for transitions by using the end node + + if (end_node != StringName() && end_node != current) { + + rem = blend_node(states[end_node], 0, true, 0, FILTER_IGNORE, false); + } + + return rem; +} + +bool AnimationNodeStateMachine::travel(const StringName &p_state) { + ERR_FAIL_COND_V(!playing, false); + ERR_FAIL_COND_V(!states.has(p_state), false); + ERR_FAIL_COND_V(!states.has(current), false); + + path.clear(); //a new one will be needed + + if (current == p_state) + return true; //nothing to do + + loops_current = 0; // reset loops, so fade does not happen immediately + + Vector2 current_pos = states[current]->get_position(); + Vector2 target_pos = states[p_state]->get_position(); + + Map<StringName, AStarCost> cost_map; + + List<int> open_list; + + //build open list + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == current) { + open_list.push_back(i); + float cost = states[transitions[i].to]->get_position().distance_to(current_pos); + cost *= transitions[i].transition->get_priority(); + AStarCost ap; + ap.prev = current; + ap.distance = cost; + cost_map[transitions[i].to] = ap; + + if (transitions[i].to == p_state) { //prematurely found it! :D + path.push_back(p_state); + return true; + } + } + } + + //begin astar + bool found_route = false; + while (!found_route) { + + if (open_list.size() == 0) { + return false; //no path found + } + + //find the last cost transition + List<int>::Element *least_cost_transition = NULL; + float least_cost = 1e20; + + for (List<int>::Element *E = open_list.front(); E; E = E->next()) { + + float cost = cost_map[transitions[E->get()].to].distance; + cost += states[transitions[E->get()].to]->get_position().distance_to(target_pos); + + if (cost < least_cost) { + least_cost_transition = E; + } + } + + StringName transition_prev = transitions[least_cost_transition->get()].from; + StringName transition = transitions[least_cost_transition->get()].to; + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from != transition || transitions[i].to == transition_prev) { + continue; //not interested on those + } + + float distance = states[transitions[i].from]->get_position().distance_to(states[transitions[i].to]->get_position()); + distance *= transitions[i].transition->get_priority(); + distance += cost_map[transitions[i].from].distance; + + if (cost_map.has(transitions[i].to)) { + //oh this was visited already, can we win the cost? + if (distance < cost_map[transitions[i].to].distance) { + cost_map[transitions[i].to].distance = distance; + cost_map[transitions[i].to].prev = transitions[i].from; + } + } else { + //add to open list + AStarCost ac; + ac.prev = transitions[i].from; + ac.distance = distance; + cost_map[transitions[i].to] = ac; + + open_list.push_back(i); + + if (transitions[i].to == p_state) { + found_route = true; + break; + } + } + } + + if (found_route) { + break; + } + + open_list.erase(least_cost_transition); + } + + //make path + StringName at = p_state; + while (at != current) { + path.push_back(at); + at = cost_map[at].prev; + } + + path.invert(); + + return true; +} + +void AnimationNodeStateMachine::start(const StringName &p_state) { + + ERR_FAIL_COND(!states.has(p_state)); + path.clear(); + current = p_state; + playing = true; + play_start = true; +} +void AnimationNodeStateMachine::stop() { + playing = false; + play_start = false; + current = StringName(); +} +bool AnimationNodeStateMachine::is_playing() const { + + return playing; +} +StringName AnimationNodeStateMachine::get_current_node() const { + if (!playing) { + return StringName(); + } + + return current; +} + +StringName AnimationNodeStateMachine::get_blend_from_node() const { + if (!playing) { + return StringName(); + } + + return fading_from; +} + +float AnimationNodeStateMachine::get_current_play_pos() const { + return pos_current; +} +float AnimationNodeStateMachine::get_current_length() const { + return len_current; +} + +Vector<StringName> AnimationNodeStateMachine::get_travel_path() const { + return path; +} +String AnimationNodeStateMachine::get_caption() const { + return "StateMachine"; +} + +void AnimationNodeStateMachine::_notification(int p_what) { +} + +void AnimationNodeStateMachine::set_tree(AnimationTree *p_player) { + + AnimationNode::set_tree(p_player); + + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + Ref<AnimationRootNode> node = E->get(); + node->set_tree(p_player); + } +} + +bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_value) { + + String name = p_name; + if (name.begins_with("states/")) { + String node_name = name.get_slicec('/', 1); + String what = name.get_slicec('/', 2); + + if (what == "node") { + Ref<AnimationNode> anode = p_value; + if (anode.is_valid()) { + add_node(node_name, p_value); + } + return true; + } + + if (what == "position") { + + if (states.has(node_name)) { + states[node_name]->set_position(p_value); + } + return true; + } + } else if (name == "transitions") { + + Array trans = p_value; + ERR_FAIL_COND_V(trans.size() % 3 != 0, false); + + for (int i = 0; i < trans.size(); i += 3) { + add_transition(trans[i], trans[i + 1], trans[i + 2]); + } + return true; + } else if (name == "start_node") { + set_start_node(p_value); + return true; + } else if (name == "end_node") { + set_end_node(p_value); + return true; + } else if (name == "graph_offset") { + set_graph_offset(p_value); + return true; + } + + return false; +} + +bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) const { + + String name = p_name; + if (name.begins_with("states/")) { + String node_name = name.get_slicec('/', 1); + String what = name.get_slicec('/', 2); + + if (what == "node") { + if (states.has(node_name)) { + r_ret = states[node_name]; + return true; + } + } + + if (what == "position") { + + if (states.has(node_name)) { + r_ret = states[node_name]->get_position(); + return true; + } + } + } else if (name == "transitions") { + Array trans; + trans.resize(transitions.size() * 3); + + for (int i = 0; i < transitions.size(); i++) { + trans[i * 3 + 0] = transitions[i].from; + trans[i * 3 + 1] = transitions[i].to; + trans[i * 3 + 2] = transitions[i].transition; + } + + r_ret = trans; + return true; + } else if (name == "start_node") { + r_ret = get_start_node(); + return true; + } else if (name == "end_node") { + r_ret = get_end_node(); + return true; + } else if (name == "graph_offset") { + r_ret = get_graph_offset(); + return true; + } + + return false; +} +void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) const { + + List<StringName> names; + for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) { + names.push_back(E->key()); + } + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + String name = E->get(); + p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + } + + p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); +} + +void AnimationNodeStateMachine::_bind_methods() { + + ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeStateMachine::add_node); + ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node); + ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeStateMachine::remove_node); + ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeStateMachine::rename_node); + ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeStateMachine::has_node); + ClassDB::bind_method(D_METHOD("get_node_name", "node"), &AnimationNodeStateMachine::get_node_name); + + ClassDB::bind_method(D_METHOD("has_transition", "from", "to"), &AnimationNodeStateMachine::add_transition); + ClassDB::bind_method(D_METHOD("add_transition", "from", "to", "transition"), &AnimationNodeStateMachine::add_transition); + ClassDB::bind_method(D_METHOD("get_transition", "idx"), &AnimationNodeStateMachine::get_transition); + ClassDB::bind_method(D_METHOD("get_transition_from", "idx"), &AnimationNodeStateMachine::get_transition_from); + ClassDB::bind_method(D_METHOD("get_transition_to", "idx"), &AnimationNodeStateMachine::get_transition_to); + ClassDB::bind_method(D_METHOD("get_transition_count"), &AnimationNodeStateMachine::get_transition_count); + ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index); + ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition); + + ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node); + ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node); + + ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node); + ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node); + + ClassDB::bind_method(D_METHOD("set_graph_offset", "name"), &AnimationNodeStateMachine::set_graph_offset); + ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); + + ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachine::travel); + ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachine::start); + ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachine::stop); + ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachine::is_playing); + ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachine::get_current_node); + ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachine::get_travel_path); +} + +AnimationNodeStateMachine::AnimationNodeStateMachine() { + + play_start = false; + + playing = false; + len_current = 0; + + fading_time = 0; +} diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h new file mode 100644 index 0000000000..e7357e09ea --- /dev/null +++ b/scene/animation/animation_node_state_machine.h @@ -0,0 +1,142 @@ +#ifndef ANIMATION_NODE_STATE_MACHINE_H +#define ANIMATION_NODE_STATE_MACHINE_H + +#include "scene/animation/animation_tree.h" + +class AnimationNodeStateMachineTransition : public Resource { + GDCLASS(AnimationNodeStateMachineTransition, Resource) +public: + enum SwitchMode { + SWITCH_MODE_IMMEDIATE, + SWITCH_MODE_SYNC, + SWITCH_MODE_AT_END, + }; + +private: + SwitchMode switch_mode; + bool auto_advance; + float xfade; + bool disabled; + int priority; + +protected: + static void _bind_methods(); + +public: + void set_switch_mode(SwitchMode p_mode); + SwitchMode get_switch_mode() const; + + void set_auto_advance(bool p_enable); + bool has_auto_advance() const; + + void set_xfade_time(float p_xfade); + float get_xfade_time() const; + + void set_disabled(bool p_disabled); + bool is_disabled() const; + + void set_priority(int p_priority); + int get_priority() const; + + AnimationNodeStateMachineTransition(); +}; + +VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode) + +class AnimationNodeStateMachine : public AnimationRootNode { + + GDCLASS(AnimationNodeStateMachine, AnimationRootNode); + +private: + Map<StringName, Ref<AnimationRootNode> > states; + + struct Transition { + + StringName from; + StringName to; + Ref<AnimationNodeStateMachineTransition> transition; + }; + + struct AStarCost { + float distance; + StringName prev; + }; + + Vector<Transition> transitions; + + float len_total; + + float len_current; + float pos_current; + int loops_current; + + bool play_start; + StringName start_node; + StringName end_node; + + Vector2 graph_offset; + + StringName current; + + StringName fading_from; + float fading_time; + float fading_pos; + + Vector<StringName> path; + bool playing; + +protected: + void _notification(int p_what); + static void _bind_methods(); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void add_node(const StringName &p_name, Ref<AnimationNode> p_node); + Ref<AnimationNode> get_node(const StringName &p_name) const; + void remove_node(const StringName &p_name); + void rename_node(const StringName &p_name, const StringName &p_new_name); + bool has_node(const StringName &p_name) const; + StringName get_node_name(const Ref<AnimationNode> &p_node) const; + void get_node_list(List<StringName> *r_nodes) const; + + bool has_transition(const StringName &p_from, const StringName &p_to) const; + int find_transition(const StringName &p_from, const StringName &p_to) const; + void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition); + Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const; + StringName get_transition_from(int p_transition) const; + StringName get_transition_to(int p_transition) const; + int get_transition_count() const; + void remove_transition_by_index(int p_transition); + void remove_transition(const StringName &p_from, const StringName &p_to); + + void set_start_node(const StringName &p_node); + String get_start_node() const; + + void set_end_node(const StringName &p_node); + String get_end_node() const; + + void set_graph_offset(const Vector2 &p_offset); + Vector2 get_graph_offset() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + bool travel(const StringName &p_state); + void start(const StringName &p_state); + void stop(); + bool is_playing() const; + StringName get_current_node() const; + StringName get_blend_from_node() const; + Vector<StringName> get_travel_path() const; + float get_current_play_pos() const; + float get_current_length() const; + + virtual void set_tree(AnimationTree *p_player); + + AnimationNodeStateMachine(); +}; + +#endif // ANIMATION_NODE_STATE_MACHINE_H diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index a0e0137863..eac2c8d0c1 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -33,7 +33,7 @@ #include "engine.h" #include "message_queue.h" #include "scene/scene_string_names.h" - +#include "servers/audio/audio_stream.h" #ifdef TOOLS_ENABLED void AnimatedValuesBackup::update_skeletons() { @@ -325,10 +325,27 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) { p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa; } } + + if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) { + + if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) { + + TrackNodeCache::BezierAnim ba; + String path = leftover_path[leftover_path.size() - 1]; + Vector<String> index = path.split("."); + for (int j = 0; j < index.size(); j++) { + ba.bezier_property.push_back(index[j]); + } + ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child; + ba.owner = p_anim->node_cache[i]; + + p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba; + } + } } } -void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete) { +void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) { _ensure_node_caches(p_anim); ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); @@ -394,7 +411,51 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float TrackNodeCache::PropertyAnim *pa = &E->get(); - if (a->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (p_delta == 0 && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek + Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); + + if (update_mode == Animation::UPDATE_CAPTURE) { + + if (p_started) { + pa->capture = pa->object->get_indexed(pa->subpath); + } + + int key_count = a->track_get_key_count(i); + if (key_count == 0) + continue; //eeh not worth it + + float first_key_time = a->track_get_key_time(i, 0); + float transition = 1.0; + int first_key = 0; + + if (first_key_time == 0.0) { + //ignore, use for transition + if (key_count == 1) + continue; //with one key we cant do anything + transition = a->track_get_key_transition(i, 0); + first_key_time = a->track_get_key_time(i, 1); + first_key = 1; + } + + if (p_time < first_key_time) { + float c = Math::ease(p_time / first_key_time, transition); + Variant first_value = a->track_get_key_value(i, first_key); + Variant interp_value; + Variant::interpolate(pa->capture, first_value, c, interp_value); + + if (pa->accum_pass != accum_pass) { + ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX); + cache_update_prop[cache_update_prop_size++] = pa; + pa->value_accum = interp_value; + pa->accum_pass = accum_pass; + } else { + Variant::interpolate(pa->value_accum, interp_value, p_interp, pa->value_accum); + } + + continue; //handled + } + } + + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE || (p_delta == 0 && update_mode == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek Variant value = a->value_track_interpolate(i, p_time); @@ -415,7 +476,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float Variant::interpolate(pa->value_accum, value, p_interp, pa->value_accum); } - } else if (p_allow_discrete && p_delta != 0) { + } else if (p_is_current && p_delta != 0) { List<int> indices; a->value_track_get_key_indices(i, p_time, p_delta, &indices); @@ -470,9 +531,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float if (!nc->node) continue; - if (p_delta == 0) + if (p_delta == 0) { continue; - if (!p_allow_discrete) + } + if (!p_is_current) break; List<int> indices; @@ -500,11 +562,195 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float } } break; + case Animation::TYPE_BEZIER: { + + if (!nc->node) + continue; + + Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); + ERR_CONTINUE(!E); //should it continue, or create a new one? + + TrackNodeCache::BezierAnim *ba = &E->get(); + + float bezier = a->bezier_track_interpolate(i, p_time); + if (ba->accum_pass != accum_pass) { + ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX); + cache_update_bezier[cache_update_bezier_size++] = ba; + ba->bezier_accum = bezier; + ba->accum_pass = accum_pass; + } else { + ba->bezier_accum = Math::lerp(ba->bezier_accum, bezier, p_interp); + } + + } break; + case Animation::TYPE_AUDIO: { + + if (!nc->node) + continue; + if (p_delta == 0) { + continue; + } + + if (p_seeked) { + //find whathever should be playing + int idx = a->track_find_key(i, p_time); + if (idx < 0) + continue; + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + start_ofs += p_time - a->track_get_key_time(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + if (start_ofs > len - end_ofs) { + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + continue; + } + + nc->node->call("set_stream", stream); + nc->node->call("play", start_ofs); + + nc->audio_playing = true; + playing_caches.insert(nc); + if (len && end_ofs > 0) { //force a end at a time + nc->audio_len = len - start_ofs - end_ofs; + } else { + nc->audio_len = 0; + } + + nc->audio_start = p_time; + } + + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + nc->node->call("set_stream", stream); + nc->node->call("play", start_ofs); + + nc->audio_playing = true; + playing_caches.insert(nc); + if (len && end_ofs > 0) { //force a end at a time + nc->audio_len = len - start_ofs - end_ofs; + } else { + nc->audio_len = 0; + } + + nc->audio_start = p_time; + } + } else if (nc->audio_playing) { + + bool loop = a->has_loop(); + + bool stop = false; + + if (!loop && p_time < nc->audio_start) { + stop = true; + } else if (nc->audio_len > 0) { + float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start; + + if (len > nc->audio_len) { + stop = true; + } + } + + if (stop) { + //time to stop + nc->node->call("stop"); + nc->audio_playing = false; + playing_caches.erase(nc); + } + } + } + + } break; + case Animation::TYPE_ANIMATION: { + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(nc->node); + if (!player) + continue; + + if (p_delta == 0 || p_seeked) { + //seek + int idx = a->track_find_key(i, p_time); + if (idx < 0) + continue; + + float pos = a->track_get_key_time(i, idx); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) + continue; + + Ref<Animation> anim = player->get_animation(anim_name); + + float at_anim_pos; + + if (anim->has_loop()) { + at_anim_pos = Math::fposmod(p_time - pos, anim->get_length()); //seek to loop + } else { + at_anim_pos = MAX(anim->get_length(), p_time - pos); //seek to end + } + + if (player->is_playing() || p_seeked) { + player->play(anim_name); + player->seek(at_anim_pos); + nc->animation_playing = true; + playing_caches.insert(nc); + } else { + player->set_assigned_animation(anim_name); + player->seek(at_anim_pos, true); + } + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { + + if (playing_caches.has(nc)) { + playing_caches.erase(nc); + player->stop(); + nc->animation_playing = false; + } + } else { + player->play(anim_name); + nc->animation_playing = true; + playing_caches.insert(nc); + } + } + } + + } break; } } } -void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) { +void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started) { float delta = p_delta * speed_scale * cd.speed_scale; float next_pos = cd.pos + delta; @@ -553,22 +799,25 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f cd.pos = next_pos; - _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current); + _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started); } -void AnimationPlayer::_animation_process2(float p_delta) { +void AnimationPlayer::_animation_process2(float p_delta, bool p_started) { Playback &c = playback; accum_pass++; - _animation_process_data(c.current, p_delta, 1.0f); + _animation_process_data(c.current, p_delta, 1.0f, c.seeked && p_delta != 0, p_started); + if (p_delta != 0) { + c.seeked = false; + } List<Blend>::Element *prev = NULL; for (List<Blend>::Element *E = c.blend.back(); E; E = prev) { Blend &b = E->get(); float blend = b.blend_left / b.blend_time; - _animation_process_data(b.data, p_delta, blend); + _animation_process_data(b.data, p_delta, blend, false, false); b.blend_left -= Math::absf(speed_scale * p_delta); @@ -652,6 +901,16 @@ void AnimationPlayer::_animation_update_transforms() { } cache_update_prop_size = 0; + + for (int i = 0; i < cache_update_bezier_size; i++) { + + TrackNodeCache::BezierAnim *ba = cache_update_bezier[i]; + + ERR_CONTINUE(ba->accum_pass != accum_pass); + ba->object->set_indexed(ba->bezier_property, ba->bezier_accum); + } + + cache_update_bezier_size = 0; } void AnimationPlayer::_animation_process(float p_delta) { @@ -660,7 +919,12 @@ void AnimationPlayer::_animation_process(float p_delta) { end_reached = false; end_notify = false; - _animation_process2(p_delta); + _animation_process2(p_delta, playback.started); + + if (playback.started) { + playback.started = false; + } + _animation_update_transforms(); if (end_reached) { if (queued.size()) { @@ -865,7 +1129,7 @@ void AnimationPlayer::queue(const StringName &p_name) { void AnimationPlayer::clear_queue() { queued.clear(); -}; +} void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_blend) { @@ -930,10 +1194,14 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float } } + _stop_playing_caches(); + c.current.from = &animation_set[name]; c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; c.current.speed_scale = p_custom_scale; c.assigned = p_name; + c.seeked = false; + c.started = true; if (!end_reached) queued.clear(); @@ -1004,6 +1272,7 @@ String AnimationPlayer::get_assigned_animation() const { void AnimationPlayer::stop(bool p_reset) { + _stop_playing_caches(); Playback &c = playback; c.blend.clear(); if (p_reset) { @@ -1042,6 +1311,7 @@ void AnimationPlayer::seek(float p_time, bool p_update) { } playback.current.pos = p_time; + playback.seeked = true; if (p_update) { _animation_process(0); } @@ -1084,6 +1354,25 @@ float AnimationPlayer::get_current_animation_length() const { void AnimationPlayer::_animation_changed() { clear_caches(); + emit_signal("caches_cleared"); +} + +void AnimationPlayer::_stop_playing_caches() { + + for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) { + + if (E->get()->node && E->get()->audio_playing) { + E->get()->node->call("stop"); + } + if (E->get()->node && E->get()->animation_playing) { + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->get()->node); + if (!player) + continue; + player->stop(); + } + } + + playing_caches.clear(); } void AnimationPlayer::_node_removed(Node *p_node) { @@ -1093,6 +1382,8 @@ void AnimationPlayer::_node_removed(Node *p_node) { void AnimationPlayer::clear_caches() { + _stop_playing_caches(); + node_cache_map.clear(); for (Map<StringName, AnimationData>::Element *E = animation_set.front(); E; E = E->next()) { @@ -1102,6 +1393,7 @@ void AnimationPlayer::clear_caches() { cache_update_size = 0; cache_update_prop_size = 0; + cache_update_bezier_size = 0; } void AnimationPlayer::set_active(bool p_active) { @@ -1358,6 +1650,7 @@ void AnimationPlayer::_bind_methods() { ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING, "anim_name"))); ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING, "anim_name"))); + ADD_SIGNAL(MethodInfo("caches_cleared")); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); @@ -1368,6 +1661,7 @@ AnimationPlayer::AnimationPlayer() { accum_pass = 1; cache_update_size = 0; cache_update_prop_size = 0; + cache_update_bezier_size = 0; speed_scale = 1; end_reached = false; end_notify = false; @@ -1377,6 +1671,8 @@ AnimationPlayer::AnimationPlayer() { root = SceneStringNames::get_singleton()->path_pp; playing = false; active = true; + playback.seeked = false; + playback.started = false; } AnimationPlayer::~AnimationPlayer() { diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index af2022ddac..49c73e54ad 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -98,6 +98,12 @@ private: Vector3 scale_accum; uint64_t accum_pass; + bool audio_playing; + float audio_start; + float audio_len; + + bool animation_playing; + struct PropertyAnim { TrackNodeCache *owner; @@ -106,6 +112,7 @@ private: Object *object; Variant value_accum; uint64_t accum_pass; + Variant capture; PropertyAnim() { accum_pass = 0; object = NULL; @@ -114,6 +121,22 @@ private: Map<StringName, PropertyAnim> property_anim; + struct BezierAnim { + + Vector<StringName> bezier_property; + TrackNodeCache *owner; + float bezier_accum; + Object *object; + uint64_t accum_pass; + BezierAnim() { + accum_pass = 0; + bezier_accum = 0; + object = NULL; + } + }; + + Map<StringName, BezierAnim> bezier_anim; + TrackNodeCache() { skeleton = NULL; spatial = NULL; @@ -121,6 +144,8 @@ private: accum_pass = 0; bone_idx = -1; node_2d = NULL; + audio_playing = false; + animation_playing = false; } }; @@ -146,6 +171,10 @@ private: int cache_update_size; TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX]; int cache_update_prop_size; + TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX]; + int cache_update_bezier_size; + Set<TrackNodeCache *> playing_caches; + Map<Ref<Animation>, int> used_anims; uint64_t accum_pass; @@ -202,6 +231,8 @@ private: List<Blend> blend; PlaybackData current; StringName assigned; + bool seeked; + bool started; } playback; List<StringName> queued; @@ -216,15 +247,16 @@ private: NodePath root; - void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete = true); + void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false); void _ensure_node_caches(AnimationData *p_anim); - void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend); - void _animation_process2(float p_delta); + void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started); + void _animation_process2(float p_delta, bool p_started); void _animation_update_transforms(); void _animation_process(float p_delta); void _node_removed(Node *p_node); + void _stop_playing_caches(); // bind helpers PoolVector<String> _get_animation_list() const { diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp new file mode 100644 index 0000000000..83ec9f819b --- /dev/null +++ b/scene/animation/animation_tree.cpp @@ -0,0 +1,1338 @@ +#include "animation_tree.h" +#include "animation_blend_tree.h" +#include "core/method_bind_ext.gen.inc" +#include "engine.h" +#include "scene/scene_string_names.h" +#include "servers/audio/audio_stream.h" + +void AnimationNode::blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend) { + + ERR_FAIL_COND(!state); + ERR_FAIL_COND(!state->player->has_animation(p_animation)); + + Ref<Animation> animation = state->player->get_animation(p_animation); + + if (animation.is_null()) { + + Ref<AnimationNodeBlendTree> btree = get_parent(); + if (btree.is_valid()) { + String name = btree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), name, p_animation)); + } else { + make_invalid(vformat(RTR("Invalid animation: '%s'."), p_animation)); + } + return; + } + + ERR_FAIL_COND(!animation.is_valid()); + + AnimationState anim_state; + anim_state.blend = p_blend; + anim_state.track_blends = &blends; + anim_state.delta = p_delta; + anim_state.time = p_time; + anim_state.animation = animation; + anim_state.seeked = p_seeked; + + state->animation_states.push_back(anim_state); +} + +float AnimationNode::_pre_process(State *p_state, float p_time, bool p_seek) { + state = p_state; + float t = process(p_time, p_seek); + state = NULL; + return t; +} + +void AnimationNode::make_invalid(const String &p_reason) { + ERR_FAIL_COND(!state); + state->valid = false; + if (state->invalid_reasons != String()) { + state->invalid_reasons += "\n"; + } + state->invalid_reasons += "- " + p_reason; +} + +float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); + ERR_FAIL_COND_V(!state, 0); + ERR_FAIL_COND_V(!get_tree(), 0); //should not happen, but used to catch bugs + + Ref<AnimationNodeBlendTree> tree = get_parent(); + + if (!tree.is_valid() && get_tree()->get_tree_root().ptr() != this) { + make_invalid(RTR("Can't blend input because node is not in a tree")); + return 0; + } + + ERR_FAIL_COND_V(!tree.is_valid(), 0); //should not happen + + StringName anim_name = inputs[p_input].connected_to; + + Ref<AnimationNode> node = tree->get_node(anim_name); + + if (node.is_null()) { + + String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), name)); + return 0; + } + + inputs[p_input].last_pass = state->last_pass; + + return _blend_node(node, p_time, p_seek, p_blend, p_filter, p_optimize, &inputs[p_input].activity); +} + +float AnimationNode::blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) { + + return _blend_node(p_node, p_time, p_seek, p_blend, p_filter, p_optimize); +} + +float AnimationNode::_blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max) { + + ERR_FAIL_COND_V(!p_node.is_valid(), 0); + ERR_FAIL_COND_V(!state, 0); + + int blend_count = blends.size(); + + if (p_node->blends.size() != blend_count) { + p_node->blends.resize(blend_count); + } + + float *blendw = p_node->blends.ptrw(); + const float *blendr = blends.ptr(); + + bool any_valid = false; + + if (has_filter() && is_filter_enabled() && p_filter != FILTER_IGNORE) { + + for (int i = 0; i < blend_count; i++) { + blendw[i] = 0.0; //all to zero by default + } + + const NodePath *K = NULL; + while ((K = filter.next(K))) { + if (!state->track_map.has(*K)) { + continue; + } + int idx = state->track_map[*K]; + blendw[idx] = 1.0; //filtered goes to one + } + + switch (p_filter) { + case FILTER_IGNORE: + break; //will not happen anyway + case FILTER_PASS: { + //values filtered pass, the rest dont + for (int i = 0; i < blend_count; i++) { + if (blendw[i] == 0) //not filtered, does not pass + continue; + + blendw[i] = blendr[i] * p_blend; + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + + } break; + case FILTER_STOP: { + + //values filtered dont pass, the rest are blended + + for (int i = 0; i < blend_count; i++) { + if (blendw[i] > 0) //filtered, does not pass + continue; + + blendw[i] = blendr[i] * p_blend; + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + + } break; + case FILTER_BLEND: { + + //filtered values are blended, the rest are passed without blending + + for (int i = 0; i < blend_count; i++) { + if (blendw[i] == 1.0) { + blendw[i] = blendr[i] * p_blend; //filtered, blend + } else { + blendw[i] = blendr[i]; //not filtered, do not blend + } + + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + + } break; + } + } else { + for (int i = 0; i < blend_count; i++) { + + //regular blend + blendw[i] = blendr[i] * p_blend; + if (blendw[i] > CMP_EPSILON) { + any_valid = true; + } + } + } + + if (r_max) { + *r_max = 0; + for (int i = 0; i < blend_count; i++) { + *r_max = MAX(*r_max, blendw[i]); + } + } + + if (!p_seek && p_optimize && !any_valid) //pointless to go on, all are zero + return 0; + + return p_node->_pre_process(state, p_time, p_seek); +} + +int AnimationNode::get_input_count() const { + + return inputs.size(); +} +String AnimationNode::get_input_name(int p_input) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), String()); + return inputs[p_input].name; +} + +float AnimationNode::get_input_activity(int p_input) const { + + ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); + if (!get_tree()) + return 0; + + if (get_tree()->get_last_process_pass() != inputs[p_input].last_pass) { + return 0; + } + return inputs[p_input].activity; +} +StringName AnimationNode::get_input_connection(int p_input) { + + ERR_FAIL_INDEX_V(p_input, inputs.size(), StringName()); + return inputs[p_input].connected_to; +} + +void AnimationNode::set_input_connection(int p_input, const StringName &p_connection) { + + ERR_FAIL_INDEX(p_input, inputs.size()); + inputs[p_input].connected_to = p_connection; +} + +String AnimationNode::get_caption() const { + + if (get_script_instance()) { + return get_script_instance()->call("get_caption"); + } + + return "Node"; +} + +void AnimationNode::add_input(const String &p_name) { + //root nodes cant add inputs + ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != NULL) + Input input; + ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1); + input.name = p_name; + input.activity = 0; + input.last_pass = 0; + inputs.push_back(input); + emit_changed(); +} + +void AnimationNode::set_input_name(int p_input, const String &p_name) { + ERR_FAIL_INDEX(p_input, inputs.size()); + ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1); + inputs[p_input].name = p_name; + emit_changed(); +} + +void AnimationNode::remove_input(int p_index) { + ERR_FAIL_INDEX(p_index, inputs.size()); + inputs.remove(p_index); + emit_changed(); +} + +void AnimationNode::_set_parent(Object *p_parent) { + set_parent(Object::cast_to<AnimationNode>(p_parent)); +} + +void AnimationNode::set_parent(AnimationNode *p_parent) { + parent = p_parent; //do not use ref because parent contains children + if (get_script_instance()) { + get_script_instance()->call("_parent_set", p_parent); + } +} + +Ref<AnimationNode> AnimationNode::get_parent() const { + if (parent) { + return Ref<AnimationNode>(parent); + } + + return Ref<AnimationNode>(); +} + +AnimationTree *AnimationNode::get_tree() const { + + return player; +} + +AnimationPlayer *AnimationNode::get_player() const { + ERR_FAIL_COND_V(!state, NULL); + return state->player; +} + +float AnimationNode::process(float p_time, bool p_seek) { + + if (get_script_instance()) { + return get_script_instance()->call("process", p_time, p_seek); + } + + return 0; +} + +void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { + if (p_enable) { + filter[p_path] = true; + } else { + filter.erase(p_path); + } +} + +void AnimationNode::set_filter_enabled(bool p_enable) { + filter_enabled = p_enable; +} + +bool AnimationNode::is_filter_enabled() const { + return filter_enabled; +} + +bool AnimationNode::is_path_filtered(const NodePath &p_path) const { + return filter.has(p_path); +} + +bool AnimationNode::has_filter() const { + return false; +} + +void AnimationNode::set_position(const Vector2 &p_position) { + position = p_position; +} + +Vector2 AnimationNode::get_position() const { + return position; +} + +void AnimationNode::set_tree(AnimationTree *p_player) { + + if (player != NULL && p_player == NULL) { + emit_signal("removed_from_graph"); + } + player = p_player; +} + +Array AnimationNode::_get_filters() const { + + Array paths; + + const NodePath *K = NULL; + while ((K = filter.next(K))) { + paths.push_back(String(*K)); //use strings, so sorting is possible + } + paths.sort(); //done so every time the scene is saved, it does not change + + return paths; +} +void AnimationNode::_set_filters(const Array &p_filters) { + filter.clear(); + for (int i = 0; i < p_filters.size(); i++) { + set_filter_path(p_filters[i], true); + } +} + +void AnimationNode::_validate_property(PropertyInfo &property) const { + if (!has_filter() && (property.name == "filter_enabled" || property.name == "filters")) { + property.usage = 0; + } +} + +void AnimationNode::_bind_methods() { + + ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count); + ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name); + ClassDB::bind_method(D_METHOD("get_input_connection", "input"), &AnimationNode::get_input_connection); + ClassDB::bind_method(D_METHOD("get_input_activity", "input"), &AnimationNode::get_input_activity); + + ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); + ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); + + ClassDB::bind_method(D_METHOD("set_filter_path", "path", "enable"), &AnimationNode::set_filter_path); + ClassDB::bind_method(D_METHOD("is_path_filtered", "path"), &AnimationNode::is_path_filtered); + + ClassDB::bind_method(D_METHOD("set_filter_enabled", "enable"), &AnimationNode::set_filter_enabled); + ClassDB::bind_method(D_METHOD("is_filter_enabled"), &AnimationNode::is_filter_enabled); + + ClassDB::bind_method(D_METHOD("set_position", "position"), &AnimationNode::set_position); + ClassDB::bind_method(D_METHOD("get_position"), &AnimationNode::get_position); + + ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters); + ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters); + + ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation); + ClassDB::bind_method(D_METHOD("blend_node", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + + ClassDB::bind_method(D_METHOD("set_parent", "parent"), &AnimationNode::_set_parent); + ClassDB::bind_method(D_METHOD("get_parent"), &AnimationNode::get_parent); + ClassDB::bind_method(D_METHOD("get_tree"), &AnimationNode::get_tree); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters"); + + BIND_VMETHOD(MethodInfo("process", PropertyInfo(Variant::REAL, "time"), PropertyInfo(Variant::BOOL, "seek"))); + BIND_VMETHOD(MethodInfo(Variant::STRING, "get_caption")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "has_filter")); + BIND_VMETHOD(MethodInfo("_parent_set", PropertyInfo(Variant::OBJECT, "parent"))); + + ADD_SIGNAL(MethodInfo("removed_from_graph")); + BIND_ENUM_CONSTANT(FILTER_IGNORE); + BIND_ENUM_CONSTANT(FILTER_PASS); + BIND_ENUM_CONSTANT(FILTER_STOP); + BIND_ENUM_CONSTANT(FILTER_BLEND); +} + +AnimationNode::AnimationNode() { + + state = NULL; + parent = NULL; + player = NULL; + set_local_to_scene(true); + filter_enabled = false; +} + +//////////////////// + +void AnimationTree::set_tree_root(const Ref<AnimationNode> &p_root) { + + if (root.is_valid()) { + root->set_tree(NULL); + } + if (p_root.is_valid()) { + ERR_EXPLAIN("root node already set to another player"); + ERR_FAIL_COND(p_root->player); + } + root = p_root; + + if (root.is_valid()) { + root->set_tree(this); + } + + update_configuration_warning(); +} + +Ref<AnimationNode> AnimationTree::get_tree_root() const { + return root; +} + +void AnimationTree::set_active(bool p_active) { + + if (active == p_active) + return; + + active = p_active; + started = active; + + if (process_mode == ANIMATION_PROCESS_IDLE) { + set_process_internal(active); + } else { + + set_physics_process_internal(active); + } + + if (!active && is_inside_tree()) { + for (Set<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) { + + if (ObjectDB::get_instance(E->get()->object_id)) { + E->get()->object->call("stop"); + } + } + + playing_caches.clear(); + } +} + +bool AnimationTree::is_active() const { + + return active; +} + +void AnimationTree::set_process_mode(AnimationProcessMode p_mode) { + + if (process_mode == p_mode) + return; + + bool was_active = is_active(); + if (was_active) { + set_active(false); + } + + process_mode = p_mode; + + if (was_active) { + set_active(true); + } +} + +AnimationTree::AnimationProcessMode AnimationTree::get_process_mode() const { + return process_mode; +} + +void AnimationTree::_node_removed(Node *p_node) { + cache_valid = false; +} + +bool AnimationTree::_update_caches(AnimationPlayer *player) { + + setup_pass++; + + if (!player->has_node(player->get_root())) { + ERR_PRINT("AnimationTree: AnimationPlayer root is invalid."); + set_active(false); + return false; + } + Node *parent = player->get_node(player->get_root()); + + List<StringName> sname; + player->get_animation_list(&sname); + + for (List<StringName>::Element *E = sname.front(); E; E = E->next()) { + Ref<Animation> anim = player->get_animation(E->get()); + for (int i = 0; i < anim->get_track_count(); i++) { + NodePath path = anim->track_get_path(i); + Animation::TrackType track_type = anim->track_get_type(i); + + TrackCache *track = NULL; + if (track_cache.has(path)) { + track = track_cache.get(path); + } + + //if not valid, delete track + if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == NULL)) { + playing_caches.erase(track); + memdelete(track); + track_cache.erase(path); + track = NULL; + } + + if (!track) { + + RES resource; + Vector<StringName> leftover_path; + Node *child = parent->get_node_and_resource(path, resource, leftover_path); + + if (!child) { + ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', couldn't resolve track: '" + String(path) + "'"); + continue; + } + + if (!child->is_connected("tree_exited", this, "_node_removed")) { + child->connect("tree_exited", this, "_node_removed", varray(child)); + } + + switch (track_type) { + case Animation::TYPE_VALUE: { + + TrackCacheValue *track_value = memnew(TrackCacheValue); + + if (resource.is_valid()) { + track_value->object = resource.ptr(); + } else { + track_value->object = child; + } + + track_value->subpath = leftover_path; + track_value->object_id = track_value->object->get_instance_id(); + + track = track_value; + + } break; + case Animation::TYPE_TRANSFORM: { + + Spatial *spatial = Object::cast_to<Spatial>(child); + + if (!spatial) { + ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'"); + continue; + } + + TrackCacheTransform *track_xform = memnew(TrackCacheTransform); + + track_xform->spatial = spatial; + track_xform->skeleton = NULL; + track_xform->bone_idx = -1; + + if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) { + + Skeleton *sk = Object::cast_to<Skeleton>(spatial); + int bone_idx = sk->find_bone(path.get_subname(0)); + if (bone_idx != -1 && !sk->is_bone_ignore_animation(bone_idx)) { + + track_xform->skeleton = sk; + track_xform->bone_idx = bone_idx; + } + } + + track_xform->object = spatial; + track_xform->object_id = track_xform->object->get_instance_id(); + + track = track_xform; + + } break; + case Animation::TYPE_METHOD: { + + TrackCacheMethod *track_method = memnew(TrackCacheMethod); + + if (resource.is_valid()) { + track_method->object = resource.ptr(); + } else { + track_method->object = child; + } + + track_method->object_id = track_method->object->get_instance_id(); + + track = track_method; + + } break; + case Animation::TYPE_BEZIER: { + + TrackCacheBezier *track_bezier = memnew(TrackCacheBezier); + + if (resource.is_valid()) { + track_bezier->object = resource.ptr(); + } else { + track_bezier->object = child; + } + + track_bezier->subpath = leftover_path; + track_bezier->object_id = track_bezier->object->get_instance_id(); + + track = track_bezier; + } break; + case Animation::TYPE_AUDIO: { + + TrackCacheAudio *track_audio = memnew(TrackCacheAudio); + + track_audio->object = child; + track_audio->object_id = track_audio->object->get_instance_id(); + + track = track_audio; + + } break; + case Animation::TYPE_ANIMATION: { + + TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation); + + track_animation->object = child; + track_animation->object_id = track_animation->object->get_instance_id(); + + track = track_animation; + + } break; + } + + track_cache[path] = track; + } + + track->setup_pass = setup_pass; + } + } + + List<NodePath> to_delete; + + const NodePath *K = NULL; + while ((K = track_cache.next(K))) { + TrackCache *tc = track_cache[*K]; + if (tc->setup_pass != setup_pass) { + to_delete.push_back(*K); + } + } + + while (to_delete.front()) { + NodePath np = to_delete.front()->get(); + memdelete(track_cache[np]); + track_cache.erase(np); + to_delete.pop_front(); + } + + state.track_map.clear(); + + K = NULL; + int idx = 0; + while ((K = track_cache.next(K))) { + state.track_map[*K] = idx; + idx++; + } + + state.track_count = idx; + + cache_valid = true; + + return true; +} + +void AnimationTree::_clear_caches() { + + const NodePath *K = NULL; + while ((K = track_cache.next(K))) { + memdelete(track_cache[*K]); + } + playing_caches.clear(); + + track_cache.clear(); + cache_valid = false; +} + +void AnimationTree::_process_graph(float p_delta) { + + //check all tracks, see if they need modification + root_motion_transform = Transform(); + + if (!root.is_valid()) { + ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback."); + set_active(false); + cache_valid = false; + return; + } + + if (!has_node(animation_player)) { + ERR_PRINT("AnimationTree: no valid AnimationPlayer path set, disabling playback"); + set_active(false); + cache_valid = false; + return; + } + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); + + if (!player) { + ERR_PRINT("AnimationTree: path points to a node not an AnimationPlayer, disabling playback"); + set_active(false); + cache_valid = false; + return; + } + + if (!cache_valid) { + if (!_update_caches(player)) { + return; + } + } + + { //setup + + process_pass++; + + state.valid = true; + state.invalid_reasons = ""; + state.animation_states.clear(); //will need to be re-created + state.valid = true; + state.player = player; + state.last_pass = process_pass; + + // root source blends + + root->blends.resize(state.track_count); + float *src_blendsw = root->blends.ptrw(); + for (int i = 0; i < state.track_count; i++) { + src_blendsw[i] = 1.0; //by default all go to 1 for the root input + } + } + + //process + + { + + if (started) { + //if started, seek + root->_pre_process(&state, 0, true); + started = false; + } + + root->_pre_process(&state, p_delta, false); + } + + if (!state.valid) { + return; //state is not valid. do nothing. + } + //apply value/transform/bezier blends to track caches and execute method/audio/animation tracks + + { + + bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); + + for (List<AnimationNode::AnimationState>::Element *E = state.animation_states.front(); E; E = E->next()) { + + const AnimationNode::AnimationState &as = E->get(); + + Ref<Animation> a = as.animation; + float time = as.time; + float delta = as.delta; + bool seeked = as.seeked; + + for (int i = 0; i < a->get_track_count(); i++) { + + NodePath path = a->track_get_path(i); + TrackCache *track = track_cache[path]; + if (track->type != a->track_get_type(i)) { + continue; //may happen should not + } + + track->root_motion = root_motion_track == path; + + ERR_CONTINUE(!state.track_map.has(path)); + int blend_idx = state.track_map[path]; + + ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); + + float blend = (*as.track_blends)[blend_idx]; + + if (blend < CMP_EPSILON) + continue; //nothing to blend + + switch (track->type) { + + case Animation::TYPE_TRANSFORM: { + + TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + + if (t->process_pass != process_pass) { + + t->process_pass = process_pass; + t->loc = Vector3(); + t->rot = Quat(); + t->rot_blend_accum = 0; + t->scale = Vector3(); + } + + if (track->root_motion) { + + float prev_time = time - delta; + if (prev_time < 0) { + if (!a->has_loop()) { + prev_time = 0; + } else { + prev_time = a->get_length() + prev_time; + } + } + + Vector3 loc[2]; + Quat rot[2]; + Vector3 scale[2]; + + if (prev_time > time) { + + Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]); + if (err != OK) { + continue; + } + + a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]); + + t->loc += (loc[1] - loc[0]) * blend; + t->scale += (scale[1] - scale[0]) * blend; + Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + + prev_time = 0; + } + + Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]); + if (err != OK) { + continue; + } + + a->transform_track_interpolate(i, time, &loc[1], &rot[1], &scale[1]); + + t->loc += (loc[1] - loc[0]) * blend; + t->scale += (scale[1] - scale[0]) * blend; + Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized(); + t->rot = (t->rot * q).normalized(); + + prev_time = 0; + + } else { + Vector3 loc; + Quat rot; + Vector3 scale; + + Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale); + //ERR_CONTINUE(err!=OK); //used for testing, should be removed + + scale -= Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes + + if (err != OK) + continue; + + t->loc = t->loc.linear_interpolate(loc, blend); + if (t->rot_blend_accum==0) { + t->rot = rot; + t->rot_blend_accum = blend; + } else { + float rot_total = t->rot_blend_accum + blend; + t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized(); + t->rot_blend_accum = rot_total; + } + t->scale = t->scale.linear_interpolate(scale, blend); + } + + } break; + case Animation::TYPE_VALUE: { + + TrackCacheValue *t = static_cast<TrackCacheValue *>(track); + + Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); + + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek + + Variant value = a->value_track_interpolate(i, time); + + if (value == Variant()) + continue; + + if (t->process_pass != process_pass) { + Variant::CallError ce; + t->value = Variant::construct(value.get_type(), NULL, 0, ce); //reset + t->process_pass = process_pass; + } + + Variant::interpolate(t->value, value, blend, t->value); + + } else if (delta != 0) { + + List<int> indices; + a->value_track_get_key_indices(i, time, delta, &indices); + + for (List<int>::Element *F = indices.front(); F; F = F->next()) { + + Variant value = a->track_get_key_value(i, F->get()); + t->object->set_indexed(t->subpath, value); + } + } + + } break; + case Animation::TYPE_METHOD: { + + if (delta == 0) { + continue; + } + TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track); + + List<int> indices; + + a->method_track_get_key_indices(i, time, delta, &indices); + + for (List<int>::Element *E = indices.front(); E; E = E->next()) { + + StringName method = a->method_track_get_name(i, E->get()); + Vector<Variant> params = a->method_track_get_params(i, E->get()); + + int s = params.size(); + + ERR_CONTINUE(s > VARIANT_ARG_MAX); + if (can_call) { + t->object->call_deferred( + method, + s >= 1 ? params[0] : Variant(), + s >= 2 ? params[1] : Variant(), + s >= 3 ? params[2] : Variant(), + s >= 4 ? params[3] : Variant(), + s >= 5 ? params[4] : Variant()); + } + } + + } break; + case Animation::TYPE_BEZIER: { + + TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); + + float bezier = a->bezier_track_interpolate(i, time); + + if (t->process_pass != process_pass) { + t->value = 0; + t->process_pass = process_pass; + } + + t->value = Math::lerp(t->value, bezier, blend); + + } break; + case Animation::TYPE_AUDIO: { + + TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); + + if (seeked) { + //find whathever should be playing + int idx = a->track_find_key(i, time); + if (idx < 0) + continue; + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + start_ofs += time - a->track_get_key_time(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + if (start_ofs > len - end_ofs) { + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + continue; + } + + t->object->call("set_stream", stream); + t->object->call("play", start_ofs); + + t->playing = true; + playing_caches.insert(t); + if (len && end_ofs > 0) { //force a end at a time + t->len = len - start_ofs - end_ofs; + } else { + t->len = 0; + } + + t->start = time; + } + + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, time, delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (!stream.is_valid()) { + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } else { + float start_ofs = a->audio_track_get_key_start_offset(i, idx); + float end_ofs = a->audio_track_get_key_end_offset(i, idx); + float len = stream->get_length(); + + t->object->call("set_stream", stream); + t->object->call("play", start_ofs); + + t->playing = true; + playing_caches.insert(t); + if (len && end_ofs > 0) { //force a end at a time + t->len = len - start_ofs - end_ofs; + } else { + t->len = 0; + } + + t->start = time; + } + } else if (t->playing) { + + bool loop = a->has_loop(); + + bool stop = false; + + if (!loop && time < t->start) { + stop = true; + } else if (t->len > 0) { + float len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + + if (len > t->len) { + stop=true; + } + } + + if (stop) { + //time to stop + t->object->call("stop"); + t->playing = false; + playing_caches.erase(t); + } + } + } + + float db = Math::linear2db(MAX(blend,0.00001)); + if (t->object->has_method("set_unit_db")) { + t->object->call("set_unit_db", db); + } else { + t->object->call("set_volume_db", db); + } + } break; + case Animation::TYPE_ANIMATION: { + + TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track); + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(t->object); + + if (!player) + continue; + + if (delta == 0 || seeked) { + //seek + int idx = a->track_find_key(i, time); + if (idx < 0) + continue; + + float pos = a->track_get_key_time(i, idx); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) + continue; + + Ref<Animation> anim = player->get_animation(anim_name); + + float at_anim_pos; + + if (anim->has_loop()) { + at_anim_pos = Math::fposmod(time - pos, anim->get_length()); //seek to loop + } else { + at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end + } + + if (player->is_playing() || seeked) { + player->play(anim_name); + player->seek(at_anim_pos); + t->playing = true; + playing_caches.insert(t); + } else { + player->set_assigned_animation(anim_name); + player->seek(at_anim_pos, true); + } + } else { + //find stuff to play + List<int> to_play; + a->track_get_key_indices_in_range(i, time, delta, &to_play); + if (to_play.size()) { + int idx = to_play.back()->get(); + + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { + + if (playing_caches.has(t)) { + playing_caches.erase(t); + player->stop(); + t->playing = false; + } + } else { + player->play(anim_name); + t->playing = true; + playing_caches.insert(t); + } + } + } + + } break; + } + } + } + } + + { + // finally, set the tracks + const NodePath *K = NULL; + while ((K = track_cache.next(K))) { + TrackCache *track = track_cache[*K]; + if (track->process_pass != process_pass) + continue; //not processed, ignore + + switch (track->type) { + + case Animation::TYPE_TRANSFORM: { + + TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + + Transform xform; + xform.origin = t->loc; + + t->scale += Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes and root motion + + xform.basis.set_quat_scale(t->rot, t->scale); + + if (t->root_motion) { + + root_motion_transform = xform; + + if (t->skeleton && t->bone_idx >= 0) { + root_motion_transform = (t->skeleton->get_bone_rest(t->bone_idx) * root_motion_transform) * t->skeleton->get_bone_rest(t->bone_idx).affine_inverse(); + } + } else if (t->skeleton && t->bone_idx >= 0) { + + t->skeleton->set_bone_pose(t->bone_idx, xform); + + } else { + + t->spatial->set_transform(xform); + } + + } break; + case Animation::TYPE_VALUE: { + + TrackCacheValue *t = static_cast<TrackCacheValue *>(track); + + t->object->set_indexed(t->subpath, t->value); + + } break; + case Animation::TYPE_BEZIER: { + + TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); + + t->object->set_indexed(t->subpath, t->value); + + } break; + default: {} //the rest dont matter + } + } + } +} + +void AnimationTree::_notification(int p_what) { + + if (active && p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) { + _process_graph(get_physics_process_delta_time()); + } + + if (active && p_what == NOTIFICATION_INTERNAL_PROCESS && process_mode == ANIMATION_PROCESS_IDLE) { + _process_graph(get_process_delta_time()); + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + _clear_caches(); + } +} + +void AnimationTree::set_animation_player(const NodePath &p_player) { + animation_player = p_player; + update_configuration_warning(); +} + +NodePath AnimationTree::get_animation_player() const { + return animation_player; +} + +bool AnimationTree::is_state_invalid() const { + + return !state.valid; +} +String AnimationTree::get_invalid_state_reason() const { + + return state.invalid_reasons; +} + +uint64_t AnimationTree::get_last_process_pass() const { + return process_pass; +} + +String AnimationTree::get_configuration_warning() const { + + String warning = Node::get_configuration_warning(); + + if (!root.is_valid()) { + if (warning != String()) { + warning += "\n"; + } + warning += TTR("A root AnimationNode for the graph is not set."); + } + + if (!has_node(animation_player)) { + + if (warning != String()) { + warning += "\n"; + } + + warning += TTR("Path to an AnimationPlayer node containing animations is not set."); + return warning; + } + + AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); + + if (!player) { + if (warning != String()) { + warning += "\n"; + } + + warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node."); + return warning; + } + + if (!player->has_node(player->get_root())) { + if (warning != String()) { + warning += "\n"; + } + + warning += TTR("AnimationPlayer root is not a valid node."); + return warning; + } + + return warning; +} + +void AnimationTree::set_root_motion_track(const NodePath &p_track) { + root_motion_track = p_track; +} + +NodePath AnimationTree::get_root_motion_track() const { + return root_motion_track; +} + +Transform AnimationTree::get_root_motion_transform() const { + return root_motion_transform; +} + +void AnimationTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active); + ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active); + + ClassDB::bind_method(D_METHOD("set_tree_root", "root"), &AnimationTree::set_tree_root); + ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_tree_root); + + ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &AnimationTree::set_process_mode); + ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationTree::get_process_mode); + + ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); + + ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); + ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); + + ClassDB::bind_method(D_METHOD("get_root_motion_transform"), &AnimationTree::get_root_motion_transform); + + + + ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_tree_root", "get_tree_root"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player",PROPERTY_HINT_NODE_PATH_VALID_TYPES,"AnimationPlayer"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode"); + ADD_GROUP("Root Motion", "root_motion_"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); +} + +AnimationTree::AnimationTree() { + + process_mode = ANIMATION_PROCESS_IDLE; + active = false; + cache_valid = false; + setup_pass = 1; + started = true; +} + +AnimationTree::~AnimationTree() { + if (root.is_valid()) { + root->player = NULL; + } +} diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h new file mode 100644 index 0000000000..540c36437a --- /dev/null +++ b/scene/animation/animation_tree.h @@ -0,0 +1,281 @@ +#ifndef ANIMATION_GRAPH_PLAYER_H +#define ANIMATION_GRAPH_PLAYER_H + +#include "animation_player.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/spatial.h" +#include "scene/resources/animation.h" + +class AnimationNodeBlendTree; +class AnimationPlayer; +class AnimationTree; + +class AnimationNode : public Resource { + GDCLASS(AnimationNode, Resource) +public: + enum FilterAction { + FILTER_IGNORE, + FILTER_PASS, + FILTER_STOP, + FILTER_BLEND + }; + + struct Input { + + String name; + StringName connected_to; + float activity; + uint64_t last_pass; + }; + + Vector<Input> inputs; + + float process_input(int p_input, float p_time, bool p_seek, float p_blend); + + friend class AnimationTree; + + struct AnimationState { + + Ref<Animation> animation; + float time; + float delta; + const Vector<float> *track_blends; + float blend; + bool seeked; + }; + + struct State { + + int track_count; + HashMap<NodePath, int> track_map; + List<AnimationState> animation_states; + bool valid; + AnimationPlayer *player; + String invalid_reasons; + uint64_t last_pass; + }; + + Vector<float> blends; + State *state; + float _pre_process(State *p_state, float p_time, bool p_seek); + void _pre_update_animations(HashMap<NodePath, int> *track_map); + Vector2 position; + + AnimationNode *parent; + AnimationTree *player; + + float _blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = NULL); + + HashMap<NodePath, bool> filter; + bool filter_enabled; + + Array _get_filters() const; + void _set_filters(const Array &p_filters); + +protected: + void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend); + float blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); + void make_invalid(const String &p_reason); + + static void _bind_methods(); + + void _validate_property(PropertyInfo &property) const; + + void _set_parent(Object *p_parent); + +public: + void set_parent(AnimationNode *p_parent); + Ref<AnimationNode> get_parent() const; + virtual void set_tree(AnimationTree *p_player); + AnimationTree *get_tree() const; + AnimationPlayer *get_player() const; + + virtual float process(float p_time, bool p_seek); + virtual String get_caption() const; + + int get_input_count() const; + String get_input_name(int p_input); + StringName get_input_connection(int p_input); + void set_input_connection(int p_input, const StringName &p_connection); + float get_input_activity(int p_input) const; + + void add_input(const String &p_name); + void set_input_name(int p_input, const String &p_name); + void remove_input(int p_index); + + void set_filter_path(const NodePath &p_path, bool p_enable); + bool is_path_filtered(const NodePath &p_path) const; + + void set_filter_enabled(bool p_enable); + bool is_filter_enabled() const; + + virtual bool has_filter() const; + + void set_position(const Vector2 &p_position); + Vector2 get_position() const; + + AnimationNode(); +}; + +VARIANT_ENUM_CAST(AnimationNode::FilterAction) + +//root node does not allow inputs +class AnimationRootNode : public AnimationNode { + GDCLASS(AnimationRootNode, AnimationNode) +public: + AnimationRootNode() {} +}; + +class AnimationTree : public Node { + GDCLASS(AnimationTree, Node) +public: + enum AnimationProcessMode { + ANIMATION_PROCESS_PHYSICS, + ANIMATION_PROCESS_IDLE, + }; + +private: + struct TrackCache { + + bool root_motion; + uint64_t setup_pass; + uint64_t process_pass; + Animation::TrackType type; + Object *object; + ObjectID object_id; + + TrackCache() { + root_motion = false; + setup_pass = 0; + process_pass = 0; + object = NULL; + object_id = 0; + } + virtual ~TrackCache() {} + }; + + struct TrackCacheTransform : public TrackCache { + Spatial *spatial; + Skeleton *skeleton; + int bone_idx; + Vector3 loc; + Quat rot; + float rot_blend_accum; + Vector3 scale; + + TrackCacheTransform() { + type = Animation::TYPE_TRANSFORM; + spatial = NULL; + bone_idx = -1; + skeleton = NULL; + } + }; + + struct TrackCacheValue : public TrackCache { + + Variant value; + Vector<StringName> subpath; + TrackCacheValue() { type = Animation::TYPE_VALUE; } + }; + + struct TrackCacheMethod : public TrackCache { + + TrackCacheMethod() { type = Animation::TYPE_METHOD; } + }; + + struct TrackCacheBezier : public TrackCache { + + float value; + Vector<StringName> subpath; + TrackCacheBezier() { + type = Animation::TYPE_BEZIER; + value = 0; + } + }; + + struct TrackCacheAudio : public TrackCache { + + bool playing; + float start; + float len; + + TrackCacheAudio() { + type = Animation::TYPE_AUDIO; + playing = false; + start = 0; + len = 0; + } + }; + + struct TrackCacheAnimation : public TrackCache { + + bool playing; + + TrackCacheAnimation() { + type = Animation::TYPE_ANIMATION; + playing = false; + } + }; + + HashMap<NodePath, TrackCache *> track_cache; + Set<TrackCache *> playing_caches; + + Ref<AnimationNode> root; + + AnimationProcessMode process_mode; + bool active; + NodePath animation_player; + + AnimationNode::State state; + bool cache_valid; + void _node_removed(Node *p_node); + void _caches_cleared(); + + void _clear_caches(); + bool _update_caches(AnimationPlayer *player); + void _process_graph(float p_delta); + + uint64_t setup_pass; + uint64_t process_pass; + + bool started; + + NodePath root_motion_track; + Transform root_motion_transform; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_tree_root(const Ref<AnimationNode> &p_root); + Ref<AnimationNode> get_tree_root() const; + + void set_active(bool p_active); + bool is_active() const; + + void set_process_mode(AnimationProcessMode p_mode); + AnimationProcessMode get_process_mode() const; + + void set_animation_player(const NodePath &p_player); + NodePath get_animation_player() const; + + virtual String get_configuration_warning() const; + + bool is_state_invalid() const; + String get_invalid_state_reason() const; + + void set_root_motion_track(const NodePath &p_track); + NodePath get_root_motion_track() const; + + Transform get_root_motion_transform() const; + + uint64_t get_last_process_pass() const; + AnimationTree(); + ~AnimationTree(); +}; + +VARIANT_ENUM_CAST(AnimationTree::AnimationProcessMode) + +#endif // ANIMATION_GRAPH_PLAYER_H diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp index 143684bdf9..026215508b 100644 --- a/scene/animation/animation_tree_player.cpp +++ b/scene/animation/animation_tree_player.cpp @@ -1814,7 +1814,7 @@ void AnimationTreePlayer::_bind_methods() { ADD_GROUP("Playback", "playback_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_animation_process_mode", "get_animation_process_mode"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player"), "set_master_player", "get_master_player"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_master_player", "get_master_player"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "base_path"), "set_base_path", "get_base_path"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp new file mode 100644 index 0000000000..a28c63064a --- /dev/null +++ b/scene/animation/root_motion_view.cpp @@ -0,0 +1,178 @@ +#include "root_motion_view.h" +#include "scene/animation/animation_tree.h" +#include "scene/resources/material.h" +void RootMotionView::set_animation_path(const NodePath &p_path) { + path = p_path; + first = true; +} + +NodePath RootMotionView::get_animation_path() const { + return path; +} + +void RootMotionView::set_color(const Color &p_color) { + color = p_color; + first = true; +} + +Color RootMotionView::get_color() const { + return color; +} + +void RootMotionView::set_cell_size(float p_size) { + cell_size = p_size; + first = true; +} + +float RootMotionView::get_cell_size() const { + return cell_size; +} + +void RootMotionView::set_radius(float p_radius) { + radius = p_radius; + first = true; +} + +float RootMotionView::get_radius() const { + return radius; +} + +void RootMotionView::set_zero_y(bool p_zero_y) { + zero_y = p_zero_y; +} + +bool RootMotionView::get_zero_y() const { + return zero_y; +} + +void RootMotionView::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + VS::get_singleton()->immediate_set_material(immediate, SpatialMaterial::get_material_rid_for_2d(false, true, false, false, false)); + first = true; + } + + if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + Transform transform; + + if (has_node(path)) { + + Node *node = get_node(path); + + AnimationTree *tree = Object::cast_to<AnimationTree>(node); + if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) { + if (is_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_PHYSICS) { + set_process_internal(false); + set_physics_process_internal(true); + } + + if (is_physics_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_IDLE) { + set_process_internal(true); + set_physics_process_internal(false); + } + + transform = tree->get_root_motion_transform(); + } + } + + if (!first && transform == Transform()) { + return; + } + + first = false; + + transform.orthonormalize(); //dont want scale, too imprecise + transform.affine_invert(); + + accumulated = transform * accumulated; + accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size); + if (zero_y) { + accumulated.origin.y = 0; + } + accumulated.origin.z = Math::fposmod(accumulated.origin.z, cell_size); + + VS::get_singleton()->immediate_clear(immediate); + + int cells_in_radius = int((radius / cell_size) + 1.0); + + VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_LINES); + for (int i = -cells_in_radius; i < cells_in_radius; i++) { + for (int j = -cells_in_radius; j < cells_in_radius; j++) { + + Vector3 from(i * cell_size, 0, j * cell_size); + Vector3 from_i((i + 1) * cell_size, 0, j * cell_size); + Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size); + from = accumulated.xform(from); + from_i = accumulated.xform(from_i); + from_j = accumulated.xform(from_j); + + Color c = color, c_i = color, c_j = color; + c.a *= MAX(0, 1.0 - from.length() / radius); + c_i.a *= MAX(0, 1.0 - from_i.length() / radius); + c_j.a *= MAX(0, 1.0 - from_j.length() / radius); + + VS::get_singleton()->immediate_color(immediate, c); + VS::get_singleton()->immediate_vertex(immediate, from); + + VS::get_singleton()->immediate_color(immediate, c_i); + VS::get_singleton()->immediate_vertex(immediate, from_i); + + VS::get_singleton()->immediate_color(immediate, c); + VS::get_singleton()->immediate_vertex(immediate, from); + + VS::get_singleton()->immediate_color(immediate, c_j); + VS::get_singleton()->immediate_vertex(immediate, from_j); + } + } + + VS::get_singleton()->immediate_end(immediate); + } +} + +AABB RootMotionView::get_aabb() const { + + return AABB(Vector3(-radius, 0, -radius), Vector3(radius * 2, 0.001, radius * 2)); +} +PoolVector<Face3> RootMotionView::get_faces(uint32_t p_usage_flags) const { + return PoolVector<Face3>(); +} + +void RootMotionView::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path); + ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path); + + ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color); + ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color); + + ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &RootMotionView::set_cell_size); + ClassDB::bind_method(D_METHOD("get_cell_size"), &RootMotionView::get_cell_size); + + ClassDB::bind_method(D_METHOD("set_radius", "size"), &RootMotionView::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &RootMotionView::get_radius); + + ClassDB::bind_method(D_METHOD("set_zero_y", "enable"), &RootMotionView::set_zero_y); + ClassDB::bind_method(D_METHOD("get_zero_y"), &RootMotionView::get_zero_y); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationTree"), "set_animation_path", "get_animation_path"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_y"), "set_zero_y", "get_zero_y"); +} + +RootMotionView::RootMotionView() { + zero_y = true; + radius = 10; + cell_size = 1; + set_process_internal(true); + immediate = VisualServer::get_singleton()->immediate_create(); + set_base(immediate); + color = Color(0.5, 0.5, 1.0); +} + +RootMotionView::~RootMotionView() { + set_base(RID()); + VisualServer::get_singleton()->free(immediate); +} diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h new file mode 100644 index 0000000000..611183d364 --- /dev/null +++ b/scene/animation/root_motion_view.h @@ -0,0 +1,47 @@ +#ifndef ROOT_MOTION_VIEW_H +#define ROOT_MOTION_VIEW_H + +#include "scene/3d/visual_instance.h" + +class RootMotionView : public VisualInstance { + GDCLASS(RootMotionView, VisualInstance) +public: + RID immediate; + NodePath path; + float cell_size; + float radius; + bool use_in_game; + Color color; + bool first; + bool zero_y; + + Transform accumulated; + +private: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_animation_path(const NodePath &p_path); + NodePath get_animation_path() const; + + void set_color(const Color &p_path); + Color get_color() const; + + void set_cell_size(float p_size); + float get_cell_size() const; + + void set_radius(float p_radius); + float get_radius() const; + + void set_zero_y(bool p_zero_y); + bool get_zero_y() const; + + virtual AABB get_aabb() const; + virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + + RootMotionView(); + ~RootMotionView(); +}; + +#endif // ROOT_MOTION_VIEW_H diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 4eefcc9ced..9f7503577b 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -201,6 +201,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all); ClassDB::bind_method(D_METHOD("stop", "object", "key"), &Tween::stop, DEFVAL("")); ClassDB::bind_method(D_METHOD("stop_all"), &Tween::stop_all); + ClassDB::bind_method(D_METHOD("is_stopped"), &Tween::is_stopped); ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Tween::resume, DEFVAL("")); ClassDB::bind_method(D_METHOD("resume_all"), &Tween::resume_all); ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Tween::remove, DEFVAL("")); @@ -743,6 +744,10 @@ bool Tween::stop(Object *p_object, StringName p_key) { return true; } +bool Tween::is_stopped() const { + return tell() >= get_runtime(); +} + bool Tween::stop_all() { set_active(false); diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 757d80e90a..36094bf294 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -162,6 +162,7 @@ public: bool reset_all(); bool stop(Object *p_object, StringName p_key); bool stop_all(); + bool is_stopped() const; bool resume(Object *p_object, StringName p_key); bool resume_all(); bool remove(Object *p_object, StringName p_key); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 34891832e2..f8c188d33d 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -242,6 +242,14 @@ bool ColorPicker::is_raw_mode() const { return raw_mode_enabled; } +void ColorPicker::set_deferred_mode(bool p_enabled) { + deferred_mode_enabled = p_enabled; +} + +bool ColorPicker::is_deferred_mode() const { + return deferred_mode_enabled; +} + void ColorPicker::_update_text_value() { bool visible = true; if (text_is_constructor) { @@ -328,7 +336,11 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); + } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { emit_signal("color_changed", color); + changing_color = false; } else { changing_color = false; } @@ -347,7 +359,8 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); } } @@ -368,7 +381,10 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); + else if (!bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) + emit_signal("color_changed", color); } Ref<InputEventMouseMotion> mev = p_event; @@ -383,7 +399,8 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { last_hsv = color; set_pick_color(color); _update_color(); - emit_signal("color_changed", color); + if (!deferred_mode_enabled) + emit_signal("color_changed", color); } } @@ -500,6 +517,8 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); ClassDB::bind_method(D_METHOD("set_raw_mode", "mode"), &ColorPicker::set_raw_mode); ClassDB::bind_method(D_METHOD("is_raw_mode"), &ColorPicker::is_raw_mode); + ClassDB::bind_method(D_METHOD("set_deferred_mode", "mode"), &ColorPicker::set_deferred_mode); + ClassDB::bind_method(D_METHOD("is_deferred_mode"), &ColorPicker::is_deferred_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); @@ -522,6 +541,7 @@ void ColorPicker::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); } @@ -533,6 +553,7 @@ ColorPicker::ColorPicker() : edit_alpha = true; text_is_constructor = false; raw_mode_enabled = false; + deferred_mode_enabled = false; changing_color = false; screen = NULL; diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 6b63e5fe60..c8d8e1aa8a 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -67,6 +67,7 @@ private: Color color; bool raw_mode_enabled; + bool deferred_mode_enabled; bool updating; bool changing_color; float h, s, v; @@ -107,6 +108,9 @@ public: void set_raw_mode(bool p_enabled); bool is_raw_mode() const; + void set_deferred_mode(bool p_enabled); + bool is_deferred_mode() const; + void set_focus_on_line_edit(); ColorPicker(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index d07b5a9f65..6ef8016dd5 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -160,8 +160,16 @@ void Control::_update_minimum_size_cache() { Size2 minsize = get_minimum_size(); minsize.x = MAX(minsize.x, data.custom_minimum_size.x); minsize.y = MAX(minsize.y, data.custom_minimum_size.y); + + bool size_changed = false; + if (data.minimum_size_cache != minsize) + size_changed = true; + data.minimum_size_cache = minsize; data.minimum_size_valid = true; + + if (size_changed) + minimum_size_changed(); } Size2 Control::get_combined_minimum_size() const { @@ -452,10 +460,8 @@ void Control::_notification(int p_notification) { } break; case NOTIFICATION_POST_ENTER_TREE: { - if (is_visible_in_tree()) { - data.minimum_size_valid = false; - _size_changed(); - } + data.minimum_size_valid = false; + _size_changed(); } break; case NOTIFICATION_EXIT_TREE: { @@ -2889,12 +2895,12 @@ void Control::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip"); ADD_GROUP("Focus", "focus_"); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_left"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_LEFT); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); - ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); - ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next"), "set_focus_next", "get_focus_next"); - ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous"), "set_focus_previous", "get_focus_previous"); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_left", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_LEFT); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); + ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next"); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous"); ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_GROUP("Mouse", "mouse_"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 9124256624..633f92f733 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -202,12 +202,12 @@ private: NodePath focus_next; NodePath focus_prev; - HashMap<StringName, Ref<Texture>, StringNameHasher> icon_override; - HashMap<StringName, Ref<Shader>, StringNameHasher> shader_override; - HashMap<StringName, Ref<StyleBox>, StringNameHasher> style_override; - HashMap<StringName, Ref<Font>, StringNameHasher> font_override; - HashMap<StringName, Color, StringNameHasher> color_override; - HashMap<StringName, int, StringNameHasher> constant_override; + HashMap<StringName, Ref<Texture> > icon_override; + HashMap<StringName, Ref<Shader> > shader_override; + HashMap<StringName, Ref<StyleBox> > style_override; + HashMap<StringName, Ref<Font> > font_override; + HashMap<StringName, Color> color_override; + HashMap<StringName, int> constant_override; Map<Ref<Font>, int> font_refcount; } data; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 4bd92d888d..25cb74a494 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -777,6 +777,7 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mode", "mode"), &FileDialog::set_mode); ClassDB::bind_method(D_METHOD("get_mode"), &FileDialog::get_mode); ClassDB::bind_method(D_METHOD("get_vbox"), &FileDialog::get_vbox); + ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit); ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access); ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access); ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 38ce91a4df..e2c730a56e 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -58,6 +58,7 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S c.from_port = p_from_port; c.to = p_to; c.to_port = p_to_port; + c.activity = 0; connections.push_back(c); top_layer->update(); update(); @@ -624,6 +625,7 @@ void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const void GraphEdit::_connections_layer_draw() { + Color activity_color = get_color("activity"); //draw connections List<List<Connection>::Element *> to_erase; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { @@ -661,6 +663,11 @@ void GraphEdit::_connections_layer_draw() { Color color = gfrom->get_connection_output_color(E->get().from_port); Vector2 topos = gto->get_connection_input_position(E->get().to_port) + gto->get_offset() * zoom; Color tocolor = gto->get_connection_input_color(E->get().to_port); + + if (E->get().activity > 0) { + color = color.linear_interpolate(activity_color, E->get().activity); + tocolor = tocolor.linear_interpolate(activity_color, E->get().activity); + } _draw_cos_line(connections_layer, frompos, topos, color, tocolor); } @@ -980,6 +987,23 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } } +void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) { + + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + + if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { + + if (ABS(E->get().activity != p_activity)) { + //update only if changed + top_layer->update(); + connections_layer->update(); + } + E->get().activity = p_activity; + return; + } + } +} + void GraphEdit::clear_connections() { connections.clear(); @@ -1141,11 +1165,16 @@ void GraphEdit::_snap_value_changed(double) { update(); } +HBoxContainer *GraphEdit::get_zoom_hbox() { + return zoom_hb; +} + void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node); ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected); ClassDB::bind_method(D_METHOD("disconnect_node", "from", "from_port", "to", "to_port"), &GraphEdit::disconnect_node); + ClassDB::bind_method(D_METHOD("set_connection_activity", "from", "from_port", "to", "to_port", "amount"), &GraphEdit::set_connection_activity); ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list); ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections); ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs); @@ -1187,6 +1216,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset); ClassDB::bind_method(D_METHOD("_connections_layer_draw"), &GraphEdit::_connections_layer_draw); + ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); + ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); @@ -1253,7 +1284,7 @@ GraphEdit::GraphEdit() { zoom = 1; - HBoxContainer *zoom_hb = memnew(HBoxContainer); + zoom_hb = memnew(HBoxContainer); top_layer->add_child(zoom_hb); zoom_hb->set_position(Vector2(10, 10)); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 3bfde44854..14789001e4 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -31,6 +31,7 @@ #ifndef GRAPH_EDIT_H #define GRAPH_EDIT_H +#include "scene/gui/box_container.h" #include "scene/gui/graph_node.h" #include "scene/gui/scroll_bar.h" #include "scene/gui/slider.h" @@ -62,6 +63,7 @@ public: StringName to; int from_port; int to_port; + float activity; }; private: @@ -157,6 +159,8 @@ private: Set<int> valid_left_disconnect_types; Set<int> valid_right_disconnect_types; + HBoxContainer *zoom_hb; + friend class GraphEditFilter; bool _filter_input(const Point2 &p_point); void _snap_toggled(); @@ -175,6 +179,8 @@ public: void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void clear_connections(); + void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity); + void add_valid_connection_type(int p_type, int p_with_type); void remove_valid_connection_type(int p_type, int p_with_type); bool is_valid_connection_type(int p_type, int p_with_type) const; @@ -206,6 +212,8 @@ public: int get_snap() const; void set_snap(int p_snap); + HBoxContainer *get_zoom_hbox(); + GraphEdit(); }; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 57b9a9a11b..72ed0e9b81 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -942,6 +942,7 @@ void ItemList::_notification(int p_what) { } } + minimum_size_changed(); shape_changed = false; } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index f1f1a66b47..0cd5219f8f 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -565,6 +565,9 @@ void LineEdit::_notification(int p_what) { #endif case NOTIFICATION_RESIZED: { + if (expand_to_text_length) { + window_pos = 0; //force scroll back since it's expanding to text length + } set_cursor_position(get_cursor_position()); } break; @@ -610,7 +613,8 @@ void LineEdit::_notification(int p_what) { } int x_ofs = 0; - int cached_text_width = text.empty() ? cached_placeholder_width : cached_width; + bool using_placeholder = text.empty(); + int cached_text_width = using_placeholder ? cached_placeholder_width : cached_width; switch (align) { @@ -645,9 +649,9 @@ void LineEdit::_notification(int p_what) { Color font_color_selected = get_color("font_color_selected"); Color cursor_color = get_color("cursor_color"); - const String &t = text.empty() ? placeholder : text; + const String &t = using_placeholder ? placeholder : text; // draw placeholder color - if (text.empty()) + if (using_placeholder) font_color.a *= placeholder_alpha; font_color.a *= disabled_alpha; @@ -756,7 +760,8 @@ void LineEdit::_notification(int p_what) { if (has_focus()) { - OS::get_singleton()->set_ime_position(get_global_position() + Point2(x_ofs, y_ofs + caret_height)); + OS::get_singleton()->set_ime_active(true); + OS::get_singleton()->set_ime_position(get_global_position() + Point2(using_placeholder ? 0 : x_ofs, y_ofs + caret_height)); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); } } break; @@ -766,6 +771,7 @@ void LineEdit::_notification(int p_what) { draw_caret = true; } + OS::get_singleton()->set_ime_active(true); Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height; OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); @@ -778,6 +784,7 @@ void LineEdit::_notification(int p_what) { OS::get_singleton()->set_ime_position(Point2()); OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL); + OS::get_singleton()->set_ime_active(false); ime_text = ""; ime_selection = Point2(); @@ -1094,11 +1101,12 @@ void LineEdit::set_cursor_position(int p_pos) { for (int i = cursor_pos; i >= window_pos; i--) { if (i >= text.length()) { - accum_width = font->get_char_size(' ').width; //anything should do + //do not do this, because if the cursor is at the end, its just fine that it takes no space + //accum_width = font->get_char_size(' ').width; //anything should do } else { accum_width += font->get_char_size(text[i], i + 1 < text.length() ? text[i + 1] : 0).width; //anything should do } - if (accum_width >= window_width) + if (accum_width > window_width) break; wp = i; @@ -1165,7 +1173,7 @@ Size2 LineEdit::get_minimum_size() const { int mstext = get_constant("minimum_spaces") * space_size; if (expand_to_text_length) { - mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact + mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact, and because cursor needs a bit more when at the end } min.width += mstext; diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index d18a3a3f2f..26d01ecc09 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -113,37 +113,9 @@ void Popup::set_as_minsize() { void Popup::popup_centered_minsize(const Size2 &p_minsize) { - Size2 total_minsize = p_minsize; - - for (int i = 0; i < get_child_count(); i++) { - - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) - continue; - if (!c->is_visible()) - continue; - - Size2 minsize = c->get_combined_minimum_size(); - - for (int j = 0; j < 2; j++) { - - Margin m_beg = Margin(0 + j); - Margin m_end = Margin(2 + j); - - float margin_begin = c->get_margin(m_beg); - float margin_end = c->get_margin(m_end); - float anchor_begin = c->get_anchor(m_beg); - float anchor_end = c->get_anchor(m_end); - - minsize[j] += margin_begin * (ANCHOR_END - anchor_begin) + margin_end * anchor_end; - } - - total_minsize.width = MAX(total_minsize.width, minsize.width); - total_minsize.height = MAX(total_minsize.height, minsize.height); - } - - popup_centered(total_minsize); - popped_up = true; + set_custom_minimum_size(p_minsize); + _fix_size(); + popup_centered(); } void Popup::popup_centered(const Size2 &p_size) { diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 93865cebde..18e609c798 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -440,6 +440,8 @@ void PopupMenu::_notification(int p_what) { float h; Size2 icon_size; + Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + item_ofs.x += items[i].h_ofs; if (!items[i].icon.is_null()) { @@ -463,18 +465,18 @@ void PopupMenu::_notification(int p_what) { if (items[i].checkable_type) { Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr(); - icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0))); + icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color); item_ofs.x += icon->get_width() + hseparation; } if (!items[i].icon.is_null()) { - items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0))); + items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); item_ofs.x += items[i].icon->get_width(); item_ofs.x += hseparation; } if (items[i].submenu != "") { - submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2)); + submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); } item_ofs.y += font->get_ascent(); @@ -913,6 +915,13 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) { update(); } +void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { + + ERR_FAIL_INDEX(p_idx, items.size()); + items[p_idx].shortcut_is_disabled = p_disabled; + update(); +} + void PopupMenu::toggle_item_multistate(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); @@ -937,6 +946,12 @@ bool PopupMenu::is_item_radio_checkable(int p_idx) const { return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON; } +bool PopupMenu::is_item_shortcut_disabled(int p_idx) const { + + ERR_FAIL_INDEX_V(p_idx, items.size(), false); + return items[p_idx].shortcut_is_disabled; +} + int PopupMenu::get_item_count() const { return items.size(); @@ -963,7 +978,7 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo int il = items.size(); for (int i = 0; i < il; i++) { - if (is_item_disabled(i)) + if (is_item_disabled(i) || items[i].shortcut_is_disabled) continue; if (items[i].shortcut.is_valid() && items[i].shortcut->is_shortcut(p_event) && (items[i].shortcut_is_global || !p_for_global_only)) { @@ -1248,6 +1263,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip); ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate); + ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "idx", "disabled"), &PopupMenu::set_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked); ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate); @@ -1264,6 +1280,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator); ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable); ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable); + ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "idx"), &PopupMenu::is_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index fde91bd845..d3ee9be1c0 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -64,6 +64,7 @@ class PopupMenu : public Popup { int h_ofs; Ref<ShortCut> shortcut; bool shortcut_is_global; + bool shortcut_is_disabled; Item() { checked = false; @@ -76,6 +77,7 @@ class PopupMenu : public Popup { _ofs_cache = 0; h_ofs = 0; shortcut_is_global = false; + shortcut_is_disabled = false; } }; @@ -149,6 +151,7 @@ public: void set_item_h_offset(int p_idx, int p_offset); void set_item_multistate(int p_idx, int p_state); void toggle_item_multistate(int p_idx); + void set_item_shortcut_disabled(int p_idx, bool p_disabled); void toggle_item_checked(int p_idx); @@ -165,6 +168,7 @@ public: bool is_item_separator(int p_idx) const; bool is_item_checkable(int p_idx) const; bool is_item_radio_checkable(int p_idx) const; + bool is_item_shortcut_disabled(int p_idx) const; String get_item_tooltip(int p_idx) const; Ref<ShortCut> get_item_shortcut(int p_idx) const; int get_item_state(int p_idx) const; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 37e519e375..fc5d56237a 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -39,9 +39,9 @@ Size2 ProgressBar::get_minimum_size() const { Size2 minimum_size = bg->get_minimum_size(); minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height); minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width); - if (percent_visible) { - minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height()); - } + //if (percent_visible) { this is needed, else the progressbar will collapse + minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height()); + //} return minimum_size; } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index f34559fc8d..ce2e3538da 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -324,7 +324,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & color = _find_color(text, p_base_color); font_color_shadow = _find_color(text, p_font_color_shadow); underline = _find_underline(text); - if (_find_meta(text, &meta)) { + if (_find_meta(text, &meta) && underline_meta) { underline = true; } diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 2dd5c64378..495d618930 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -241,10 +241,10 @@ void ScrollContainer::_notification(int p_what) { size -= sb->get_minimum_size(); ofs += sb->get_offset(); - if (h_scroll->is_visible_in_tree()) + if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) //scrolls may have been moved out for reasons size.y -= h_scroll->get_minimum_size().y; - if (v_scroll->is_visible_in_tree()) + if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) //scrolls may have been moved out for reasons size.x -= h_scroll->get_minimum_size().x; for (int i = 0; i < get_child_count(); i++) { @@ -482,6 +482,16 @@ String ScrollContainer::get_configuration_warning() const { return ""; } +HScrollBar *ScrollContainer::get_h_scrollbar() { + + return h_scroll; +} + +VScrollBar *ScrollContainer::get_v_scrollbar() { + + return v_scroll; +} + void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("_scroll_moved"), &ScrollContainer::_scroll_moved); @@ -498,6 +508,9 @@ void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone); ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone); + ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar); + ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar); + ADD_SIGNAL(MethodInfo("scroll_started")); ADD_SIGNAL(MethodInfo("scroll_ended")); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 3fe1ed447a..abef80294a 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -92,6 +92,9 @@ public: int get_deadzone() const; void set_deadzone(int p_deadzone); + HScrollBar *get_h_scrollbar(); + VScrollBar *get_v_scrollbar(); + virtual bool clips_input() const; virtual String get_configuration_warning() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 0363dd44c2..4f72b5c6ed 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -143,6 +143,42 @@ void TabContainer::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_RESIZED: { + + Vector<Control *> tabs = _get_tabs(); + int side_margin = get_constant("side_margin"); + Ref<Texture> menu = get_icon("menu"); + Ref<Texture> increment = get_icon("increment"); + Ref<Texture> decrement = get_icon("decrement"); + int header_width = get_size().width - side_margin * 2; + + // Find the width of the header area. + if (popup) + header_width -= menu->get_width(); + if (buttons_visible_cache) + header_width -= increment->get_width() + decrement->get_width(); + if (popup || buttons_visible_cache) + header_width += side_margin; + + // Find the width of all tabs after first_tab_cache. + int all_tabs_width = 0; + for (int i = first_tab_cache; i < tabs.size(); i++) { + int tab_width = _get_tab_width(i); + all_tabs_width += tab_width; + } + + // Check if tabs before first_tab_cache would fit into the header area. + for (int i = first_tab_cache - 1; i >= 0; i--) { + int tab_width = _get_tab_width(i); + + if (all_tabs_width + tab_width > header_width) + break; + + all_tabs_width += tab_width; + first_tab_cache--; + } + } break; + case NOTIFICATION_DRAW: { RID canvas = get_canvas_item(); @@ -197,6 +233,10 @@ void TabContainer::_notification(int p_what) { header_width += side_margin; } + if (!buttons_visible_cache) { + first_tab_cache = 0; + } + // Go through the visible tabs to find the width they occupy. all_tabs_width = 0; Vector<int> tab_widths; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 55a650ff12..4fe06e9a4c 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -426,6 +426,9 @@ void TextEdit::_update_scrollbars() { void TextEdit::_click_selection_held() { + // Warning: is_mouse_button_pressed(BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD + // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. + // I'm unsure if there's an actual fix that doesn't have a ton of side effects. if (Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT) && selection.selecting_mode != Selection::MODE_NONE) { switch (selection.selecting_mode) { case Selection::MODE_POINTER: { @@ -447,7 +450,7 @@ void TextEdit::_click_selection_held() { } void TextEdit::_update_selection_mode_pointer() { - Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position(); + Point2 mp = get_local_mouse_position(); int row, col; _get_mouse_pos(Point2i(mp.x, mp.y), row, col); @@ -455,14 +458,14 @@ void TextEdit::_update_selection_mode_pointer() { select(selection.selecting_line, selection.selecting_column, row, col); cursor_set_line(row, false); - cursor_set_column(col, false); + cursor_set_column(col); update(); click_select_held->start(); } void TextEdit::_update_selection_mode_word() { - Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position(); + Point2 mp = get_local_mouse_position(); int row, col; _get_mouse_pos(Point2i(mp.x, mp.y), row, col); @@ -496,26 +499,29 @@ void TextEdit::_update_selection_mode_word() { selection.selected_word_beg = beg; selection.selected_word_end = end; selection.selected_word_origin = beg; + cursor_set_line(selection.to_line, false); cursor_set_column(selection.to_column); } else { if ((col <= selection.selected_word_origin && row == selection.selecting_line) || row < selection.selecting_line) { selection.selecting_column = selection.selected_word_end; select(row, beg, selection.selecting_line, selection.selected_word_end); + cursor_set_line(selection.from_line, false); cursor_set_column(selection.from_column); } else { selection.selecting_column = selection.selected_word_beg; select(selection.selecting_line, selection.selected_word_beg, row, end); + cursor_set_line(selection.to_line, false); cursor_set_column(selection.to_column); } } - cursor_set_line(row, false); update(); + click_select_held->start(); } void TextEdit::_update_selection_mode_line() { - Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position(); + Point2 mp = get_local_mouse_position(); int row, col; _get_mouse_pos(Point2i(mp.x, mp.y), row, col); @@ -531,7 +537,7 @@ void TextEdit::_update_selection_mode_line() { selection.selecting_column = 0; col = text[row].length(); } - cursor_set_column(0, false); + cursor_set_column(0); select(selection.selecting_line, selection.selecting_column, row, col); update(); @@ -1388,6 +1394,7 @@ void TextEdit::_notification(int p_what) { } if (has_focus()) { + OS::get_singleton()->set_ime_active(true); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height())); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); } @@ -1399,6 +1406,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } + OS::get_singleton()->set_ime_active(true); Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height(); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this); @@ -1413,6 +1421,7 @@ void TextEdit::_notification(int p_what) { OS::get_singleton()->set_ime_position(Point2()); OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL); + OS::get_singleton()->set_ime_active(false); ime_text = ""; ime_selection = Point2(); @@ -1657,14 +1666,17 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co rows /= get_row_height(); rows += get_v_scroll_offset(); int first_vis_line = get_first_visible_line(); + int last_vis_line = get_last_visible_line(); int row = first_vis_line + Math::floor(rows); int wrap_index = 0; if (is_wrap_enabled() || is_hiding_enabled()) { - int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + 1, wrap_index) - 1; - row = first_vis_line + f_ofs; - row = CLAMP(row, 0, get_last_visible_line() + 1); + int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; + if (rows < 0) + row = first_vis_line - f_ofs; + else + row = first_vis_line + f_ofs; } if (row < 0) diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 3643aedb85..6199f52ec5 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -40,7 +40,6 @@ #include "viewport.h" VARIANT_ENUM_CAST(Node::PauseMode); -VARIANT_ENUM_CAST(Node::RPCMode); void Node::_notification(int p_notification) { @@ -485,18 +484,18 @@ bool Node::is_network_master() const { /***** RPC CONFIG ********/ -void Node::rpc_config(const StringName &p_method, RPCMode p_mode) { +void Node::rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode) { - if (p_mode == RPC_MODE_DISABLED) { + if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) { data.rpc_methods.erase(p_method); } else { data.rpc_methods[p_method] = p_mode; }; } -void Node::rset_config(const StringName &p_property, RPCMode p_mode) { +void Node::rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode) { - if (p_mode == RPC_MODE_DISABLED) { + if (p_mode == MultiplayerAPI::RPC_MODE_DISABLED) { data.rpc_properties.erase(p_property); } else { data.rpc_properties[p_property] = p_mode; @@ -718,121 +717,14 @@ void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { multiplayer = p_multiplayer; } -const Map<StringName, Node::RPCMode>::Element *Node::get_node_rpc_mode(const StringName &p_method) { +const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rpc_mode(const StringName &p_method) { return data.rpc_methods.find(p_method); } -const Map<StringName, Node::RPCMode>::Element *Node::get_node_rset_mode(const StringName &p_property) { +const Map<StringName, MultiplayerAPI::RPCMode>::Element *Node::get_node_rset_mode(const StringName &p_property) { return data.rpc_properties.find(p_property); } -bool Node::can_call_rpc(const StringName &p_method, int p_from) const { - - const Map<StringName, RPCMode>::Element *E = data.rpc_methods.find(p_method); - if (E) { - - switch (E->get()) { - - case RPC_MODE_DISABLED: { - return false; - } break; - case RPC_MODE_REMOTE: { - return true; - } break; - case RPC_MODE_SYNC: { - return true; - } break; - case RPC_MODE_MASTER: { - return is_network_master(); - } break; - case RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - if (get_script_instance()) { - //attempt with script - ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rpc_mode(p_method); - - switch (rpc_mode) { - - case ScriptInstance::RPC_MODE_DISABLED: { - return false; - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - return true; - } break; - case ScriptInstance::RPC_MODE_SYNC: { - return true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - return is_network_master(); - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - ERR_PRINTS("RPC from " + itos(p_from) + " on unauthorized method attempted: " + String(p_method) + " on base: " + String(Variant(this))); - return false; -} - -bool Node::can_call_rset(const StringName &p_property, int p_from) const { - - const Map<StringName, RPCMode>::Element *E = data.rpc_properties.find(p_property); - if (E) { - - switch (E->get()) { - - case RPC_MODE_DISABLED: { - return false; - } break; - case RPC_MODE_REMOTE: { - return true; - } break; - case RPC_MODE_SYNC: { - return true; - } break; - case RPC_MODE_MASTER: { - return is_network_master(); - } break; - case RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - if (get_script_instance()) { - //attempt with script - ScriptInstance::RPCMode rpc_mode = get_script_instance()->get_rset_mode(p_property); - - switch (rpc_mode) { - - case ScriptInstance::RPC_MODE_DISABLED: { - return false; - } break; - case ScriptInstance::RPC_MODE_REMOTE: { - return true; - } break; - case ScriptInstance::RPC_MODE_SYNC: { - return true; - } break; - case ScriptInstance::RPC_MODE_MASTER: { - return is_network_master(); - } break; - case ScriptInstance::RPC_MODE_SLAVE: { - return !is_network_master() && p_from == get_network_master(); - } break; - } - } - - ERR_PRINTS("RSET from " + itos(p_from) + " on unauthorized property attempted: " + String(p_property) + " on base: " + String(Variant(this))); - - return false; -} - bool Node::can_process() const { ERR_FAIL_COND_V(!is_inside_tree(), false); @@ -1203,6 +1095,10 @@ void Node::add_child(Node *p_child, bool p_legible_unique_name) { } void Node::add_child_below_node(Node *p_node, Node *p_child, bool p_legible_unique_name) { + + ERR_FAIL_NULL(p_node); + ERR_FAIL_NULL(p_child); + add_child(p_child, p_legible_unique_name); if (is_a_parent_of(p_node)) { @@ -2038,8 +1934,9 @@ Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const if (E->get().usage & PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE) { Resource *res = Object::cast_to<Resource>(value); - if (res) // Duplicate only if it's a resource + if (res) { // Duplicate only if it's a resource current_node->set(name, res->duplicate()); + } } else { @@ -2802,12 +2699,6 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); - BIND_ENUM_CONSTANT(RPC_MODE_DISABLED); - BIND_ENUM_CONSTANT(RPC_MODE_REMOTE); - BIND_ENUM_CONSTANT(RPC_MODE_SYNC); - BIND_ENUM_CONSTANT(RPC_MODE_MASTER); - BIND_ENUM_CONSTANT(RPC_MODE_SLAVE); - BIND_ENUM_CONSTANT(PAUSE_MODE_INHERIT); BIND_ENUM_CONSTANT(PAUSE_MODE_STOP); BIND_ENUM_CONSTANT(PAUSE_MODE_PROCESS); diff --git a/scene/main/node.h b/scene/main/node.h index 540f34cba7..341349de79 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -65,15 +65,6 @@ public: #endif }; - enum RPCMode { - - RPC_MODE_DISABLED, //no rpc for this method, calls to this will be blocked (default) - RPC_MODE_REMOTE, // using rpc() on it will call method / set property in all other peers - RPC_MODE_SYNC, // using rpc() on it will call method / set property in all other peers and locally - RPC_MODE_MASTER, // usinc rpc() on it will call method on wherever the master is, be it local or remote - RPC_MODE_SLAVE, // usinc rpc() on it will call method for all slaves, be it local or remote - }; - struct Comparator { bool operator()(const Node *p_a, const Node *p_b) const { return p_b->is_greater_than(p_a); } @@ -120,8 +111,8 @@ private: Node *pause_owner; int network_master; - Map<StringName, RPCMode> rpc_methods; - Map<StringName, RPCMode> rpc_properties; + Map<StringName, MultiplayerAPI::RPCMode> rpc_methods; + Map<StringName, MultiplayerAPI::RPCMode> rpc_properties; // variables used to properly sort the node when processing, ignored otherwise //should move all the stuff below to bits @@ -404,8 +395,8 @@ public: int get_network_master() const; bool is_network_master() const; - void rpc_config(const StringName &p_method, RPCMode p_mode); // config a local method for RPC - void rset_config(const StringName &p_property, RPCMode p_mode); // config a local property for RPC + void rpc_config(const StringName &p_method, MultiplayerAPI::RPCMode p_mode); // config a local method for RPC + void rset_config(const StringName &p_property, MultiplayerAPI::RPCMode p_mode); // config a local property for RPC void rpc(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode void rpc_unreliable(const StringName &p_method, VARIANT_ARG_LIST); //rpc call, honors RPCMode @@ -423,11 +414,8 @@ public: Ref<MultiplayerAPI> get_multiplayer() const; Ref<MultiplayerAPI> get_custom_multiplayer() const; void set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer); - const Map<StringName, RPCMode>::Element *get_node_rpc_mode(const StringName &p_method); - const Map<StringName, RPCMode>::Element *get_node_rset_mode(const StringName &p_property); - - bool can_call_rpc(const StringName &p_method, int p_from) const; - bool can_call_rset(const StringName &p_property, int p_from) const; + const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rpc_mode(const StringName &p_method); + const Map<StringName, MultiplayerAPI::RPCMode>::Element *get_node_rset_mode(const StringName &p_property); Node(); ~Node(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 607dbebf6c..8d6e57b335 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -485,7 +485,9 @@ bool SceneTree::idle(float p_time) { idle_process_time = p_time; - multiplayer->poll(); + if (multiplayer_poll) { + multiplayer->poll(); + } emit_signal("idle_frame"); @@ -1672,6 +1674,14 @@ Ref<MultiplayerAPI> SceneTree::get_multiplayer() const { return multiplayer; } +void SceneTree::set_multiplayer_poll_enabled(bool p_enabled) { + multiplayer_poll = p_enabled; +} + +bool SceneTree::is_multiplayer_poll_enabled() const { + return multiplayer_poll; +} + void SceneTree::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { ERR_FAIL_COND(!p_multiplayer.is_valid()); @@ -1802,6 +1812,8 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer"), &SceneTree::set_multiplayer); ClassDB::bind_method(D_METHOD("get_multiplayer"), &SceneTree::get_multiplayer); + ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled); + ClassDB::bind_method(D_METHOD("is_multiplayer_poll_enabled"), &SceneTree::is_multiplayer_poll_enabled); ClassDB::bind_method(D_METHOD("set_network_peer", "peer"), &SceneTree::set_network_peer); ClassDB::bind_method(D_METHOD("get_network_peer"), &SceneTree::get_network_peer); ClassDB::bind_method(D_METHOD("is_network_server"), &SceneTree::is_network_server); @@ -1830,6 +1842,7 @@ void SceneTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "network_peer", PROPERTY_HINT_RESOURCE_TYPE, "NetworkedMultiplayerPeer", 0), "set_network_peer", "get_network_peer"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "", "get_root"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_multiplayer", "get_multiplayer"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled"); ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("node_added", PropertyInfo(Variant::OBJECT, "node"))); @@ -1934,6 +1947,7 @@ SceneTree::SceneTree() { root->set_world(Ref<World>(memnew(World))); // Initialize network state + multiplayer_poll = true; set_multiplayer(Ref<MultiplayerAPI>(memnew(MultiplayerAPI))); //root->set_world_2d( Ref<World2D>( memnew( World2D ))); diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 6e0156546e..aa8d78b1e1 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -186,6 +186,7 @@ private: ///network/// Ref<MultiplayerAPI> multiplayer; + bool multiplayer_poll; void _network_peer_connected(int p_id); void _network_peer_disconnected(int p_id); @@ -411,6 +412,8 @@ public: //network API Ref<MultiplayerAPI> get_multiplayer() const; + void set_multiplayer_poll_enabled(bool p_enabled); + bool is_multiplayer_poll_enabled() const; void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer); void set_network_peer(const Ref<NetworkedMultiplayerPeer> &p_network_peer); Ref<NetworkedMultiplayerPeer> get_network_peer() const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index ca9be9823a..a894b82a94 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -144,7 +144,7 @@ void ViewportTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_viewport_path_in_scene", "path"), &ViewportTexture::set_viewport_path_in_scene); ClassDB::bind_method(D_METHOD("get_viewport_path_in_scene"), &ViewportTexture::get_viewport_path_in_scene); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path"), "set_viewport_path_in_scene", "get_viewport_path_in_scene"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Viewport"), "set_viewport_path_in_scene", "get_viewport_path_in_scene"); } ViewportTexture::ViewportTexture() { @@ -1308,13 +1308,37 @@ void Viewport::_gui_cancel_tooltip() { } } +String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos) { + + Vector2 pos = p_pos; + String tooltip; + + while (p_control) { + + tooltip = p_control->get_tooltip(pos); + + if (tooltip != String()) + break; + pos = p_control->get_transform().xform(pos); + + if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP) + break; + if (p_control->is_set_as_toplevel()) + break; + + p_control = p_control->get_parent_control(); + } + + return tooltip; +} + void Viewport::_gui_show_tooltip() { if (!gui.tooltip) { return; } - String tooltip = gui.tooltip->get_tooltip(gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos)); + String tooltip = _gui_get_tooltip(gui.tooltip, gui.tooltip->get_global_transform().xform_inv(gui.tooltip_pos)); if (tooltip.length() == 0) return; // bye @@ -1388,12 +1412,14 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu Control *control = Object::cast_to<Control>(ci); if (control) { - control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev); + + control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); //signal should be first, so it's possible to override an event (and then accept it) if (gui.key_event_accepted) break; if (!control->is_inside_tree()) break; - control->emit_signal(SceneStringNames::get_singleton()->gui_input, ev); + control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev); + if (!control->is_inside_tree() || control->is_set_as_toplevel()) break; if (gui.key_event_accepted) @@ -1864,7 +1890,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.tooltip_popup) { if (can_tooltip) { - String tooltip = over->get_tooltip(gui.tooltip->get_global_transform().xform_inv(mpos)); + String tooltip = _gui_get_tooltip(over, gui.tooltip->get_global_transform().xform_inv(mpos)); if (tooltip.length() == 0) _gui_cancel_tooltip(); @@ -1886,7 +1912,23 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { mm->set_position(pos); - Control::CursorShape cursor_shape = over->get_cursor_shape(pos); + Control::CursorShape cursor_shape = Control::CURSOR_ARROW; + { + Control *c = over; + Vector2 cpos = pos; + while (c) { + cursor_shape = c->get_cursor_shape(cpos); + cpos = c->get_transform().xform(cpos); + if (cursor_shape != Control::CURSOR_ARROW) + break; + if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP) + break; + if (c->is_set_as_toplevel()) + break; + c = c->get_parent_control(); + } + } + OS::get_singleton()->set_cursor_shape((OS::CursorShape)cursor_shape); if (over->can_process()) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index c1ef58de69..3000398540 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -312,6 +312,7 @@ private: void _gui_remove_root_control(List<Control *>::Element *RI); void _gui_remove_subwindow_control(List<Control *>::Element *SI); + String _gui_get_tooltip(Control *p_control, const Vector2 &p_pos); void _gui_cancel_tooltip(); void _gui_show_tooltip(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 2f3d4df329..55aa0024c8 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -63,8 +63,14 @@ #include "scene/2d/tile_map.h" #include "scene/2d/visibility_notifier_2d.h" #include "scene/2d/y_sort.h" +#include "scene/animation/animation_blend_space_1d.h" +#include "scene/animation/animation_blend_space_2d.h" +#include "scene/animation/animation_blend_tree.h" +#include "scene/animation/animation_node_state_machine.h" #include "scene/animation/animation_player.h" +#include "scene/animation/animation_tree.h" #include "scene/animation/animation_tree_player.h" +#include "scene/animation/root_motion_view.h" #include "scene/animation/tween.h" #include "scene/audio/audio_player.h" #include "scene/gui/box_container.h" @@ -382,6 +388,28 @@ void register_scene_types() { ClassDB::register_class<NavigationMesh>(); ClassDB::register_class<Navigation>(); + ClassDB::register_class<RootMotionView>(); + ClassDB::set_class_enabled("RootMotionView", false); //disabled by default, enabled by editor + + ClassDB::register_class<AnimationTree>(); + ClassDB::register_class<AnimationNode>(); + ClassDB::register_class<AnimationRootNode>(); + ClassDB::register_class<AnimationNodeBlendTree>(); + ClassDB::register_class<AnimationNodeBlendSpace1D>(); + ClassDB::register_class<AnimationNodeBlendSpace2D>(); + ClassDB::register_class<AnimationNodeStateMachine>(); + ClassDB::register_class<AnimationNodeStateMachineTransition>(); + ClassDB::register_class<AnimationNodeOutput>(); + ClassDB::register_class<AnimationNodeOneShot>(); + ClassDB::register_class<AnimationNodeAnimation>(); + ClassDB::register_class<AnimationNodeAdd2>(); + ClassDB::register_class<AnimationNodeAdd3>(); + ClassDB::register_class<AnimationNodeBlend2>(); + ClassDB::register_class<AnimationNodeBlend3>(); + ClassDB::register_class<AnimationNodeTimeScale>(); + ClassDB::register_class<AnimationNodeTimeSeek>(); + ClassDB::register_class<AnimationNodeTransition>(); + OS::get_singleton()->yield(); //may take time to init ClassDB::register_virtual_class<CollisionObject>(); diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 7a1fffaa26..3185fb6768 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -32,6 +32,8 @@ #include "geometry.h" +#define ANIM_MIN_LENGTH 0.001 + bool Animation::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; @@ -54,6 +56,15 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } else if (type == "method") { add_track(TYPE_METHOD); + } else if (type == "bezier") { + + add_track(TYPE_BEZIER); + } else if (type == "audio") { + + add_track(TYPE_AUDIO); + } else if (type == "animation") { + + add_track(TYPE_ANIMATION); } else { return false; @@ -123,8 +134,8 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { int um = d["update"]; if (um < 0) um = 0; - else if (um > 2) - um = 2; + else if (um > 3) + um = 3; vt->update_mode = UpdateMode(um); } @@ -163,7 +174,7 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { return true; - } else { + } else if (track_get_type(track) == TYPE_METHOD) { while (track_get_key_count(track)) track_remove_key(track, 0); //well shouldn't be set anyway @@ -201,6 +212,114 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) { } } } + } else if (track_get_type(track) == TYPE_BEZIER) { + + BezierTrack *bt = static_cast<BezierTrack *>(tracks[track]); + Dictionary d = p_value; + ERR_FAIL_COND_V(!d.has("times"), false); + ERR_FAIL_COND_V(!d.has("points"), false); + + PoolVector<float> times = d["times"]; + PoolRealArray values = d["points"]; + + ERR_FAIL_COND_V(times.size() * 5 != values.size(), false); + + if (times.size()) { + + int valcount = times.size(); + + PoolVector<float>::Read rt = times.read(); + PoolVector<float>::Read rv = values.read(); + + bt->values.resize(valcount); + + for (int i = 0; i < valcount; i++) { + + bt->values[i].time = rt[i]; + bt->values[i].transition = 0; //unused in bezier + bt->values[i].value.value = rv[i * 5 + 0]; + bt->values[i].value.in_handle.x = rv[i * 5 + 1]; + bt->values[i].value.in_handle.y = rv[i * 5 + 2]; + bt->values[i].value.out_handle.x = rv[i * 5 + 3]; + bt->values[i].value.out_handle.y = rv[i * 5 + 4]; + } + } + + return true; + } else if (track_get_type(track) == TYPE_AUDIO) { + + AudioTrack *ad = static_cast<AudioTrack *>(tracks[track]); + Dictionary d = p_value; + ERR_FAIL_COND_V(!d.has("times"), false); + ERR_FAIL_COND_V(!d.has("clips"), false); + + PoolVector<float> times = d["times"]; + Array clips = d["clips"]; + + ERR_FAIL_COND_V(clips.size() != times.size(), false); + + if (times.size()) { + + int valcount = times.size(); + + PoolVector<float>::Read rt = times.read(); + + ad->values.clear(); + + for (int i = 0; i < valcount; i++) { + + Dictionary d = clips[i]; + if (!d.has("start_offset")) + continue; + if (!d.has("end_offset")) + continue; + if (!d.has("stream")) + continue; + + TKey<AudioKey> ak; + ak.time = rt[i]; + ak.value.start_offset = d["start_offset"]; + ak.value.end_offset = d["end_offset"]; + ak.value.stream = d["stream"]; + + ad->values.push_back(ak); + } + } + + return true; + } else if (track_get_type(track) == TYPE_ANIMATION) { + + AnimationTrack *an = static_cast<AnimationTrack *>(tracks[track]); + Dictionary d = p_value; + ERR_FAIL_COND_V(!d.has("times"), false); + ERR_FAIL_COND_V(!d.has("clips"), false); + + PoolVector<float> times = d["times"]; + PoolVector<String> clips = d["clips"]; + + ERR_FAIL_COND_V(clips.size() != times.size(), false); + + if (times.size()) { + + int valcount = times.size(); + + PoolVector<float>::Read rt = times.read(); + PoolVector<String>::Read rc = clips.read(); + + an->values.resize(valcount); + + for (int i = 0; i < valcount; i++) { + + TKey<StringName> ak; + ak.time = rt[i]; + ak.value = rc[i]; + an->values[i] = ak; + } + } + + return true; + } else { + return false; } } else return false; @@ -232,6 +351,9 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { case TYPE_TRANSFORM: r_ret = "transform"; break; case TYPE_VALUE: r_ret = "value"; break; case TYPE_METHOD: r_ret = "method"; break; + case TYPE_BEZIER: r_ret = "bezier"; break; + case TYPE_AUDIO: r_ret = "audio"; break; + case TYPE_ANIMATION: r_ret = "animation"; break; } return true; @@ -329,7 +451,7 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { return true; - } else { + } else if (track_get_type(track) == TYPE_METHOD) { Dictionary d; @@ -368,6 +490,119 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const { r_ret = d; return true; + } else if (track_get_type(track) == TYPE_BEZIER) { + + const BezierTrack *bt = static_cast<const BezierTrack *>(tracks[track]); + + Dictionary d; + + PoolVector<float> key_times; + PoolVector<float> key_points; + + int kk = bt->values.size(); + + key_times.resize(kk); + key_points.resize(kk * 5); + + PoolVector<float>::Write wti = key_times.write(); + PoolVector<float>::Write wpo = key_points.write(); + + int idx = 0; + + const TKey<BezierKey> *vls = bt->values.ptr(); + + for (int i = 0; i < kk; i++) { + + wti[idx] = vls[i].time; + wpo[idx * 5 + 0] = vls[i].value.value; + wpo[idx * 5 + 1] = vls[i].value.in_handle.x; + wpo[idx * 5 + 2] = vls[i].value.in_handle.y; + wpo[idx * 5 + 3] = vls[i].value.out_handle.x; + wpo[idx * 5 + 4] = vls[i].value.out_handle.y; + idx++; + } + + wti = PoolVector<float>::Write(); + wpo = PoolVector<float>::Write(); + + d["times"] = key_times; + d["points"] = key_points; + + r_ret = d; + + return true; + } else if (track_get_type(track) == TYPE_AUDIO) { + + const AudioTrack *ad = static_cast<const AudioTrack *>(tracks[track]); + + Dictionary d; + + PoolVector<float> key_times; + Array clips; + + int kk = ad->values.size(); + + key_times.resize(kk); + + PoolVector<float>::Write wti = key_times.write(); + + int idx = 0; + + const TKey<AudioKey> *vls = ad->values.ptr(); + + for (int i = 0; i < kk; i++) { + + wti[idx] = vls[i].time; + Dictionary clip; + clip["start_offset"] = vls[i].value.start_offset; + clip["end_offset"] = vls[i].value.end_offset; + clip["stream"] = vls[i].value.stream; + clips.push_back(clip); + idx++; + } + + wti = PoolVector<float>::Write(); + + d["times"] = key_times; + d["clips"] = clips; + + r_ret = d; + + return true; + } else if (track_get_type(track) == TYPE_ANIMATION) { + + const AnimationTrack *an = static_cast<const AnimationTrack *>(tracks[track]); + + Dictionary d; + + PoolVector<float> key_times; + PoolVector<String> clips; + + int kk = an->values.size(); + + key_times.resize(kk); + clips.resize(kk); + + PoolVector<float>::Write wti = key_times.write(); + PoolVector<String>::Write wcl = clips.write(); + + const TKey<StringName> *vls = an->values.ptr(); + + for (int i = 0; i < kk; i++) { + + wti[i] = vls[i].time; + wcl[i] = vls[i].value; + } + + wti = PoolVector<float>::Write(); + wcl = PoolVector<String>::Write(); + + d["times"] = key_times; + d["clips"] = clips; + + r_ret = d; + + return true; } } else return false; @@ -412,6 +647,21 @@ int Animation::add_track(TrackType p_type, int p_at_pos) { tracks.insert(p_at_pos, memnew(MethodTrack)); } break; + case TYPE_BEZIER: { + + tracks.insert(p_at_pos, memnew(BezierTrack)); + + } break; + case TYPE_AUDIO: { + + tracks.insert(p_at_pos, memnew(AudioTrack)); + + } break; + case TYPE_ANIMATION: { + + tracks.insert(p_at_pos, memnew(AnimationTrack)); + + } break; default: { ERR_PRINT("Unknown track type"); @@ -446,6 +696,24 @@ void Animation::remove_track(int p_track) { _clear(mt->methods); } break; + case TYPE_BEZIER: { + + BezierTrack *bz = static_cast<BezierTrack *>(t); + _clear(bz->values); + + } break; + case TYPE_AUDIO: { + + AudioTrack *ad = static_cast<AudioTrack *>(t); + _clear(ad->values); + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *an = static_cast<AnimationTrack *>(t); + _clear(an->values); + + } break; } memdelete(t); @@ -642,6 +910,27 @@ void Animation::track_remove_key(int p_track, int p_idx) { mt->methods.remove(p_idx); } break; + case TYPE_BEZIER: { + + BezierTrack *bz = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX(p_idx, bz->values.size()); + bz->values.remove(p_idx); + + } break; + case TYPE_AUDIO: { + + AudioTrack *ad = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX(p_idx, ad->values.size()); + ad->values.remove(p_idx); + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *an = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX(p_idx, an->values.size()); + an->values.remove(p_idx); + + } break; } emit_changed(); @@ -686,6 +975,39 @@ int Animation::track_find_key(int p_track, float p_time, bool p_exact) const { return k; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + int k = _find(bt->values, p_time); + if (k < 0 || k >= bt->values.size()) + return -1; + if (bt->values[k].time != p_time && p_exact) + return -1; + return k; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + int k = _find(at->values, p_time); + if (k < 0 || k >= at->values.size()) + return -1; + if (at->values[k].time != p_time && p_exact) + return -1; + return k; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + int k = _find(at->values, p_time); + if (k < 0 || k >= at->values.size()) + return -1; + if (at->values[k].time != p_time && p_exact) + return -1; + return k; + + } break; } return -1; @@ -748,6 +1070,51 @@ void Animation::track_insert_key(int p_track, float p_time, const Variant &p_key _insert(p_time, mt->methods, k); } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + Array arr = p_key; + ERR_FAIL_COND(arr.size() != 5); + + TKey<BezierKey> k; + k.time = p_time; + k.value.value = arr[0]; + k.value.in_handle.x = arr[1]; + k.value.in_handle.y = arr[2]; + k.value.out_handle.x = arr[3]; + k.value.out_handle.y = arr[4]; + _insert(p_time, bt->values, k); + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + + Dictionary k = p_key; + ERR_FAIL_COND(!k.has("start_offset")); + ERR_FAIL_COND(!k.has("end_offset")); + ERR_FAIL_COND(!k.has("stream")); + + TKey<AudioKey> ak; + ak.time = p_time; + ak.value.start_offset = k["start_offset"]; + ak.value.end_offset = k["end_offset"]; + ak.value.stream = k["stream"]; + _insert(p_time, at->values, ak); + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + TKey<StringName> ak; + ak.time = p_time; + ak.value = p_key; + + _insert(p_time, at->values, ak); + + } break; } emit_changed(); @@ -776,6 +1143,21 @@ int Animation::track_get_key_count(int p_track) const { MethodTrack *mt = static_cast<MethodTrack *>(t); return mt->methods.size(); } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + return bt->values.size(); + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + return at->values.size(); + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + return at->values.size(); + } break; } ERR_FAIL_V(-1); @@ -817,6 +1199,41 @@ Variant Animation::track_get_key_value(int p_track, int p_key_idx) const { return d; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), Variant()); + + Array arr; + arr.resize(5); + arr[0] = bt->values[p_key_idx].value.value; + arr[1] = bt->values[p_key_idx].value.in_handle.x; + arr[2] = bt->values[p_key_idx].value.in_handle.y; + arr[3] = bt->values[p_key_idx].value.out_handle.x; + arr[4] = bt->values[p_key_idx].value.out_handle.y; + return arr; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant()); + + Dictionary k; + k["start_offset"] = at->values[p_key_idx].value.start_offset; + k["end_offset"] = at->values[p_key_idx].value.end_offset; + k["stream"] = at->values[p_key_idx].value.stream; + return k; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), Variant()); + + return at->values[p_key_idx].value; + + } break; } ERR_FAIL_V(Variant()); @@ -849,6 +1266,27 @@ float Animation::track_get_key_time(int p_track, int p_key_idx) const { return mt->methods[p_key_idx].time; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, bt->values.size(), -1); + return bt->values[p_key_idx].time; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1); + return at->values[p_key_idx].time; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + ERR_FAIL_INDEX_V(p_key_idx, at->values.size(), -1); + return at->values[p_key_idx].time; + + } break; } ERR_FAIL_V(-1); @@ -881,6 +1319,18 @@ float Animation::track_get_key_transition(int p_track, int p_key_idx) const { return mt->methods[p_key_idx].transition; } break; + case TYPE_BEZIER: { + + return 1; //bezier does not really use transitions + } break; + case TYPE_AUDIO: { + + return 1; //audio does not really use transitions + } break; + case TYPE_ANIMATION: { + + return 1; //animation does not really use transitions + } break; } ERR_FAIL_V(0); @@ -923,6 +1373,42 @@ void Animation::track_set_key_value(int p_track, int p_key_idx, const Variant &p if (d.has("args")) mt->methods[p_key_idx].params = d["args"]; } break; + case TYPE_BEZIER: { + + BezierTrack *bt = static_cast<BezierTrack *>(t); + ERR_FAIL_INDEX(p_key_idx, bt->values.size()); + + Array arr = p_value; + ERR_FAIL_COND(arr.size() != 5); + + bt->values[p_key_idx].value.value = arr[0]; + bt->values[p_key_idx].value.in_handle.x = arr[1]; + bt->values[p_key_idx].value.in_handle.y = arr[2]; + bt->values[p_key_idx].value.out_handle.x = arr[3]; + bt->values[p_key_idx].value.out_handle.y = arr[4]; + + } break; + case TYPE_AUDIO: { + + AudioTrack *at = static_cast<AudioTrack *>(t); + + Dictionary k = p_value; + ERR_FAIL_COND(!k.has("start_offset")); + ERR_FAIL_COND(!k.has("end_offset")); + ERR_FAIL_COND(!k.has("stream")); + + at->values[p_key_idx].value.start_offset = k["start_offset"]; + at->values[p_key_idx].value.end_offset = k["end_offset"]; + at->values[p_key_idx].value.stream = k["stream"]; + + } break; + case TYPE_ANIMATION: { + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + at->values[p_key_idx].value = p_value; + + } break; } } @@ -953,6 +1439,11 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, float p_tra mt->methods[p_key_idx].transition = p_transition; } break; + case TYPE_BEZIER: + case TYPE_AUDIO: + case TYPE_ANIMATION: { + // they dont use transition + } break; } } @@ -1410,7 +1901,7 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_VALUE); - ERR_FAIL_INDEX(p_mode, 3); + ERR_FAIL_INDEX(p_mode, 4); ValueTrack *vt = static_cast<ValueTrack *>(t); vt->update_mode = p_mode; @@ -1426,6 +1917,161 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const return vt->update_mode; } +template <class T> +void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const { + + if (from_time != length && to_time == length) + to_time = length * 1.01; //include a little more if at the end + + int to = _find(p_array, to_time); + + // can't really send the events == time, will be sent in the next frame. + // if event>=len then it will probably never be requested by the anim player. + + if (to >= 0 && p_array[to].time >= to_time) + to--; + + if (to < 0) + return; // not bother + + int from = _find(p_array, from_time); + + // position in the right first event.+ + if (from < 0 || p_array[from].time < from_time) + from++; + + int max = p_array.size(); + + for (int i = from; i <= to; i++) { + + ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen + p_indices->push_back(i); + } +} + +void Animation::track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const { + + ERR_FAIL_INDEX(p_track, tracks.size()); + const Track *t = tracks[p_track]; + + float from_time = p_time - p_delta; + float to_time = p_time; + + if (from_time > to_time) + SWAP(from_time, to_time); + + if (loop) { + + if (from_time > length || from_time < 0) + from_time = Math::fposmod(from_time, length); + + if (to_time > length || to_time < 0) + to_time = Math::fposmod(to_time, length); + + if (from_time > to_time) { + // handle loop by splitting + + switch (t->type) { + + case TYPE_TRANSFORM: { + + const TransformTrack *tt = static_cast<const TransformTrack *>(t); + _track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices); + _track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices); + + } break; + case TYPE_VALUE: { + + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, from_time, length, p_indices); + _track_get_key_indices_in_range(vt->values, 0, to_time, p_indices); + + } break; + case TYPE_METHOD: { + + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, from_time, length, p_indices); + _track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices); + + } break; + case TYPE_BEZIER: { + + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, from_time, length, p_indices); + _track_get_key_indices_in_range(bz->values, 0, to_time, p_indices); + + } break; + case TYPE_AUDIO: { + + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, from_time, length, p_indices); + _track_get_key_indices_in_range(ad->values, 0, to_time, p_indices); + + } break; + case TYPE_ANIMATION: { + + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, from_time, length, p_indices); + _track_get_key_indices_in_range(an->values, 0, to_time, p_indices); + + } break; + } + return; + } + } else { + + if (from_time < 0) + from_time = 0; + if (from_time > length) + from_time = length; + + if (to_time < 0) + to_time = 0; + if (to_time > length) + to_time = length; + } + + switch (t->type) { + + case TYPE_TRANSFORM: { + + const TransformTrack *tt = static_cast<const TransformTrack *>(t); + _track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices); + + } break; + case TYPE_VALUE: { + + const ValueTrack *vt = static_cast<const ValueTrack *>(t); + _track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices); + + } break; + case TYPE_METHOD: { + + const MethodTrack *mt = static_cast<const MethodTrack *>(t); + _track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices); + + } break; + case TYPE_BEZIER: { + + const BezierTrack *bz = static_cast<const BezierTrack *>(t); + _track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices); + + } break; + case TYPE_AUDIO: { + + const AudioTrack *ad = static_cast<const AudioTrack *>(t); + _track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices); + + } break; + case TYPE_ANIMATION: { + + const AnimationTrack *an = static_cast<const AnimationTrack *>(t); + _track_get_key_indices_in_range(an->values, from_time, to_time, p_indices); + + } break; + } +} + void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const { if (from_time != length && to_time == length) @@ -1527,9 +2173,362 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const { return pm->methods[p_key_idx].method; } +int Animation::bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, -1); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + TKey<BezierKey> k; + k.time = p_time; + k.value.value = p_value; + k.value.in_handle = p_in_handle; + if (k.value.in_handle.x > 0) { + k.value.in_handle.x = 0; + } + k.value.out_handle = p_out_handle; + if (k.value.out_handle.x < 0) { + k.value.out_handle.x = 0; + } + + int key = _insert(p_time, bt->values, k); + + emit_changed(); + + return key; +} + +void Animation::bezier_track_set_key_value(int p_track, int p_index, float p_value) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values[p_index].value.value = p_value; + emit_changed(); +} + +void Animation::bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values[p_index].value.in_handle = p_handle; + if (bt->values[p_index].value.in_handle.x > 0) { + bt->values[p_index].value.in_handle.x = 0; + } + emit_changed(); +} +void Animation::bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_BEZIER); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX(p_index, bt->values.size()); + + bt->values[p_index].value.out_handle = p_handle; + if (bt->values[p_index].value.out_handle.x < 0) { + bt->values[p_index].value.out_handle.x = 0; + } + emit_changed(); +} +float Animation::bezier_track_get_key_value(int p_track, int p_index) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, 0); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), 0); + + return bt->values[p_index].value.value; +} +Vector2 Animation::bezier_track_get_key_in_handle(int p_track, int p_index) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2()); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2()); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2()); + + return bt->values[p_index].value.in_handle; +} +Vector2 Animation::bezier_track_get_key_out_handle(int p_track, int p_index) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), Vector2()); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_BEZIER, Vector2()); + + BezierTrack *bt = static_cast<BezierTrack *>(t); + + ERR_FAIL_INDEX_V(p_index, bt->values.size(), Vector2()); + + return bt->values[p_index].value.out_handle; +} + +static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, const Vector2 &control_1, const Vector2 &control_2, const Vector2 &end) { + /* Formula from Wikipedia article on Bezier curves. */ + real_t omt = (1.0 - t); + real_t omt2 = omt * omt; + real_t omt3 = omt2 * omt; + real_t t2 = t * t; + real_t t3 = t2 * t; + + return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3; +} + +float Animation::bezier_track_interpolate(int p_track, float p_time) const { + //this uses a different interpolation scheme + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + Track *track = tracks[p_track]; + ERR_FAIL_COND_V(track->type != TYPE_BEZIER, 0); + + BezierTrack *bt = static_cast<BezierTrack *>(track); + + int len = _find(bt->values, length) + 1; // try to find last key (there may be more past the end) + + if (len <= 0) { + // (-1 or -2 returned originally) (plus one above) + return 0; + } else if (len == 1) { // one key found (0+1), return it + return bt->values[0].value.value; + } + + int idx = _find(bt->values, p_time); + + ERR_FAIL_COND_V(idx == -2, 0); + + //there really is no looping interpolation on bezier + + if (idx < 0) { + return bt->values[0].value.value; + } + + if (idx >= bt->values.size() - 1) { + return bt->values[bt->values.size() - 1].value.value; + } + + float t = p_time - bt->values[idx].time; + + int iterations = 10; + + float low = 0; + float high = bt->values[idx + 1].time - bt->values[idx].time; + float middle = 0; + + Vector2 start(0, bt->values[idx].value.value); + Vector2 start_out = start + bt->values[idx].value.out_handle; + Vector2 end(high, bt->values[idx + 1].value.value); + Vector2 end_in = end + bt->values[idx + 1].value.in_handle; + + //narrow high and low as much as possible + for (int i = 0; i < iterations; i++) { + + middle = (low + high) / 2; + + Vector2 interp = _bezier_interp(middle, start, start_out, end_in, end); + + if (interp.x < t) { + low = middle; + } else { + high = middle; + } + } + + //interpolate the result: + Vector2 low_pos = _bezier_interp(low, start, start_out, end_in, end); + Vector2 high_pos = _bezier_interp(high, start, start_out, end_in, end); + + float c = (t - low_pos.x) / (high_pos.x - low_pos.x); + + return low_pos.linear_interpolate(high_pos, c).y; +} + +int Animation::audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset, float p_end_offset) { + + print_line("really insert key? "); + ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, -1); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + TKey<AudioKey> k; + k.time = p_time; + k.value.stream = p_stream; + k.value.start_offset = p_start_offset; + if (k.value.start_offset < 0) + k.value.start_offset = 0; + k.value.end_offset = p_end_offset; + if (k.value.end_offset < 0) + k.value.end_offset = 0; + + int key = _insert(p_time, at->values, k); + + emit_changed(); + + return key; +} + +void Animation::audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + at->values[p_key].value.stream = p_stream; + + emit_changed(); +} + +void Animation::audio_track_set_key_start_offset(int p_track, int p_key, float p_offset) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + if (p_offset < 0) + p_offset = 0; + + at->values[p_key].value.start_offset = p_offset; + + emit_changed(); +} + +void Animation::audio_track_set_key_end_offset(int p_track, int p_key, float p_offset) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_AUDIO); + + AudioTrack *at = static_cast<AudioTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + if (p_offset < 0) + p_offset = 0; + + at->values[p_key].value.end_offset = p_offset; + + emit_changed(); +} + +RES Animation::audio_track_get_key_stream(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), RES()); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, RES()); + + const AudioTrack *at = static_cast<const AudioTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), RES()); + + return at->values[p_key].value.stream; +} +float Animation::audio_track_get_key_start_offset(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0); + + const AudioTrack *at = static_cast<const AudioTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), 0); + + return at->values[p_key].value.start_offset; +} +float Animation::audio_track_get_key_end_offset(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), 0); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_AUDIO, 0); + + const AudioTrack *at = static_cast<const AudioTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), 0); + + return at->values[p_key].value.end_offset; +} + +// + +int Animation::animation_track_insert_key(int p_track, float p_time, const StringName &p_animation) { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); + Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, -1); + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + TKey<StringName> k; + k.time = p_time; + k.value = p_animation; + + int key = _insert(p_time, at->values, k); + + emit_changed(); + + return key; +} + +void Animation::animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + Track *t = tracks[p_track]; + ERR_FAIL_COND(t->type != TYPE_ANIMATION); + + AnimationTrack *at = static_cast<AnimationTrack *>(t); + + ERR_FAIL_INDEX(p_key, at->values.size()); + + at->values[p_key].value = p_animation; + + emit_changed(); +} + +StringName Animation::animation_track_get_key_animation(int p_track, int p_key) const { + + ERR_FAIL_INDEX_V(p_track, tracks.size(), StringName()); + const Track *t = tracks[p_track]; + ERR_FAIL_COND_V(t->type != TYPE_ANIMATION, StringName()); + + const AnimationTrack *at = static_cast<const AnimationTrack *>(t); + + ERR_FAIL_INDEX_V(p_key, at->values.size(), StringName()); + + return at->values[p_key].value; +} + void Animation::set_length(float p_length) { - ERR_FAIL_COND(length < 0); + if (p_length < ANIM_MIN_LENGTH) { + p_length = ANIM_MIN_LENGTH; + } length = p_length; emit_changed(); } @@ -1592,6 +2591,16 @@ void Animation::track_move_down(int p_track) { emit_changed(); } +void Animation::track_swap(int p_track, int p_with_track) { + + ERR_FAIL_INDEX(p_track, tracks.size()); + ERR_FAIL_INDEX(p_with_track, tracks.size()); + if (p_track == p_with_track) + return; + SWAP(tracks[p_track], tracks[p_with_track]); + emit_changed(); +} + void Animation::set_step(float p_step) { step = p_step; @@ -1631,6 +2640,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_move_up", "idx"), &Animation::track_move_up); ClassDB::bind_method(D_METHOD("track_move_down", "idx"), &Animation::track_move_down); + ClassDB::bind_method(D_METHOD("track_swap", "idx", "with_idx"), &Animation::track_swap); ClassDB::bind_method(D_METHOD("track_set_imported", "idx", "imported"), &Animation::track_set_imported); ClassDB::bind_method(D_METHOD("track_is_imported", "idx"), &Animation::track_is_imported); @@ -1667,6 +2677,30 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("method_track_get_name", "idx", "key_idx"), &Animation::method_track_get_name); ClassDB::bind_method(D_METHOD("method_track_get_params", "idx", "key_idx"), &Animation::method_track_get_params); + ClassDB::bind_method(D_METHOD("bezier_track_insert_key", "track", "time", "value", "in_handle", "out_handle"), &Animation::bezier_track_insert_key, DEFVAL(Vector2()), DEFVAL(Vector2())); + + ClassDB::bind_method(D_METHOD("bezier_track_set_key_value", "idx", "key_idx", "value"), &Animation::bezier_track_set_key_value); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_in_handle", "idx", "key_idx", "in_handle"), &Animation::bezier_track_set_key_in_handle); + ClassDB::bind_method(D_METHOD("bezier_track_set_key_out_handle", "idx", "key_idx", "out_handle"), &Animation::bezier_track_set_key_out_handle); + + ClassDB::bind_method(D_METHOD("bezier_track_get_key_value", "idx", "key_idx"), &Animation::bezier_track_get_key_value); + ClassDB::bind_method(D_METHOD("bezier_track_get_key_in_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_in_handle); + ClassDB::bind_method(D_METHOD("bezier_track_get_key_out_handle", "idx", "key_idx"), &Animation::bezier_track_get_key_out_handle); + + ClassDB::bind_method(D_METHOD("bezier_track_interpolate", "track", "time"), &Animation::bezier_track_interpolate); + + ClassDB::bind_method(D_METHOD("audio_track_insert_key", "track", "time", "stream", "start_offset", "end_offset"), &Animation::audio_track_insert_key, DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("audio_track_set_key_stream", "idx", "key_idx", "stream"), &Animation::audio_track_set_key_stream); + ClassDB::bind_method(D_METHOD("audio_track_set_key_start_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_start_offset); + ClassDB::bind_method(D_METHOD("audio_track_set_key_end_offset", "idx", "key_idx", "offset"), &Animation::audio_track_set_key_end_offset); + ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "idx", "key_idx"), &Animation::audio_track_get_key_stream); + ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "idx", "key_idx"), &Animation::audio_track_get_key_start_offset); + ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "idx", "key_idx"), &Animation::audio_track_get_key_end_offset); + + ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track", "time", "animation"), &Animation::animation_track_insert_key); + ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation); + ClassDB::bind_method(D_METHOD("animation_track_get_key_animation", "idx", "key_idx"), &Animation::animation_track_get_key_animation); + ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length); ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length); @@ -1686,6 +2720,9 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(TYPE_VALUE); BIND_ENUM_CONSTANT(TYPE_TRANSFORM); BIND_ENUM_CONSTANT(TYPE_METHOD); + BIND_ENUM_CONSTANT(TYPE_BEZIER); + BIND_ENUM_CONSTANT(TYPE_AUDIO); + BIND_ENUM_CONSTANT(TYPE_ANIMATION); BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST); BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR); @@ -1694,6 +2731,7 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS); BIND_ENUM_CONSTANT(UPDATE_DISCRETE); BIND_ENUM_CONSTANT(UPDATE_TRIGGER); + BIND_ENUM_CONSTANT(UPDATE_CAPTURE); } void Animation::clear() { diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 73691a69f2..a41e6ea5d7 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -45,6 +45,9 @@ public: TYPE_VALUE, ///< Set a value in a property, can be interpolated. TYPE_TRANSFORM, ///< Transform a node or a bone. TYPE_METHOD, ///< Call any method on a specific node. + TYPE_BEZIER, ///< Bezier curve + TYPE_AUDIO, + TYPE_ANIMATION, }; enum InterpolationType { @@ -57,6 +60,7 @@ public: UPDATE_CONTINUOUS, UPDATE_DISCRETE, UPDATE_TRIGGER, + UPDATE_CAPTURE, }; @@ -137,6 +141,55 @@ private: MethodTrack() { type = TYPE_METHOD; } }; + /* BEZIER TRACK */ + + struct BezierKey { + Vector2 in_handle; //relative (x always <0) + Vector2 out_handle; //relative (x always >0) + float value; + }; + + struct BezierTrack : public Track { + + Vector<TKey<BezierKey> > values; + + BezierTrack() { + type = TYPE_BEZIER; + } + }; + + /* AUDIO TRACK */ + + struct AudioKey { + RES stream; + float start_offset; //offset from start + float end_offset; //offset from end, if 0 then full length or infinite + AudioKey() { + start_offset = 0; + end_offset = 0; + } + }; + + struct AudioTrack : public Track { + + Vector<TKey<AudioKey> > values; + + AudioTrack() { + type = TYPE_AUDIO; + } + }; + + /* AUDIO TRACK */ + + struct AnimationTrack : public Track { + + Vector<TKey<StringName> > values; + + AnimationTrack() { + type = TYPE_ANIMATION; + } + }; + Vector<Track *> tracks; /* @@ -168,6 +221,9 @@ private: template <class T> _FORCE_INLINE_ T _interpolate(const Vector<TKey<T> > &p_keys, float p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const; + template <class T> + _FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, float from_time, float to_time, List<int> *p_indices) const; + _FORCE_INLINE_ void _value_track_get_key_indices_in_range(const ValueTrack *vt, float from_time, float to_time, List<int> *p_indices) const; _FORCE_INLINE_ void _method_track_get_key_indices_in_range(const MethodTrack *mt, float from_time, float to_time, List<int> *p_indices) const; @@ -238,6 +294,7 @@ public: void track_move_up(int p_track); void track_move_down(int p_track); + void track_swap(int p_track, int p_with_track); void track_set_imported(int p_track, bool p_imported); bool track_is_imported(int p_track) const; @@ -245,7 +302,6 @@ public: void track_set_enabled(int p_track, bool p_enabled); bool track_is_enabled(int p_track) const; - int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3()); void track_insert_key(int p_track, float p_time, const Variant &p_key, float p_transition = 1); void track_set_key_transition(int p_track, int p_key_idx, float p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); @@ -257,10 +313,33 @@ public: float track_get_key_time(int p_track, int p_key_idx) const; float track_get_key_transition(int p_track, int p_key_idx) const; + int transform_track_insert_key(int p_track, float p_time, const Vector3 p_loc, const Quat &p_rot = Quat(), const Vector3 &p_scale = Vector3()); Error transform_track_get_key(int p_track, int p_key, Vector3 *r_loc, Quat *r_rot, Vector3 *r_scale) const; void track_set_interpolation_type(int p_track, InterpolationType p_interp); InterpolationType track_get_interpolation_type(int p_track) const; + int bezier_track_insert_key(int p_track, float p_time, float p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle); + void bezier_track_set_key_value(int p_track, int p_index, float p_value); + void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle); + void bezier_track_set_key_out_handle(int p_track, int p_index, const Vector2 &p_handle); + float bezier_track_get_key_value(int p_track, int p_index) const; + Vector2 bezier_track_get_key_in_handle(int p_track, int p_index) const; + Vector2 bezier_track_get_key_out_handle(int p_track, int p_index) const; + + float bezier_track_interpolate(int p_track, float p_time) const; + + int audio_track_insert_key(int p_track, float p_time, const RES &p_stream, float p_start_offset = 0, float p_end_offset = 0); + void audio_track_set_key_stream(int p_track, int p_key, const RES &p_stream); + void audio_track_set_key_start_offset(int p_track, int p_key, float p_offset); + void audio_track_set_key_end_offset(int p_track, int p_key, float p_offset); + RES audio_track_get_key_stream(int p_track, int p_key) const; + float audio_track_get_key_start_offset(int p_track, int p_key) const; + float audio_track_get_key_end_offset(int p_track, int p_key) const; + + int animation_track_insert_key(int p_track, float p_time, const StringName &p_animation); + void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation); + StringName animation_track_get_key_animation(int p_track, int p_key) const; + void track_set_interpolation_loop_wrap(int p_track, bool p_enable); bool track_get_interpolation_loop_wrap(int p_track) const; @@ -277,6 +356,8 @@ public: void copy_track(int p_track, Ref<Animation> p_to_animation); + void track_get_key_indices_in_range(int p_track, float p_time, float p_delta, List<int> *p_indices) const; + void set_length(float p_length); float get_length() const; diff --git a/scene/resources/default_theme/arrow_down.png b/scene/resources/default_theme/arrow_down.png Binary files differindex fc837d120a..bfb87a4761 100644 --- a/scene/resources/default_theme/arrow_down.png +++ b/scene/resources/default_theme/arrow_down.png diff --git a/scene/resources/default_theme/arrow_right.png b/scene/resources/default_theme/arrow_right.png Binary files differindex ebe6e26ace..1e4c8e5529 100644 --- a/scene/resources/default_theme/arrow_right.png +++ b/scene/resources/default_theme/arrow_right.png diff --git a/scene/resources/default_theme/background.png b/scene/resources/default_theme/background.png Binary files differindex 03cfab1de3..6c5f43e3ce 100644 --- a/scene/resources/default_theme/background.png +++ b/scene/resources/default_theme/background.png diff --git a/scene/resources/default_theme/base_green.png b/scene/resources/default_theme/base_green.png Binary files differindex d03d6f08d8..03a5b313d7 100644 --- a/scene/resources/default_theme/base_green.png +++ b/scene/resources/default_theme/base_green.png diff --git a/scene/resources/default_theme/button_disabled.png b/scene/resources/default_theme/button_disabled.png Binary files differindex d75e76989d..708748dfe9 100644 --- a/scene/resources/default_theme/button_disabled.png +++ b/scene/resources/default_theme/button_disabled.png diff --git a/scene/resources/default_theme/button_focus.png b/scene/resources/default_theme/button_focus.png Binary files differindex 44e354be95..70e16b953b 100644 --- a/scene/resources/default_theme/button_focus.png +++ b/scene/resources/default_theme/button_focus.png diff --git a/scene/resources/default_theme/button_hover.png b/scene/resources/default_theme/button_hover.png Binary files differindex 6e609f435f..ef226e3caf 100644 --- a/scene/resources/default_theme/button_hover.png +++ b/scene/resources/default_theme/button_hover.png diff --git a/scene/resources/default_theme/button_normal.png b/scene/resources/default_theme/button_normal.png Binary files differindex 92482aaf28..7d0bd16221 100644 --- a/scene/resources/default_theme/button_normal.png +++ b/scene/resources/default_theme/button_normal.png diff --git a/scene/resources/default_theme/checked.png b/scene/resources/default_theme/checked.png Binary files differindex 93e291a29e..bde031b6a2 100644 --- a/scene/resources/default_theme/checked.png +++ b/scene/resources/default_theme/checked.png diff --git a/scene/resources/default_theme/checker_bg.png b/scene/resources/default_theme/checker_bg.png Binary files differindex f58dfed29c..3eff2f0e08 100644 --- a/scene/resources/default_theme/checker_bg.png +++ b/scene/resources/default_theme/checker_bg.png diff --git a/scene/resources/default_theme/close.png b/scene/resources/default_theme/close.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/close.png +++ b/scene/resources/default_theme/close.png diff --git a/scene/resources/default_theme/close_hl.png b/scene/resources/default_theme/close_hl.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/close_hl.png +++ b/scene/resources/default_theme/close_hl.png diff --git a/scene/resources/default_theme/color_picker_sample.png b/scene/resources/default_theme/color_picker_sample.png Binary files differindex b145a3e384..e6ec28d307 100644 --- a/scene/resources/default_theme/color_picker_sample.png +++ b/scene/resources/default_theme/color_picker_sample.png diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 3ea856541e..702953fa40 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -844,7 +844,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("separation", "HBoxContainer", 4 * scale); theme->set_constant("separation", "VBoxContainer", 4 * scale); - theme->set_constant("margin_left", "MarginContainer", 8 * scale); + theme->set_constant("margin_left", "MarginContainer", 0 * scale); theme->set_constant("margin_top", "MarginContainer", 0 * scale); theme->set_constant("margin_right", "MarginContainer", 0 * scale); theme->set_constant("margin_bottom", "MarginContainer", 0 * scale); @@ -874,6 +874,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5)); theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05)); theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2)); + theme->set_color("activity", "GraphEdit", Color(1, 1, 1)); theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale); theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale); diff --git a/scene/resources/default_theme/dosfont.png b/scene/resources/default_theme/dosfont.png Binary files differindex 814d2e9060..e2739b94ea 100644 --- a/scene/resources/default_theme/dosfont.png +++ b/scene/resources/default_theme/dosfont.png diff --git a/scene/resources/default_theme/dropdown.png b/scene/resources/default_theme/dropdown.png Binary files differindex 3a6a2ed778..b5d9ffbbb4 100644 --- a/scene/resources/default_theme/dropdown.png +++ b/scene/resources/default_theme/dropdown.png diff --git a/scene/resources/default_theme/error_icon.png b/scene/resources/default_theme/error_icon.png Binary files differindex f291362350..7741d00749 100644 --- a/scene/resources/default_theme/error_icon.png +++ b/scene/resources/default_theme/error_icon.png diff --git a/scene/resources/default_theme/focus.png b/scene/resources/default_theme/focus.png Binary files differindex 5d37028f2d..f51ea89e8f 100644 --- a/scene/resources/default_theme/focus.png +++ b/scene/resources/default_theme/focus.png diff --git a/scene/resources/default_theme/frame_focus.png b/scene/resources/default_theme/frame_focus.png Binary files differindex 9170db38ed..1b24ba47d8 100644 --- a/scene/resources/default_theme/frame_focus.png +++ b/scene/resources/default_theme/frame_focus.png diff --git a/scene/resources/default_theme/full_panel_bg.png b/scene/resources/default_theme/full_panel_bg.png Binary files differindex 7f02dc7259..85f753cc13 100644 --- a/scene/resources/default_theme/full_panel_bg.png +++ b/scene/resources/default_theme/full_panel_bg.png diff --git a/scene/resources/default_theme/graph_node_breakpoint.png b/scene/resources/default_theme/graph_node_breakpoint.png Binary files differindex 0e36f31bd4..e18c6f42e1 100644 --- a/scene/resources/default_theme/graph_node_breakpoint.png +++ b/scene/resources/default_theme/graph_node_breakpoint.png diff --git a/scene/resources/default_theme/graph_node_close.png b/scene/resources/default_theme/graph_node_close.png Binary files differindex 144a8b9c4c..5c962ae1c6 100644 --- a/scene/resources/default_theme/graph_node_close.png +++ b/scene/resources/default_theme/graph_node_close.png diff --git a/scene/resources/default_theme/graph_node_comment.png b/scene/resources/default_theme/graph_node_comment.png Binary files differindex f2d6daa259..cdec1d1eac 100644 --- a/scene/resources/default_theme/graph_node_comment.png +++ b/scene/resources/default_theme/graph_node_comment.png diff --git a/scene/resources/default_theme/graph_node_comment_focus.png b/scene/resources/default_theme/graph_node_comment_focus.png Binary files differindex a4b7b5a618..472a6b6f53 100644 --- a/scene/resources/default_theme/graph_node_comment_focus.png +++ b/scene/resources/default_theme/graph_node_comment_focus.png diff --git a/scene/resources/default_theme/graph_node_default.png b/scene/resources/default_theme/graph_node_default.png Binary files differindex e3a220301f..359bbdc205 100644 --- a/scene/resources/default_theme/graph_node_default.png +++ b/scene/resources/default_theme/graph_node_default.png diff --git a/scene/resources/default_theme/graph_node_default_focus.png b/scene/resources/default_theme/graph_node_default_focus.png Binary files differindex 9972b07593..204dd16ac0 100644 --- a/scene/resources/default_theme/graph_node_default_focus.png +++ b/scene/resources/default_theme/graph_node_default_focus.png diff --git a/scene/resources/default_theme/graph_node_position.png b/scene/resources/default_theme/graph_node_position.png Binary files differindex 7ec15e2ff4..24c2759be6 100644 --- a/scene/resources/default_theme/graph_node_position.png +++ b/scene/resources/default_theme/graph_node_position.png diff --git a/scene/resources/default_theme/graph_node_selected.png b/scene/resources/default_theme/graph_node_selected.png Binary files differindex f76c9703dd..cc4eb7f753 100644 --- a/scene/resources/default_theme/graph_node_selected.png +++ b/scene/resources/default_theme/graph_node_selected.png diff --git a/scene/resources/default_theme/graph_port.png b/scene/resources/default_theme/graph_port.png Binary files differindex 9d5082cfdb..f33ae3baf3 100644 --- a/scene/resources/default_theme/graph_port.png +++ b/scene/resources/default_theme/graph_port.png diff --git a/scene/resources/default_theme/hseparator.png b/scene/resources/default_theme/hseparator.png Binary files differindex 99609ac118..d4fd71ace5 100644 --- a/scene/resources/default_theme/hseparator.png +++ b/scene/resources/default_theme/hseparator.png diff --git a/scene/resources/default_theme/hslider_bg.png b/scene/resources/default_theme/hslider_bg.png Binary files differindex 9c2a2df62a..b402bd370d 100644 --- a/scene/resources/default_theme/hslider_bg.png +++ b/scene/resources/default_theme/hslider_bg.png diff --git a/scene/resources/default_theme/hslider_grabber.png b/scene/resources/default_theme/hslider_grabber.png Binary files differindex 2acd33879a..d273b491ee 100644 --- a/scene/resources/default_theme/hslider_grabber.png +++ b/scene/resources/default_theme/hslider_grabber.png diff --git a/scene/resources/default_theme/hslider_grabber_disabled.png b/scene/resources/default_theme/hslider_grabber_disabled.png Binary files differindex 0d75182b8f..dddd1a468e 100644 --- a/scene/resources/default_theme/hslider_grabber_disabled.png +++ b/scene/resources/default_theme/hslider_grabber_disabled.png diff --git a/scene/resources/default_theme/hslider_grabber_hl.png b/scene/resources/default_theme/hslider_grabber_hl.png Binary files differindex f8a011e64b..e3defb3610 100644 --- a/scene/resources/default_theme/hslider_grabber_hl.png +++ b/scene/resources/default_theme/hslider_grabber_hl.png diff --git a/scene/resources/default_theme/hslider_tick.png b/scene/resources/default_theme/hslider_tick.png Binary files differindex f7afd78529..1ba19c37a1 100644 --- a/scene/resources/default_theme/hslider_tick.png +++ b/scene/resources/default_theme/hslider_tick.png diff --git a/scene/resources/default_theme/hsplit_bg.png b/scene/resources/default_theme/hsplit_bg.png Binary files differindex 7dd1d48b29..a5749f6d5c 100644 --- a/scene/resources/default_theme/hsplit_bg.png +++ b/scene/resources/default_theme/hsplit_bg.png diff --git a/scene/resources/default_theme/hsplitter.png b/scene/resources/default_theme/hsplitter.png Binary files differindex 71a3914d7e..2287753c9d 100644 --- a/scene/resources/default_theme/hsplitter.png +++ b/scene/resources/default_theme/hsplitter.png diff --git a/scene/resources/default_theme/icon_add.png b/scene/resources/default_theme/icon_add.png Binary files differindex fa675045bc..eccb69b363 100644 --- a/scene/resources/default_theme/icon_add.png +++ b/scene/resources/default_theme/icon_add.png diff --git a/scene/resources/default_theme/icon_close.png b/scene/resources/default_theme/icon_close.png Binary files differindex 5ac6357dcd..4d4ac4a551 100644 --- a/scene/resources/default_theme/icon_close.png +++ b/scene/resources/default_theme/icon_close.png diff --git a/scene/resources/default_theme/icon_color_pick.png b/scene/resources/default_theme/icon_color_pick.png Binary files differindex 15679a9558..46953febb8 100644 --- a/scene/resources/default_theme/icon_color_pick.png +++ b/scene/resources/default_theme/icon_color_pick.png diff --git a/scene/resources/default_theme/icon_folder.png b/scene/resources/default_theme/icon_folder.png Binary files differindex cc05e98ebb..d1b308e88d 100644 --- a/scene/resources/default_theme/icon_folder.png +++ b/scene/resources/default_theme/icon_folder.png diff --git a/scene/resources/default_theme/icon_parent_folder.png b/scene/resources/default_theme/icon_parent_folder.png Binary files differindex 47fee1ad81..35d218722e 100644 --- a/scene/resources/default_theme/icon_parent_folder.png +++ b/scene/resources/default_theme/icon_parent_folder.png diff --git a/scene/resources/default_theme/icon_play.png b/scene/resources/default_theme/icon_play.png Binary files differindex 864e4e4fb9..b9ed6e6d5b 100644 --- a/scene/resources/default_theme/icon_play.png +++ b/scene/resources/default_theme/icon_play.png diff --git a/scene/resources/default_theme/icon_reload.png b/scene/resources/default_theme/icon_reload.png Binary files differindex 9303fabb9c..bec5f3f4f9 100644 --- a/scene/resources/default_theme/icon_reload.png +++ b/scene/resources/default_theme/icon_reload.png diff --git a/scene/resources/default_theme/icon_snap_grid.png b/scene/resources/default_theme/icon_snap_grid.png Binary files differindex 44db9bdfdc..0680317d86 100644 --- a/scene/resources/default_theme/icon_snap_grid.png +++ b/scene/resources/default_theme/icon_snap_grid.png diff --git a/scene/resources/default_theme/icon_stop.png b/scene/resources/default_theme/icon_stop.png Binary files differindex 1f194d0e14..0c1371ceb9 100644 --- a/scene/resources/default_theme/icon_stop.png +++ b/scene/resources/default_theme/icon_stop.png diff --git a/scene/resources/default_theme/icon_zoom_less.png b/scene/resources/default_theme/icon_zoom_less.png Binary files differindex 888ddc995d..03119c60ca 100644 --- a/scene/resources/default_theme/icon_zoom_less.png +++ b/scene/resources/default_theme/icon_zoom_less.png diff --git a/scene/resources/default_theme/icon_zoom_more.png b/scene/resources/default_theme/icon_zoom_more.png Binary files differindex fa675045bc..31467ec3de 100644 --- a/scene/resources/default_theme/icon_zoom_more.png +++ b/scene/resources/default_theme/icon_zoom_more.png diff --git a/scene/resources/default_theme/icon_zoom_reset.png b/scene/resources/default_theme/icon_zoom_reset.png Binary files differindex 953ae47d24..cac68c09fa 100644 --- a/scene/resources/default_theme/icon_zoom_reset.png +++ b/scene/resources/default_theme/icon_zoom_reset.png diff --git a/scene/resources/default_theme/line_edit.png b/scene/resources/default_theme/line_edit.png Binary files differindex bf2b91f1be..2b0c506f34 100644 --- a/scene/resources/default_theme/line_edit.png +++ b/scene/resources/default_theme/line_edit.png diff --git a/scene/resources/default_theme/line_edit_disabled.png b/scene/resources/default_theme/line_edit_disabled.png Binary files differindex a0fa505e4c..69d78febd9 100644 --- a/scene/resources/default_theme/line_edit_disabled.png +++ b/scene/resources/default_theme/line_edit_disabled.png diff --git a/scene/resources/default_theme/line_edit_focus.png b/scene/resources/default_theme/line_edit_focus.png Binary files differindex e66d7b60e3..1d74b74068 100644 --- a/scene/resources/default_theme/line_edit_focus.png +++ b/scene/resources/default_theme/line_edit_focus.png diff --git a/scene/resources/default_theme/logo.png b/scene/resources/default_theme/logo.png Binary files differindex 2161402438..d0ef9d8aa7 100644 --- a/scene/resources/default_theme/logo.png +++ b/scene/resources/default_theme/logo.png diff --git a/scene/resources/default_theme/mini_checkerboard.png b/scene/resources/default_theme/mini_checkerboard.png Binary files differindex 3e53183847..d8279bda80 100644 --- a/scene/resources/default_theme/mini_checkerboard.png +++ b/scene/resources/default_theme/mini_checkerboard.png diff --git a/scene/resources/default_theme/option_arrow.png b/scene/resources/default_theme/option_arrow.png Binary files differindex 007de16bfa..40590fd60a 100644 --- a/scene/resources/default_theme/option_arrow.png +++ b/scene/resources/default_theme/option_arrow.png diff --git a/scene/resources/default_theme/option_button_disabled.png b/scene/resources/default_theme/option_button_disabled.png Binary files differindex ce727d56e1..1961b673cd 100644 --- a/scene/resources/default_theme/option_button_disabled.png +++ b/scene/resources/default_theme/option_button_disabled.png diff --git a/scene/resources/default_theme/option_button_focus.png b/scene/resources/default_theme/option_button_focus.png Binary files differindex c76d91287e..402670f9a2 100644 --- a/scene/resources/default_theme/option_button_focus.png +++ b/scene/resources/default_theme/option_button_focus.png diff --git a/scene/resources/default_theme/option_button_hover.png b/scene/resources/default_theme/option_button_hover.png Binary files differindex fd1e987ceb..826fe1c9ca 100644 --- a/scene/resources/default_theme/option_button_hover.png +++ b/scene/resources/default_theme/option_button_hover.png diff --git a/scene/resources/default_theme/option_button_normal.png b/scene/resources/default_theme/option_button_normal.png Binary files differindex 9d7fb98d1c..2dadb40338 100644 --- a/scene/resources/default_theme/option_button_normal.png +++ b/scene/resources/default_theme/option_button_normal.png diff --git a/scene/resources/default_theme/option_button_pressed.png b/scene/resources/default_theme/option_button_pressed.png Binary files differindex 28b1d93468..68796f9d85 100644 --- a/scene/resources/default_theme/option_button_pressed.png +++ b/scene/resources/default_theme/option_button_pressed.png diff --git a/scene/resources/default_theme/panel_bg.png b/scene/resources/default_theme/panel_bg.png Binary files differindex 320819ad6d..b496e2177e 100644 --- a/scene/resources/default_theme/panel_bg.png +++ b/scene/resources/default_theme/panel_bg.png diff --git a/scene/resources/default_theme/popup_bg.png b/scene/resources/default_theme/popup_bg.png Binary files differindex 63f5994441..023029f936 100644 --- a/scene/resources/default_theme/popup_bg.png +++ b/scene/resources/default_theme/popup_bg.png diff --git a/scene/resources/default_theme/popup_bg_disabled.png b/scene/resources/default_theme/popup_bg_disabled.png Binary files differindex 611d949380..8eab5f27bc 100644 --- a/scene/resources/default_theme/popup_bg_disabled.png +++ b/scene/resources/default_theme/popup_bg_disabled.png diff --git a/scene/resources/default_theme/popup_checked.png b/scene/resources/default_theme/popup_checked.png Binary files differindex a24e0543c0..b7b05640e1 100644 --- a/scene/resources/default_theme/popup_checked.png +++ b/scene/resources/default_theme/popup_checked.png diff --git a/scene/resources/default_theme/popup_hover.png b/scene/resources/default_theme/popup_hover.png Binary files differindex 85d4e48475..bdb6ae8bd0 100644 --- a/scene/resources/default_theme/popup_hover.png +++ b/scene/resources/default_theme/popup_hover.png diff --git a/scene/resources/default_theme/popup_unchecked.png b/scene/resources/default_theme/popup_unchecked.png Binary files differindex c1137e6fbf..ff922335c3 100644 --- a/scene/resources/default_theme/popup_unchecked.png +++ b/scene/resources/default_theme/popup_unchecked.png diff --git a/scene/resources/default_theme/popup_window.png b/scene/resources/default_theme/popup_window.png Binary files differindex 59362a8ffd..174a29ef45 100644 --- a/scene/resources/default_theme/popup_window.png +++ b/scene/resources/default_theme/popup_window.png diff --git a/scene/resources/default_theme/progress_bar.png b/scene/resources/default_theme/progress_bar.png Binary files differindex bf81e3adea..057557e567 100644 --- a/scene/resources/default_theme/progress_bar.png +++ b/scene/resources/default_theme/progress_bar.png diff --git a/scene/resources/default_theme/progress_fill.png b/scene/resources/default_theme/progress_fill.png Binary files differindex 3a34dfdda6..e39bb2a021 100644 --- a/scene/resources/default_theme/progress_fill.png +++ b/scene/resources/default_theme/progress_fill.png diff --git a/scene/resources/default_theme/radio_checked.png b/scene/resources/default_theme/radio_checked.png Binary files differindex 95d472022f..0ce575c15f 100644 --- a/scene/resources/default_theme/radio_checked.png +++ b/scene/resources/default_theme/radio_checked.png diff --git a/scene/resources/default_theme/radio_unchecked.png b/scene/resources/default_theme/radio_unchecked.png Binary files differindex 7f0535c3a4..fe5bcf6ab1 100644 --- a/scene/resources/default_theme/radio_unchecked.png +++ b/scene/resources/default_theme/radio_unchecked.png diff --git a/scene/resources/default_theme/reference_border.png b/scene/resources/default_theme/reference_border.png Binary files differindex 96219676bf..6a680f393c 100644 --- a/scene/resources/default_theme/reference_border.png +++ b/scene/resources/default_theme/reference_border.png diff --git a/scene/resources/default_theme/scroll_bg.png b/scene/resources/default_theme/scroll_bg.png Binary files differindex cefadb2c08..fb151a48b1 100644 --- a/scene/resources/default_theme/scroll_bg.png +++ b/scene/resources/default_theme/scroll_bg.png diff --git a/scene/resources/default_theme/scroll_button_down.png b/scene/resources/default_theme/scroll_button_down.png Binary files differindex caeac9b286..1df4ef5b6b 100644 --- a/scene/resources/default_theme/scroll_button_down.png +++ b/scene/resources/default_theme/scroll_button_down.png diff --git a/scene/resources/default_theme/scroll_button_down_hl.png b/scene/resources/default_theme/scroll_button_down_hl.png Binary files differindex 48036e0297..ba79087393 100644 --- a/scene/resources/default_theme/scroll_button_down_hl.png +++ b/scene/resources/default_theme/scroll_button_down_hl.png diff --git a/scene/resources/default_theme/scroll_button_left.png b/scene/resources/default_theme/scroll_button_left.png Binary files differindex 3b50938d97..e430cb4673 100644 --- a/scene/resources/default_theme/scroll_button_left.png +++ b/scene/resources/default_theme/scroll_button_left.png diff --git a/scene/resources/default_theme/scroll_button_left_hl.png b/scene/resources/default_theme/scroll_button_left_hl.png Binary files differindex b3d348c24f..2a6ef17a34 100644 --- a/scene/resources/default_theme/scroll_button_left_hl.png +++ b/scene/resources/default_theme/scroll_button_left_hl.png diff --git a/scene/resources/default_theme/scroll_button_right.png b/scene/resources/default_theme/scroll_button_right.png Binary files differindex 1c622a41ad..4f61687aa4 100644 --- a/scene/resources/default_theme/scroll_button_right.png +++ b/scene/resources/default_theme/scroll_button_right.png diff --git a/scene/resources/default_theme/scroll_button_right_hl.png b/scene/resources/default_theme/scroll_button_right_hl.png Binary files differindex 108796ca02..10e2722509 100644 --- a/scene/resources/default_theme/scroll_button_right_hl.png +++ b/scene/resources/default_theme/scroll_button_right_hl.png diff --git a/scene/resources/default_theme/scroll_button_up.png b/scene/resources/default_theme/scroll_button_up.png Binary files differindex 2c8238ae4c..f425412f50 100644 --- a/scene/resources/default_theme/scroll_button_up.png +++ b/scene/resources/default_theme/scroll_button_up.png diff --git a/scene/resources/default_theme/scroll_button_up_hl.png b/scene/resources/default_theme/scroll_button_up_hl.png Binary files differindex 4283bd114a..615a236c52 100644 --- a/scene/resources/default_theme/scroll_button_up_hl.png +++ b/scene/resources/default_theme/scroll_button_up_hl.png diff --git a/scene/resources/default_theme/scroll_grabber.png b/scene/resources/default_theme/scroll_grabber.png Binary files differindex 1d625a9b7b..732725a28f 100644 --- a/scene/resources/default_theme/scroll_grabber.png +++ b/scene/resources/default_theme/scroll_grabber.png diff --git a/scene/resources/default_theme/scroll_grabber_hl.png b/scene/resources/default_theme/scroll_grabber_hl.png Binary files differindex 99eb24b7e7..006cfa3361 100644 --- a/scene/resources/default_theme/scroll_grabber_hl.png +++ b/scene/resources/default_theme/scroll_grabber_hl.png diff --git a/scene/resources/default_theme/scroll_grabber_pressed.png b/scene/resources/default_theme/scroll_grabber_pressed.png Binary files differindex a46d242ddd..f4886158fa 100644 --- a/scene/resources/default_theme/scroll_grabber_pressed.png +++ b/scene/resources/default_theme/scroll_grabber_pressed.png diff --git a/scene/resources/default_theme/selection.png b/scene/resources/default_theme/selection.png Binary files differindex 501877a8b4..7d1c985b35 100644 --- a/scene/resources/default_theme/selection.png +++ b/scene/resources/default_theme/selection.png diff --git a/scene/resources/default_theme/selection_oof.png b/scene/resources/default_theme/selection_oof.png Binary files differindex 9594fe0913..2da0538389 100644 --- a/scene/resources/default_theme/selection_oof.png +++ b/scene/resources/default_theme/selection_oof.png diff --git a/scene/resources/default_theme/spinbox_updown.png b/scene/resources/default_theme/spinbox_updown.png Binary files differindex b40b1e9fd2..74fab19f34 100644 --- a/scene/resources/default_theme/spinbox_updown.png +++ b/scene/resources/default_theme/spinbox_updown.png diff --git a/scene/resources/default_theme/submenu.png b/scene/resources/default_theme/submenu.png Binary files differindex ec727eecf1..8f7de446d4 100644 --- a/scene/resources/default_theme/submenu.png +++ b/scene/resources/default_theme/submenu.png diff --git a/scene/resources/default_theme/tab.png b/scene/resources/default_theme/tab.png Binary files differindex 3e4d792a48..895daa65e2 100644 --- a/scene/resources/default_theme/tab.png +++ b/scene/resources/default_theme/tab.png diff --git a/scene/resources/default_theme/tab_behind.png b/scene/resources/default_theme/tab_behind.png Binary files differindex 12f07c3a91..2803d9db65 100644 --- a/scene/resources/default_theme/tab_behind.png +++ b/scene/resources/default_theme/tab_behind.png diff --git a/scene/resources/default_theme/tab_close.png b/scene/resources/default_theme/tab_close.png Binary files differindex 20d9b5c810..af2775a132 100644 --- a/scene/resources/default_theme/tab_close.png +++ b/scene/resources/default_theme/tab_close.png diff --git a/scene/resources/default_theme/tab_container_bg.png b/scene/resources/default_theme/tab_container_bg.png Binary files differindex 92482aaf28..7d0bd16221 100644 --- a/scene/resources/default_theme/tab_container_bg.png +++ b/scene/resources/default_theme/tab_container_bg.png diff --git a/scene/resources/default_theme/tab_current.png b/scene/resources/default_theme/tab_current.png Binary files differindex 7289e032da..520d147217 100644 --- a/scene/resources/default_theme/tab_current.png +++ b/scene/resources/default_theme/tab_current.png diff --git a/scene/resources/default_theme/tab_menu.png b/scene/resources/default_theme/tab_menu.png Binary files differindex 148b64b8aa..fa4421a28a 100644 --- a/scene/resources/default_theme/tab_menu.png +++ b/scene/resources/default_theme/tab_menu.png diff --git a/scene/resources/default_theme/tab_menu_hl.png b/scene/resources/default_theme/tab_menu_hl.png Binary files differindex 148b64b8aa..fa4421a28a 100644 --- a/scene/resources/default_theme/tab_menu_hl.png +++ b/scene/resources/default_theme/tab_menu_hl.png diff --git a/scene/resources/default_theme/toggle_off.png b/scene/resources/default_theme/toggle_off.png Binary files differindex 71cd64b001..64b51c8c9d 100644 --- a/scene/resources/default_theme/toggle_off.png +++ b/scene/resources/default_theme/toggle_off.png diff --git a/scene/resources/default_theme/toggle_on.png b/scene/resources/default_theme/toggle_on.png Binary files differindex 6ea1b589c7..f0c699c181 100644 --- a/scene/resources/default_theme/toggle_on.png +++ b/scene/resources/default_theme/toggle_on.png diff --git a/scene/resources/default_theme/tool_button_pressed.png b/scene/resources/default_theme/tool_button_pressed.png Binary files differindex bcf70b486d..5494475792 100644 --- a/scene/resources/default_theme/tool_button_pressed.png +++ b/scene/resources/default_theme/tool_button_pressed.png diff --git a/scene/resources/default_theme/tooltip_bg.png b/scene/resources/default_theme/tooltip_bg.png Binary files differindex eca0675a98..07b7d942ca 100644 --- a/scene/resources/default_theme/tooltip_bg.png +++ b/scene/resources/default_theme/tooltip_bg.png diff --git a/scene/resources/default_theme/tree_bg.png b/scene/resources/default_theme/tree_bg.png Binary files differindex 839a6a272a..2b0c506f34 100644 --- a/scene/resources/default_theme/tree_bg.png +++ b/scene/resources/default_theme/tree_bg.png diff --git a/scene/resources/default_theme/tree_bg_disabled.png b/scene/resources/default_theme/tree_bg_disabled.png Binary files differindex a0fa505e4c..69d78febd9 100644 --- a/scene/resources/default_theme/tree_bg_disabled.png +++ b/scene/resources/default_theme/tree_bg_disabled.png diff --git a/scene/resources/default_theme/tree_bg_focus.png b/scene/resources/default_theme/tree_bg_focus.png Binary files differindex 692cf71926..aadc6b0db4 100644 --- a/scene/resources/default_theme/tree_bg_focus.png +++ b/scene/resources/default_theme/tree_bg_focus.png diff --git a/scene/resources/default_theme/tree_cursor.png b/scene/resources/default_theme/tree_cursor.png Binary files differindex 94d2a08818..2b8722d066 100644 --- a/scene/resources/default_theme/tree_cursor.png +++ b/scene/resources/default_theme/tree_cursor.png diff --git a/scene/resources/default_theme/tree_cursor_unfocus.png b/scene/resources/default_theme/tree_cursor_unfocus.png Binary files differindex 3f023bbabe..bfaebbea85 100644 --- a/scene/resources/default_theme/tree_cursor_unfocus.png +++ b/scene/resources/default_theme/tree_cursor_unfocus.png diff --git a/scene/resources/default_theme/tree_title.png b/scene/resources/default_theme/tree_title.png Binary files differindex b0ddcffbbe..e5f3f49695 100644 --- a/scene/resources/default_theme/tree_title.png +++ b/scene/resources/default_theme/tree_title.png diff --git a/scene/resources/default_theme/tree_title_pressed.png b/scene/resources/default_theme/tree_title_pressed.png Binary files differindex 746d10039e..35e2bb3008 100644 --- a/scene/resources/default_theme/tree_title_pressed.png +++ b/scene/resources/default_theme/tree_title_pressed.png diff --git a/scene/resources/default_theme/unchecked.png b/scene/resources/default_theme/unchecked.png Binary files differindex d6f790cbc2..8c818af755 100644 --- a/scene/resources/default_theme/unchecked.png +++ b/scene/resources/default_theme/unchecked.png diff --git a/scene/resources/default_theme/updown.png b/scene/resources/default_theme/updown.png Binary files differindex 916284a3cf..56f81921e8 100644 --- a/scene/resources/default_theme/updown.png +++ b/scene/resources/default_theme/updown.png diff --git a/scene/resources/default_theme/vseparator.png b/scene/resources/default_theme/vseparator.png Binary files differindex 498768c05b..51e79f3ac5 100644 --- a/scene/resources/default_theme/vseparator.png +++ b/scene/resources/default_theme/vseparator.png diff --git a/scene/resources/default_theme/vslider_bg.png b/scene/resources/default_theme/vslider_bg.png Binary files differindex 8d9ead3c5a..ba3244e3e5 100644 --- a/scene/resources/default_theme/vslider_bg.png +++ b/scene/resources/default_theme/vslider_bg.png diff --git a/scene/resources/default_theme/vslider_grabber.png b/scene/resources/default_theme/vslider_grabber.png Binary files differindex afc490be45..6c6bf93e68 100644 --- a/scene/resources/default_theme/vslider_grabber.png +++ b/scene/resources/default_theme/vslider_grabber.png diff --git a/scene/resources/default_theme/vslider_grabber_disabled.png b/scene/resources/default_theme/vslider_grabber_disabled.png Binary files differindex c830359f45..49cced5055 100644 --- a/scene/resources/default_theme/vslider_grabber_disabled.png +++ b/scene/resources/default_theme/vslider_grabber_disabled.png diff --git a/scene/resources/default_theme/vslider_grabber_hl.png b/scene/resources/default_theme/vslider_grabber_hl.png Binary files differindex 548972e115..28774fdbf8 100644 --- a/scene/resources/default_theme/vslider_grabber_hl.png +++ b/scene/resources/default_theme/vslider_grabber_hl.png diff --git a/scene/resources/default_theme/vslider_tick.png b/scene/resources/default_theme/vslider_tick.png Binary files differindex 873ebb00d8..bde788b563 100644 --- a/scene/resources/default_theme/vslider_tick.png +++ b/scene/resources/default_theme/vslider_tick.png diff --git a/scene/resources/default_theme/vsplit_bg.png b/scene/resources/default_theme/vsplit_bg.png Binary files differindex 7dd1d48b29..a5749f6d5c 100644 --- a/scene/resources/default_theme/vsplit_bg.png +++ b/scene/resources/default_theme/vsplit_bg.png diff --git a/scene/resources/default_theme/vsplitter.png b/scene/resources/default_theme/vsplitter.png Binary files differindex ec5542bf69..dde1f390df 100644 --- a/scene/resources/default_theme/vsplitter.png +++ b/scene/resources/default_theme/vsplitter.png diff --git a/scene/resources/default_theme/window_resizer.png b/scene/resources/default_theme/window_resizer.png Binary files differindex ed51968c4e..b06e6f5366 100644 --- a/scene/resources/default_theme/window_resizer.png +++ b/scene/resources/default_theme/window_resizer.png diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 05493d5777..e5d463d391 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -625,7 +625,7 @@ void DynamicFontAtSize::_update_char(CharType p_char) { break; } - int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + int error = FT_Load_Char(face, p_char, FT_HAS_COLOR(face) ? FT_LOAD_COLOR : FT_LOAD_DEFAULT | (font->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0) | ft_hinting); if (error) { char_map[p_char] = character; return; diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 3fab4d3cfc..d3da842b79 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -378,7 +378,7 @@ bool Environment::is_ssr_rough() const { void Environment::set_ssao_enabled(bool p_enable) { ssao_enabled = p_enable; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); _change_notify(); } @@ -390,7 +390,7 @@ bool Environment::is_ssao_enabled() const { void Environment::set_ssao_radius(float p_radius) { ssao_radius = p_radius; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_radius() const { @@ -400,7 +400,7 @@ float Environment::get_ssao_radius() const { void Environment::set_ssao_intensity(float p_intensity) { ssao_intensity = p_intensity; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_intensity() const { @@ -411,7 +411,7 @@ float Environment::get_ssao_intensity() const { void Environment::set_ssao_radius2(float p_radius) { ssao_radius2 = p_radius; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_radius2() const { @@ -421,7 +421,7 @@ float Environment::get_ssao_radius2() const { void Environment::set_ssao_intensity2(float p_intensity) { ssao_intensity2 = p_intensity; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_intensity2() const { @@ -431,7 +431,7 @@ float Environment::get_ssao_intensity2() const { void Environment::set_ssao_bias(float p_bias) { ssao_bias = p_bias; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_bias() const { @@ -441,17 +441,27 @@ float Environment::get_ssao_bias() const { void Environment::set_ssao_direct_light_affect(float p_direct_light_affect) { ssao_direct_light_affect = p_direct_light_affect; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_direct_light_affect() const { return ssao_direct_light_affect; } +void Environment::set_ssao_ao_channel_affect(float p_ao_channel_affect) { + + ssao_ao_channel_affect = p_ao_channel_affect; + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); +} +float Environment::get_ssao_ao_channel_affect() const { + + return ssao_ao_channel_affect; +} + void Environment::set_ssao_color(const Color &p_color) { ssao_color = p_color; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Color Environment::get_ssao_color() const { @@ -462,7 +472,7 @@ Color Environment::get_ssao_color() const { void Environment::set_ssao_blur(SSAOBlur p_blur) { ssao_blur = p_blur; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Environment::SSAOBlur Environment::get_ssao_blur() const { @@ -472,7 +482,7 @@ Environment::SSAOBlur Environment::get_ssao_blur() const { void Environment::set_ssao_quality(SSAOQuality p_quality) { ssao_quality = p_quality; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } Environment::SSAOQuality Environment::get_ssao_quality() const { @@ -483,7 +493,7 @@ Environment::SSAOQuality Environment::get_ssao_quality() const { void Environment::set_ssao_edge_sharpness(float p_edge_sharpness) { ssao_edge_sharpness = p_edge_sharpness; - VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); + VS::get_singleton()->environment_set_ssao(environment, ssao_enabled, ssao_radius, ssao_intensity, ssao_radius2, ssao_intensity2, ssao_bias, ssao_direct_light_affect, ssao_ao_channel_affect, ssao_color, VS::EnvironmentSSAOQuality(ssao_quality), VS::EnvironmentSSAOBlur(ssao_blur), ssao_edge_sharpness); } float Environment::get_ssao_edge_sharpness() const { @@ -1008,6 +1018,9 @@ void Environment::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ssao_direct_light_affect", "amount"), &Environment::set_ssao_direct_light_affect); ClassDB::bind_method(D_METHOD("get_ssao_direct_light_affect"), &Environment::get_ssao_direct_light_affect); + ClassDB::bind_method(D_METHOD("set_ssao_ao_channel_affect", "amount"), &Environment::set_ssao_ao_channel_affect); + ClassDB::bind_method(D_METHOD("get_ssao_ao_channel_affect"), &Environment::get_ssao_ao_channel_affect); + ClassDB::bind_method(D_METHOD("set_ssao_color", "color"), &Environment::set_ssao_color); ClassDB::bind_method(D_METHOD("get_ssao_color"), &Environment::get_ssao_color); @@ -1028,6 +1041,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_intensity2", PROPERTY_HINT_RANGE, "0.0,128,0.1"), "set_ssao_intensity2", "get_ssao_intensity2"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_bias", PROPERTY_HINT_RANGE, "0.001,8,0.001"), "set_ssao_bias", "get_ssao_bias"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_light_affect", PROPERTY_HINT_RANGE, "0.00,1,0.01"), "set_ssao_direct_light_affect", "get_ssao_direct_light_affect"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "ssao_ao_channel_affect", PROPERTY_HINT_RANGE, "0.00,1,0.01"), "set_ssao_ao_channel_affect", "get_ssao_ao_channel_affect"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ssao_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ssao_color", "get_ssao_color"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ssao_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_ssao_quality", "get_ssao_quality"); ADD_PROPERTY(PropertyInfo(Variant::INT, "ssao_blur", PROPERTY_HINT_ENUM, "Disabled,1x1,2x2,3x3"), "set_ssao_blur", "get_ssao_blur"); @@ -1220,6 +1234,7 @@ Environment::Environment() { ssao_intensity2 = 1; ssao_bias = 0.01; ssao_direct_light_affect = 0.0; + ssao_ao_channel_affect = 0.0; ssao_blur = SSAO_BLUR_3x3; set_ssao_edge_sharpness(4); set_ssao_quality(SSAO_QUALITY_LOW); diff --git a/scene/resources/environment.h b/scene/resources/environment.h index 27fd57aa09..7d66c7e60b 100644 --- a/scene/resources/environment.h +++ b/scene/resources/environment.h @@ -127,6 +127,7 @@ private: float ssao_intensity2; float ssao_bias; float ssao_direct_light_affect; + float ssao_ao_channel_affect; Color ssao_color; SSAOBlur ssao_blur; float ssao_edge_sharpness; @@ -274,6 +275,9 @@ public: void set_ssao_direct_light_affect(float p_direct_light_affect); float get_ssao_direct_light_affect() const; + void set_ssao_ao_channel_affect(float p_ao_channel_affect); + float get_ssao_ao_channel_affect() const; + void set_ssao_color(const Color &p_color); Color get_ssao_color() const; diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index e8b7ecaf9a..a3fb068569 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -100,7 +100,7 @@ public: ARRAY_FLAG_USE_16_BIT_BONES = ARRAY_COMPRESS_INDEX << 2, ARRAY_FLAG_USE_DYNAMIC_UPDATE = ARRAY_COMPRESS_INDEX << 3, - ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_VERTEX | ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS + ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS }; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 5b600623b9..28aa6f1aa7 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -419,10 +419,10 @@ void CapsuleMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CapsuleMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CapsuleMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_mid_height", "get_mid_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "mid_height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_mid_height", "get_mid_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); } void CapsuleMesh::set_radius(const float p_radius) { @@ -677,9 +677,9 @@ void CubeMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &CubeMesh::get_subdivide_depth); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); } void CubeMesh::set_size(const Vector3 &p_size) { @@ -881,11 +881,11 @@ void CylinderMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_top_radius", "get_top_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_bottom_radius", "get_bottom_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "top_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_top_radius", "get_top_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bottom_radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_bottom_radius", "get_bottom_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); } void CylinderMesh::set_top_radius(const float p_radius) { @@ -1017,8 +1017,8 @@ void PlaneMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); } void PlaneMesh::set_size(const Size2 &p_size) { @@ -1283,9 +1283,9 @@ void PrismMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "left_to_right", PROPERTY_HINT_RANGE, "-2.0,2.0,0.1"), "set_left_to_right", "get_left_to_right"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_height", "get_subdivide_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1"), "set_subdivide_depth", "get_subdivide_depth"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); } void PrismMesh::set_left_to_right(const float p_left_to_right) { @@ -1352,10 +1352,10 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { PoolVector<float> tangents; PoolVector<Vector2> uvs; - faces.resize(4); - normals.resize(4); - tangents.resize(4 * 4); - uvs.resize(4); + faces.resize(6); + normals.resize(6); + tangents.resize(6 * 4); + uvs.resize(6); Vector2 _size = Vector2(size.x / 2.0f, size.y / 2.0f); @@ -1366,9 +1366,15 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { Vector3(_size.x, -_size.y, 0), }; - for (int i = 0; i < 4; i++) { + static const int indices[6] = { + 0, 1, 2, + 0, 2, 3 + }; + + for (int i = 0; i < 6; i++) { - faces.set(i, quad_faces[i]); + int j = indices[i]; + faces.set(i, quad_faces[j]); normals.set(i, Vector3(0, 0, 1)); tangents.set(i * 4 + 0, 1.0); tangents.set(i * 4 + 1, 0.0); @@ -1382,14 +1388,14 @@ void QuadMesh::_create_mesh_array(Array &p_arr) const { Vector2(1, 1), }; - uvs.set(i, quad_uv[i]); + uvs.set(i, quad_uv[j]); } p_arr[VS::ARRAY_VERTEX] = faces; p_arr[VS::ARRAY_NORMAL] = normals; p_arr[VS::ARRAY_TANGENT] = tangents; p_arr[VS::ARRAY_TEX_UV] = uvs; -}; +} void QuadMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_size", "size"), &QuadMesh::set_size); @@ -1398,7 +1404,7 @@ void QuadMesh::_bind_methods() { } QuadMesh::QuadMesh() { - primitive_type = PRIMITIVE_TRIANGLE_FAN; + primitive_type = PRIMITIVE_TRIANGLES; size = Size2(1.0, 1.0); } @@ -1499,10 +1505,10 @@ void SphereMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere); ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere"); } diff --git a/scene/resources/theme.h b/scene/resources/theme.h index c23f237c75..e0d4038e7e 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -55,12 +55,12 @@ class Theme : public Resource { void _unref_font(Ref<Font> p_sc); void _emit_theme_changed(); - HashMap<StringName, HashMap<StringName, Ref<Texture>, StringNameHasher>, StringNameHasher> icon_map; - HashMap<StringName, HashMap<StringName, Ref<StyleBox>, StringNameHasher>, StringNameHasher> style_map; - HashMap<StringName, HashMap<StringName, Ref<Font>, StringNameHasher>, StringNameHasher> font_map; - HashMap<StringName, HashMap<StringName, Ref<Shader>, StringNameHasher>, StringNameHasher> shader_map; - HashMap<StringName, HashMap<StringName, Color, StringNameHasher>, StringNameHasher> color_map; - HashMap<StringName, HashMap<StringName, int, StringNameHasher>, StringNameHasher> constant_map; + HashMap<StringName, HashMap<StringName, Ref<Texture> > > icon_map; + HashMap<StringName, HashMap<StringName, Ref<StyleBox> > > style_map; + HashMap<StringName, HashMap<StringName, Ref<Font> > > font_map; + HashMap<StringName, HashMap<StringName, Ref<Shader> > > shader_map; + HashMap<StringName, HashMap<StringName, Color> > color_map; + HashMap<StringName, HashMap<StringName, int> > constant_map; protected: bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 2dc32b893d..bf765385d0 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -187,6 +187,8 @@ SceneStringNames::SceneStringNames() { node_configuration_warning_changed = StaticCString::create("node_configuration_warning_changed"); + output = StaticCString::create("output"); + path_pp = NodePath(".."); _default = StaticCString::create("default"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 2e6da26d68..b88cf7d8d7 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -199,6 +199,8 @@ public: StringName node_configuration_warning_changed; + StringName output; + enum { MAX_MATERIALS = 32 }; diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 0ad30987e7..113f23f8f2 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -89,6 +89,7 @@ void AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, } } } + //////////////////////////////// void AudioStream::_bind_methods() { diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index fda4fc2ccc..3312ce1ff6 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -31,6 +31,7 @@ #ifndef AUDIO_STREAM_H #define AUDIO_STREAM_H +#include "image.h" #include "resource.h" #include "servers/audio/audio_filter_sw.h" #include "servers/audio_server.h" diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index b08e41301a..f2df7119e7 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -234,29 +234,6 @@ void AudioServer::_driver_process(int p_frames, int32_t *p_buffer) { todo -= to_copy; to_mix -= to_copy; } - -#ifdef DEBUG_ENABLED - if (OS::get_singleton() && OS::get_singleton()->is_stdout_verbose()) { - static uint64_t first_ticks = 0; - static uint64_t last_ticks = 0; - static uint64_t ticks = 0; - static int count = 0; - static int total = 0; - - ticks = OS::get_singleton()->get_ticks_msec(); - if ((ticks - first_ticks) > 10 * 1000 && count > 0) { - print_line("Audio Driver " + String(AudioDriver::get_singleton()->get_name()) + " average latency: " + itos(total / count) + "ms (frame=" + itos(p_frames) + ")"); - first_ticks = ticks; - total = 0; - count = 0; - } - - total += ticks - last_ticks; - count++; - - last_ticks = ticks; - } -#endif } void AudioServer::_mix_step() { @@ -939,9 +916,6 @@ void AudioServer::finish() { buses.clear(); } -void AudioServer::update() { -} - /* MISC config */ void AudioServer::lock() { diff --git a/servers/audio_server.h b/servers/audio_server.h index af2668b69e..b7fcd9c093 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -273,7 +273,6 @@ public: virtual void init(); virtual void finish(); - virtual void update(); virtual void load_default_bus_layout(); /* MISC config */ diff --git a/servers/server_wrap_mt_common.h b/servers/server_wrap_mt_common.h index 4681dd46f0..611e25af2a 100644 --- a/servers/server_wrap_mt_common.h +++ b/servers/server_wrap_mt_common.h @@ -810,3 +810,12 @@ server_name->m_type(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12); \ } \ } + +#define FUNC13(m_type, m_arg1, m_arg2, m_arg3, m_arg4, m_arg5, m_arg6, m_arg7, m_arg8, m_arg9, m_arg10, m_arg11, m_arg12, m_arg13) \ + virtual void m_type(m_arg1 p1, m_arg2 p2, m_arg3 p3, m_arg4 p4, m_arg5 p5, m_arg6 p6, m_arg7 p7, m_arg8 p8, m_arg9 p9, m_arg10 p10, m_arg11 p11, m_arg12 p12, m_arg13 p13) { \ + if (Thread::get_caller_id() != server_thread) { \ + command_queue.push(server_name, &ServerName::m_type, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13); \ + } else { \ + server_name->m_type(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13); \ + } \ + } diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index 8d8e9e693e..f07adf6d0e 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -66,7 +66,7 @@ public: virtual void environment_set_fog(RID p_env, bool p_enable, float p_begin, float p_end, RID p_gradient_texture) = 0; virtual void environment_set_ssr(RID p_env, bool p_enable, int p_max_steps, float p_fade_int, float p_fade_out, float p_depth_tolerance, bool p_roughness) = 0; - virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) = 0; + virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, VS::EnvironmentSSAOQuality p_quality, VS::EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) = 0; virtual void environment_set_tonemap(RID p_env, VS::EnvironmentToneMapper p_tone_mapper, float p_exposure, float p_white, bool p_auto_exposure, float p_min_luminance, float p_max_luminance, float p_auto_exp_speed, float p_auto_exp_scale) = 0; diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp index f8661638c3..2069e64c43 100644 --- a/servers/visual/shader_language.cpp +++ b/servers/visual/shader_language.cpp @@ -1625,14 +1625,14 @@ const ShaderLanguage::BuiltinFuncDef ShaderLanguage::builtin_func_defs[] = { { "smoothstep", TYPE_VEC4, { TYPE_FLOAT, TYPE_FLOAT, TYPE_VEC4, TYPE_VOID } }, { "isnan", TYPE_BOOL, { TYPE_FLOAT, TYPE_VOID } }, - { "isnan", TYPE_BOOL, { TYPE_VEC2, TYPE_VOID } }, - { "isnan", TYPE_BOOL, { TYPE_VEC3, TYPE_VOID } }, - { "isnan", TYPE_BOOL, { TYPE_VEC4, TYPE_VOID } }, + { "isnan", TYPE_BVEC2, { TYPE_VEC2, TYPE_VOID } }, + { "isnan", TYPE_BVEC3, { TYPE_VEC3, TYPE_VOID } }, + { "isnan", TYPE_BVEC4, { TYPE_VEC4, TYPE_VOID } }, { "isinf", TYPE_BOOL, { TYPE_FLOAT, TYPE_VOID } }, - { "isinf", TYPE_BOOL, { TYPE_VEC2, TYPE_VOID } }, - { "isinf", TYPE_BOOL, { TYPE_VEC3, TYPE_VOID } }, - { "isinf", TYPE_BOOL, { TYPE_VEC4, TYPE_VOID } }, + { "isinf", TYPE_BVEC2, { TYPE_VEC2, TYPE_VOID } }, + { "isinf", TYPE_BVEC3, { TYPE_VEC3, TYPE_VOID } }, + { "isinf", TYPE_BVEC4, { TYPE_VEC4, TYPE_VOID } }, { "floatBitsToInt", TYPE_INT, { TYPE_FLOAT, TYPE_VOID } }, { "floatBitsToInt", TYPE_IVEC2, { TYPE_VEC2, TYPE_VOID } }, diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index 8f19de9f8b..f7151e54f9 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -139,6 +139,8 @@ public: void m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5, m_type6 arg6, m_type7 arg7, m_type8 arg8, m_type9 arg9, m_type10 arg10, m_type11 arg11) { DISPLAY_CHANGED BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); } #define BIND12(m_name, m_type1, m_type2, m_type3, m_type4, m_type5, m_type6, m_type7, m_type8, m_type9, m_type10, m_type11, m_type12) \ void m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5, m_type6 arg6, m_type7 arg7, m_type8 arg8, m_type9 arg9, m_type10 arg10, m_type11 arg11, m_type12 arg12) { DISPLAY_CHANGED BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); } +#define BIND13(m_name, m_type1, m_type2, m_type3, m_type4, m_type5, m_type6, m_type7, m_type8, m_type9, m_type10, m_type11, m_type12, m_type13) \ + void m_name(m_type1 arg1, m_type2 arg2, m_type3 arg3, m_type4 arg4, m_type5 arg5, m_type6 arg6, m_type7 arg7, m_type8 arg8, m_type9 arg9, m_type10 arg10, m_type11 arg11, m_type12 arg12, m_type13 arg13) { DISPLAY_CHANGED BINDBASE->m_name(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); } //from now on, calls forwarded to this singleton #define BINDBASE VSG::storage @@ -489,7 +491,7 @@ public: BIND2(environment_set_canvas_max_layer, RID, int) BIND4(environment_set_ambient_light, RID, const Color &, float, float) BIND7(environment_set_ssr, RID, bool, int, float, float, float, bool) - BIND12(environment_set_ssao, RID, bool, float, float, float, float, float, float, const Color &, EnvironmentSSAOQuality, EnvironmentSSAOBlur, float) + BIND13(environment_set_ssao, RID, bool, float, float, float, float, float, float, float, const Color &, EnvironmentSSAOQuality, EnvironmentSSAOBlur, float) BIND6(environment_set_dof_blur_near, RID, bool, float, float, float, EnvironmentDOFBlurQuality) BIND6(environment_set_dof_blur_far, RID, bool, float, float, float, EnvironmentDOFBlurQuality) diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp index 04dcde1365..697c890c9a 100644 --- a/servers/visual/visual_server_scene.cpp +++ b/servers/visual/visual_server_scene.cpp @@ -1257,6 +1257,9 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data); + Transform light_transform = p_instance->transform; + light_transform.orthonormalize(); //scale does not count on lights + switch (VSG::storage->light_get_type(p_instance->base)) { case VS::LIGHT_DIRECTIONAL: { @@ -1359,7 +1362,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons // obtain the light frustm ranges (given endpoints) - Transform transform = p_instance->transform.orthonormalized(); //discard scale and stabilize light + Transform transform = light_transform; //discard scale and stabilize light Vector3 x_vec = transform.basis.get_axis(Vector3::AXIS_X).normalized(); Vector3 y_vec = transform.basis.get_axis(Vector3::AXIS_Y).normalized(); @@ -1469,7 +1472,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons // a pre pass will need to be needed to determine the actual z-near to be used - Plane near_plane(p_instance->transform.origin, -p_instance->transform.basis.get_axis(2)); + Plane near_plane(light_transform.origin, -light_transform.basis.get_axis(2)); for (int j = 0; j < cull_count; j++) { @@ -1524,14 +1527,14 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons float z = i == 0 ? -1 : 1; Vector<Plane> planes; planes.resize(5); - planes[0] = p_instance->transform.xform(Plane(Vector3(0, 0, z), radius)); - planes[1] = p_instance->transform.xform(Plane(Vector3(1, 0, z).normalized(), radius)); - planes[2] = p_instance->transform.xform(Plane(Vector3(-1, 0, z).normalized(), radius)); - planes[3] = p_instance->transform.xform(Plane(Vector3(0, 1, z).normalized(), radius)); - planes[4] = p_instance->transform.xform(Plane(Vector3(0, -1, z).normalized(), radius)); + planes[0] = light_transform.xform(Plane(Vector3(0, 0, z), radius)); + planes[1] = light_transform.xform(Plane(Vector3(1, 0, z).normalized(), radius)); + planes[2] = light_transform.xform(Plane(Vector3(-1, 0, z).normalized(), radius)); + planes[3] = light_transform.xform(Plane(Vector3(0, 1, z).normalized(), radius)); + planes[4] = light_transform.xform(Plane(Vector3(0, -1, z).normalized(), radius)); int cull_count = p_scenario->octree.cull_convex(planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, VS::INSTANCE_GEOMETRY_MASK); - Plane near_plane(p_instance->transform.origin, p_instance->transform.basis.get_axis(2) * z); + Plane near_plane(light_transform.origin, light_transform.basis.get_axis(2) * z); for (int j = 0; j < cull_count; j++) { @@ -1546,7 +1549,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons } } - VSG::scene_render->light_instance_set_shadow_transform(light->instance, CameraMatrix(), p_instance->transform, radius, 0, i); + VSG::scene_render->light_instance_set_shadow_transform(light->instance, CameraMatrix(), light_transform, radius, 0, i); VSG::scene_render->render_shadow(light->instance, p_shadow_atlas, i, (RasterizerScene::InstanceBase **)instance_shadow_cull_result, cull_count); } } break; @@ -1577,7 +1580,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons Vector3(0, -1, 0) }; - Transform xform = p_instance->transform * Transform().looking_at(view_normals[i], view_up[i]); + Transform xform = light_transform * Transform().looking_at(view_normals[i], view_up[i]); Vector<Plane> planes = cm.get_projection_planes(xform); @@ -1602,7 +1605,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons } //restore the regular DP matrix - VSG::scene_render->light_instance_set_shadow_transform(light->instance, CameraMatrix(), p_instance->transform, radius, 0, 0); + VSG::scene_render->light_instance_set_shadow_transform(light->instance, CameraMatrix(), light_transform, radius, 0, 0); } break; } @@ -1616,10 +1619,10 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons CameraMatrix cm; cm.set_perspective(angle * 2.0, 1.0, 0.01, radius); - Vector<Plane> planes = cm.get_projection_planes(p_instance->transform); + Vector<Plane> planes = cm.get_projection_planes(light_transform); int cull_count = p_scenario->octree.cull_convex(planes, instance_shadow_cull_result, MAX_INSTANCE_CULL, VS::INSTANCE_GEOMETRY_MASK); - Plane near_plane(p_instance->transform.origin, -p_instance->transform.basis.get_axis(2)); + Plane near_plane(light_transform.origin, -light_transform.basis.get_axis(2)); for (int j = 0; j < cull_count; j++) { Instance *instance = instance_shadow_cull_result[j]; @@ -1633,7 +1636,7 @@ void VisualServerScene::_light_instance_update_shadow(Instance *p_instance, cons } } - VSG::scene_render->light_instance_set_shadow_transform(light->instance, cm, p_instance->transform, radius, 0, 0); + VSG::scene_render->light_instance_set_shadow_transform(light->instance, cm, light_transform, radius, 0, 0); VSG::scene_render->render_shadow(light->instance, p_shadow_atlas, 0, (RasterizerScene::InstanceBase **)instance_shadow_cull_result, cull_count); } break; @@ -1674,7 +1677,8 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view } break; } - _render_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), -1); + _prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); + _render_scene(camera->transform, camera_matrix, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); } void VisualServerScene::render_camera(Ref<ARVRInterface> &p_interface, ARVRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas) { @@ -1684,7 +1688,6 @@ void VisualServerScene::render_camera(Ref<ARVRInterface> &p_interface, ARVRInter ERR_FAIL_COND(!camera); /* SETUP CAMERA, we are ignoring type and FOV here */ - bool ortho = false; float aspect = p_viewport_size.width / (float)p_viewport_size.height; CameraMatrix camera_matrix = p_interface->get_projection_for_eye(p_eye, aspect, camera->znear, camera->zfar); @@ -1693,10 +1696,79 @@ void VisualServerScene::render_camera(Ref<ARVRInterface> &p_interface, ARVRInter Transform world_origin = ARVRServer::get_singleton()->get_world_origin(); Transform cam_transform = p_interface->get_transform_for_eye(p_eye, world_origin); - _render_scene(cam_transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), -1); + // For stereo render we only prepare for our left eye and then reuse the outcome for our right eye + if (p_eye == ARVRInterface::EYE_LEFT) { + ///@TODO possibly move responsibility for this into our ARVRServer or ARVRInterface? + + // Center our transform, we assume basis is equal. + Transform mono_transform = cam_transform; + Transform right_transform = p_interface->get_transform_for_eye(ARVRInterface::EYE_RIGHT, world_origin); + mono_transform.origin += right_transform.origin; + mono_transform.origin *= 0.5; + + // We need to combine our projection frustums for culling. + // Ideally we should use our clipping planes for this and combine them, + // however our shadow map logic uses our projection matrix. + // Note: as our left and right frustums should be mirrored, we don't need our right projection matrix. + + // - get some base values we need + float eye_dist = (mono_transform.origin - cam_transform.origin).length(); + float z_near = camera_matrix.get_z_near(); // get our near plane + float z_far = camera_matrix.get_z_far(); // get our far plane + float width = (2.0 * z_near) / camera_matrix.matrix[0][0]; + float x_shift = width * camera_matrix.matrix[2][0]; + float height = (2.0 * z_near) / camera_matrix.matrix[1][1]; + float y_shift = height * camera_matrix.matrix[2][1]; + + // printf("Eye_dist = %f, Near = %f, Far = %f, Width = %f, Shift = %f\n", eye_dist, z_near, z_far, width, x_shift); + + // - calculate our near plane size (horizontal only, right_near is mirrored) + float left_near = -eye_dist - ((width - x_shift) * 0.5); + + // - calculate our far plane size (horizontal only, right_far is mirrored) + float left_far = -eye_dist - (z_far * (width - x_shift) * 0.5 / z_near); + float left_far_right_eye = eye_dist - (z_far * (width + x_shift) * 0.5 / z_near); + if (left_far > left_far_right_eye) { + // on displays smaller then double our iod, the right eye far frustrum can overtake the left eyes. + left_far = left_far_right_eye; + } + + // - figure out required z-shift + float slope = (left_far - left_near) / (z_far - z_near); + float z_shift = (left_near / slope) - z_near; + + // - figure out new vertical near plane size (this will be slightly oversized thanks to our z-shift) + float top_near = (height - y_shift) * 0.5; + top_near += (top_near / z_near) * z_shift; + float bottom_near = -(height + y_shift) * 0.5; + bottom_near += (bottom_near / z_near) * z_shift; + + // printf("Left_near = %f, Left_far = %f, Top_near = %f, Bottom_near = %f, Z_shift = %f\n", left_near, left_far, top_near, bottom_near, z_shift); + + // - generate our frustum + CameraMatrix combined_matrix; + combined_matrix.set_frustum(left_near, -left_near, bottom_near, top_near, z_near + z_shift, z_far + z_shift); + + // and finally move our camera back + Transform apply_z_shift; + apply_z_shift.origin = Vector3(0.0, 0.0, z_shift); // z negative is forward so this moves it backwards + mono_transform *= apply_z_shift; + + // now prepare our scene with our adjusted transform projection matrix + _prepare_scene(mono_transform, combined_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); + } else if (p_eye == ARVRInterface::EYE_MONO) { + // For mono render, prepare as per usual + _prepare_scene(cam_transform, camera_matrix, false, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID()); + } + + // And render our scene... + _render_scene(cam_transform, camera_matrix, false, camera->env, p_scenario, p_shadow_atlas, RID(), -1); }; -void VisualServerScene::_render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass) { +void VisualServerScene::_prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe) { + // Note, in stereo rendering: + // - p_cam_transform will be a transform in the middle of our two eyes + // - p_cam_projection is a wider frustrum that encompasses both eyes Scenario *scenario = scenario_owner.getornull(p_scenario); @@ -1713,7 +1785,7 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam float z_far = p_cam_projection.get_z_far(); /* STEP 2 - CULL */ - int cull_count = scenario->octree.cull_convex(planes, instance_cull_result, MAX_INSTANCE_CULL); + instance_cull_count = scenario->octree.cull_convex(planes, instance_cull_result, MAX_INSTANCE_CULL); light_cull_count = 0; reflection_probe_cull_count = 0; @@ -1731,7 +1803,7 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam /* STEP 4 - REMOVE FURTHER CULLED OBJECTS, ADD LIGHTS */ - for (int i = 0; i < cull_count; i++) { + for (int i = 0; i < instance_cull_count; i++) { Instance *ins = instance_cull_result[i]; @@ -1857,8 +1929,8 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam if (!keep) { // remove, no reason to keep - cull_count--; - SWAP(instance_cull_result[i], instance_cull_result[cull_count]); + instance_cull_count--; + SWAP(instance_cull_result[i], instance_cull_result[instance_cull_count]); i--; ins->last_render_pass = 0; // make invalid } else { @@ -1870,7 +1942,7 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam /* STEP 5 - PROCESS LIGHTS */ RID *directional_light_ptr = &light_instance_cull_result[light_cull_count]; - int directional_light_count = 0; + directional_light_count = 0; // directional lights { @@ -2007,6 +2079,11 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam } } } +} + +void VisualServerScene::_render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass) { + + Scenario *scenario = scenario_owner.getornull(p_scenario); /* ENVIRONMENT */ @@ -2018,9 +2095,9 @@ void VisualServerScene::_render_scene(const Transform p_cam_transform, const Cam else environment = scenario->fallback_environment; - /* STEP 6 - PROCESS GEOMETRY AND DRAW SCENE*/ + /* PROCESS GEOMETRY AND DRAW SCENE */ - VSG::scene_render->render_scene(p_cam_transform, p_cam_projection, p_cam_orthogonal, (RasterizerScene::InstanceBase **)instance_cull_result, cull_count, light_instance_cull_result, light_cull_count + directional_light_count, reflection_probe_instance_cull_result, reflection_probe_cull_count, environment, p_shadow_atlas, scenario->reflection_atlas, p_reflection_probe, p_reflection_probe_pass); + VSG::scene_render->render_scene(p_cam_transform, p_cam_projection, p_cam_orthogonal, (RasterizerScene::InstanceBase **)instance_cull_result, instance_cull_count, light_instance_cull_result, light_cull_count + directional_light_count, reflection_probe_instance_cull_result, reflection_probe_cull_count, environment, p_shadow_atlas, scenario->reflection_atlas, p_reflection_probe, p_reflection_probe_pass); } void VisualServerScene::render_empty_scene(RID p_scenario, RID p_shadow_atlas) { @@ -2093,7 +2170,8 @@ bool VisualServerScene::_render_reflection_probe_step(Instance *p_instance, int shadow_atlas = scenario->reflection_probe_shadow_atlas; } - _render_scene(xform, cm, false, RID(), VSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, p_step); + _prepare_scene(xform, cm, false, RID(), VSG::storage->reflection_probe_get_cull_mask(p_instance->base), p_instance->scenario->self, shadow_atlas, reflection_probe->instance); + _render_scene(xform, cm, false, RID(), p_instance->scenario->self, shadow_atlas, reflection_probe->instance, p_step); } else { //do roughness postprocess step until it believes it's done diff --git a/servers/visual/visual_server_scene.h b/servers/visual/visual_server_scene.h index 109cdf711c..12d732724a 100644 --- a/servers/visual/visual_server_scene.h +++ b/servers/visual/visual_server_scene.h @@ -434,11 +434,13 @@ public: } }; + int instance_cull_count; Instance *instance_cull_result[MAX_INSTANCE_CULL]; Instance *instance_shadow_cull_result[MAX_INSTANCE_CULL]; //used for generating shadowmaps Instance *light_cull_result[MAX_LIGHTS_CULLED]; RID light_instance_cull_result[MAX_LIGHTS_CULLED]; int light_cull_count; + int directional_light_count; RID reflection_probe_instance_cull_result[MAX_REFLECTION_PROBES_CULLED]; int reflection_probe_cull_count; @@ -483,7 +485,8 @@ public: _FORCE_INLINE_ void _light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario); - void _render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass); + void _prepare_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, uint32_t p_visible_layers, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe); + void _render_scene(const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_force_environment, RID p_scenario, RID p_shadow_atlas, RID p_reflection_probe, int p_reflection_probe_pass); void render_empty_scene(RID p_scenario, RID p_shadow_atlas); void render_camera(RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 19bb58f3ad..8b5a334341 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -416,7 +416,7 @@ public: FUNC2(environment_set_canvas_max_layer, RID, int) FUNC4(environment_set_ambient_light, RID, const Color &, float, float) FUNC7(environment_set_ssr, RID, bool, int, float, float, float, bool) - FUNC12(environment_set_ssao, RID, bool, float, float, float, float, float, float, const Color &, EnvironmentSSAOQuality, EnvironmentSSAOBlur, float) + FUNC13(environment_set_ssao, RID, bool, float, float, float, float, float, float, float, const Color &, EnvironmentSSAOQuality, EnvironmentSSAOBlur, float) FUNC6(environment_set_dof_blur_near, RID, bool, float, float, float, EnvironmentDOFBlurQuality) FUNC6(environment_set_dof_blur_far, RID, bool, float, float, float, EnvironmentDOFBlurQuality) diff --git a/servers/visual_server.h b/servers/visual_server.h index 65d0f07a43..73d96d60f7 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -233,7 +233,7 @@ public: ARRAY_FLAG_USE_16_BIT_BONES = ARRAY_COMPRESS_INDEX << 2, ARRAY_FLAG_USE_DYNAMIC_UPDATE = ARRAY_COMPRESS_INDEX << 3, - ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_VERTEX | ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS + ARRAY_COMPRESS_DEFAULT = ARRAY_COMPRESS_NORMAL | ARRAY_COMPRESS_TANGENT | ARRAY_COMPRESS_COLOR | ARRAY_COMPRESS_TEX_UV | ARRAY_COMPRESS_TEX_UV2 | ARRAY_COMPRESS_WEIGHTS }; @@ -719,7 +719,7 @@ public: ENV_SSAO_BLUR_3x3, }; - virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, const Color &p_color, EnvironmentSSAOQuality p_quality, EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) = 0; + virtual void environment_set_ssao(RID p_env, bool p_enable, float p_radius, float p_intensity, float p_radius2, float p_intensity2, float p_bias, float p_light_affect, float p_ao_channel_affect, const Color &p_color, EnvironmentSSAOQuality p_quality, EnvironmentSSAOBlur p_blur, float p_bilateral_sharpness) = 0; virtual void environment_set_fog(RID p_env, bool p_enable, const Color &p_color, const Color &p_sun_color, float p_sun_amount) = 0; virtual void environment_set_fog_depth(RID p_env, bool p_enable, float p_depth_begin, float p_depth_curve, bool p_transmit, float p_transmit_curve) = 0; diff --git a/thirdparty/README.md b/thirdparty/README.md index 25d6e3df9a..d30b70fede 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -15,6 +15,7 @@ Important: Some files have Godot-made changes. They are marked with `// -- GODOT start --` and `// -- GODOT end --` comments. + ## bullet - Upstream: https://github.com/bulletphysics/bullet3 @@ -76,6 +77,7 @@ Important: Some files have Godot-made changes. They are marked with `// -- GODOT start --` and `// -- GODOT end --` comments. + ## fonts ### Noto Sans @@ -175,6 +177,7 @@ Important: Some files have Godot-made changes. They are marked with `// -- GODOT start --` and `// -- GODOT end --` comments. + ## libtheora - Upstream: https://www.theora.org @@ -234,24 +237,29 @@ changes are marked with `// -- GODOT --` comments. ## libwebsockets - Upstream: https://github.com/warmcat/libwebsockets -- Version: 2.4.2 +- Version: 3.0.0 - License: LGPLv2.1 + static linking exception File extracted from upstream source: -- Everything in `lib/` except `minilex.c`, `http2/`, `event-libs/`. - - From `misc/` exclude `lws-genhash.c`, `lws-ring.c`, `romfs.{c,h}`, `smtp.c`. - - From `plat/` exclude `lws-plat-{esp*,optee}.c`. - - From `server/` exclude `access-log.c`, `cgi.c`, `daemonize.c`, `lws-spa.c`, -`peer-limits.c`, `rewrite.c` -- Also copy `win32helpers/` from `win32port/` -- `mbedtls_wrapper/include/platform/ssl_port.h` has a small change to check for OSX and FreeBSD (missing `malloc.h`). - The bug is fixed in upstream master via `LWS_HAVE_MALLOC_H`, but not in the 2.4.1 branch (as the file structure has changed). -- You might need to apply the patch in `thirdparty/lws/mbedtls_verify.diff` (port of PR 1215) to future `2.4.x` releases if it does not get cherry picked. +- From `lib/` into `thirdparty/libwebsockets`: + - Everything from `core` + - From `event-libs` only the `poll` subfolder + - From `misc` only `base64-decode.c`, `getifaddrs.c`, `getifaddrs.h`, `lejp.c`, and `sha-1.c` + - From `plat` only `lws-plat-unix.c` and `lws-plat-win.c` + - From `roles` only `private.h`, `h1`, `http`, `listen`, `pipe`, `raw`, `ws` + - From `roles/http` exclude `minilex.c` + - From `roles/http/server` exclude `access-log.c`, `lws-spa.c`, `ranges.c`, and `rewrite.c` + - From `roles/ws` exclude `ext` folder. + - From `tls` exclude `openssl` folder. +- Also copy `win32helpers/` from `win32port/` inside `thirdparty/libwebsockets` +- A small fix has been added in `libwebsockets/libwebsockets.h` to `#include <sys/socket.h>` for the BSD family. + This change has been PRed upstream, and should be merged before the next update. Remember to check and remove this line. Important: `lws_config.h` and `lws_config_private.h` contains custom Godot build configurations, check them out when updating. -## mbedTLS + +## mbedtls - Upstream: https://tls.mbed.org/ - Version: 2.8.0 @@ -264,6 +272,16 @@ File extracted from upstream release tarball `mbedtls-2.8.0-apache.tgz`: Be sure to check the Godot addition to only redfine it when undefined or `< 0x0501` (PRed upstream). - Applied the patch in `thirdparty/mbedtls/1453.diff` (PR 1453). Soon to be merged upstream. Check it out at next update. + +## miniupnpc + +- Upstream: https://github.com/miniupnp/miniupnp/tree/master/miniupnpc +- Version: 2.1 (git 25615e0, 2018-05-08) with modifications +- License: BSD-3-Clause + +The only modified file is miniupnpcstrings.h, which was created for Godot (is usually autogenerated by cmake). + + ## minizip - Upstream: http://www.zlib.net @@ -293,6 +311,10 @@ Collection of single-file libraries used in Godot components. * Upstream: http://episec.com/people/edelkind/c.html * Version: latest, as of April 2017 * License: Public Domain + - `clipper.{cpp,hpp}` + * Upstream: https://sourceforge.net/projects/polyclipping + * Version: 6.4.2 + * License: BSL-1.0 - `fastlz.{c,h}` * Upstream: https://github.com/ariya/FastLZ * Version: git (f121734, 2007) @@ -361,6 +383,7 @@ Files extracted from the upstream source: - All .h files in `src/` - LICENSE.txt + ## opus - Upstream: https://opus-codec.org diff --git a/thirdparty/lws/LICENSE.txt b/thirdparty/libwebsockets/LICENSE index 34a42d5687..6c7cd90cdc 100644 --- a/thirdparty/lws/LICENSE.txt +++ b/thirdparty/libwebsockets/LICENSE @@ -33,19 +33,21 @@ to get original sources with the liberal terms. Original liberal license retained - - lib/sha-1.c - 3-clause BSD license retained, link to original + - lib/misc/sha-1.c - 3-clause BSD license retained, link to original - win32port/zlib - ZLIB license (see zlib.h) + - lib/tls/mbedtls/wrapper - Apache 2.0 (only built if linked against mbedtls) Relicensed to libwebsocket license - - lib/base64-decode.c - relicensed to LGPL2.1+SLE, link to original - - lib/daemonize.c - relicensed from Public Domain to LGPL2.1+SLE, - link to original Public Domain version + - lib/misc/base64-decode.c - relicensed to LGPL2.1+SLE, link to original + - lib/misc/daemonize.c - relicensed from Public Domain to LGPL2.1+SLE, + link to original Public Domain version Public Domain (CC-zero) to simplify reuse - - test-server/*.c - - test-server/*.h + - test-apps/*.c + - test-apps/*.h + - minimal-examples/* - lwsws/* ------ end of exceptions @@ -107,7 +109,7 @@ modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. - + Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a @@ -163,7 +165,7 @@ modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. - + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -210,7 +212,7 @@ Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - + 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 @@ -268,7 +270,7 @@ instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. - + Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. @@ -319,7 +321,7 @@ Library will still fall under Section 6.) distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. - + 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work @@ -381,7 +383,7 @@ restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. - + 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined @@ -422,7 +424,7 @@ subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. - + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or @@ -474,7 +476,7 @@ conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. - + 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is @@ -508,7 +510,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest @@ -552,4 +554,3 @@ necessary. Here is a sample; alter the names: Ty Coon, President of Vice That's all there is to it! - diff --git a/thirdparty/lws/alloc.c b/thirdparty/libwebsockets/core/alloc.c index 898db12464..f169fc3767 100644 --- a/thirdparty/lws/alloc.c +++ b/thirdparty/libwebsockets/core/alloc.c @@ -1,4 +1,4 @@ -#include "private-libwebsockets.h" +#include "core/private.h" #if defined(LWS_PLAT_OPTEE) @@ -51,10 +51,12 @@ void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason)) static void *_realloc(void *ptr, size_t size, const char *reason) { if (size) { -#if defined(LWS_PLAT_ESP32) - lwsl_notice("%s: size %lu: %s\n", __func__, (unsigned long)size, reason); +#if defined(LWS_WITH_ESP32) + lwsl_notice("%s: size %lu: %s (free heap %d)\n", __func__, + (unsigned long)size, reason, (unsigned int)esp_get_free_heap_size() - (int)size); #else - lwsl_debug("%s: size %lu: %s\n", __func__, (unsigned long)size, reason); + lwsl_debug("%s: size %lu: %s\n", __func__, + (unsigned long)size, reason); #endif #if defined(LWS_PLAT_OPTEE) return (void *)TEE_Realloc(ptr, size); diff --git a/thirdparty/lws/context.c b/thirdparty/libwebsockets/core/context.c index 9f221f50f1..db9151b95f 100644 --- a/thirdparty/lws/context.c +++ b/thirdparty/libwebsockets/core/context.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,12 +19,41 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include "core/private.h" #ifndef LWS_BUILD_HASH #define LWS_BUILD_HASH "unknown-build-hash" #endif +const struct lws_role_ops *available_roles[] = { +#if defined(LWS_ROLE_H2) + &role_ops_h2, +#endif +#if defined(LWS_ROLE_H1) + &role_ops_h1, +#endif +#if defined(LWS_ROLE_WS) + &role_ops_ws, +#endif + NULL +}; + +const struct lws_event_loop_ops *available_event_libs[] = { +#if defined(LWS_WITH_POLL) + &event_loop_ops_poll, +#endif +#if defined(LWS_WITH_LIBUV) + &event_loop_ops_uv, +#endif +#if defined(LWS_WITH_LIBEVENT) + &event_loop_ops_event, +#endif +#if defined(LWS_WITH_LIBEV) + &event_loop_ops_ev, +#endif + NULL +}; + static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH; /** @@ -40,6 +69,23 @@ lws_get_library_version(void) return library_version; } +int +lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn) +{ +#if defined(LWS_WITH_TLS) + if (!alpn) + return 0; + + lwsl_info("%s: '%s'\n", __func__, alpn); + + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) + if (ar->alpn && !strcmp(ar->alpn, alpn) && ar->alpn_negotiated) + return ar->alpn_negotiated(wsi, alpn); + LWS_FOR_EVERY_AVAILABLE_ROLE_END; +#endif + return 0; +} + static const char * const mount_protocols[] = { "http://", "https://", @@ -50,66 +96,6 @@ static const char * const mount_protocols[] = { "callback://" }; -#if defined(LWS_WITH_HTTP2) -/* - * These are the standardized defaults. - * Override what actually goes in the vhost settings in platform or user code. - * Leave these alone because they are used to determine "what is different - * from the protocol defaults". - */ -const struct http2_settings lws_h2_defaults = { { - 1, - /* H2SET_HEADER_TABLE_SIZE */ 4096, - /* *** This controls how many entries in the dynamic table *** - * Allows the sender to inform the remote endpoint of the maximum - * size of the header compression table used to decode header - * blocks, in octets. The encoder can select any size equal to or - * less than this value by using signaling specific to the header - * compression format inside a header block (see [COMPRESSION]). - * The initial value is 4,096 octets. - */ - /* H2SET_ENABLE_PUSH */ 1, - /* H2SET_MAX_CONCURRENT_STREAMS */ 0x7fffffff, - /* H2SET_INITIAL_WINDOW_SIZE */ 65535, - /* H2SET_MAX_FRAME_SIZE */ 16384, - /* H2SET_MAX_HEADER_LIST_SIZE */ 0x7fffffff, - /*< This advisory setting informs a peer of the maximum size of - * header list that the sender is prepared to accept, in octets. - * The value is based on the uncompressed size of header fields, - * including the length of the name and value in octets plus an - * overhead of 32 octets for each header field. - */ - -}}; - -const struct http2_settings lws_h2_stock_settings = { { - 1, - /* H2SET_HEADER_TABLE_SIZE */ 4096, - /* *** This controls how many entries in the dynamic table *** - * Allows the sender to inform the remote endpoint of the maximum - * size of the header compression table used to decode header - * blocks, in octets. The encoder can select any size equal to or - * less than this value by using signaling specific to the header - * compression format inside a header block (see [COMPRESSION]). - * The initial value is 4,096 octets. - * - * Can't pass h2spec with less than 4096 here... - */ - /* H2SET_ENABLE_PUSH */ 1, - /* H2SET_MAX_CONCURRENT_STREAMS */ 24, - /* H2SET_INITIAL_WINDOW_SIZE */ 65535, - /* H2SET_MAX_FRAME_SIZE */ 16384, - /* H2SET_MAX_HEADER_LIST_SIZE */ 4096, - /*< This advisory setting informs a peer of the maximum size of - * header list that the sender is prepared to accept, in octets. - * The value is based on the uncompressed size of header fields, - * including the length of the name and value in octets plus an - * overhead of 32 octets for each header field. - */ - -}}; -#endif - LWS_VISIBLE void * lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot, int size) @@ -119,7 +105,8 @@ lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, /* allocate the vh priv array only on demand */ if (!vhost->protocol_vh_privs) { vhost->protocol_vh_privs = (void **)lws_zalloc( - vhost->count_protocols * sizeof(void *), "protocol_vh_privs"); + vhost->count_protocols * sizeof(void *), + "protocol_vh_privs"); if (!vhost->protocol_vh_privs) return NULL; } @@ -195,7 +182,7 @@ lws_protocol_init(struct lws_context *context) struct lws_vhost *vh = context->vhost_list; const struct lws_protocol_vhost_options *pvo, *pvo1; struct lws wsi; - int n; + int n, any = 0; if (context->doing_protocol_init) return 0; @@ -211,7 +198,8 @@ lws_protocol_init(struct lws_context *context) wsi.vhost = vh; /* only do the protocol init once for a given vhost */ - if (vh->created_vhost_protocols) + if (vh->created_vhost_protocols || + (vh->options & LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT)) goto next; /* initialize supported protocols on this vhost */ @@ -231,21 +219,23 @@ lws_protocol_init(struct lws_context *context) pvo = pvo1->options; while (pvo) { - lwsl_notice( - " vhost \"%s\", protocol \"%s\", option \"%s\"\n", + lwsl_debug( + " vhost \"%s\", " + "protocol \"%s\", " + "option \"%s\"\n", vh->name, vh->protocols[n].name, pvo->name); if (!strcmp(pvo->name, "default")) { - lwsl_notice("Setting default " + lwsl_info("Setting default " "protocol for vh %s to %s\n", vh->name, vh->protocols[n].name); vh->default_protocol_index = n; } if (!strcmp(pvo->name, "raw")) { - lwsl_notice("Setting raw " + lwsl_info("Setting raw " "protocol for vh %s to %s\n", vh->name, vh->protocols[n].name); @@ -257,6 +247,10 @@ lws_protocol_init(struct lws_context *context) pvo = pvo1->options; } +#if defined(LWS_WITH_TLS) + any |= !!vh->tls.ssl_ctx; +#endif + /* * inform all the protocols that they are doing their * one-time initialization if they want to. @@ -267,10 +261,10 @@ lws_protocol_init(struct lws_context *context) if (vh->protocols[n].callback(&wsi, LWS_CALLBACK_PROTOCOL_INIT, NULL, (void *)pvo, 0)) { - lwsl_err("%s: vhost %s failed init\n", __func__, + lws_free(vh->protocol_vh_privs[n]); + vh->protocol_vh_privs[n] = NULL; + lwsl_err("%s: protocol %s failed init\n", __func__, vh->protocols[n].name); - context->doing_protocol_init = 0; - return 1; } } @@ -286,6 +280,9 @@ next: context->protocol_init_done = 1; + if (any) + lws_tls_check_all_cert_lifetimes(context); + return 0; } @@ -303,6 +300,7 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, #endif switch (reason) { +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) case LWS_CALLBACK_HTTP: #ifndef LWS_NO_SERVER if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) @@ -325,14 +323,16 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, LWS_CB_REASON_AUX_BF__CGI)) { n = lws_cgi_write_split_stdout_headers(wsi); if (n < 0) { - lwsl_debug("LWS_CB_REASON_AUX_BF__CGI forcing close\n"); + lwsl_debug("AUX_BF__CGI forcing close\n"); return -1; } if (!n) - lws_rx_flow_control(wsi->cgi->stdwsi[LWS_STDOUT], 1); + lws_rx_flow_control( + wsi->http.cgi->stdwsi[LWS_STDOUT], 1); if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) - wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; + wsi->reason_bf &= + ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; else wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; break; @@ -341,12 +341,13 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { if (!wsi->http2_substream) { memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); - lwsl_debug("writing chunk terminator and exiting\n"); - n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, - 5, LWS_WRITE_HTTP); + lwsl_debug("writing chunk term and exiting\n"); + n = lws_write(wsi, (unsigned char *)buf + + LWS_PRE, 5, LWS_WRITE_HTTP); } else - n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, - 0, LWS_WRITE_HTTP_FINAL); + n = lws_write(wsi, (unsigned char *)buf + + LWS_PRE, 0, + LWS_WRITE_HTTP_FINAL); /* always close after sending it */ return -1; @@ -366,7 +367,8 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; if (!lws_get_child(wsi)) break; - if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0) + if (lws_http_client_read(lws_get_child(wsi), &px, + &lenx) < 0) return -1; break; } @@ -467,10 +469,10 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_CGI_TERMINATED: lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", - wsi->cgi->explicitly_chunked, - (uint64_t)wsi->cgi->content_length); - if (!wsi->cgi->explicitly_chunked && - !wsi->cgi->content_length) { + wsi->http.cgi->explicitly_chunked, + (uint64_t)wsi->http.cgi->content_length); + if (!wsi->http.cgi->explicitly_chunked && + !wsi->http.cgi->content_length) { /* send terminating chunk */ lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; @@ -492,7 +494,7 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, "sent %d only %d went", n, args->len); return n; #endif - +#endif case LWS_CALLBACK_SSL_INFO: si = in; @@ -514,11 +516,13 @@ static const struct lws_protocols protocols_dummy[] = { /* first protocol must always be HTTP handler */ { - "http-only", /* name */ - lws_callback_http_dummy, /* callback */ - 0, /* per_session_data_size */ - 0, /* max frame size / rx buffer */ - 0, NULL, 0 + "http-only", /* name */ + lws_callback_http_dummy, /* callback */ + 0, /* per_session_data_size */ + 0, /* rx_buffer_size */ + 0, /* id */ + NULL, /* user */ + 0 /* tx_packet_size */ }, /* * the other protocols are provided by lws plugins @@ -530,20 +534,25 @@ static const struct lws_protocols protocols_dummy[] = { #undef LWS_HAVE_GETENV #endif +static void +lws_vhost_destroy2(struct lws_vhost *vh); + LWS_VISIBLE struct lws_vhost * lws_create_vhost(struct lws_context *context, - struct lws_context_creation_info *info) + const struct lws_context_creation_info *info) { struct lws_vhost *vh = lws_zalloc(sizeof(*vh), "create vhost"), **vh1 = &context->vhost_list; const struct lws_http_mount *mounts; + const struct lws_protocols *pcols = info->protocols; const struct lws_protocol_vhost_options *pvo; #ifdef LWS_WITH_PLUGINS struct lws_plugin *plugin = context->plugin_list; #endif struct lws_protocols *lwsp; int m, f = !info->pvo; -#ifdef LWS_HAVE_GETENV + char buf[20]; +#if !defined(LWS_WITHOUT_CLIENT) && defined(LWS_HAVE_GETENV) char *p; #endif int n; @@ -551,8 +560,12 @@ lws_create_vhost(struct lws_context *context, if (!vh) return NULL; - if (!info->protocols) - info->protocols = &protocols_dummy[0]; +#if LWS_MAX_SMP > 1 + pthread_mutex_init(&vh->lock, NULL); +#endif + + if (!pcols) + pcols = &protocols_dummy[0]; vh->context = context; if (!info->vhost_name) @@ -560,23 +573,21 @@ lws_create_vhost(struct lws_context *context, else vh->name = info->vhost_name; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + vh->http.error_document_404 = info->error_document_404; +#endif + if (info->options & LWS_SERVER_OPTION_ONLY_RAW) lwsl_info("%s set to only support RAW\n", vh->name); -#if defined(LWS_WITH_HTTP2) - vh->set = context->set; - if (info->http2_settings[0]) - for (n = 1; n < LWS_H2_SETTINGS_LEN; n++) - vh->set.s[n] = info->http2_settings[n]; -#endif - vh->iface = info->iface; -#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) +#if !defined(LWS_WITH_ESP32) && \ + !defined(OPTEE_TA) && !defined(WIN32) vh->bind_iface = info->bind_iface; #endif for (vh->count_protocols = 0; - info->protocols[vh->count_protocols].callback; + pcols[vh->count_protocols].callback; vh->count_protocols++) ; @@ -584,7 +595,14 @@ lws_create_vhost(struct lws_context *context, vh->pvo = info->pvo; vh->headers = info->headers; vh->user = info->user; - vh->ssl_info_event_mask = info->ssl_info_event_mask; + + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) + if (ar->init_vhost) + if (ar->init_vhost(vh, info)) + return NULL; + LWS_FOR_EVERY_AVAILABLE_ROLE_END; + + if (info->keepalive_timeout) vh->keepalive_timeout = info->keepalive_timeout; else @@ -595,20 +613,49 @@ lws_create_vhost(struct lws_context *context, else vh->timeout_secs_ah_idle = 10; +#if defined(LWS_WITH_TLS) + + vh->tls.alpn = info->alpn; + vh->tls.ssl_info_event_mask = info->ssl_info_event_mask; + + if (info->ecdh_curve) + lws_strncpy(vh->tls.ecdh_curve, info->ecdh_curve, + sizeof(vh->tls.ecdh_curve)); + + /* carefully allocate and take a copy of cert + key paths if present */ + n = 0; + if (info->ssl_cert_filepath) + n += (int)strlen(info->ssl_cert_filepath) + 1; + if (info->ssl_private_key_filepath) + n += (int)strlen(info->ssl_private_key_filepath) + 1; + + if (n) { + vh->tls.key_path = vh->tls.alloc_cert_path = lws_malloc(n, "vh paths"); + if (info->ssl_cert_filepath) { + n = (int)strlen(info->ssl_cert_filepath) + 1; + memcpy(vh->tls.alloc_cert_path, info->ssl_cert_filepath, n); + vh->tls.key_path += n; + } + if (info->ssl_private_key_filepath) + memcpy(vh->tls.key_path, info->ssl_private_key_filepath, + strlen(info->ssl_private_key_filepath) + 1); + } +#endif + /* * give the vhost a unified list of protocols including the * ones that came from plugins */ - lwsp = lws_zalloc(sizeof(struct lws_protocols) * - (vh->count_protocols + - context->plugin_protocol_count + 1), "vhost-specific plugin table"); + lwsp = lws_zalloc(sizeof(struct lws_protocols) * (vh->count_protocols + + context->plugin_protocol_count + 1), + "vhost-specific plugin table"); if (!lwsp) { lwsl_err("OOM\n"); return NULL; } m = vh->count_protocols; - memcpy(lwsp, info->protocols, sizeof(struct lws_protocols) * m); + memcpy(lwsp, pcols, sizeof(struct lws_protocols) * m); /* for compatibility, all protocols enabled on vhost if only * the default vhost exists. Otherwise only vhosts who ask @@ -648,43 +695,58 @@ lws_create_vhost(struct lws_context *context, context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) vh->protocols = lwsp; else { - vh->protocols = info->protocols; + vh->protocols = pcols; lws_free(lwsp); } vh->same_vh_protocol_list = (struct lws **) - lws_zalloc(sizeof(struct lws *) * vh->count_protocols, "same vh list"); - - vh->mount_list = info->mounts; + lws_zalloc(sizeof(struct lws *) * vh->count_protocols, + "same vh list"); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + vh->http.mount_list = info->mounts; +#endif #ifdef LWS_WITH_UNIX_SOCK if (LWS_UNIX_SOCK_ENABLED(context)) { lwsl_notice("Creating Vhost '%s' path \"%s\", %d protocols\n", - vh->name, info->iface, vh->count_protocols); + vh->name, vh->iface, vh->count_protocols); } else #endif - lwsl_notice("Creating Vhost '%s' port %d, %d protocols, IPv6 %s\n", - vh->name, info->port, vh->count_protocols, - LWS_IPV6_ENABLED(vh) ? "on" : "off"); - + { + switch(info->port) { + case CONTEXT_PORT_NO_LISTEN: + strcpy(buf, "(serving disabled)"); + break; + case CONTEXT_PORT_NO_LISTEN_SERVER: + strcpy(buf, "(no listener)"); + break; + default: + lws_snprintf(buf, sizeof(buf), "port %u", info->port); + break; + } + lwsl_notice("Creating Vhost '%s' %s, %d protocols, IPv6 %s\n", + vh->name, buf, vh->count_protocols, + LWS_IPV6_ENABLED(vh) ? "on" : "off"); + } mounts = info->mounts; while (mounts) { (void)mount_protocols[0]; - lwsl_notice(" mounting %s%s to %s\n", - mount_protocols[mounts->origin_protocol], - mounts->origin, mounts->mountpoint); + lwsl_info(" mounting %s%s to %s\n", + mount_protocols[mounts->origin_protocol], + mounts->origin, mounts->mountpoint); /* convert interpreter protocol names to pointers */ pvo = mounts->interpret; while (pvo) { - for (n = 0; n < vh->count_protocols; n++) - if (!strcmp(pvo->value, vh->protocols[n].name)) { - ((struct lws_protocol_vhost_options *)pvo)->value = - (const char *)(lws_intptr_t)n; - break; - } + for (n = 0; n < vh->count_protocols; n++) { + if (strcmp(pvo->value, vh->protocols[n].name)) + continue; + ((struct lws_protocol_vhost_options *)pvo)-> + value = (const char *)(lws_intptr_t)n; + break; + } if (n == vh->count_protocols) - lwsl_err("ignoring unknown interpret protocol %s\n", + lwsl_err("ignoring unknown interp pr %s\n", pvo->value); pvo = pvo->next; } @@ -692,64 +754,35 @@ lws_create_vhost(struct lws_context *context, mounts = mounts->mount_next; } -#ifndef LWS_NO_EXTENSIONS -#ifdef LWS_WITH_PLUGINS - if (context->plugin_extension_count) { - - m = 0; - while (info->extensions && info->extensions[m].callback) - m++; - - /* - * give the vhost a unified list of extensions including the - * ones that came from plugins - */ - vh->extensions = lws_zalloc(sizeof(struct lws_extension) * - (m + - context->plugin_extension_count + 1), "extensions"); - if (!vh->extensions) - return NULL; - - memcpy((struct lws_extension *)vh->extensions, info->extensions, - sizeof(struct lws_extension) * m); - plugin = context->plugin_list; - while (plugin) { - memcpy((struct lws_extension *)&vh->extensions[m], - plugin->caps.extensions, - sizeof(struct lws_extension) * - plugin->caps.count_extensions); - m += plugin->caps.count_extensions; - plugin = plugin->list; - } - } else -#endif - vh->extensions = info->extensions; -#endif - vh->listen_port = info->port; -#if !defined(LWS_WITH_ESP8266) - vh->http_proxy_port = 0; - vh->http_proxy_address[0] = '\0'; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + vh->http.http_proxy_port = 0; + vh->http.http_proxy_address[0] = '\0'; +#endif #if defined(LWS_WITH_SOCKS5) vh->socks_proxy_port = 0; vh->socks_proxy_address[0] = '\0'; #endif +#if !defined(LWS_WITHOUT_CLIENT) /* either use proxy from info, or try get it from env var */ - +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* http proxy */ if (info->http_proxy_address) { /* override for backwards compatibility */ if (info->http_proxy_port) - vh->http_proxy_port = info->http_proxy_port; + vh->http.http_proxy_port = info->http_proxy_port; lws_set_proxy(vh, info->http_proxy_address); - } else { + } else +#endif + { #ifdef LWS_HAVE_GETENV p = getenv("http_proxy"); if (p) lws_set_proxy(vh, p); #endif } +#endif #if defined(LWS_WITH_SOCKS5) /* socks proxy */ if (info->socks_proxy_address) { @@ -765,7 +798,6 @@ lws_create_vhost(struct lws_context *context, #endif } #endif -#endif vh->ka_time = info->ka_time; vh->ka_interval = info->ka_interval; @@ -793,15 +825,23 @@ lws_create_vhost(struct lws_context *context, } else vh->log_fd = (int)LWS_INVALID_FILE; #endif - if (lws_context_init_server_ssl(info, vh)) - goto bail; - if (lws_context_init_client_ssl(info, vh)) - goto bail; - if (lws_context_init_server(info, vh)) { + if (lws_context_init_server_ssl(info, vh)) { + lwsl_err("%s: lws_context_init_server_ssl failed\n", __func__); + goto bail1; + } + if (lws_context_init_client_ssl(info, vh)) { + lwsl_err("%s: lws_context_init_client_ssl failed\n", __func__); + goto bail1; + } + lws_context_lock(context); + n = _lws_vhost_init_server(info, vh); + lws_context_unlock(context); + if (n < 0) { lwsl_err("init server failed\n"); - goto bail; + goto bail1; } + while (1) { if (!(*vh1)) { *vh1 = vh; @@ -809,15 +849,27 @@ lws_create_vhost(struct lws_context *context, } vh1 = &(*vh1)->vhost_next; }; + /* for the case we are adding a vhost much later, after server init */ if (context->protocol_init_done) - lws_protocol_init(context); + if (lws_protocol_init(context)) { + lwsl_err("%s: lws_protocol_init failed\n", __func__); + goto bail1; + } return vh; +bail1: + lws_vhost_destroy(vh); + lws_vhost_destroy2(vh); + + return NULL; + +#ifdef LWS_WITH_ACCESS_LOG bail: lws_free(vh); +#endif return NULL; } @@ -834,8 +886,100 @@ lws_init_vhost_client_ssl(const struct lws_context_creation_info *info, return lws_context_init_client_ssl(&i, vhost); } +LWS_VISIBLE void +lws_cancel_service_pt(struct lws *wsi) +{ + lws_plat_pipe_signal(wsi); +} + +LWS_VISIBLE void +lws_cancel_service(struct lws_context *context) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + short m = context->count_threads; + + if (context->being_destroyed1) + return; + + lwsl_info("%s\n", __func__); + + while (m--) { + if (pt->pipe_wsi) + lws_plat_pipe_signal(pt->pipe_wsi); + pt++; + } +} + +int +lws_create_event_pipes(struct lws_context *context) +{ + struct lws *wsi; + int n; + + /* + * Create the pt event pipes... these are unique in that they are + * not bound to a vhost or protocol (both are NULL) + */ + + for (n = 0; n < context->count_threads; n++) { + if (context->pt[n].pipe_wsi) + continue; + + wsi = lws_zalloc(sizeof(*wsi), "event pipe wsi"); + if (!wsi) { + lwsl_err("Out of mem\n"); + return 1; + } + wsi->context = context; + lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_pipe); + wsi->protocol = NULL; + wsi->tsi = n; + wsi->vhost = NULL; + wsi->event_pipe = 1; + + if (lws_plat_pipe_create(wsi)) { + lws_free(wsi); + continue; + } + wsi->desc.sockfd = context->pt[n].dummy_pipe_fds[0]; + lwsl_debug("event pipe fd %d\n", wsi->desc.sockfd); + + context->pt[n].pipe_wsi = wsi; + + if (context->event_loop_ops->accept) + context->event_loop_ops->accept(wsi); + + if (__insert_wsi_socket_into_fds(context, wsi)) + return 1; + + //lws_change_pollfd(context->pt[n].pipe_wsi, 0, LWS_POLLIN); + context->count_wsi_allocated++; + } + + return 0; +} + +void +lws_destroy_event_pipe(struct lws *wsi) +{ + lwsl_info("%s\n", __func__); + __remove_wsi_socket_from_fds(wsi); + + if (wsi->context->event_loop_ops->wsi_logical_close) { + wsi->context->event_loop_ops->wsi_logical_close(wsi); + lws_plat_pipe_close(wsi); + return; + } + + if (wsi->context->event_loop_ops->destroy_wsi) + wsi->context->event_loop_ops->destroy_wsi(wsi); + lws_plat_pipe_close(wsi); + wsi->context->count_wsi_allocated--; + lws_free(wsi); +} + LWS_VISIBLE struct lws_context * -lws_create_context(struct lws_context_creation_info *info) +lws_create_context(const struct lws_context_creation_info *info) { struct lws_context *context = NULL; struct lws_plat_file_ops *prev; @@ -847,12 +991,14 @@ lws_create_context(struct lws_context_creation_info *info) struct rlimit rt; #endif + + lwsl_info("Initial logging level %d\n", log_level); lwsl_info("Libwebsockets version: %s\n", library_version); #if defined(GCC_VER) lwsl_info("Compiled with %s\n", GCC_VER); #endif -#if LWS_POSIX + #ifdef LWS_WITH_IPV6 if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_IPV6)) lwsl_info("IPV6 compiled in and enabled\n"); @@ -861,11 +1007,7 @@ lws_create_context(struct lws_context_creation_info *info) #else lwsl_info("IPV6 not compiled in\n"); #endif -#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_ESP32) - lws_feature_status_libev(info); - lws_feature_status_libuv(info); -#endif -#endif + lwsl_info(" LWS_DEF_HEADER_LEN : %u\n", LWS_DEF_HEADER_LEN); lwsl_info(" LWS_MAX_PROTOCOLS : %u\n", LWS_MAX_PROTOCOLS); lwsl_info(" LWS_MAX_SMP : %u\n", LWS_MAX_SMP); @@ -873,13 +1015,11 @@ lws_create_context(struct lws_context_creation_info *info) #if defined(LWS_WITH_STATS) lwsl_info(" LWS_WITH_STATS : on\n"); #endif -#if LWS_POSIX lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH); -#endif #if defined(LWS_WITH_HTTP2) lwsl_info(" HTTP2 support : available\n"); #else - lwsl_info(" HTTP2 support : not configured"); + lwsl_info(" HTTP2 support : not configured\n"); #endif if (lws_plat_context_early_init()) return NULL; @@ -889,13 +1029,22 @@ lws_create_context(struct lws_context_creation_info *info) lwsl_err("No memory for websocket context\n"); return NULL; } + +#if defined(LWS_WITH_TLS) +#if defined(LWS_WITH_MBEDTLS) + context->tls_ops = &tls_ops_mbedtls; +#else + context->tls_ops = &tls_ops_openssl; +#endif +#endif + if (info->pt_serv_buf_size) context->pt_serv_buf_size = info->pt_serv_buf_size; else context->pt_serv_buf_size = 4096; -#if defined(LWS_WITH_HTTP2) - context->set = lws_h2_stock_settings; +#if defined(LWS_ROLE_H2) + role_ops_h2.init_context(context, info); #endif #if LWS_MAX_SMP > 1 @@ -943,8 +1092,10 @@ lws_create_context(struct lws_context_creation_info *info) info->external_baggage_free_on_destroy; context->time_up = time(NULL); + context->pcontext_finalize = info->pcontext; - context->simultaneous_ssl_restriction = info->simultaneous_ssl_restriction; + context->simultaneous_ssl_restriction = + info->simultaneous_ssl_restriction; #ifndef LWS_NO_DAEMONIZE if (pid_daemon) { @@ -975,6 +1126,67 @@ lws_create_context(struct lws_context_creation_info *info) context->options = info->options; + /* + * set the context event loops ops struct + * + * after this, all event_loop actions use the generic ops + */ + +#if defined(LWS_WITH_POLL) + context->event_loop_ops = &event_loop_ops_poll; +#endif + + if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV)) +#if defined(LWS_WITH_LIBUV) + context->event_loop_ops = &event_loop_ops_uv; +#else + goto fail_event_libs; +#endif + + if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV)) +#if defined(LWS_WITH_LIBEV) + context->event_loop_ops = &event_loop_ops_ev; +#else + goto fail_event_libs; +#endif + + if (lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT)) +#if defined(LWS_WITH_LIBEVENT) + context->event_loop_ops = &event_loop_ops_event; +#else + goto fail_event_libs; +#endif + + if (!context->event_loop_ops) + goto fail_event_libs; + + lwsl_info("Using event loop: %s\n", context->event_loop_ops->name); + +#if defined(LWS_WITH_TLS) + time(&context->tls.last_cert_check_s); + if (info->alpn) + context->tls.alpn_default = info->alpn; + else { + char *p = context->tls.alpn_discovered, first = 1; + + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) { + if (ar->alpn) { + if (!first) + *p++ = ','; + p += lws_snprintf(p, + context->tls.alpn_discovered + + sizeof(context->tls.alpn_discovered) - + 2 - p, "%s", ar->alpn); + first = 0; + } + } LWS_FOR_EVERY_AVAILABLE_ROLE_END; + + context->tls.alpn_default = context->tls.alpn_discovered; + } + + lwsl_info("Default ALPN advertisment: %s\n", context->tls.alpn_default); +#endif + if (info->timeout_secs) context->timeout_secs = info->timeout_secs; else @@ -992,10 +1204,17 @@ lws_create_context(struct lws_context_creation_info *info) info->max_http_header_data2; else context->max_http_header_data = LWS_DEF_HEADER_LEN; + if (info->max_http_header_pool) context->max_http_header_pool = info->max_http_header_pool; else - context->max_http_header_pool = LWS_DEF_HEADER_POOL; + context->max_http_header_pool = context->max_fds; + + if (info->fd_limit_per_thread) + context->fd_limit_per_thread = info->fd_limit_per_thread; + else + context->fd_limit_per_thread = context->max_fds / + context->count_threads; /* * Allocate the per-thread storage for scratchpad buffers, @@ -1009,22 +1228,16 @@ lws_create_context(struct lws_context_creation_info *info) return NULL; } -#ifdef LWS_WITH_LIBUV context->pt[n].context = context; -#endif context->pt[n].tid = n; - context->pt[n].ah_list = NULL; - context->pt[n].ah_pool_length = 0; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + context->pt[n].http.ah_list = NULL; + context->pt[n].http.ah_pool_length = 0; +#endif lws_pt_mutex_init(&context->pt[n]); } - if (info->fd_limit_per_thread) - context->fd_limit_per_thread = info->fd_limit_per_thread; - else - context->fd_limit_per_thread = context->max_fds / - context->count_threads; - lwsl_info(" Threads: %d each %d fds\n", context->count_threads, context->fd_limit_per_thread); @@ -1033,36 +1246,6 @@ lws_create_context(struct lws_context_creation_info *info) return NULL; } -#ifdef LWS_WITH_LIBEV - /* (Issue #264) In order to *avoid breaking backwards compatibility*, we - * enable libev mediated SIGINT handling with a default handler of - * lws_sigint_cb. The handler can be overridden or disabled - * by invoking lws_sigint_cfg after creating the context, but - * before invoking lws_initloop: - */ - context->use_ev_sigint = 1; - context->lws_ev_sigint_cb = &lws_ev_sigint_cb; -#endif /* LWS_WITH_LIBEV */ -#ifdef LWS_WITH_LIBUV - /* (Issue #264) In order to *avoid breaking backwards compatibility*, we - * enable libev mediated SIGINT handling with a default handler of - * lws_sigint_cb. The handler can be overridden or disabled - * by invoking lws_sigint_cfg after creating the context, but - * before invoking lws_initloop: - */ - context->use_ev_sigint = 1; - context->lws_uv_sigint_cb = &lws_uv_sigint_cb; -#endif -#ifdef LWS_WITH_LIBEVENT - /* (Issue #264) In order to *avoid breaking backwards compatibility*, we - * enable libev mediated SIGINT handling with a default handler of - * lws_sigint_cb. The handler can be overridden or disabled - * by invoking lws_sigint_cfg after creating the context, but - * before invoking lws_initloop: - */ - context->use_ev_sigint = 1; - context->lws_event_sigint_cb = &lws_event_sigint_cb; -#endif /* LWS_WITH_LIBEVENT */ #if defined(LWS_WITH_PEER_LIMITS) /* scale the peer hash table according to the max fds for the process, @@ -1077,14 +1260,14 @@ lws_create_context(struct lws_context_creation_info *info) context->ip_limit_wsi = info->ip_limit_wsi; #endif - lwsl_info(" mem: context: %5lu bytes (%ld ctx + (%ld thr x %d))\n", + lwsl_info(" mem: context: %5lu B (%ld ctx + (%ld thr x %d))\n", (long)sizeof(struct lws_context) + (context->count_threads * context->pt_serv_buf_size), (long)sizeof(struct lws_context), (long)context->count_threads, context->pt_serv_buf_size); - - lwsl_info(" mem: http hdr rsvd: %5lu bytes (%u thr x (%u + %lu) x %u))\n", +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + lwsl_info(" mem: http hdr rsvd: %5lu B (%u thr x (%u + %lu) x %u))\n", (long)(context->max_http_header_data + sizeof(struct allocated_headers)) * context->max_http_header_pool * context->count_threads, @@ -1092,6 +1275,7 @@ lws_create_context(struct lws_context_creation_info *info) context->max_http_header_data, (long)sizeof(struct allocated_headers), context->max_http_header_pool); +#endif n = sizeof(struct lws_pollfd) * context->count_threads * context->fd_limit_per_thread; context->pt[0].fds = lws_zalloc(n, "fds table"); @@ -1117,14 +1301,24 @@ lws_create_context(struct lws_context_creation_info *info) if (lws_plat_init(context, info)) goto bail; -#if defined(LWS_WITH_HTTP2) - /* - * let the user code see what the platform default SETTINGS were, he - * can modify them when he creates the vhosts. - */ - for (n = 1; n < LWS_H2_SETTINGS_LEN; n++) - info->http2_settings[n] = context->set.s[n]; -#endif + if (context->event_loop_ops->init_context) + if (context->event_loop_ops->init_context(context, info)) + goto bail; + + + if (context->event_loop_ops->init_pt) + for (n = 0; n < context->count_threads; n++) { + void *lp = NULL; + + if (info->foreign_loops) + lp = info->foreign_loops[n]; + + if (context->event_loop_ops->init_pt(context, lp, n)) + goto bail; + } + + if (lws_create_event_pipes(context)) + goto bail; lws_context_init_ssl_library(info); @@ -1137,6 +1331,14 @@ lws_create_context(struct lws_context_creation_info *info) if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) if (!lws_create_vhost(context, info)) { lwsl_err("Failed to create default vhost\n"); + for (n = 0; n < context->count_threads; n++) + lws_free_set_NULL(context->pt[n].serv_buf); +#if defined(LWS_WITH_PEER_LIMITS) + lws_free_set_NULL(context->pl_hash_table); +#endif + lws_free_set_NULL(context->pt[0].fds); + lws_plat_context_late_destroy(context); + lws_free_set_NULL(context); return NULL; } @@ -1164,23 +1366,32 @@ lws_create_context(struct lws_context_creation_info *info) if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) lws_plat_drop_app_privileges(info); - /* - * give all extensions a chance to create any per-context - * allocations they need - */ - if (info->port != CONTEXT_PORT_NO_LISTEN) { - if (lws_ext_cb_all_exts(context, NULL, - LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT, NULL, 0) < 0) - goto bail; - } else - if (lws_ext_cb_all_exts(context, NULL, - LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT, NULL, 0) < 0) - goto bail; + /* expedite post-context init (eg, protocols) */ + lws_cancel_service(context); + +#if defined(LWS_WITH_SELFTESTS) + lws_jws_selftest(); +#endif return context; bail: lws_context_destroy(context); + + return NULL; + +fail_event_libs: + lwsl_err("Requested event library support not configured, available:\n"); + { + const struct lws_event_loop_ops **elops = available_event_libs; + + while (*elops) { + lwsl_err(" - %s\n", (*elops)->name); + elops++; + } + } + lws_free(context); + return NULL; } @@ -1205,7 +1416,7 @@ lws_context_deprecate(struct lws_context *context, lws_reload_func cb) wsi = vh->lserv_wsi; if (wsi) { wsi->socket_is_permanently_unusable = 1; - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "ctx deprecate"); wsi->context->deprecation_pending_listen_close_count++; /* * other vhosts can share the listen port, they @@ -1231,11 +1442,7 @@ lws_context_is_deprecated(struct lws_context *context) return context->deprecated; } -LWS_VISIBLE void -lws_context_destroy2(struct lws_context *context); - - -static void +void lws_vhost_destroy1(struct lws_vhost *vh) { const struct lws_protocols *protocol = NULL; @@ -1259,7 +1466,8 @@ lws_vhost_destroy1(struct lws_vhost *vh) */ if (vh->lserv_wsi) - lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { + lws_start_foreach_ll(struct lws_vhost *, v, + context->vhost_list) { if (v != vh && !v->being_destroyed && v->listen_port == vh->listen_port && @@ -1302,13 +1510,21 @@ lws_vhost_destroy1(struct lws_vhost *vh) continue; lws_close_free_wsi(wsi, - LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY + LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY, + "vh destroy" /* no protocol close */); n--; } } /* + * destroy any pending timed events + */ + + while (vh->timed_vh_protocol_list) + lws_timed_callback_remove(vh, vh->timed_vh_protocol_list); + + /* * let the protocols destroy the per-vhost protocol objects */ @@ -1316,7 +1532,7 @@ lws_vhost_destroy1(struct lws_vhost *vh) wsi.context = vh->context; wsi.vhost = vh; protocol = vh->protocols; - if (protocol) { + if (protocol && vh->created_vhost_protocols) { n = 0; while (n < vh->count_protocols) { wsi.protocol = protocol; @@ -1397,28 +1613,37 @@ lws_vhost_destroy2(struct lws_vhost *vh) lws_free(vh->protocol_vh_privs); lws_ssl_SSL_CTX_destroy(vh); lws_free(vh->same_vh_protocol_list); -#ifdef LWS_WITH_PLUGINS - if (LWS_LIBUV_ENABLED(context)) { - if (context->plugin_list) - lws_free((void *)vh->protocols); - } else -#endif - { - if (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) - lws_free((void *)vh->protocols); - } -#ifdef LWS_WITH_PLUGINS -#ifndef LWS_NO_EXTENSIONS - if (context->plugin_extension_count) - lws_free((void *)vh->extensions); -#endif -#endif + if (context->plugin_list || + (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) + lws_free((void *)vh->protocols); + + LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) + if (ar->destroy_vhost) + ar->destroy_vhost(vh); + LWS_FOR_EVERY_AVAILABLE_ROLE_END; + #ifdef LWS_WITH_ACCESS_LOG if (vh->log_fd != (int)LWS_INVALID_FILE) close(vh->log_fd); #endif +#if defined (LWS_WITH_TLS) + lws_free_set_NULL(vh->tls.alloc_cert_path); +#endif + +#if LWS_MAX_SMP > 1 + pthread_mutex_destroy(&vh->lock); +#endif + +#if defined(LWS_WITH_UNIX_SOCK) + if (LWS_UNIX_SOCK_ENABLED(context)) { + n = unlink(vh->iface); + if (n) + lwsl_info("Closing unix socket %s: errno %d\n", + vh->iface, errno); + } +#endif /* * although async event callbacks may still come for wsi handles with * pending close in the case of asycn event library like libuv, @@ -1439,7 +1664,8 @@ lws_check_deferred_free(struct lws_context *context, int force) lws_start_foreach_llp(struct lws_deferred_free **, pdf, context->deferred_free_list) { - if (now > (*pdf)->deadline || force) { + if (force || + lws_compare_time_t(context, now, (*pdf)->deadline) > 5) { df = *pdf; *pdf = df->next; /* finalize vh destruction */ @@ -1466,119 +1692,86 @@ lws_vhost_destroy(struct lws_vhost *vh) /* part 2 is deferred to allow all the handle closes to complete */ df->next = vh->context->deferred_free_list; - df->deadline = lws_now_secs() + 5; + df->deadline = lws_now_secs(); df->payload = vh; vh->context->deferred_free_list = df; } -LWS_VISIBLE void -lws_context_destroy(struct lws_context *context) -{ - struct lws_context_per_thread *pt; - struct lws_vhost *vh = NULL; - struct lws wsi; - int n, m; - - if (!context) { - lwsl_notice("%s: ctx %p\n", __func__, context); - return; - } - if (context->being_destroyed1) { - lwsl_notice("%s: ctx %p: already being destroyed\n", - __func__, context); - return; - } - - lwsl_info("%s: ctx %p\n", __func__, context); - - m = context->count_threads; - context->being_destroyed = 1; - context->being_destroyed1 = 1; - - memset(&wsi, 0, sizeof(wsi)); - wsi.context = context; - -#ifdef LWS_LATENCY - if (context->worst_latency_info[0]) - lwsl_notice("Worst latency: %s\n", context->worst_latency_info); -#endif - - while (m--) { - pt = &context->pt[m]; - - for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { - struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); - if (!wsi) - continue; - - lws_close_free_wsi(wsi, - LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY - /* no protocol close */); - n--; - } - lws_pt_mutex_destroy(pt); - } - - /* - * give all extensions a chance to clean up any per-context - * allocations they might have made - */ - - n = lws_ext_cb_all_exts(context, NULL, - LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0); +/* + * When using an event loop, the context destruction is in three separate + * parts. This is to cover both internal and foreign event loops cleanly. + * + * - lws_context_destroy() simply starts a soft close of all wsi and + * related allocations. The event loop continues. + * + * As the closes complete in the event loop, reference counting is used + * to determine when everything is closed. It then calls + * lws_context_destroy2(). + * + * - lws_context_destroy2() cleans up the rest of the higher-level logical + * lws pieces like vhosts. If the loop was foreign, it then proceeds to + * lws_context_destroy3(). If it the loop is internal, it stops the + * internal loops and waits for lws_context_destroy() to be called again + * outside the event loop (since we cannot destroy the loop from + * within the loop). That will cause lws_context_destroy3() to run + * directly. + * + * - lws_context_destroy3() destroys any internal event loops and then + * destroys the context itself, setting what was info.pcontext to NULL. + */ - n = lws_ext_cb_all_exts(context, NULL, - LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0); +/* + * destroy the actual context itself + */ - /* - * inform all the protocols that they are done and will have no more - * callbacks. - * - * We can't free things until after the event loop shuts down. - */ - if (context->protocol_init_done) - vh = context->vhost_list; - while (vh) { - struct lws_vhost *vhn = vh->vhost_next; - lws_vhost_destroy1(vh); - vh = vhn; - } +static void +lws_context_destroy3(struct lws_context *context) +{ + struct lws_context **pcontext_finalize = context->pcontext_finalize; + struct lws_context_per_thread *pt; + int n; for (n = 0; n < context->count_threads; n++) { pt = &context->pt[n]; - lws_libev_destroyloop(context, n); - lws_libuv_destroyloop(context, n); - lws_libevent_destroyloop(context, n); + if (context->event_loop_ops->destroy_pt) + context->event_loop_ops->destroy_pt(context, n); lws_free_set_NULL(context->pt[n].serv_buf); - while (pt->ah_list) - _lws_destroy_ah(pt, pt->ah_list); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + while (pt->http.ah_list) + _lws_destroy_ah(pt, pt->http.ah_list); +#endif } - lws_plat_context_early_destroy(context); - if (context->pt[0].fds) - lws_free_set_NULL(context->pt[0].fds); + lws_free(context); + lwsl_info("%s: ctx %p freed\n", __func__, context); - if (!LWS_LIBUV_ENABLED(context)) - lws_context_destroy2(context); + if (pcontext_finalize) + *pcontext_finalize = NULL; } /* - * call the second one after the event loop has been shut down cleanly + * really start destroying things */ -LWS_VISIBLE void +void lws_context_destroy2(struct lws_context *context) { struct lws_vhost *vh = NULL, *vh1; #if defined(LWS_WITH_PEER_LIMITS) - uint32_t n; + uint32_t nu; #endif + int n; lwsl_info("%s: ctx %p\n", __func__, context); + context->being_destroyed2 = 1; + + if (context->pt[0].fds) + lws_free_set_NULL(context->pt[0].fds); + /* * free all the per-vhost allocations */ @@ -1603,9 +1796,9 @@ lws_context_destroy2(struct lws_context *context) lws_plat_context_late_destroy(context); #if defined(LWS_WITH_PEER_LIMITS) - for (n = 0; n < context->pl_hash_elements; n++) { + for (nu = 0; nu < context->pl_hash_elements; nu++) { lws_start_foreach_llp(struct lws_peer **, peer, - context->pl_hash_table[n]) { + context->pl_hash_table[nu]) { struct lws_peer *df = *peer; *peer = df->next; lws_free(df); @@ -1624,5 +1817,141 @@ lws_context_destroy2(struct lws_context *context) pthread_mutex_destroy(&context->lock); #endif - lws_free(context); + if (context->event_loop_ops->destroy_context2) + if (context->event_loop_ops->destroy_context2(context)) { + context->finalize_destroy_after_internal_loops_stopped = 1; + return; + } + + if (!context->pt[0].event_loop_foreign) + for (n = 0; n < context->count_threads; n++) + if (context->pt[n].inside_service) + return; + + lws_context_destroy3(context); +} + +/* + * Begin the context takedown + */ + +LWS_VISIBLE void +lws_context_destroy(struct lws_context *context) +{ + volatile struct lws_foreign_thread_pollfd *ftp, *next; + volatile struct lws_context_per_thread *vpt; + struct lws_context_per_thread *pt; + struct lws_vhost *vh = NULL; + struct lws wsi; + int n, m; + + if (!context) + return; + + if (context->finalize_destroy_after_internal_loops_stopped) { + if (context->event_loop_ops->destroy_context2) + context->event_loop_ops->destroy_context2(context); + + lws_context_destroy3(context); + + return; + } + + if (context->being_destroyed1) { + if (!context->being_destroyed2) { + lws_context_destroy2(context); + + return; + } + lwsl_info("%s: ctx %p: already being destroyed\n", + __func__, context); + + lws_context_destroy3(context); + return; + } + + lwsl_info("%s: ctx %p\n", __func__, context); + + m = context->count_threads; + context->being_destroyed = 1; + context->being_destroyed1 = 1; + context->requested_kill = 1; + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = context; + +#ifdef LWS_LATENCY + if (context->worst_latency_info[0]) + lwsl_notice("Worst latency: %s\n", context->worst_latency_info); +#endif + + while (m--) { + pt = &context->pt[m]; + vpt = (volatile struct lws_context_per_thread *)pt; + + ftp = vpt->foreign_pfd_list; + while (ftp) { + next = ftp->next; + lws_free((void *)ftp); + ftp = next; + } + vpt->foreign_pfd_list = NULL; + + for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { + struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + + if (wsi->event_pipe) + lws_destroy_event_pipe(wsi); + else + lws_close_free_wsi(wsi, + LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY, + "ctx destroy" + /* no protocol close */); + n--; + } + lws_pt_mutex_destroy(pt); + } + + /* + * inform all the protocols that they are done and will have no more + * callbacks. + * + * We can't free things until after the event loop shuts down. + */ + if (context->protocol_init_done) + vh = context->vhost_list; + while (vh) { + struct lws_vhost *vhn = vh->vhost_next; + lws_vhost_destroy1(vh); + vh = vhn; + } + + lws_plat_context_early_destroy(context); + + /* + * We face two different needs depending if foreign loop or not. + * + * 1) If foreign loop, we really want to advance the destroy_context() + * past here, and block only for libuv-style async close completion. + * + * 2a) If poll, and we exited by ourselves and are calling a final + * destroy_context() outside of any service already, we want to + * advance all the way in one step. + * + * 2b) If poll, and we are reacting to a SIGINT, service thread(s) may + * be in poll wait or servicing. We can't advance the + * destroy_context() to the point it's freeing things; we have to + * leave that for the final destroy_context() after the service + * thread(s) are finished calling for service. + */ + + if (context->event_loop_ops->destroy_context1) { + context->event_loop_ops->destroy_context1(context); + + return; + } + + lws_context_destroy2(context); } diff --git a/thirdparty/lws/libwebsockets.c b/thirdparty/libwebsockets/core/libwebsockets.c index 8fe0854041..0da02b17e4 100644 --- a/thirdparty/lws/libwebsockets.c +++ b/thirdparty/libwebsockets/core/libwebsockets.c @@ -19,7 +19,7 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include "core/private.h" #ifdef LWS_HAVE_SYS_TYPES_H #include <sys/types.h> @@ -27,6 +27,7 @@ #ifdef LWS_WITH_IPV6 #if defined(WIN32) || defined(_WIN32) +#include <wincrypt.h> #include <Iphlpapi.h> #else #include <net/if.h> @@ -57,16 +58,41 @@ static const char * const log_level_names[] = { }; #endif -void -lws_free_wsi(struct lws *wsi) +#if defined (_DEBUG) +void lwsi_set_role(struct lws *wsi, lws_wsi_state_t role) { - struct lws_context_per_thread *pt; - struct allocated_headers *ah; + wsi->wsistate = (wsi->wsistate & (~LWSI_ROLE_MASK)) | role; + + lwsl_debug("lwsi_set_role(%p, 0x%x)\n", wsi, wsi->wsistate); +} + +void lwsi_set_state(struct lws *wsi, lws_wsi_state_t lrs) +{ + wsi->wsistate = (wsi->wsistate & (~LRS_MASK)) | lrs; + lwsl_debug("lwsi_set_state(%p, 0x%x)\n", wsi, wsi->wsistate); +} +#endif + +signed char char_to_hex(const char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +void +__lws_free_wsi(struct lws *wsi) +{ if (!wsi) return; - - pt = &wsi->context->pt[(int)wsi->tsi]; /* * Protocol user data may be allocated either internally by lws @@ -76,51 +102,32 @@ lws_free_wsi(struct lws *wsi) wsi->user_space && !wsi->user_space_externally_allocated) lws_free(wsi->user_space); - lws_free_set_NULL(wsi->rxflow_buffer); + lws_buflist_destroy_all_segments(&wsi->buflist); lws_free_set_NULL(wsi->trunc_alloc); + lws_free_set_NULL(wsi->udp); - /* we may not have an ah, but may be on the waiting list... */ - lwsl_info("ah det due to close\n"); - /* we're closing, losing some rx is OK */ - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 0); - - if (wsi->vhost->lserv_wsi == wsi) + if (wsi->vhost && wsi->vhost->lserv_wsi == wsi) wsi->vhost->lserv_wsi = NULL; - lws_pt_lock(pt); - ah = pt->ah_list; - while (ah) { - if (ah->in_use && ah->wsi == wsi) { - lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi); - ah->in_use = 0; - ah->wsi = NULL; - pt->ah_count_in_use--; - break; - } - ah = ah->next; - } + // lws_peer_dump_from_wsi(wsi); + + if (wsi->role_ops->destroy_role) + wsi->role_ops->destroy_role(wsi); #if defined(LWS_WITH_PEER_LIMITS) lws_peer_track_wsi_close(wsi->context, wsi->peer); wsi->peer = NULL; #endif -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2 || wsi->http2_substream) { - lws_hpack_destroy_dynamic_header(wsi); + /* since we will destroy the wsi, make absolutely sure now */ - if (wsi->u.h2.h2n) - lws_free_set_NULL(wsi->u.h2.h2n); - } +#if defined(LWS_WITH_OPENSSL) + __lws_ssl_remove_wsi_from_buffered_list(wsi); #endif + __lws_remove_from_timeout_list(wsi); - lws_pt_unlock(pt); - - /* since we will destroy the wsi, make absolutely sure now */ - - lws_ssl_remove_wsi_from_buffered_list(wsi); - lws_remove_from_timeout_list(wsi); + if (wsi->context->event_loop_ops->destroy_wsi) + wsi->context->event_loop_ops->destroy_wsi(wsi); wsi->context->count_wsi_allocated--; lwsl_debug("%s: %p, remaining wsi %d\n", __func__, wsi, @@ -130,63 +137,301 @@ lws_free_wsi(struct lws *wsi) } void -lws_remove_from_timeout_list(struct lws *wsi) +lws_dll_add_front(struct lws_dll *d, struct lws_dll *phead) { - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + if (d->prev) + return; + + /* our next guy is current first guy */ + d->next = phead->next; + /* if there is a next guy, set his prev ptr to our next ptr */ + if (d->next) + d->next->prev = d; + /* our prev ptr is first ptr */ + d->prev = phead; + /* set the first guy to be us */ + phead->next = d; +} + +/* situation is: + * + * HEAD: struct lws_dll * = &entry1 + * + * Entry 1: struct lws_dll .pprev = &HEAD , .next = Entry 2 + * Entry 2: struct lws_dll .pprev = &entry1 , .next = &entry2 + * Entry 3: struct lws_dll .pprev = &entry2 , .next = NULL + * + * Delete Entry1: + * + * - HEAD = &entry2 + * - Entry2: .pprev = &HEAD, .next = &entry3 + * - Entry3: .pprev = &entry2, .next = NULL + * + * Delete Entry2: + * + * - HEAD = &entry1 + * - Entry1: .pprev = &HEAD, .next = &entry3 + * - Entry3: .pprev = &entry1, .next = NULL + * + * Delete Entry3: + * + * - HEAD = &entry1 + * - Entry1: .pprev = &HEAD, .next = &entry2 + * - Entry2: .pprev = &entry1, .next = NULL + * + */ - if (!wsi->timeout_list_prev) /* ie, not part of the list */ +void +lws_dll_remove(struct lws_dll *d) +{ + if (!d->prev) /* ie, not part of the list */ return; - lws_pt_lock(pt); + /* + * remove us + * + * USp <-> us <-> USn --> USp <-> USn + */ + /* if we have a next guy, set his prev to our prev */ - if (wsi->timeout_list) - wsi->timeout_list->timeout_list_prev = wsi->timeout_list_prev; + if (d->next) + d->next->prev = d->prev; + /* set our prev guy to our next guy instead of us */ - *wsi->timeout_list_prev = wsi->timeout_list; + if (d->prev) + d->prev->next = d->next; /* we're out of the list, we should not point anywhere any more */ - wsi->timeout_list_prev = NULL; - wsi->timeout_list = NULL; + d->prev = NULL; + d->next = NULL; +} + +void +__lws_remove_from_timeout_list(struct lws *wsi) +{ + lws_dll_lws_remove(&wsi->dll_timeout); +} + +void +lws_remove_from_timeout_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt, __func__); + __lws_remove_from_timeout_list(wsi); lws_pt_unlock(pt); } +void +lws_dll_dump(struct lws_dll_lws *head, const char *title) +{ + int n = 0; + + (void)n; + lwsl_notice("%s: %s (head.next %p)\n", __func__, title, head->next); + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, head->next) { + struct lws *wsi = lws_container_of(d, struct lws, dll_hrtimer); + + (void)wsi; + + lwsl_notice(" %d: wsi %p: %llu\n", n++, wsi, + (unsigned long long)wsi->pending_timer); + } lws_end_foreach_dll_safe(d, d1); +} + +void +__lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_dll_lws *dd = &pt->dll_head_hrtimer; + struct timeval now; + struct lws *wsi1; + int bef = 0; + + lws_dll_lws_remove(&wsi->dll_hrtimer); + + if (usecs == LWS_SET_TIMER_USEC_CANCEL) + return; + + gettimeofday(&now, NULL); + wsi->pending_timer = ((now.tv_sec * 1000000ll) + now.tv_usec) + usecs; + + /* + * we sort the hrtimer list with the earliest timeout first + */ + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + pt->dll_head_hrtimer.next) { + dd = d; + wsi1 = lws_container_of(d, struct lws, dll_hrtimer); + + if (wsi1->pending_timer >= wsi->pending_timer) { + /* d, dprev's next, is >= our time */ + bef = 1; + break; + } + } lws_end_foreach_dll_safe(d, d1); + + if (bef) { + /* + * we go before dd + * DDp <-> DD <-> DDn --> DDp <-> us <-> DD <-> DDn + */ + /* we point forward to dd */ + wsi->dll_hrtimer.next = dd; + /* we point back to what dd used to point back to */ + wsi->dll_hrtimer.prev = dd->prev; + /* DDp points forward to us now */ + dd->prev->next = &wsi->dll_hrtimer; + /* DD points back to us now */ + dd->prev = &wsi->dll_hrtimer; + } else { + /* + * we go after dd + * DDp <-> DD <-> DDn --> DDp <-> DD <-> us <-> DDn + */ + /* we point forward to what dd used to point forward to */ + wsi->dll_hrtimer.next = dd->next; + /* we point back to dd */ + wsi->dll_hrtimer.prev = dd; + /* DDn points back to us */ + if (dd->next) + dd->next->prev = &wsi->dll_hrtimer; + /* DD points forward to us */ + dd->next = &wsi->dll_hrtimer; + } + +// lws_dll_dump(&pt->dll_head_hrtimer, "after set_timer_usec"); +} + LWS_VISIBLE void -lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) +lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs) +{ + __lws_set_timer_usecs(wsi, usecs); +} + +lws_usec_t +__lws_hrtimer_service(struct lws_context_per_thread *pt) +{ + struct timeval now; + struct lws *wsi; + lws_usec_t t; + + gettimeofday(&now, NULL); + t = (now.tv_sec * 1000000ll) + now.tv_usec; + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + pt->dll_head_hrtimer.next) { + wsi = lws_container_of(d, struct lws, dll_hrtimer); + + /* + * if we met one in the future, we are done, because the list + * is sorted by time in the future. + */ + if (wsi->pending_timer > t) + break; + + lws_set_timer_usecs(wsi, LWS_SET_TIMER_USEC_CANCEL); + + /* it's time for the timer to be serviced */ + + if (wsi->protocol && + wsi->protocol->callback(wsi, LWS_CALLBACK_TIMER, + wsi->user_space, NULL, 0)) + __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "timer cb errored"); + } lws_end_foreach_dll_safe(d, d1); + + /* return an estimate how many us until next timer hit */ + + if (!pt->dll_head_hrtimer.next) + return LWS_HRTIMER_NOWAIT; + + wsi = lws_container_of(pt->dll_head_hrtimer.next, struct lws, dll_hrtimer); + + gettimeofday(&now, NULL); + t = (now.tv_sec * 1000000ll) + now.tv_usec; + + if (wsi->pending_timer < t) + return 0; + + return wsi->pending_timer - t; +} + +void +__lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; time_t now; + time(&now); + + lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs); + wsi->pending_timeout_limit = secs; + wsi->pending_timeout_set = now; + wsi->pending_timeout = reason; + + if (!reason) + lws_dll_lws_remove(&wsi->dll_timeout); + else + lws_dll_lws_add_front(&wsi->dll_timeout, &pt->dll_head_timeout); +} + +LWS_VISIBLE void +lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + if (secs == LWS_TO_KILL_SYNC) { lws_remove_from_timeout_list(wsi); lwsl_debug("synchronously killing %p\n", wsi); - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "to sync kill"); return; } - lws_pt_lock(pt); + if (secs == LWS_TO_KILL_ASYNC) + secs = 0; - time(&now); + lws_pt_lock(pt, __func__); + __lws_set_timeout(wsi, reason, secs); + lws_pt_unlock(pt); +} - if (reason && !wsi->timeout_list_prev) { - /* our next guy is current first guy */ - wsi->timeout_list = pt->timeout_list; - /* if there is a next guy, set his prev ptr to our next ptr */ - if (wsi->timeout_list) - wsi->timeout_list->timeout_list_prev = &wsi->timeout_list; - /* our prev ptr is first ptr */ - wsi->timeout_list_prev = &pt->timeout_list; - /* set the first guy to be us */ - *wsi->timeout_list_prev = wsi; - } +int +lws_timed_callback_remove(struct lws_vhost *vh, struct lws_timed_vh_protocol *p) +{ + lws_start_foreach_llp(struct lws_timed_vh_protocol **, pt, + vh->timed_vh_protocol_list) { + if (*pt == p) { + *pt = p->next; + lws_free(p); - lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs); - wsi->pending_timeout_limit = now + secs; - wsi->pending_timeout = reason; + return 0; + } + } lws_end_foreach_llp(pt, next); - lws_pt_unlock(pt); + return 1; +} - if (!reason) - lws_remove_from_timeout_list(wsi); +LWS_VISIBLE LWS_EXTERN int +lws_timed_callback_vh_protocol(struct lws_vhost *vh, const struct lws_protocols *prot, + int reason, int secs) +{ + struct lws_timed_vh_protocol *p = (struct lws_timed_vh_protocol *) + lws_malloc(sizeof(*p), "timed_vh"); + + if (!p) + return 1; + + p->protocol = prot; + p->reason = reason; + p->time = lws_now_secs() + secs; + p->next = vh->timed_vh_protocol_list; + + vh->timed_vh_protocol_list = p; + + return 0; } static void @@ -229,9 +474,11 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) // return 0; const struct lws_protocols *vp = wsi->vhost->protocols, *vpo; - if (wsi->protocol) + if (wsi->protocol && wsi->protocol_bind_balance) { wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, wsi->user_space, NULL, 0); + wsi->protocol_bind_balance = 0; + } if (!wsi->user_space_externally_allocated) lws_free_set_NULL(wsi->user_space); @@ -245,7 +492,7 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) return 1; if (p > vp && p < &vp[wsi->vhost->count_protocols]) - lws_same_vh_protocol_insert(wsi, p - vp); + lws_same_vh_protocol_insert(wsi, (int)(p - vp)); else { int n = wsi->vhost->count_protocols; int hit = 0; @@ -255,7 +502,7 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) while (n--) { if (p->name && vp->name && !strcmp(p->name, vp->name)) { hit = 1; - lws_same_vh_protocol_insert(wsi, vp - vpo); + lws_same_vh_protocol_insert(wsi, (int)(vp - vpo)); break; } vp++; @@ -269,42 +516,65 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) wsi->user_space, NULL, 0)) return 1; + wsi->protocol_bind_balance = 1; + return 0; } void -lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) +__lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) { struct lws_context_per_thread *pt; struct lws *wsi1, *wsi2; struct lws_context *context; - struct lws_tokens eff_buf; - int n, m, ret; + int n; - lwsl_debug("%s: %p\n", __func__, wsi); + lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller); if (!wsi) return; lws_access_log(wsi); -#if defined(LWS_WITH_ESP8266) - if (wsi->premature_rx) - lws_free(wsi->premature_rx); - - if (wsi->pending_send_completion && !wsi->close_is_pending_send_completion) { - lwsl_notice("delaying close\n"); - wsi->close_is_pending_send_completion = 1; - return; - } -#endif - - /* we're closing, losing some rx is OK */ - lws_header_table_force_to_detachable_state(wsi); context = wsi->context; pt = &context->pt[(int)wsi->tsi]; lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_CLOSE, 1); +#if !defined(LWS_NO_CLIENT) + + lws_free_set_NULL(wsi->client_hostname_copy); + /* we are no longer an active client connection that can piggyback */ + lws_dll_lws_remove(&wsi->dll_active_client_conns); + + /* + * if we have wsi in our transaction queue, if we are closing we + * must go through and close all those first + */ + if (wsi->vhost) { + if ((int)reason != -1) + lws_vhost_lock(wsi->vhost); + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + struct lws *w = lws_container_of(d, struct lws, + dll_client_transaction_queue); + + __lws_close_free_wsi(w, -1, "trans q leader closing"); + } lws_end_foreach_dll_safe(d, d1); + + /* + * !!! If we are closing, but we have pending pipelined transaction + * results we already sent headers for, that's going to destroy sync + * for HTTP/1 and leave H2 stream with no live swsi. + * + * However this is normal if we are being closed because the transaction + * queue leader is closing. + */ + lws_dll_lws_remove(&wsi->dll_client_transaction_queue); + if ((int)reason !=-1) + lws_vhost_unlock(wsi->vhost); + } +#endif + /* if we have children, close them first */ if (wsi->child_list) { wsi2 = wsi->child_list; @@ -313,302 +583,146 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) wsi2->parent = NULL; /* stop it doing shutdown processing */ wsi2->socket_is_permanently_unusable = 1; - lws_close_free_wsi(wsi2, reason); + __lws_close_free_wsi(wsi2, reason, "general child recurse"); wsi2 = wsi1; } wsi->child_list = NULL; } -#if defined(LWS_WITH_HTTP2) - - if (wsi->u.h2.parent_wsi) { - lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, wsi->u.h2.parent_wsi); - lws_start_foreach_llp(struct lws **, w, wsi->u.h2.parent_wsi->u.h2.child_list) { - lwsl_info(" \\---- child %p\n", *w); - } lws_end_foreach_llp(w, u.h2.sibling_list); - } - - if (wsi->upgraded_to_http2 || wsi->http2_substream) { - lwsl_info("closing %p: parent %p\n", wsi, wsi->u.h2.parent_wsi); - - if (wsi->u.h2.child_list) { - lwsl_info(" parent %p: closing children: list:\n", wsi); - lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { - lwsl_info(" \\---- child %p\n", *w); - } lws_end_foreach_llp(w, u.h2.sibling_list); - /* trigger closing of all of our http2 children first */ - lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { - lwsl_info(" closing child %p\n", *w); - /* disconnect from siblings */ - wsi2 = (*w)->u.h2.sibling_list; - (*w)->u.h2.sibling_list = NULL; - (*w)->socket_is_permanently_unusable = 1; - lws_close_free_wsi(*w, reason); - *w = wsi2; - continue; - } lws_end_foreach_llp(w, u.h2.sibling_list); - } - } - - if (wsi->upgraded_to_http2) { - /* remove pps */ - struct lws_h2_protocol_send *w = wsi->u.h2.h2n->pps, *w1; - while (w) { - w1 = wsi->u.h2.h2n->pps->next; - free(w); - w = w1; - } - wsi->u.h2.h2n->pps = NULL; - } - - if (wsi->http2_substream && wsi->u.h2.parent_wsi) { - lwsl_info(" %p: disentangling from siblings\n", wsi); - lws_start_foreach_llp(struct lws **, w, - wsi->u.h2.parent_wsi->u.h2.child_list) { - /* disconnect from siblings */ - if (*w == wsi) { - wsi2 = (*w)->u.h2.sibling_list; - (*w)->u.h2.sibling_list = NULL; - *w = wsi2; - lwsl_info(" %p disentangled from sibling %p\n", wsi, wsi2); - break; - } - } lws_end_foreach_llp(w, u.h2.sibling_list); - wsi->u.h2.parent_wsi->u.h2.child_count--; - wsi->u.h2.parent_wsi = NULL; - if (wsi->u.h2.pending_status_body) - lws_free_set_NULL(wsi->u.h2.pending_status_body); - } - - if (wsi->upgraded_to_http2 && wsi->u.h2.h2n && - wsi->u.h2.h2n->rx_scratch) - lws_free_set_NULL(wsi->u.h2.h2n->rx_scratch); -#endif - - if (wsi->mode == LWSCM_RAW_FILEDESC) { + if (wsi->role_ops == &role_ops_raw_file) { lws_remove_child_from_any_parent(wsi); - remove_wsi_socket_from_fds(wsi); - wsi->protocol->callback(wsi, - LWS_CALLBACK_RAW_CLOSE_FILE, + __remove_wsi_socket_from_fds(wsi); + wsi->protocol->callback(wsi, wsi->role_ops->close_cb[0], wsi->user_space, NULL, 0); goto async_close; } + wsi->wsistate_pre_close = wsi->wsistate; + #ifdef LWS_WITH_CGI - if (wsi->mode == LWSCM_CGI) { + if (wsi->role_ops == &role_ops_cgi) { /* we are not a network connection, but a handler for CGI io */ - if (wsi->parent && wsi->parent->cgi) { + if (wsi->parent && wsi->parent->http.cgi) { if (wsi->cgi_channel == LWS_STDOUT) lws_cgi_remove_and_kill(wsi->parent); /* end the binding between us and master */ - wsi->parent->cgi->stdwsi[(int)wsi->cgi_channel] = NULL; + wsi->parent->http.cgi->stdwsi[(int)wsi->cgi_channel] = NULL; } wsi->socket_is_permanently_unusable = 1; goto just_kill_connection; } - if (wsi->cgi) + if (wsi->http.cgi) lws_cgi_remove_and_kill(wsi); #endif #if !defined(LWS_NO_CLIENT) - if (wsi->mode == LWSCM_HTTP_CLIENT || - wsi->mode == LWSCM_WSCL_WAITING_CONNECT || - wsi->mode == LWSCM_WSCL_WAITING_PROXY_REPLY || - wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE || - wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE2 || - wsi->mode == LWSCM_WSCL_WAITING_SSL || - wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY || - wsi->mode == LWSCM_WSCL_WAITING_EXTENSION_CONNECT || - wsi->mode == LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY || - wsi->mode == LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY || - wsi->mode == LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY) - if (wsi->u.hdr.stash) - lws_free_set_NULL(wsi->u.hdr.stash); -#endif - - if (wsi->mode == LWSCM_RAW) { - wsi->protocol->callback(wsi, - LWS_CALLBACK_RAW_CLOSE, wsi->user_space, NULL, 0); + lws_client_stash_destroy(wsi); +#endif + + if (wsi->role_ops == &role_ops_raw_skt) { wsi->socket_is_permanently_unusable = 1; goto just_kill_connection; } +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && + wsi->http.fop_fd != NULL) + lws_vfs_file_close(&wsi->http.fop_fd); +#endif + + if (lwsi_state(wsi) == LRS_DEAD_SOCKET) + return; - if ((wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED || - wsi->mode == LWSCM_HTTP2_SERVING) && - wsi->u.http.fop_fd != NULL) { - lws_vfs_file_close(&wsi->u.http.fop_fd); - wsi->vhost->protocols->callback(wsi, - LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); - wsi->told_user_closed = 1; - } if (wsi->socket_is_permanently_unusable || reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY || - wsi->state == LWSS_SHUTDOWN) + lwsi_state(wsi) == LRS_SHUTDOWN) goto just_kill_connection; - wsi->state_pre_close = wsi->state; - - switch (wsi->state_pre_close) { - case LWSS_DEAD_SOCKET: + switch (lwsi_state_PRE_CLOSE(wsi)) { + case LRS_DEAD_SOCKET: return; /* we tried the polite way... */ - case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION: - case LWSS_AWAITING_CLOSE_ACK: + case LRS_WAITING_TO_SEND_CLOSE: + case LRS_AWAITING_CLOSE_ACK: + case LRS_RETURNED_CLOSE: goto just_kill_connection; - case LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE: + case LRS_FLUSHING_BEFORE_CLOSE: if (wsi->trunc_len) { lws_callback_on_writable(wsi); return; } - lwsl_info("%p: end FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); + lwsl_info("%p: end LRS_FLUSHING_BEFORE_CLOSE\n", wsi); goto just_kill_connection; default: if (wsi->trunc_len) { - lwsl_info("%p: start FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); - wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE; - lws_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); + lwsl_info("%p: LRS_FLUSHING_BEFORE_CLOSE\n", wsi); + lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE); + __lws_set_timeout(wsi, + PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); return; } break; } - if (wsi->mode == LWSCM_WSCL_WAITING_CONNECT || - wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE) + if (lwsi_state(wsi) == LRS_WAITING_CONNECT || + lwsi_state(wsi) == LRS_H1C_ISSUE_HANDSHAKE) goto just_kill_connection; - if (!wsi->told_user_closed && - (wsi->mode == LWSCM_HTTP_SERVING || - wsi->mode == LWSCM_HTTP2_SERVING)) { - if (wsi->user_space) - wsi->vhost->protocols->callback(wsi, + if (!wsi->told_user_closed && lwsi_role_http(wsi) && + lwsi_role_server(wsi)) { + if (wsi->user_space && wsi->protocol && + wsi->protocol_bind_balance) { + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, wsi->user_space, NULL, 0); - wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP, - wsi->user_space, NULL, 0); - wsi->told_user_closed = 1; - } - - /* - * are his extensions okay with him closing? Eg he might be a mux - * parent and just his ch1 aspect is closing? - */ - - if (lws_ext_cb_active(wsi, LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE, NULL, 0) > 0) { - lwsl_ext("extension vetoed close\n"); - return; - } - - /* - * flush any tx pending from extensions, since we may send close packet - * if there are problems with send, just nuke the connection - */ - do { - ret = 0; - eff_buf.token = NULL; - eff_buf.token_len = 0; - - /* show every extension the new incoming data */ - - m = lws_ext_cb_active(wsi, - LWS_EXT_CB_FLUSH_PENDING_TX, &eff_buf, 0); - if (m < 0) { - lwsl_ext("Extension reports fatal error\n"); - goto just_kill_connection; + wsi->protocol_bind_balance = 0; } - if (m) - /* - * at least one extension told us he has more - * to spill, so we will go around again after - */ - ret = 1; - - /* assuming they left us something to send, send it */ - - if (eff_buf.token_len) - if (lws_issue_raw(wsi, (unsigned char *)eff_buf.token, - eff_buf.token_len) != - eff_buf.token_len) { - lwsl_debug("close: ext spill failed\n"); - goto just_kill_connection; - } - } while (ret); + } /* * signal we are closing, lws_write will * add any necessary version-specific stuff. If the write fails, * no worries we are closing anyway. If we didn't initiate this * close, then our state has been changed to - * LWSS_RETURNED_CLOSE_ALREADY and we will skip this. + * LRS_RETURNED_CLOSE and we will skip this. * * Likewise if it's a second call to close this connection after we * sent the close indication to the peer already, we are in state - * LWSS_AWAITING_CLOSE_ACK and will skip doing this a second time. + * LRS_AWAITING_CLOSE_ACK and will skip doing this a second time. */ - if (wsi->state_pre_close == LWSS_ESTABLISHED && - (wsi->u.ws.close_in_ping_buffer_len || /* already a reason */ - (reason != LWS_CLOSE_STATUS_NOSTATUS && - (reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)))) { - lwsl_debug("sending close indication...\n"); - - /* if no prepared close reason, use 1000 and no aux data */ - if (!wsi->u.ws.close_in_ping_buffer_len) { - wsi->u.ws.close_in_ping_buffer_len = 2; - wsi->u.ws.ping_payload_buf[LWS_PRE] = - (reason >> 8) & 0xff; - wsi->u.ws.ping_payload_buf[LWS_PRE + 1] = - reason & 0xff; - } - -#if defined (LWS_WITH_ESP8266) - wsi->close_is_pending_send_completion = 1; -#endif - - lwsl_debug("waiting for chance to send close\n"); - wsi->waiting_to_send_close_frame = 1; - wsi->state = LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION; - lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 2); - lws_callback_on_writable(wsi); - + if (wsi->role_ops->close_via_role_protocol && + wsi->role_ops->close_via_role_protocol(wsi, reason)) return; - } just_kill_connection: + if (wsi->role_ops->close_kill_connection) + wsi->role_ops->close_kill_connection(wsi, reason); + lws_remove_child_from_any_parent(wsi); n = 0; - if (!wsi->told_user_closed && wsi->user_space) { - lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, + if (!wsi->told_user_closed && wsi->user_space && + wsi->protocol_bind_balance) { + lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, wsi->protocol->name); - wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_DROP_PROTOCOL, + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, wsi->user_space, NULL, 0); + wsi->protocol_bind_balance = 0; } - if ((wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY || - wsi->mode == LWSCM_WSCL_WAITING_CONNECT) && - !wsi->already_did_cce) { - wsi->vhost->protocols[0].callback(wsi, - LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + if ((lwsi_state(wsi) == LRS_WAITING_SERVER_REPLY || + lwsi_state(wsi) == LRS_WAITING_CONNECT) && !wsi->already_did_cce) + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, NULL, 0); - } - if (wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) { - wsi->vhost->protocols[0].callback(wsi, - LWS_CALLBACK_CLOSED_CLIENT_HTTP, - wsi->user_space, NULL, 0); - wsi->told_user_closed = 1; - } - - -#if LWS_POSIX /* * Testing with ab shows that we have to stage the socket close when * the system is under stress... shutdown any further TX, change the @@ -616,55 +730,38 @@ just_kill_connection: * for the POLLIN to show a zero-size rx before coming back and doing * the actual close. */ - if (wsi->mode != LWSCM_RAW && - !(wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) && - wsi->state != LWSS_SHUTDOWN && - wsi->state != LWSS_CLIENT_UNCONNECTED && + if (wsi->role_ops != &role_ops_raw_skt && !lwsi_role_client(wsi) && + lwsi_state(wsi) != LRS_SHUTDOWN && + lwsi_state(wsi) != LRS_UNCONNECTED && reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && !wsi->socket_is_permanently_unusable) { -#ifdef LWS_OPENSSL_SUPPORT - if (lws_is_ssl(wsi) && wsi->ssl) { - n = SSL_shutdown(wsi->ssl); - /* - * If finished the SSL shutdown, then do socket - * shutdown, else need to retry SSL shutdown - */ - switch (n) { - case 0: - lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); - break; - case 1: - n = shutdown(wsi->desc.sockfd, SHUT_WR); - break; - default: - if (SSL_want_read(wsi->ssl)) { - lws_change_pollfd(wsi, 0, LWS_POLLIN); - n = 0; - break; - } - if (SSL_want_write(wsi->ssl)) { - lws_change_pollfd(wsi, 0, LWS_POLLOUT); - n = 0; - break; - } - n = shutdown(wsi->desc.sockfd, SHUT_WR); - break; - } - } else + +#if defined(LWS_WITH_TLS) + if (lws_is_ssl(wsi) && wsi->tls.ssl) { + n = 0; + switch (__lws_tls_shutdown(wsi)) { + case LWS_SSL_CAPABLE_DONE: + case LWS_SSL_CAPABLE_ERROR: + case LWS_SSL_CAPABLE_MORE_SERVICE_READ: + case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: + case LWS_SSL_CAPABLE_MORE_SERVICE: + break; + } + } else #endif { - lwsl_info("%s: shutdown conn: %p (sock %d, state %d)\n", + lwsl_info("%s: shutdown conn: %p (sock %d, state 0x%x)\n", __func__, wsi, (int)(long)wsi->desc.sockfd, - wsi->state); + lwsi_state(wsi)); if (!wsi->socket_is_permanently_unusable && - lws_sockfd_valid(wsi->desc.sockfd)) { + lws_socket_is_valid(wsi->desc.sockfd)) { wsi->socket_is_permanently_unusable = 1; n = shutdown(wsi->desc.sockfd, SHUT_WR); } } if (n) - lwsl_debug("closing: shutdown (state %d) ret %d\n", - wsi->state, LWS_ERRNO); + lwsl_debug("closing: shutdown (state 0x%x) ret %d\n", + lwsi_state(wsi), LWS_ERRNO); /* * This causes problems on WINCE / ESP32 with disconnection @@ -673,199 +770,272 @@ just_kill_connection: #if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP32) /* libuv: no event available to guarantee completion */ if (!wsi->socket_is_permanently_unusable && - lws_sockfd_valid(wsi->desc.sockfd) && - !LWS_LIBUV_ENABLED(context)) { - lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); - wsi->state = LWSS_SHUTDOWN; - lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, - context->timeout_secs); + lws_socket_is_valid(wsi->desc.sockfd) && + lwsi_state(wsi) != LRS_SHUTDOWN && + context->event_loop_ops->periodic_events_available) { + __lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); + lwsi_set_state(wsi, LRS_SHUTDOWN); + __lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, + context->timeout_secs); return; } #endif } -#endif lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, wsi, wsi->desc.sockfd); #ifdef LWS_WITH_HTTP_PROXY - if (wsi->rw) { - lws_rewrite_destroy(wsi->rw); - wsi->rw = NULL; + if (wsi->http.rw) { + lws_rewrite_destroy(wsi->http.rw); + wsi->http.rw = NULL; } #endif /* * we won't be servicing or receiving anything further from this guy * delete socket from the internal poll list if still present */ - lws_ssl_remove_wsi_from_buffered_list(wsi); - lws_remove_from_timeout_list(wsi); + __lws_ssl_remove_wsi_from_buffered_list(wsi); + __lws_remove_from_timeout_list(wsi); + lws_dll_lws_remove(&wsi->dll_hrtimer); + + /* don't repeat event loop stuff */ + if (wsi->told_event_loop_closed) + return; /* checking return redundant since we anyway close */ if (wsi->desc.sockfd != LWS_SOCK_INVALID) - remove_wsi_socket_from_fds(wsi); + __remove_wsi_socket_from_fds(wsi); else lws_same_vh_protocol_remove(wsi); -#if defined(LWS_WITH_ESP8266) - espconn_disconnect(wsi->desc.sockfd); -#endif - - wsi->state = LWSS_DEAD_SOCKET; - - lws_free_set_NULL(wsi->rxflow_buffer); - if (wsi->state_pre_close == LWSS_ESTABLISHED || - wsi->mode == LWSCM_WS_SERVING || - wsi->mode == LWSCM_WS_CLIENT) { - - if (wsi->u.ws.rx_draining_ext) { - struct lws **w = &pt->rx_draining_ext_list; - - wsi->u.ws.rx_draining_ext = 0; - /* remove us from context draining ext list */ - while (*w) { - if (*w == wsi) { - *w = wsi->u.ws.rx_draining_ext_list; - break; - } - w = &((*w)->u.ws.rx_draining_ext_list); - } - wsi->u.ws.rx_draining_ext_list = NULL; - } - - if (wsi->u.ws.tx_draining_ext) { - struct lws **w = &pt->tx_draining_ext_list; - - wsi->u.ws.tx_draining_ext = 0; - /* remove us from context draining ext list */ - while (*w) { - if (*w == wsi) { - *w = wsi->u.ws.tx_draining_ext_list; - break; - } - w = &((*w)->u.ws.tx_draining_ext_list); - } - wsi->u.ws.tx_draining_ext_list = NULL; - } - lws_free_set_NULL(wsi->u.ws.rx_ubuf); + lwsi_set_state(wsi, LRS_DEAD_SOCKET); + lws_buflist_destroy_all_segments(&wsi->buflist); + lws_dll_lws_remove(&wsi->dll_buflist); - if (wsi->trunc_alloc) - /* not going to be completed... nuke it */ - lws_free_set_NULL(wsi->trunc_alloc); - - wsi->u.ws.ping_payload_len = 0; - wsi->u.ws.ping_pending_flag = 0; - } + if (wsi->role_ops->close_role) + wsi->role_ops->close_role(pt, wsi); /* tell the user it's all over for this guy */ - if (!wsi->told_user_closed && - wsi->mode != LWSCM_RAW && wsi->protocol && - wsi->protocol->callback && - (wsi->state_pre_close == LWSS_ESTABLISHED || - wsi->state_pre_close == LWSS_HTTP2_ESTABLISHED || - wsi->state_pre_close == LWSS_HTTP_BODY || - wsi->state_pre_close == LWSS_HTTP || - wsi->state_pre_close == LWSS_RETURNED_CLOSE_ALREADY || - wsi->state_pre_close == LWSS_AWAITING_CLOSE_ACK || - wsi->state_pre_close == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || - wsi->state_pre_close == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE || - (wsi->mode == LWSCM_WS_CLIENT && wsi->state_pre_close == LWSS_HTTP) || - (wsi->mode == LWSCM_WS_SERVING && wsi->state_pre_close == LWSS_HTTP))) { - lwsl_debug("calling back CLOSED %d %d\n", wsi->mode, wsi->state); - wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED, - wsi->user_space, NULL, 0); - } else if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED) { - lwsl_debug("calling back CLOSED_HTTP\n"); - wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP, - wsi->user_space, NULL, 0 ); - } else - lwsl_debug("not calling back closed mode=%d state=%d\n", - wsi->mode, wsi->state_pre_close); + if (lwsi_state_est_PRE_CLOSE(wsi) && !wsi->told_user_closed && + wsi->role_ops->close_cb[lwsi_role_server(wsi)]) { + const struct lws_protocols *pro = wsi->protocol; - /* deallocate any active extension contexts */ + if (!wsi->protocol) + pro = &wsi->vhost->protocols[0]; - if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0) - lwsl_warn("extension destruction failed\n"); - /* - * inform all extensions in case they tracked this guy out of band - * even though not active on him specifically - */ - if (lws_ext_cb_all_exts(context, wsi, - LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING, NULL, 0) < 0) - lwsl_warn("ext destroy wsi failed\n"); + pro->callback(wsi, + wsi->role_ops->close_cb[lwsi_role_server(wsi)], + wsi->user_space, NULL, 0); + wsi->told_user_closed = 1; + } async_close: wsi->socket_is_permanently_unusable = 1; -#ifdef LWS_WITH_LIBUV - if (!wsi->parent_carries_io && - lws_sockfd_valid(wsi->desc.sockfd)) - if (LWS_LIBUV_ENABLED(context)) { - if (wsi->listener) { - lwsl_debug("%s: stop listener poll\n", __func__); - uv_poll_stop(&wsi->w_read.uv_watcher); - } - lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", - __func__, wsi); - /* - * libuv has to do his own close handle processing - * asynchronously - */ - lws_libuv_closehandle(wsi); - + if (wsi->context->event_loop_ops->wsi_logical_close) + if (wsi->context->event_loop_ops->wsi_logical_close(wsi)) return; - } -#endif - lws_close_free_wsi_final(wsi); + __lws_close_free_wsi_final(wsi); } void -lws_close_free_wsi_final(struct lws *wsi) +__lws_close_free_wsi_final(struct lws *wsi) { int n; if (lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { -#if LWS_POSIX n = compatible_close(wsi->desc.sockfd); if (n) lwsl_debug("closing: close ret %d\n", LWS_ERRNO); -#else - compatible_close(wsi->desc.sockfd); - (void)n; -#endif wsi->desc.sockfd = LWS_SOCK_INVALID; } /* outermost destroy notification for wsi (user_space still intact) */ - wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, - wsi->user_space, NULL, 0); + if (wsi->vhost) + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, + wsi->user_space, NULL, 0); #ifdef LWS_WITH_CGI - if (wsi->cgi) { + if (wsi->http.cgi) { for (n = 0; n < 3; n++) { - if (wsi->cgi->pipe_fds[n][!!(n == 0)] == 0) + if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] == 0) lwsl_err("ZERO FD IN CGI CLOSE"); - if (wsi->cgi->pipe_fds[n][!!(n == 0)] >= 0) - close(wsi->cgi->pipe_fds[n][!!(n == 0)]); + if (wsi->http.cgi->pipe_fds[n][!!(n == 0)] >= 0) + close(wsi->http.cgi->pipe_fds[n][!!(n == 0)]); } - lws_free(wsi->cgi); + lws_free(wsi->http.cgi); } #endif - lws_free_wsi(wsi); + __lws_free_wsi(wsi); +} + + +void +lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt, __func__); + __lws_close_free_wsi(wsi, reason, caller); + lws_pt_unlock(pt); +} + +/* lws_buflist */ + +int +lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf, + size_t len) +{ + struct lws_buflist *nbuf; + int first = !*head; + void *p = *head; + int sanity = 1024; + + assert(buf); + assert(len); + + /* append at the tail */ + while (*head) { + if (!--sanity || head == &((*head)->next)) { + lwsl_err("%s: corrupt list points to self\n", __func__); + return -1; + } + head = &((*head)->next); + } + + lwsl_info("%s: len %u first %d %p\n", __func__, (uint32_t)len, first, p); + + nbuf = (struct lws_buflist *) + lws_malloc(sizeof(**head) + len, __func__); + if (!nbuf) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + + nbuf->len = len; + nbuf->pos = 0; + nbuf->next = NULL; + + p = (void *)nbuf->buf; + memcpy(p, buf, len); + + *head = nbuf; + + return first; /* returns 1 if first segment just created */ +} + +static int +lws_buflist_destroy_segment(struct lws_buflist **head) +{ + struct lws_buflist *old = *head; + + assert(*head); + *head = (*head)->next; + old->next = NULL; + lws_free(old); + + return !*head; /* returns 1 if last segment just destroyed */ +} + +void +lws_buflist_destroy_all_segments(struct lws_buflist **head) +{ + struct lws_buflist *p = *head, *p1; + + while (p) { + p1 = p->next; + p->next = NULL; + lws_free(p); + p = p1; + } + + *head = NULL; +} + +size_t +lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf) +{ + if (!*head) { + if (buf) + *buf = NULL; + + return 0; + } + + if (!(*head)->len && (*head)->next) + lws_buflist_destroy_segment(head); + + if (!*head) { + if (buf) + *buf = NULL; + + return 0; + } + + assert((*head)->pos < (*head)->len); + + if (buf) + *buf = (*head)->buf + (*head)->pos; + + return (*head)->len - (*head)->pos; } +int +lws_buflist_use_segment(struct lws_buflist **head, size_t len) +{ + assert(*head); + assert(len); + assert((*head)->pos + len <= (*head)->len); + + (*head)->pos += len; + if ((*head)->pos == (*head)->len) + lws_buflist_destroy_segment(head); + + if (!*head) + return 0; + + return (int)((*head)->len - (*head)->pos); +} + +void +lws_buflist_describe(struct lws_buflist **head, void *id) +{ + struct lws_buflist *old; + int n = 0; + + if (*head == NULL) + lwsl_notice("%p: buflist empty\n", id); + + while (*head) { + lwsl_notice("%p: %d: %llu / %llu (%llu left)\n", id, n, + (unsigned long long)(*head)->pos, + (unsigned long long)(*head)->len, + (unsigned long long)(*head)->len - (*head)->pos); + old = *head; + head = &((*head)->next); + if (*head == old) { + lwsl_err("%s: next points to self\n", __func__); + break; + } + n++; + } +} + +/* ... */ + LWS_VISIBLE LWS_EXTERN const char * lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len) { - int n = 0, sl = strlen(name); + int n = 0, sl = (int)strlen(name); while (lws_hdr_copy_fragment(wsi, buf, len, WSI_TOKEN_HTTP_URI_ARGS, n) >= 0) { @@ -879,7 +1049,7 @@ lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len) return NULL; } -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) LWS_VISIBLE int interface_to_sa(struct lws_vhost *vh, const char *ifname, struct sockaddr_in *addr, size_t addrlen) @@ -895,12 +1065,10 @@ interface_to_sa(struct lws_vhost *vh, const char *ifname, #endif #ifndef LWS_PLAT_OPTEE -#if LWS_POSIX static int lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, int name_len, char *rip, int rip_len) { -#if LWS_POSIX struct addrinfo ai, *res; struct sockaddr_in addr4; @@ -921,8 +1089,8 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, if (strncmp(rip, "::ffff:", 7) == 0) memmove(rip, rip + 7, strlen(rip) - 6); - getnameinfo((struct sockaddr *)ads, - sizeof(struct sockaddr_in6), name, name_len, NULL, 0, 0); + getnameinfo((struct sockaddr *)ads, sizeof(struct sockaddr_in6), + name, name_len, NULL, 0, 0); return 0; } else @@ -933,7 +1101,6 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, memset(&ai, 0, sizeof ai); ai.ai_family = PF_UNSPEC; ai.ai_socktype = SOCK_STREAM; - ai.ai_flags = AI_CANONNAME; #if !defined(LWS_WITH_ESP32) if (getnameinfo((struct sockaddr *)ads, sizeof(struct sockaddr_in), @@ -966,24 +1133,12 @@ lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, return -1; return 0; -#else - (void)vh; - (void)ads; - (void)name; - (void)name_len; - (void)rip; - (void)rip_len; - - return -1; -#endif } -#endif LWS_VISIBLE const char * lws_get_peer_simple(struct lws *wsi, char *name, int namelen) { -#if LWS_POSIX socklen_t len, olen; #ifdef LWS_WITH_IPV6 struct sockaddr_in6 sin6; @@ -992,10 +1147,7 @@ lws_get_peer_simple(struct lws *wsi, char *name, int namelen) int af = AF_INET; void *p, *q; -#if defined(LWS_WITH_HTTP2) - if (wsi->http2_substream) - wsi = wsi->u.h2.parent_wsi; -#endif + wsi = lws_get_network_wsi(wsi); if (wsi->parent_carries_io) wsi = wsi->parent; @@ -1021,13 +1173,6 @@ lws_get_peer_simple(struct lws *wsi, char *name, int namelen) } return lws_plat_inet_ntop(af, q, name, namelen); -#else -#if defined(LWS_WITH_ESP8266) - return lws_plat_get_peer_simple(wsi, name, namelen); -#else - return NULL; -#endif -#endif } #endif @@ -1036,7 +1181,6 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, int name_len, char *rip, int rip_len) { #ifndef LWS_PLAT_OPTEE -#if LWS_POSIX socklen_t len; #ifdef LWS_WITH_IPV6 struct sockaddr_in6 sin6; @@ -1072,7 +1216,6 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, bail: lws_latency(context, wsi, "lws_get_peer_addresses", ret, 1); #endif -#endif (void)wsi; (void)fd; (void)name; @@ -1112,6 +1255,12 @@ lws_protocol_get(struct lws *wsi) return wsi->protocol; } +LWS_VISIBLE const struct lws_udp * +lws_get_udp(const struct lws *wsi) +{ + return wsi->udp; +} + LWS_VISIBLE struct lws * lws_get_network_wsi(struct lws *wsi) { @@ -1119,11 +1268,11 @@ lws_get_network_wsi(struct lws *wsi) return NULL; #if defined(LWS_WITH_HTTP2) - if (!wsi->http2_substream) + if (!wsi->http2_substream && !wsi->client_h2_substream) return wsi; - while (wsi->u.h2.parent_wsi) - wsi = wsi->u.h2.parent_wsi; + while (wsi->h2.parent_wsi) + wsi = wsi->h2.parent_wsi; #endif return wsi; @@ -1209,6 +1358,29 @@ lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len) return 0; } +LWS_VISIBLE LWS_EXTERN int +lws_callback_vhost_protocols_vhost(struct lws_vhost *vh, int reason, void *in, + size_t len) +{ + int n; + struct lws *wsi = lws_zalloc(sizeof(*wsi), "fake wsi"); + + wsi->context = vh->context; + wsi->vhost = vh; + + for (n = 0; n < wsi->vhost->count_protocols; n++) { + wsi->protocol = &vh->protocols[n]; + if (wsi->protocol->callback(wsi, reason, NULL, in, len)) { + lws_free(wsi); + return 1; + } + } + + lws_free(wsi); + + return 0; +} + LWS_VISIBLE LWS_EXTERN void lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops) { @@ -1281,7 +1453,7 @@ lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path, pf = fops->next; while (pf) { n = 0; - while (n < ARRAY_SIZE(pf->fi) && pf->fi[n].sig) { + while (n < (int)ARRAY_SIZE(pf->fi) && pf->fi[n].sig) { if (p >= vfs_path + pf->fi[n].len) if (!strncmp(p - (pf->fi[n].len - 1), pf->fi[n].sig, @@ -1327,10 +1499,19 @@ lws_now_secs(void) return tv.tv_sec; } +LWS_VISIBLE LWS_EXTERN int +lws_compare_time_t(struct lws_context *context, time_t t1, time_t t2) +{ + if (t1 < context->time_discontiguity) + t1 += context->time_fixup; -#if LWS_POSIX + if (t2 < context->time_discontiguity) + t2 += context->time_fixup; -LWS_VISIBLE int + return (int)(t1 - t2); +} + +LWS_VISIBLE lws_sockfd_type lws_get_socket_fd(struct lws *wsi) { if (!wsi) @@ -1338,8 +1519,6 @@ lws_get_socket_fd(struct lws *wsi) return wsi->desc.sockfd; } -#endif - #ifdef LWS_LATENCY void lws_latency(struct lws_context *context, struct lws *wsi, const char *action, @@ -1384,8 +1563,14 @@ lws_latency(struct lws_context *context, struct lws *wsi, const char *action, LWS_VISIBLE int lws_rx_flow_control(struct lws *wsi, int _enable) { + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; int en = _enable; + // h2 ignores rx flow control atm + if (lwsi_role_h2(wsi) || wsi->http2_substream || + lwsi_role_h2_ENCAPSULATION(wsi)) + return 0; // !!! + lwsl_info("%s: %p 0x%x\n", __func__, wsi, _enable); if (!(_enable & LWS_RXFLOW_REASON_APPLIES)) { @@ -1398,6 +1583,8 @@ lws_rx_flow_control(struct lws *wsi, int _enable) en |= LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT; } + lws_pt_lock(pt, __func__); + /* any bit set in rxflow_bitmap DISABLEs rxflow control */ if (en & LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT) wsi->rxflow_bitmap &= ~(en & 0xff); @@ -1406,16 +1593,23 @@ lws_rx_flow_control(struct lws *wsi, int _enable) if ((LWS_RXFLOW_PENDING_CHANGE | (!wsi->rxflow_bitmap)) == wsi->rxflow_change_to) - return 0; + goto skip; wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !wsi->rxflow_bitmap; - lwsl_info("%s: 0x%p: bitmap 0x%x: en 0x%x, ch 0x%x\n", __func__, wsi, + lwsl_info("%s: %p: bitmap 0x%x: en 0x%x, ch 0x%x\n", __func__, wsi, wsi->rxflow_bitmap, en, wsi->rxflow_change_to); if (_enable & LWS_RXFLOW_REASON_FLAG_PROCESS_NOW || - !wsi->rxflow_will_be_applied) - return _lws_rx_flow_control(wsi); + !wsi->rxflow_will_be_applied) { + en = __lws_rx_flow_control(wsi); + lws_pt_unlock(pt); + + return en; + } + +skip: + lws_pt_unlock(pt); return 0; } @@ -1440,12 +1634,63 @@ lws_rx_flow_allow_all_protocol(const struct lws_context *context, } } +int +lws_broadcast(struct lws_context *context, int reason, void *in, size_t len) +{ + struct lws_vhost *v = context->vhost_list; + struct lws wsi; + int n, ret = 0; + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = context; + + while (v) { + const struct lws_protocols *p = v->protocols; + wsi.vhost = v; + + for (n = 0; n < v->count_protocols; n++) { + wsi.protocol = p; + if (p->callback && + p->callback(&wsi, reason, NULL, in, len)) + ret |= 1; + p++; + } + v = v->vhost_next; + } + + return ret; +} + LWS_VISIBLE extern const char * lws_canonical_hostname(struct lws_context *context) { return (const char *)context->canonical_hostname; } +LWS_VISIBLE LWS_EXTERN const char * +lws_get_vhost_name(struct lws_vhost *vhost) +{ + return vhost->name; +} + +LWS_VISIBLE LWS_EXTERN int +lws_get_vhost_port(struct lws_vhost *vhost) +{ + return vhost->listen_port; +} + +LWS_VISIBLE LWS_EXTERN void * +lws_get_vhost_user(struct lws_vhost *vhost) +{ + return vhost->user; +} + +LWS_VISIBLE LWS_EXTERN const char * +lws_get_vhost_iface(struct lws_vhost *vhost) +{ + return vhost->iface; +} + int user_callback_handle_rxflow(lws_callback_function callback_function, struct lws *wsi, enum lws_callback_reasons reason, void *user, @@ -1457,20 +1702,15 @@ int user_callback_handle_rxflow(lws_callback_function callback_function, n = callback_function(wsi, reason, user, in, len); wsi->rxflow_will_be_applied = 0; if (!n) - n = _lws_rx_flow_control(wsi); + n = __lws_rx_flow_control(wsi); return n; } -#if defined(LWS_WITH_ESP8266) -#undef strchr -#define strchr ets_strchr -#endif - +#if !defined(LWS_WITHOUT_CLIENT) LWS_VISIBLE int lws_set_proxy(struct lws_vhost *vhost, const char *proxy) { -#if !defined(LWS_WITH_ESP8266) char *p; char authstring[96]; @@ -1481,15 +1721,15 @@ lws_set_proxy(struct lws_vhost *vhost, const char *proxy) if (!strncmp(proxy, "http://", 7)) proxy += 7; - p = strchr(proxy, '@'); + p = strrchr(proxy, '@'); if (p) { /* auth is around */ if ((unsigned int)(p - proxy) > sizeof(authstring) - 1) goto auth_too_long; - strncpy(authstring, proxy, p - proxy); + lws_strncpy(authstring, proxy, p - proxy + 1); // null termination not needed on input - if (lws_b64_encode_string(authstring, (p - proxy), + if (lws_b64_encode_string(authstring, lws_ptr_diff(p, proxy), vhost->proxy_basic_auth_token, sizeof vhost->proxy_basic_auth_token) < 0) goto auth_too_long; @@ -1500,39 +1740,38 @@ lws_set_proxy(struct lws_vhost *vhost, const char *proxy) } else vhost->proxy_basic_auth_token[0] = '\0'; - strncpy(vhost->http_proxy_address, proxy, - sizeof(vhost->http_proxy_address) - 1); - vhost->http_proxy_address[ - sizeof(vhost->http_proxy_address) - 1] = '\0'; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + lws_strncpy(vhost->http.http_proxy_address, proxy, + sizeof(vhost->http.http_proxy_address)); - p = strchr(vhost->http_proxy_address, ':'); - if (!p && !vhost->http_proxy_port) { + p = strchr(vhost->http.http_proxy_address, ':'); + if (!p && !vhost->http.http_proxy_port) { lwsl_err("http_proxy needs to be ads:port\n"); return -1; } else { if (p) { *p = '\0'; - vhost->http_proxy_port = atoi(p + 1); + vhost->http.http_proxy_port = atoi(p + 1); } } - lwsl_info(" Proxy %s:%u\n", vhost->http_proxy_address, - vhost->http_proxy_port); - + lwsl_info(" Proxy %s:%u\n", vhost->http.http_proxy_address, + vhost->http.http_proxy_port); +#endif return 0; auth_too_long: lwsl_err("proxy auth too long\n"); -#endif + return -1; } +#endif #if defined(LWS_WITH_SOCKS5) LWS_VISIBLE int lws_set_socks(struct lws_vhost *vhost, const char *socks) { -#if !defined(LWS_WITH_ESP8266) char *p_at, *p_colon; char user[96]; char password[96]; @@ -1543,7 +1782,7 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks) vhost->socks_user[0] = '\0'; vhost->socks_password[0] = '\0'; - p_at = strchr(socks, '@'); + p_at = strrchr(socks, '@'); if (p_at) { /* auth is around */ if ((unsigned int)(p_at - socks) > (sizeof(user) + sizeof(password) - 2)) { @@ -1564,9 +1803,9 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks) goto bail; } - strncpy(vhost->socks_user, socks, p_colon - socks); - strncpy(vhost->socks_password, p_colon + 1, - p_at - (p_colon + 1)); + lws_strncpy(vhost->socks_user, socks, p_colon - socks + 1); + lws_strncpy(vhost->socks_password, p_colon + 1, + p_at - (p_colon + 1) + 1); } lwsl_info(" Socks auth, user: %s, password: %s\n", @@ -1575,10 +1814,8 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks) socks = p_at + 1; } - strncpy(vhost->socks_proxy_address, socks, - sizeof(vhost->socks_proxy_address) - 1); - vhost->socks_proxy_address[sizeof(vhost->socks_proxy_address) - 1] - = '\0'; + lws_strncpy(vhost->socks_proxy_address, socks, + sizeof(vhost->socks_proxy_address)); p_colon = strchr(vhost->socks_proxy_address, ':'); if (!p_colon && !vhost->socks_proxy_port) { @@ -1597,7 +1834,6 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks) return 0; bail: -#endif return -1; } #endif @@ -1608,27 +1844,6 @@ lws_get_protocol(struct lws *wsi) return wsi->protocol; } -LWS_VISIBLE int -lws_is_final_fragment(struct lws *wsi) -{ - lwsl_info("%s: final %d, rx pk length %ld, draining %ld\n", __func__, - wsi->u.ws.final, (long)wsi->u.ws.rx_packet_length, - (long)wsi->u.ws.rx_draining_ext); - return wsi->u.ws.final && !wsi->u.ws.rx_packet_length && - !wsi->u.ws.rx_draining_ext; -} - -LWS_VISIBLE int -lws_is_first_fragment(struct lws *wsi) -{ - return wsi->u.ws.first_fragment; -} - -LWS_VISIBLE unsigned char -lws_get_reserved_bits(struct lws *wsi) -{ - return wsi->u.ws.rsv; -} int lws_ensure_user_space(struct lws *wsi) @@ -1639,7 +1854,8 @@ lws_ensure_user_space(struct lws *wsi) /* allocate the per-connection user memory (if any) */ if (wsi->protocol->per_session_data_size && !wsi->user_space) { - wsi->user_space = lws_zalloc(wsi->protocol->per_session_data_size, "user space"); + wsi->user_space = lws_zalloc( + wsi->protocol->per_session_data_size, "user space"); if (wsi->user_space == NULL) { lwsl_err("%s: OOM\n", __func__); return 1; @@ -1704,10 +1920,14 @@ lwsl_timestamp(int level, char *p, int len) (int)(now % 10000), log_level_names[n]); return n; } +#else + p[0] = '\0'; #endif + return 0; } +#ifndef LWS_PLAT_OPTEE static const char * const colours[] = { "[31;1m", /* LLL_ERR */ "[36;1m", /* LLL_WARN */ @@ -1722,17 +1942,14 @@ static const char * const colours[] = { "[30;1m", /* LLL_USER */ }; -#ifndef LWS_PLAT_OPTEE LWS_VISIBLE void lwsl_emit_stderr(int level, const char *line) { -#if !defined(LWS_WITH_ESP8266) char buf[50]; - static char tty; + static char tty = 3; int n, m = ARRAY_SIZE(colours) - 1; if (!tty) tty = isatty(2) | 2; - lwsl_timestamp(level, buf, sizeof(buf)); if (tty == 3) { @@ -1746,17 +1963,12 @@ LWS_VISIBLE void lwsl_emit_stderr(int level, const char *line) fprintf(stderr, "%c%s%s%s%c[0m", 27, colours[m], buf, line, 27); } else fprintf(stderr, "%s%s", buf, line); -#endif } #endif LWS_VISIBLE void _lws_logv(int filter, const char *format, va_list vl) { -#if defined(LWS_WITH_ESP8266) - char buf[128]; -#else char buf[256]; -#endif int n; if (!(log_level & filter)) @@ -1764,15 +1976,11 @@ LWS_VISIBLE void _lws_logv(int filter, const char *format, va_list vl) n = vsnprintf(buf, sizeof(buf) - 1, format, vl); (void)n; -#if defined(LWS_WITH_ESP8266) - buf[sizeof(buf) - 1] = '\0'; -#else /* vnsprintf returns what it would have written, even if truncated */ - if (n > sizeof(buf) - 1) + if (n > (int)sizeof(buf) - 1) n = sizeof(buf) - 1; if (n > 0) buf[n] = '\0'; -#endif lwsl_emit(filter, buf); } @@ -1810,6 +2018,12 @@ lwsl_hexdump_level(int hexdump_level, const void *vbuf, size_t len) if (!lwsl_visible(hexdump_level)) return; + if (!len) + return; + + if (!vbuf) + return; + _lws_log(hexdump_level, "\n"); for (n = 0; n < len;) { @@ -1852,19 +2066,19 @@ lwsl_hexdump(const void *vbuf, size_t len) LWS_VISIBLE int lws_is_ssl(struct lws *wsi) { -#ifdef LWS_OPENSSL_SUPPORT - return wsi->use_ssl; +#if defined(LWS_WITH_TLS) + return wsi->tls.use_ssl & LCCSCF_USE_SSL; #else (void)wsi; return 0; #endif } -#ifdef LWS_OPENSSL_SUPPORT -LWS_VISIBLE SSL* +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) +LWS_VISIBLE lws_tls_conn* lws_get_ssl(struct lws *wsi) { - return wsi->ssl; + return wsi->tls.ssl; } #endif @@ -1874,27 +2088,28 @@ lws_partial_buffered(struct lws *wsi) return !!wsi->trunc_len; } -LWS_VISIBLE size_t +LWS_VISIBLE lws_fileofs_t lws_get_peer_write_allowance(struct lws *wsi) { -#ifdef LWS_WITH_HTTP2 - /* only if we are using HTTP2 on this connection */ - if (wsi->mode != LWSCM_HTTP2_SERVING) - return -1; - - return lws_h2_tx_cr_get(wsi); -#else - (void)wsi; - return -1; -#endif + return wsi->role_ops->tx_credit(wsi); } LWS_VISIBLE void -lws_union_transition(struct lws *wsi, enum connection_mode mode) +lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state, + struct lws_role_ops *ops) { - lwsl_debug("%s: %p: mode %d\n", __func__, wsi, mode); - memset(&wsi->u, 0, sizeof(wsi->u)); - wsi->mode = mode; +#if defined(_DEBUG) + const char *name = "(unset)"; +#endif + wsi->wsistate = role | state; + if (ops) + wsi->role_ops = ops; +#if defined(_DEBUG) + if (wsi->role_ops) + name = wsi->role_ops->name; + lwsl_debug("%s: %p: wsistate 0x%x, ops %s\n", __func__, wsi, + wsi->wsistate, name); +#endif } LWS_VISIBLE struct lws_plat_file_ops * @@ -1973,48 +2188,21 @@ lws_clear_child_pending_on_writable(struct lws *wsi) wsi->parent_pending_cb_on_writable = 0; } -LWS_VISIBLE LWS_EXTERN int -lws_get_close_length(struct lws *wsi) -{ - return wsi->u.ws.close_in_ping_buffer_len; -} - -LWS_VISIBLE LWS_EXTERN unsigned char * -lws_get_close_payload(struct lws *wsi) -{ - return &wsi->u.ws.ping_payload_buf[LWS_PRE]; -} - -LWS_VISIBLE LWS_EXTERN void -lws_close_reason(struct lws *wsi, enum lws_close_status status, - unsigned char *buf, size_t len) -{ - unsigned char *p, *start; - int budget = sizeof(wsi->u.ws.ping_payload_buf) - LWS_PRE; - - assert(wsi->mode == LWSCM_WS_SERVING || wsi->mode == LWSCM_WS_CLIENT); - - start = p = &wsi->u.ws.ping_payload_buf[LWS_PRE]; - - *p++ = (((int)status) >> 8) & 0xff; - *p++ = ((int)status) & 0xff; - - if (buf) - while (len-- && p < start + budget) - *p++ = *buf++; - - wsi->u.ws.close_in_ping_buffer_len = p - start; -} LWS_EXTERN int -_lws_rx_flow_control(struct lws *wsi) +__lws_rx_flow_control(struct lws *wsi) { struct lws *wsic = wsi->child_list; + // h2 ignores rx flow control atm + if (lwsi_role_h2(wsi) || wsi->http2_substream || + lwsi_role_h2_ENCAPSULATION(wsi)) + return 0; // !!! + /* if he has children, do those if they were changed */ while (wsic) { if (wsic->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE) - _lws_rx_flow_control(wsic); + __lws_rx_flow_control(wsic); wsic = wsic->sibling_list; } @@ -2024,13 +2212,13 @@ _lws_rx_flow_control(struct lws *wsi) return 0; /* stuff is still buffered, not ready to really accept new input */ - if (wsi->rxflow_buffer) { + if (lws_buflist_next_segment_len(&wsi->buflist, NULL)) { /* get ourselves called back to deal with stashed buffer */ lws_callback_on_writable(wsi); return 0; } - /* pending is cleared, we can change rxflow state */ + /* now the pending is cleared, we can change rxflow state */ wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE; @@ -2040,12 +2228,12 @@ _lws_rx_flow_control(struct lws *wsi) /* adjust the pollfd for this wsi */ if (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW) { - if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + if (__lws_change_pollfd(wsi, 0, LWS_POLLIN)) { lwsl_info("%s: fail\n", __func__); return -1; } } else - if (lws_change_pollfd(wsi, LWS_POLLIN, 0)) + if (__lws_change_pollfd(wsi, LWS_POLLIN, 0)) return -1; return 0; @@ -2162,14 +2350,14 @@ lws_parse_uri(char *p, const char **prot, const char **ads, int *port, return 0; } -#ifdef LWS_NO_EXTENSIONS +#if defined(LWS_WITHOUT_EXTENSIONS) /* we need to provide dummy callbacks for internal exts * so user code runs when faced with a lib compiled with * extensions disabled. */ -int +LWS_VISIBLE int lws_extension_callback_pm_deflate(struct lws_context *context, const struct lws_extension *ext, struct lws *wsi, @@ -2186,13 +2374,19 @@ lws_extension_callback_pm_deflate(struct lws_context *context, return 0; } + +LWS_EXTERN int +lws_set_extension_option(struct lws *wsi, const char *ext_name, + const char *opt_name, const char *opt_val) +{ + return -1; +} #endif LWS_EXTERN int lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, const char *iface) { -#if LWS_POSIX #ifdef LWS_WITH_UNIX_SOCK struct sockaddr_un serv_unix; #endif @@ -2204,6 +2398,9 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, socklen_t len = sizeof(struct sockaddr_storage); #endif int n; +#if !defined(LWS_WITH_ESP32) + int m; +#endif struct sockaddr_storage sin; struct sockaddr *v; @@ -2213,6 +2410,8 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, n = sizeof(struct sockaddr_un); bzero((char *) &serv_unix, sizeof(serv_unix)); serv_unix.sun_family = AF_UNIX; + if (!iface) + return -1; if (sizeof(serv_unix.sun_path) <= strlen(iface)) { lwsl_err("\"%s\" too long for UNIX domain socket\n", iface); @@ -2230,10 +2429,17 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, n = sizeof(struct sockaddr_in6); bzero((char *) &serv_addr6, sizeof(serv_addr6)); if (iface) { - if (interface_to_sa(vhost, iface, - (struct sockaddr_in *)v, n) < 0) { - lwsl_err("Unable to find if %s\n", iface); - return -1; + m = interface_to_sa(vhost, iface, + (struct sockaddr_in *)v, n); + if (m == LWS_ITOSA_NOT_USABLE) { + lwsl_info("%s: netif %s: Not usable\n", + __func__, iface); + return m; + } + if (m == LWS_ITOSA_NOT_EXIST) { + lwsl_info("%s: netif %s: Does not exist\n", + __func__, iface); + return m; } serv_addr6.sin6_scope_id = lws_get_addr_scope(iface); } @@ -2250,16 +2456,28 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, serv_addr4.sin_family = AF_INET; #if !defined(LWS_WITH_ESP32) - if (iface && - interface_to_sa(vhost, iface, - (struct sockaddr_in *)v, n) < 0) { - lwsl_err("Unable to find interface %s\n", iface); - return -1; + if (iface) { + m = interface_to_sa(vhost, iface, + (struct sockaddr_in *)v, n); + if (m == LWS_ITOSA_NOT_USABLE) { + lwsl_info("%s: netif %s: Not usable\n", + __func__, iface); + return m; + } + if (m == LWS_ITOSA_NOT_EXIST) { + lwsl_info("%s: netif %s: Does not exist\n", + __func__, iface); + return m; + } } #endif serv_addr4.sin_port = htons(port); } /* ipv4 */ + /* just checking for the interface extant */ + if (sockfd == LWS_SOCK_INVALID) + return 0; + n = bind(sockfd, v, n); #ifdef LWS_WITH_UNIX_SOCK if (n < 0 && LWS_UNIX_SOCK_ENABLED(vhost)) { @@ -2281,8 +2499,8 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, #endif #if defined(LWS_WITH_IPV6) port = (sin.ss_family == AF_INET6) ? - ntohs(((struct sockaddr_in6 *) &sin)->sin6_port) : - ntohs(((struct sockaddr_in *) &sin)->sin_port); + ntohs(((struct sockaddr_in6 *) &sin)->sin6_port) : + ntohs(((struct sockaddr_in *) &sin)->sin_port); #else { struct sockaddr_in sain; @@ -2290,11 +2508,16 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, port = ntohs(sain.sin_port); } #endif -#endif return port; } +LWS_VISIBLE LWS_EXTERN int +lws_get_vhost_listen_port(struct lws_vhost *vhost) +{ + return vhost->listen_port; +} + #if defined(LWS_WITH_IPV6) LWS_EXTERN unsigned long lws_get_addr_scope(const char *ipaddr) @@ -2368,7 +2591,8 @@ lws_get_addr_scope(const char *ipaddr) while (adapter && !found) { addr = adapter->FirstUnicastAddress; while (addr && !found) { - if (addr->Address.lpSockaddr->sa_family == AF_INET6) { + if (addr->Address.lpSockaddr->sa_family == + AF_INET6) { sockaddr = (struct sockaddr_in6 *) (addr->Address.lpSockaddr); @@ -2395,18 +2619,73 @@ lws_get_addr_scope(const char *ipaddr) } #endif -LWS_EXTERN void -lws_restart_ws_ping_pong_timer(struct lws *wsi) +#if !defined(LWS_NO_SERVER) + +LWS_EXTERN struct lws * +lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, + const char *protocol_name, struct lws *parent_wsi) { - if (!wsi->context->ws_ping_pong_interval) - return; - if (wsi->state != LWSS_ESTABLISHED) - return; + lws_sock_file_fd_type sock; + struct addrinfo h, *r, *rp; + struct lws *wsi = NULL; + char buf[16]; + int n; - wsi->u.ws.time_next_ping_check = (time_t)lws_now_secs() + - wsi->context->ws_ping_pong_interval; + memset(&h, 0, sizeof(h)); + h.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + h.ai_socktype = SOCK_DGRAM; + h.ai_protocol = IPPROTO_UDP; + h.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + + lws_snprintf(buf, sizeof(buf), "%u", port); + n = getaddrinfo(NULL, buf, &h, &r); + if (n) { + lwsl_info("%s: getaddrinfo error: %s\n", __func__, + gai_strerror(n)); + goto bail; + } + + for (rp = r; rp; rp = rp->ai_next) { + sock.sockfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sock.sockfd >= 0) + break; + } + if (!rp) { + lwsl_err("%s: unable to create INET socket\n", __func__); + goto bail1; + } + + if ((flags & LWS_CAUDP_BIND) && bind(sock.sockfd, rp->ai_addr, +#if defined(_WIN32) + (int)rp->ai_addrlen +#else + rp->ai_addrlen +#endif + ) == -1) { + lwsl_err("%s: bind failed\n", __func__); + goto bail2; + } + + wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_RAW_SOCKET_UDP, sock, + protocol_name, parent_wsi); + if (!wsi) + lwsl_err("%s: udp adoption failed\n", __func__); + +bail2: + if (!wsi) + close((int)sock.sockfd); +bail1: + freeaddrinfo(r); + +bail: + return wsi; } +#endif + + + static const char *hex = "0123456789ABCDEF"; LWS_VISIBLE LWS_EXTERN const char * @@ -2458,6 +2737,27 @@ lws_json_purify(char *escaped, const char *string, int len) return escaped; } +LWS_VISIBLE LWS_EXTERN void +lws_filename_purify_inplace(char *filename) +{ + while (*filename) { + + if (*filename == '.' && filename[1] == '.') { + *filename = '_'; + filename[1] = '_'; + } + + if (*filename == ':' || + *filename == '/' || + *filename == '\\' || + *filename == '$' || + *filename == '%') + *filename = '_'; + + filename++; + } +} + LWS_VISIBLE LWS_EXTERN const char * lws_urlencode(char *escaped, const char *string, int len) { @@ -2570,32 +2870,42 @@ lws_snprintf(char *str, size_t size, const char *format, ...) va_end(ap); if (n >= (int)size) - return size; + return (int)size; return n; } +char * +lws_strncpy(char *dest, const char *src, size_t size) +{ + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; + + return dest; +} + LWS_VISIBLE LWS_EXTERN int lws_is_cgi(struct lws *wsi) { #ifdef LWS_WITH_CGI - return !!wsi->cgi; + return !!wsi->http.cgi; #else return 0; #endif } +const struct lws_protocol_vhost_options * +lws_pvo_search(const struct lws_protocol_vhost_options *pvo, const char *name) +{ + while (pvo) { + if (!strcmp(pvo->name, name)) + break; + pvo = pvo->next; + } -#ifdef LWS_NO_EXTENSIONS -LWS_EXTERN int -lws_set_extension_option(struct lws *wsi, const char *ext_name, - const char *opt_name, const char *opt_val) -{ - return -1; + return pvo; } -#endif - void lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs) @@ -2619,11 +2929,34 @@ lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs) } } +const char * +lws_cmdline_option(int argc, const char **argv, const char *val) +{ + int n = (int)strlen(val), c = argc; + + while (--c > 0) { + + if (!strncmp(argv[c], val, n)) { + if (!*(argv[c] + n) && c < argc - 1) { + /* coverity treats unchecked argv as "tainted" */ + if (!argv[c + 1] || strlen(argv[c + 1]) > 1024) + return NULL; + return argv[c + 1]; + } + + return argv[c] + n; + } + } + + return NULL; +} + #ifdef LWS_WITH_SERVER_STATUS LWS_EXTERN int lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) { +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) static const char * const prots[] = { "http://", "https://", @@ -2633,6 +2966,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) ">https://", "callback://" }; +#endif char *orig = buf, *end = buf + len - 1, first = 1; int n = 0; @@ -2656,8 +2990,8 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) " \"h2_subs\":\"%lu\"" , vh->name, vh->listen_port, -#ifdef LWS_OPENSSL_SUPPORT - vh->use_ssl, +#if defined(LWS_WITH_TLS) + vh->tls.use_ssl & LCCSCF_USE_SSL, #else 0, #endif @@ -2672,9 +3006,9 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) vh->conn_stats.h2_alpn, vh->conn_stats.h2_subs ); - - if (vh->mount_list) { - const struct lws_http_mount *m = vh->mount_list; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + if (vh->http.mount_list) { + const struct lws_http_mount *m = vh->http.mount_list; buf += lws_snprintf(buf, end - buf, ",\n \"mounts\":["); while (m) { @@ -2705,7 +3039,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) } buf += lws_snprintf(buf, end - buf, "\n ]"); } - +#endif if (vh->protocols) { n = 0; first = 1; @@ -2798,8 +3132,8 @@ lws_json_dump_context(const struct lws_context *context, char *buf, int len, " \"ah_wait_list\":\"%d\"\n" " }", pt->fds_count, - pt->ah_count_in_use, - pt->ah_wait_list_length); + pt->http.ah_count_in_use, + pt->http.ah_wait_list_length); } buf += lws_snprintf(buf, end - buf, "]"); @@ -2850,7 +3184,7 @@ lws_json_dump_context(const struct lws_context *context, char *buf, int len, #ifdef LWS_WITH_CGI for (n = 0; n < context->count_threads; n++) { pt = &context->pt[n]; - pcgi = &pt->cgi_list; + pcgi = &pt->http.cgi_list; while (*pcgi) { pcgi = &(*pcgi)->cgi_list; @@ -2898,63 +3232,117 @@ lws_stats_log_dump(struct lws_context *context) lwsl_notice("\n"); lwsl_notice("LWS internal statistics dump ----->\n"); - lwsl_notice("LWSSTATS_C_CONNECTIONS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_CONNECTIONS)); - lwsl_notice("LWSSTATS_C_API_CLOSE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_CLOSE)); - lwsl_notice("LWSSTATS_C_API_READ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_READ)); - lwsl_notice("LWSSTATS_C_API_LWS_WRITE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_LWS_WRITE)); - lwsl_notice("LWSSTATS_C_API_WRITE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_WRITE)); - lwsl_notice("LWSSTATS_C_WRITE_PARTIALS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITE_PARTIALS)); - lwsl_notice("LWSSTATS_C_WRITEABLE_CB_REQ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_REQ)); - lwsl_notice("LWSSTATS_C_WRITEABLE_CB_EFF_REQ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_EFF_REQ)); - lwsl_notice("LWSSTATS_C_WRITEABLE_CB: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB)); - lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN)); - lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_FAILED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_FAILED)); - lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)); - lwsl_notice("LWSSTATS_C_SSL_CONNS_HAD_RX: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)); - lwsl_notice("LWSSTATS_C_PEER_LIMIT_AH_DENIED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_PEER_LIMIT_AH_DENIED)); - lwsl_notice("LWSSTATS_C_PEER_LIMIT_WSI_DENIED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_PEER_LIMIT_WSI_DENIED)); - - lwsl_notice("LWSSTATS_C_TIMEOUTS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_TIMEOUTS)); - lwsl_notice("LWSSTATS_C_SERVICE_ENTRY: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SERVICE_ENTRY)); - lwsl_notice("LWSSTATS_B_READ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_READ)); - lwsl_notice("LWSSTATS_B_WRITE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_WRITE)); - lwsl_notice("LWSSTATS_B_PARTIALS_ACCEPTED_PARTS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS)); - lwsl_notice("LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY: %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / 1000); + lwsl_notice("LWSSTATS_C_CONNECTIONS: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_CONNECTIONS)); + lwsl_notice("LWSSTATS_C_API_CLOSE: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_API_CLOSE)); + lwsl_notice("LWSSTATS_C_API_READ: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_API_READ)); + lwsl_notice("LWSSTATS_C_API_LWS_WRITE: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_API_LWS_WRITE)); + lwsl_notice("LWSSTATS_C_API_WRITE: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_API_WRITE)); + lwsl_notice("LWSSTATS_C_WRITE_PARTIALS: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_WRITE_PARTIALS)); + lwsl_notice("LWSSTATS_C_WRITEABLE_CB_REQ: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_WRITEABLE_CB_REQ)); + lwsl_notice("LWSSTATS_C_WRITEABLE_CB_EFF_REQ: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_WRITEABLE_CB_EFF_REQ)); + lwsl_notice("LWSSTATS_C_WRITEABLE_CB: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_WRITEABLE_CB)); + lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN)); + lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_FAILED: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_SSL_CONNECTIONS_FAILED)); + lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)); + lwsl_notice("LWSSTATS_C_SSL_CONNS_HAD_RX: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_SSL_CONNS_HAD_RX)); + lwsl_notice("LWSSTATS_C_PEER_LIMIT_AH_DENIED: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_PEER_LIMIT_AH_DENIED)); + lwsl_notice("LWSSTATS_C_PEER_LIMIT_WSI_DENIED: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_PEER_LIMIT_WSI_DENIED)); + + lwsl_notice("LWSSTATS_C_TIMEOUTS: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_TIMEOUTS)); + lwsl_notice("LWSSTATS_C_SERVICE_ENTRY: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_C_SERVICE_ENTRY)); + lwsl_notice("LWSSTATS_B_READ: %8llu\n", + (unsigned long long)lws_stats_get(context, LWSSTATS_B_READ)); + lwsl_notice("LWSSTATS_B_WRITE: %8llu\n", + (unsigned long long)lws_stats_get(context, LWSSTATS_B_WRITE)); + lwsl_notice("LWSSTATS_B_PARTIALS_ACCEPTED_PARTS: %8llu\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_B_PARTIALS_ACCEPTED_PARTS)); + lwsl_notice("LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY: %8llums\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / 1000); if (lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) lwsl_notice(" Avg accept delay: %8llums\n", - (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / - lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) / 1000); - lwsl_notice("LWSSTATS_MS_SSL_RX_DELAY: %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) / 1000); + (unsigned long long)(lws_stats_get(context, + LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / + lws_stats_get(context, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) / 1000); + lwsl_notice("LWSSTATS_MS_SSL_RX_DELAY: %8llums\n", + (unsigned long long)lws_stats_get(context, + LWSSTATS_MS_SSL_RX_DELAY) / 1000); if (lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)) lwsl_notice(" Avg accept-rx delay: %8llums\n", - (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) / - lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)) / 1000); + (unsigned long long)(lws_stats_get(context, + LWSSTATS_MS_SSL_RX_DELAY) / + lws_stats_get(context, + LWSSTATS_C_SSL_CONNS_HAD_RX)) / 1000); lwsl_notice("LWSSTATS_MS_WRITABLE_DELAY: %8lluus\n", - (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY)); + (unsigned long long)lws_stats_get(context, + LWSSTATS_MS_WRITABLE_DELAY)); lwsl_notice("LWSSTATS_MS_WORST_WRITABLE_DELAY: %8lluus\n", - (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WORST_WRITABLE_DELAY)); + (unsigned long long)lws_stats_get(context, + LWSSTATS_MS_WORST_WRITABLE_DELAY)); if (lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB)) lwsl_notice(" Avg writable delay: %8lluus\n", - (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY) / + (unsigned long long)(lws_stats_get(context, + LWSSTATS_MS_WRITABLE_DELAY) / lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB))); - lwsl_notice("Simultaneous SSL restriction: %8d/%d/%d\n", context->simultaneous_ssl, - context->simultaneous_ssl_restriction, context->ssl_gate_accepts); + lwsl_notice("Simultaneous SSL restriction: %8d/%d\n", + context->simultaneous_ssl, + context->simultaneous_ssl_restriction); - lwsl_notice("Live wsi: %8d\n", context->count_wsi_allocated); + lwsl_notice("Live wsi: %8d\n", + context->count_wsi_allocated); context->updated = 1; while (v) { - if (v->lserv_wsi) { + if (v->lserv_wsi && + v->lserv_wsi->position_in_fds_table != LWS_NO_FDS_POS) { - struct lws_context_per_thread *pt = &context->pt[(int)v->lserv_wsi->tsi]; + struct lws_context_per_thread *pt = + &context->pt[(int)v->lserv_wsi->tsi]; struct lws_pollfd *pfd; pfd = &pt->fds[v->lserv_wsi->position_in_fds_table]; lwsl_notice(" Listen port %d actual POLLIN: %d\n", - v->listen_port, (int)pfd->events & LWS_POLLIN); + v->listen_port, + (int)pfd->events & LWS_POLLIN); } v = v->vhost_next; @@ -2967,20 +3355,20 @@ lws_stats_log_dump(struct lws_context *context) lwsl_notice("PT %d\n", n + 1); - lws_pt_lock(pt); + lws_pt_lock(pt, __func__); lwsl_notice(" AH in use / max: %d / %d\n", - pt->ah_count_in_use, + pt->http.ah_count_in_use, context->max_http_header_pool); - wl = pt->ah_wait_list; + wl = pt->http.ah_wait_list; while (wl) { m++; - wl = wl->u.hdr.ah_wait_list; + wl = wl->ah_wait_list; } lwsl_notice(" AH wait list count / actual: %d / %d\n", - pt->ah_wait_list_length, m); + pt->http.ah_wait_list_length, m); lws_pt_unlock(pt); } @@ -3004,15 +3392,20 @@ lws_stats_log_dump(struct lws_context *context) for (n = 0; n < (int)context->pl_hash_elements; n++) { char buf[72]; - lws_start_foreach_llp(struct lws_peer **, peer, context->pl_hash_table[n]) { + lws_start_foreach_llp(struct lws_peer **, peer, + context->pl_hash_table[n]) { struct lws_peer *df = *peer; if (!lws_plat_inet_ntop(df->af, df->addr, buf, sizeof(buf) - 1)) strcpy(buf, "unknown"); - +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) lwsl_notice(" peer %s: count wsi: %d, count ah: %d\n", buf, df->count_wsi, df->count_ah); +#else + lwsl_notice(" peer %s: count wsi: %d\n", + buf, df->count_wsi); +#endif if (!--m) break; @@ -3028,23 +3421,23 @@ void lws_stats_atomic_bump(struct lws_context * context, struct lws_context_per_thread *pt, int index, uint64_t bump) { - lws_pt_lock(pt); + lws_pt_stats_lock(pt); context->lws_stats[index] += bump; if (index != LWSSTATS_C_SERVICE_ENTRY) context->updated = 1; - lws_pt_unlock(pt); + lws_pt_stats_unlock(pt); } void lws_stats_atomic_max(struct lws_context * context, struct lws_context_per_thread *pt, int index, uint64_t val) { - lws_pt_lock(pt); + lws_pt_stats_lock(pt); if (val > context->lws_stats[index]) { context->lws_stats[index] = val; context->updated = 1; } - lws_pt_unlock(pt); + lws_pt_stats_unlock(pt); } #endif diff --git a/thirdparty/libwebsockets/core/output.c b/thirdparty/libwebsockets/core/output.c new file mode 100644 index 0000000000..e2ff18ef27 --- /dev/null +++ b/thirdparty/libwebsockets/core/output.c @@ -0,0 +1,308 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +/* + * notice this returns number of bytes consumed, or -1 + */ +int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) +{ + struct lws_context *context = lws_get_context(wsi); + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + size_t real_len = len; + unsigned int n; + + // lwsl_hexdump_err(buf, len); + + /* + * Detect if we got called twice without going through the + * event loop to handle pending. This would be caused by either + * back-to-back writes in one WRITABLE (illegal) or calling lws_write() + * from outside the WRITABLE callback (illegal). + */ + if (wsi->could_have_pending) { + lwsl_hexdump_level(LLL_ERR, buf, len); + lwsl_err("** %p: vh: %s, prot: %s, role %s: " + "Illegal back-to-back write of %lu detected...\n", + wsi, wsi->vhost->name, wsi->protocol->name, + wsi->role_ops->name, + (unsigned long)len); + // assert(0); + + return -1; + } + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_WRITE, 1); + + if (!len) + return 0; + /* just ignore sends after we cleared the truncation buffer */ + if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE && !wsi->trunc_len) + return (int)len; + + if (wsi->trunc_len && (buf < wsi->trunc_alloc || + buf > (wsi->trunc_alloc + wsi->trunc_len + wsi->trunc_offset))) { + lwsl_hexdump_level(LLL_ERR, buf, len); + lwsl_err("** %p: vh: %s, prot: %s, Sending new %lu, pending truncated ...\n" + " It's illegal to do an lws_write outside of\n" + " the writable callback: fix your code\n", + wsi, wsi->vhost->name, wsi->protocol->name, + (unsigned long)len); + assert(0); + + return -1; + } + + if (!wsi->http2_substream && !lws_socket_is_valid(wsi->desc.sockfd)) + lwsl_warn("** error invalid sock but expected to send\n"); + + /* limit sending */ + if (wsi->protocol->tx_packet_size) + n = (int)wsi->protocol->tx_packet_size; + else { + n = (int)wsi->protocol->rx_buffer_size; + if (!n) + n = context->pt_serv_buf_size; + } + n += LWS_PRE + 4; + if (n > len) + n = (int)len; + + /* nope, send it on the socket directly */ + lws_latency_pre(context, wsi); + n = lws_ssl_capable_write(wsi, buf, n); + lws_latency(context, wsi, "send lws_issue_raw", n, n == len); + + /* something got written, it can have been truncated now */ + wsi->could_have_pending = 1; + + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + /* we're going to close, let close know sends aren't possible */ + wsi->socket_is_permanently_unusable = 1; + return -1; + case LWS_SSL_CAPABLE_MORE_SERVICE: + /* + * nothing got sent, not fatal. Retry the whole thing later, + * ie, implying treat it was a truncated send so it gets + * retried + */ + n = 0; + break; + } + + /* + * we were already handling a truncated send? + */ + if (wsi->trunc_len) { + lwsl_info("%p partial adv %d (vs %ld)\n", wsi, n, (long)real_len); + wsi->trunc_offset += n; + wsi->trunc_len -= n; + + if (!wsi->trunc_len) { + lwsl_info("** %p partial send completed\n", wsi); + /* done with it, but don't free it */ + n = (int)real_len; + if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) { + lwsl_info("** %p signalling to close now\n", wsi); + return -1; /* retry closing now */ + } + } + /* always callback on writeable */ + lws_callback_on_writable(wsi); + + return n; + } + + if ((unsigned int)n == real_len) + /* what we just sent went out cleanly */ + return n; + + /* + * Newly truncated send. Buffer the remainder (it will get + * first priority next time the socket is writable). + */ + lwsl_debug("%p new partial sent %d from %lu total\n", wsi, n, + (unsigned long)real_len); + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITE_PARTIALS, 1); + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, n); + + /* + * - if we still have a suitable malloc lying around, use it + * - or, if too small, reallocate it + * - or, if no buffer, create it + */ + if (!wsi->trunc_alloc || real_len - n > wsi->trunc_alloc_len) { + lws_free(wsi->trunc_alloc); + + wsi->trunc_alloc_len = (unsigned int)(real_len - n); + wsi->trunc_alloc = lws_malloc(real_len - n, + "truncated send alloc"); + if (!wsi->trunc_alloc) { + lwsl_err("truncated send: unable to malloc %lu\n", + (unsigned long)(real_len - n)); + return -1; + } + } + wsi->trunc_offset = 0; + wsi->trunc_len = (unsigned int)(real_len - n); + memcpy(wsi->trunc_alloc, buf + n, real_len - n); + +#if !defined(LWS_WITH_ESP32) + if (lws_wsi_is_udp(wsi)) { + /* stash original destination for fulfilling UDP partials */ + wsi->udp->sa_pending = wsi->udp->sa; + wsi->udp->salen_pending = wsi->udp->salen; + } +#endif + + /* since something buffered, force it to get another chance to send */ + lws_callback_on_writable(wsi); + + return (int)real_len; +} + +LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol wp) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (wsi->parent_carries_io) { + struct lws_write_passthru pas; + + pas.buf = buf; + pas.len = len; + pas.wp = wp; + pas.wsi = wsi; + + if (wsi->parent->protocol->callback(wsi->parent, + LWS_CALLBACK_CHILD_WRITE_VIA_PARENT, + wsi->parent->user_space, + (void *)&pas, 0)) + return 1; + + return (int)len; + } + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_LWS_WRITE, 1); + + if ((int)len < 0) { + lwsl_err("%s: suspicious len int %d, ulong %lu\n", __func__, + (int)len, (unsigned long)len); + return -1; + } + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_WRITE, len); + +#ifdef LWS_WITH_ACCESS_LOG + wsi->http.access_log.sent += len; +#endif + if (wsi->vhost) + wsi->vhost->conn_stats.tx += len; + + assert(wsi->role_ops); + if (!wsi->role_ops->write_role_protocol) + return lws_issue_raw(wsi, buf, len); + + return wsi->role_ops->write_role_protocol(wsi, buf, len, &wp); +} + +LWS_VISIBLE int +lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n = 0; + + lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); + + if (lws_wsi_is_udp(wsi)) { +#if !defined(LWS_WITH_ESP32) + wsi->udp->salen = sizeof(wsi->udp->sa); + n = recvfrom(wsi->desc.sockfd, (char *)buf, len, 0, + &wsi->udp->sa, &wsi->udp->salen); +#endif + } else + n = recv(wsi->desc.sockfd, (char *)buf, len, 0); + + if (n >= 0) { + if (wsi->vhost) + wsi->vhost->conn_stats.rx += n; + lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); + + return n; + } + + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK || + LWS_ERRNO == LWS_EINTR) + return LWS_SSL_CAPABLE_MORE_SERVICE; + + lwsl_notice("error on reading from skt : %d\n", LWS_ERRNO); + return LWS_SSL_CAPABLE_ERROR; +} + +LWS_VISIBLE int +lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) +{ + int n = 0; + + if (lws_wsi_is_udp(wsi)) { +#if !defined(LWS_WITH_ESP32) + if (wsi->trunc_len) + n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa_pending, wsi->udp->salen_pending); + else + n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa, wsi->udp->salen); +#endif + } else + n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL); +// lwsl_info("%s: sent len %d result %d", __func__, len, n); + if (n >= 0) + return n; + + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK || + LWS_ERRNO == LWS_EINTR) { + if (LWS_ERRNO == LWS_EWOULDBLOCK) { + lws_set_blocking_send(wsi); + } + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + + lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", + len, wsi->desc.sockfd, n, LWS_ERRNO); + + return LWS_SSL_CAPABLE_ERROR; +} + +LWS_VISIBLE int +lws_ssl_pending_no_ssl(struct lws *wsi) +{ + (void)wsi; +#if defined(LWS_WITH_ESP32) + return 100; +#else + return 0; +#endif +} diff --git a/thirdparty/lws/pollfd.c b/thirdparty/libwebsockets/core/pollfd.c index 54a4a86057..2a632ce8ec 100644 --- a/thirdparty/lws/pollfd.c +++ b/thirdparty/libwebsockets/core/pollfd.c @@ -19,21 +19,31 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include "core/private.h" int _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) { +#if !defined(LWS_WITH_LIBUV) && !defined(LWS_WITH_LIBEV) && !defined(LWS_WITH_LIBEVENT) + volatile struct lws_context_per_thread *vpt; +#endif struct lws_context_per_thread *pt; struct lws_context *context; int ret = 0, pa_events = 1; struct lws_pollfd *pfd; int sampled_tid, tid; - if (!wsi || wsi->position_in_fds_table < 0) + if (!wsi) + return 0; + + assert(wsi->position_in_fds_table == LWS_NO_FDS_POS || + wsi->position_in_fds_table >= 0); + + if (wsi->position_in_fds_table == LWS_NO_FDS_POS) return 0; - if (wsi->handling_pollout && !_and && _or == LWS_POLLOUT) { + if (((volatile struct lws *)wsi)->handling_pollout && + !_and && _or == LWS_POLLOUT) { /* * Happening alongside service thread handling POLLOUT. * The danger is when he is finished, he will disable POLLOUT, @@ -42,7 +52,7 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) * Instead of changing the fds, inform the service thread * what happened, and ask it to leave POLLOUT active on exit */ - wsi->leave_pollout_active = 1; + ((volatile struct lws *)wsi)->leave_pollout_active = 1; /* * by definition service thread is not in poll wait, so no need * to cancel service @@ -55,42 +65,106 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) context = wsi->context; pt = &context->pt[(int)wsi->tsi]; - assert(wsi->position_in_fds_table >= 0 && - wsi->position_in_fds_table < pt->fds_count); + + assert(wsi->position_in_fds_table < (int)pt->fds_count); + +#if !defined(LWS_WITH_LIBUV) && \ + !defined(LWS_WITH_LIBEV) && \ + !defined(LWS_WITH_LIBEVENT) + /* + * This only applies when we use the default poll() event loop. + * + * BSD can revert pa->events at any time, when the kernel decides to + * exit from poll(). We can't protect against it using locking. + * + * Therefore we must check first if the service thread is in poll() + * wait; if so, we know we must be being called from a foreign thread, + * and we must keep a strictly ordered list of changes we made instead + * of trying to apply them, since when poll() exits, which may happen + * at any time it would revert our changes. + * + * The plat code will apply them when it leaves the poll() wait + * before doing anything else. + */ + + vpt = (volatile struct lws_context_per_thread *)pt; + + vpt->foreign_spinlock = 1; + lws_memory_barrier(); + + if (vpt->inside_poll) { + struct lws_foreign_thread_pollfd *ftp, **ftp1; + /* + * We are certainly a foreign thread trying to change events + * while the service thread is in the poll() wait. + * + * Create a list of changes to be applied after poll() exit, + * instead of trying to apply them now. + */ + ftp = lws_malloc(sizeof(*ftp), "ftp"); + if (!ftp) { + vpt->foreign_spinlock = 0; + lws_memory_barrier(); + ret = -1; + goto bail; + } + + ftp->_and = _and; + ftp->_or = _or; + ftp->fd_index = wsi->position_in_fds_table; + ftp->next = NULL; + + /* place at END of list to maintain order */ + ftp1 = (struct lws_foreign_thread_pollfd **) + &vpt->foreign_pfd_list; + while (*ftp1) + ftp1 = &((*ftp1)->next); + + *ftp1 = ftp; + vpt->foreign_spinlock = 0; + lws_memory_barrier(); + lws_cancel_service_pt(wsi); + + return 0; + } + + vpt->foreign_spinlock = 0; + lws_memory_barrier(); +#endif pfd = &pt->fds[wsi->position_in_fds_table]; pa->fd = wsi->desc.sockfd; + lwsl_debug("%s: wsi %p: fd %d events %d -> %d\n", __func__, wsi, pa->fd, pfd->events, (pfd->events & ~_and) | _or); pa->prev_events = pfd->events; pa->events = pfd->events = (pfd->events & ~_and) | _or; if (wsi->http2_substream) return 0; - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CHANGE_MODE_POLL_FD, - wsi->user_space, (void *)pa, 0)) { + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CHANGE_MODE_POLL_FD, + wsi->user_space, (void *)pa, 0)) { ret = -1; goto bail; } - if (_and & LWS_POLLIN) { - lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ); - lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ); - lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ); - } - if (_or & LWS_POLLIN) { - lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ); - lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ); - lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ); - } - if (_and & LWS_POLLOUT) { - lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); - lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); - lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); - } - if (_or & LWS_POLLOUT) { - lws_libev_io(wsi, LWS_EV_START | LWS_EV_WRITE); - lws_libuv_io(wsi, LWS_EV_START | LWS_EV_WRITE); - lws_libevent_io(wsi, LWS_EV_START | LWS_EV_WRITE); + if (context->event_loop_ops->io) { + if (_and & LWS_POLLIN) + context->event_loop_ops->io(wsi, + LWS_EV_STOP | LWS_EV_READ); + + if (_or & LWS_POLLIN) + context->event_loop_ops->io(wsi, + LWS_EV_START | LWS_EV_READ); + + if (_and & LWS_POLLOUT) + context->event_loop_ops->io(wsi, + LWS_EV_STOP | LWS_EV_WRITE); + + if (_or & LWS_POLLOUT) + context->event_loop_ops->io(wsi, + LWS_EV_START | LWS_EV_WRITE); } /* @@ -100,20 +174,16 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) * ... and the service thread is waiting ... * then cancel it to force a restart with our changed events */ -#if LWS_POSIX pa_events = pa->prev_events != pa->events; -#endif if (pa_events) { - if (lws_plat_change_pollfd(context, wsi, pfd)) { lwsl_info("%s failed\n", __func__); ret = -1; goto bail; } - sampled_tid = context->service_tid; - if (sampled_tid) { + if (sampled_tid && wsi->vhost) { tid = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0); if (tid == -1) { @@ -124,34 +194,39 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) lws_cancel_service_pt(wsi); } } + bail: return ret; } #ifndef LWS_NO_SERVER +/* + * Enable or disable listen sockets on this pt globally... + * it's modulated according to the pt having space for a new accept. + */ static void -lws_accept_modulation(struct lws_context_per_thread *pt, int allow) +lws_accept_modulation(struct lws_context *context, + struct lws_context_per_thread *pt, int allow) { -// multithread listen seems broken -#if 0 struct lws_vhost *vh = context->vhost_list; struct lws_pollargs pa1; while (vh) { - if (allow) - _lws_change_pollfd(pt->wsi_listening, + if (vh->lserv_wsi) { + if (allow) + _lws_change_pollfd(vh->lserv_wsi, 0, LWS_POLLIN, &pa1); - else - _lws_change_pollfd(pt->wsi_listening, + else + _lws_change_pollfd(vh->lserv_wsi, LWS_POLLIN, 0, &pa1); + } vh = vh->vhost_next; } -#endif } #endif int -insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) +__insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) { struct lws_pollargs pa = { wsi->desc.sockfd, LWS_POLLIN, 0 }; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; @@ -167,52 +242,46 @@ insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) return 1; } -#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266) - if (wsi->desc.sockfd >= context->max_fds) { - lwsl_err("Socket fd %d is too high (%d)\n", - wsi->desc.sockfd, context->max_fds); +#if !defined(_WIN32) + if (wsi->desc.sockfd - lws_plat_socket_offset() >= context->max_fds) { + lwsl_err("Socket fd %d is too high (%d) offset %d\n", + wsi->desc.sockfd, context->max_fds, lws_plat_socket_offset()); return 1; } #endif assert(wsi); - assert(wsi->vhost); + assert(wsi->event_pipe || wsi->vhost); assert(lws_socket_is_valid(wsi->desc.sockfd)); - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *) &pa, 1)) return -1; - lws_pt_lock(pt); pt->count_conns++; insert_wsi(context, wsi); -#if defined(LWS_WITH_ESP8266) - if (wsi->position_in_fds_table == -1) -#endif - wsi->position_in_fds_table = pt->fds_count; + wsi->position_in_fds_table = pt->fds_count; pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd; -#if LWS_POSIX pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN; -#else - pt->fds[wsi->position_in_fds_table].events = 0; -#endif pa.events = pt->fds[pt->fds_count].events; lws_plat_insert_socket_into_fds(context, wsi); /* external POLL support via protocol 0 */ - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD, + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD, wsi->user_space, (void *) &pa, 0)) ret = -1; #ifndef LWS_NO_SERVER /* if no more room, defeat accepts on this thread */ if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1) - lws_accept_modulation(pt, 0); + lws_accept_modulation(context, pt, 0); #endif - lws_pt_unlock(pt); - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *)&pa, 1)) ret = -1; @@ -220,15 +289,13 @@ insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) } int -remove_wsi_socket_from_fds(struct lws *wsi) +__remove_wsi_socket_from_fds(struct lws *wsi) { struct lws_context *context = wsi->context; struct lws_pollargs pa = { wsi->desc.sockfd, 0, 0 }; -#if !defined(LWS_WITH_ESP8266) struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct lws *end_wsi; int v; -#endif int m, ret = 0; if (wsi->parent_carries_io) { @@ -236,15 +303,16 @@ remove_wsi_socket_from_fds(struct lws *wsi) return 0; } -#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266) - if (wsi->desc.sockfd > context->max_fds) { +#if !defined(_WIN32) + if (wsi->desc.sockfd - lws_plat_socket_offset() > context->max_fds) { lwsl_err("fd %d too high (%d)\n", wsi->desc.sockfd, context->max_fds); return 1; } #endif - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, wsi->user_space, (void *)&pa, 1)) return -1; @@ -253,101 +321,114 @@ remove_wsi_socket_from_fds(struct lws *wsi) /* the guy who is to be deleted's slot index in pt->fds */ m = wsi->position_in_fds_table; -#if !defined(LWS_WITH_ESP8266) - lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | - LWS_EV_PREPARE_DELETION); - lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | - LWS_EV_PREPARE_DELETION); + /* these are the only valid possibilities for position_in_fds_table */ + assert(m == LWS_NO_FDS_POS || (m >= 0 && + (unsigned int)m < pt->fds_count)); - lws_pt_lock(pt); + if (context->event_loop_ops->io) + context->event_loop_ops->io(wsi, + LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | + LWS_EV_PREPARE_DELETION); lwsl_debug("%s: wsi=%p, sock=%d, fds pos=%d, end guy pos=%d, endfd=%d\n", __func__, wsi, wsi->desc.sockfd, wsi->position_in_fds_table, pt->fds_count, pt->fds[pt->fds_count].fd); - /* have the last guy take up the now vacant slot */ - pt->fds[m] = pt->fds[pt->fds_count - 1]; -#endif - /* this decrements pt->fds_count */ - lws_plat_delete_socket_from_fds(context, wsi, m); -#if !defined(LWS_WITH_ESP8266) - v = (int) pt->fds[m].fd; - /* end guy's "position in fds table" is now the deletion guy's old one */ - end_wsi = wsi_from_fd(context, v); - if (!end_wsi) { - lwsl_err("no wsi found for sock fd %d at pos %d, pt->fds_count=%d\n", - (int)pt->fds[m].fd, m, pt->fds_count); - assert(0); - } else - end_wsi->position_in_fds_table = m; - - /* deletion guy's lws_lookup entry needs nuking */ - delete_from_fd(context, wsi->desc.sockfd); - /* removed wsi has no position any more */ - wsi->position_in_fds_table = -1; + if (m != LWS_NO_FDS_POS) { + + /* have the last guy take up the now vacant slot */ + pt->fds[m] = pt->fds[pt->fds_count - 1]; + /* this decrements pt->fds_count */ + lws_plat_delete_socket_from_fds(context, wsi, m); + v = (int) pt->fds[m].fd; + /* end guy's "position in fds table" is now the deletion guy's old one */ + end_wsi = wsi_from_fd(context, v); + if (!end_wsi) { + lwsl_err("no wsi for fd %d at pos %d, pt->fds_count=%d\n", + (int)pt->fds[m].fd, m, pt->fds_count); + assert(0); + } else + end_wsi->position_in_fds_table = m; + + /* deletion guy's lws_lookup entry needs nuking */ + delete_from_fd(context, wsi->desc.sockfd); + + /* removed wsi has no position any more */ + wsi->position_in_fds_table = LWS_NO_FDS_POS; + } /* remove also from external POLL support via protocol 0 */ - if (lws_socket_is_valid(wsi->desc.sockfd)) - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD, - wsi->user_space, (void *) &pa, 0)) - ret = -1; + if (lws_socket_is_valid(wsi->desc.sockfd) && wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD, + wsi->user_space, (void *) &pa, 0)) + ret = -1; + #ifndef LWS_NO_SERVER - if (!context->being_destroyed) - /* if this made some room, accept connects on this thread */ - if ((unsigned int)pt->fds_count < context->fd_limit_per_thread - 1) - lws_accept_modulation(pt, 1); + if (!context->being_destroyed && + /* if this made some room, accept connects on this thread */ + (unsigned int)pt->fds_count < context->fd_limit_per_thread - 1) + lws_accept_modulation(context, pt, 1); #endif - lws_pt_unlock(pt); - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *) &pa, 1)) ret = -1; -#endif + return ret; } int -lws_change_pollfd(struct lws *wsi, int _and, int _or) +__lws_change_pollfd(struct lws *wsi, int _and, int _or) { - struct lws_context_per_thread *pt; struct lws_context *context; struct lws_pollargs pa; int ret = 0; - if (!wsi || !wsi->protocol || wsi->position_in_fds_table < 0) - return 1; + if (!wsi || (!wsi->protocol && !wsi->event_pipe) || + wsi->position_in_fds_table == LWS_NO_FDS_POS) + return 0; context = lws_get_context(wsi); if (!context) return 1; - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, - wsi->user_space, (void *) &pa, 0)) + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, + wsi->user_space, (void *) &pa, 0)) return -1; - pt = &context->pt[(int)wsi->tsi]; - - lws_pt_lock(pt); ret = _lws_change_pollfd(wsi, _and, _or, &pa); - lws_pt_unlock(pt); - if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, + if (wsi->vhost && + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, wsi->user_space, (void *) &pa, 0)) ret = -1; return ret; } +int +lws_change_pollfd(struct lws *wsi, int _and, int _or) +{ + struct lws_context_per_thread *pt; + int ret = 0; + + pt = &wsi->context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt, __func__); + ret = __lws_change_pollfd(wsi, _and, _or); + lws_pt_unlock(pt); + + return ret; +} + LWS_VISIBLE int lws_callback_on_writable(struct lws *wsi) { struct lws_context_per_thread *pt; -#ifdef LWS_WITH_HTTP2 - struct lws *network_wsi, *wsi2; - int already; -#endif int n; - if (wsi->state == LWSS_SHUTDOWN) + if (lwsi_state(wsi) == LRS_SHUTDOWN) return 0; if (wsi->socket_is_permanently_unusable) @@ -375,72 +456,31 @@ lws_callback_on_writable(struct lws *wsi) #if defined(LWS_WITH_STATS) if (!wsi->active_writable_req_us) { wsi->active_writable_req_us = time_in_microseconds(); - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1); } #endif -#ifdef LWS_WITH_HTTP2 - lwsl_info("%s: %p\n", __func__, wsi); - - if (wsi->mode != LWSCM_HTTP2_SERVING) - goto network_sock; - if (wsi->u.h2.requested_POLLOUT) { - lwsl_info("already pending writable\n"); - return 1; + if (wsi->role_ops->callback_on_writable) { + if (wsi->role_ops->callback_on_writable(wsi)) + return 1; + wsi = lws_get_network_wsi(wsi); } - /* is this for DATA or for control messages? */ - if (wsi->upgraded_to_http2 && !wsi->u.h2.h2n->pps && - !lws_h2_tx_cr_get(wsi)) { - /* - * other side is not able to cope with us sending DATA - * anything so no matter if we have POLLOUT on our side if it's - * DATA we want to send. - * - * Delay waiting for our POLLOUT until peer indicates he has - * space for more using tx window command in http2 layer - */ - lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi, wsi->u.h2.tx_cr); - wsi->u.h2.skint = 1; - return 0; - } - - wsi->u.h2.skint = 0; - network_wsi = lws_get_network_wsi(wsi); - already = network_wsi->u.h2.requested_POLLOUT; - - /* mark everybody above him as requesting pollout */ - - wsi2 = wsi; - while (wsi2) { - wsi2->u.h2.requested_POLLOUT = 1; - lwsl_info("mark %p pending writable\n", wsi2); - wsi2 = wsi2->u.h2.parent_wsi; - } - - /* for network action, act only on the network wsi */ - - wsi = network_wsi; - if (already) - return 1; -network_sock: -#endif - - if (lws_ext_cb_active(wsi, LWS_EXT_CB_REQUEST_ON_WRITEABLE, NULL, 0)) - return 1; - - if (wsi->position_in_fds_table < 0) { - lwsl_debug("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd); + if (wsi->position_in_fds_table == LWS_NO_FDS_POS) { + lwsl_debug("%s: failed to find socket %d\n", __func__, + wsi->desc.sockfd); return -1; } - if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) + if (__lws_change_pollfd(wsi, 0, LWS_POLLOUT)) return -1; return 1; } + /* * stitch protocol choice into the vh protocol linked list * We always insert ourselves at the start of the list @@ -458,6 +498,8 @@ lws_same_vh_protocol_insert(struct lws *wsi, int n) lwsl_notice("Attempted to attach wsi twice to same vh prot\n"); } + lws_vhost_lock(wsi->vhost); + wsi->same_vh_protocol_prev = &wsi->vhost->same_vh_protocol_list[n]; /* old first guy is our next */ wsi->same_vh_protocol_next = wsi->vhost->same_vh_protocol_list[n]; @@ -468,6 +510,10 @@ lws_same_vh_protocol_insert(struct lws *wsi, int n) /* old first guy points back to us now */ wsi->same_vh_protocol_next->same_vh_protocol_prev = &wsi->same_vh_protocol_next; + + wsi->on_same_vh_list = 1; + + lws_vhost_unlock(wsi->vhost); } void @@ -482,6 +528,11 @@ lws_same_vh_protocol_remove(struct lws *wsi) */ lwsl_info("%s: removing same prot wsi %p\n", __func__, wsi); + if (!wsi->vhost || !wsi->on_same_vh_list) + return; + + lws_vhost_lock(wsi->vhost); + if (wsi->same_vh_protocol_prev) { assert (*(wsi->same_vh_protocol_prev) == wsi); lwsl_info("have prev %p, setting him to our next %p\n", @@ -493,13 +544,15 @@ lws_same_vh_protocol_remove(struct lws *wsi) } /* our next should point back to our prev */ - if (wsi->same_vh_protocol_next) { + if (wsi->same_vh_protocol_next) wsi->same_vh_protocol_next->same_vh_protocol_prev = wsi->same_vh_protocol_prev; - } wsi->same_vh_protocol_prev = NULL; wsi->same_vh_protocol_next = NULL; + wsi->on_same_vh_list = 0; + + lws_vhost_unlock(wsi->vhost); } @@ -523,7 +576,8 @@ lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost, assert(wsi->protocol == protocol); assert(*wsi->same_vh_protocol_prev == wsi); if (wsi->same_vh_protocol_next) - assert(wsi->same_vh_protocol_next->same_vh_protocol_prev == + assert(wsi->same_vh_protocol_next-> + same_vh_protocol_prev == &wsi->same_vh_protocol_next); lws_callback_on_writable(wsi); diff --git a/thirdparty/libwebsockets/core/private.h b/thirdparty/libwebsockets/core/private.h new file mode 100644 index 0000000000..d6b494ac8c --- /dev/null +++ b/thirdparty/libwebsockets/core/private.h @@ -0,0 +1,1751 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "lws_config.h" +#include "lws_config_private.h" + +#if defined(LWS_WITH_CGI) && defined(LWS_HAVE_VFORK) + #define _GNU_SOURCE +#endif + +#if defined(__COVERITY__) && !defined(LWS_COVERITY_WORKAROUND) + #define LWS_COVERITY_WORKAROUND + typedef float _Float32; + typedef float _Float64; + typedef float _Float128; + typedef float _Float32x; + typedef float _Float64x; + typedef float _Float128x; +#endif + +#ifdef LWS_HAVE_SYS_TYPES_H + #include <sys/types.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <ctype.h> +#include <limits.h> +#include <stdarg.h> +#include <inttypes.h> + +#if defined(LWS_WITH_ESP32) + #define MSG_NOSIGNAL 0 + #define SOMAXCONN 3 +#endif + +#define STORE_IN_ROM +#include <assert.h> +#if LWS_MAX_SMP > 1 + #include <pthread.h> +#endif + +#ifdef LWS_HAVE_SYS_STAT_H + #include <sys/stat.h> +#endif + +#if defined(WIN32) || defined(_WIN32) + + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + + #if (WINVER < 0x0501) + #undef WINVER + #undef _WIN32_WINNT + #define WINVER 0x0501 + #define _WIN32_WINNT WINVER + #endif + + #define LWS_NO_DAEMONIZE + #define LWS_ERRNO WSAGetLastError() + #define LWS_EAGAIN WSAEWOULDBLOCK + #define LWS_EALREADY WSAEALREADY + #define LWS_EINPROGRESS WSAEINPROGRESS + #define LWS_EINTR WSAEINTR + #define LWS_EISCONN WSAEISCONN + #define LWS_EWOULDBLOCK WSAEWOULDBLOCK + #define MSG_NOSIGNAL 0 + #define SHUT_RDWR SD_BOTH + #define SOL_TCP IPPROTO_TCP + #define SHUT_WR SD_SEND + + #define compatible_close(fd) closesocket(fd) + #define lws_set_blocking_send(wsi) wsi->sock_send_blocking = 1 + #define LWS_SOCK_INVALID (INVALID_SOCKET) + + #include <winsock2.h> + #include <ws2tcpip.h> + #include <windows.h> + #include <tchar.h> + #ifdef LWS_HAVE_IN6ADDR_H + #include <in6addr.h> + #endif + #include <mstcpip.h> + #include <io.h> + + #if !defined(LWS_HAVE_ATOLL) + #if defined(LWS_HAVE__ATOI64) + #define atoll _atoi64 + #else + #warning No atoll or _atoi64 available, using atoi + #define atoll atoi + #endif + #endif + + #ifndef __func__ + #define __func__ __FUNCTION__ + #endif + + #ifdef LWS_HAVE__VSNPRINTF + #define vsnprintf _vsnprintf + #endif + + /* we don't have an implementation for this on windows... */ + int kill(int pid, int sig); + int fork(void); + #ifndef SIGINT + #define SIGINT 2 + #endif + +#else /* not windows --> */ + + #include <fcntl.h> + #include <strings.h> + #include <unistd.h> + #include <sys/types.h> + + #ifndef __cplusplus + #include <errno.h> + #endif + #include <netdb.h> + #include <signal.h> + #include <sys/socket.h> + + #if defined(LWS_BUILTIN_GETIFADDRS) + #include "./misc/getifaddrs.h" + #else + #if !defined(LWS_WITH_ESP32) + #if defined(__HAIKU__) + #define _BSD_SOURCE + #endif + #include <ifaddrs.h> + #endif + #endif + #if defined (__ANDROID__) + #include <syslog.h> + #include <sys/resource.h> + #elif defined (__sun) || defined(__HAIKU__) || defined(__QNX__) + #include <syslog.h> + #else + #if !defined(LWS_WITH_ESP32) + #include <sys/syslog.h> + #endif + #endif + #include <netdb.h> + #if !defined(LWS_WITH_ESP32) + #include <sys/mman.h> + #include <sys/un.h> + #include <netinet/in.h> + #include <netinet/tcp.h> + #include <arpa/inet.h> + #include <poll.h> + #endif + #ifndef LWS_NO_FORK + #ifdef LWS_HAVE_SYS_PRCTL_H + #include <sys/prctl.h> + #endif + #endif + + #include <sys/time.h> + + #define LWS_ERRNO errno + #define LWS_EAGAIN EAGAIN + #define LWS_EALREADY EALREADY + #define LWS_EINPROGRESS EINPROGRESS + #define LWS_EINTR EINTR + #define LWS_EISCONN EISCONN + #define LWS_EWOULDBLOCK EWOULDBLOCK + + #define lws_set_blocking_send(wsi) + + #define LWS_SOCK_INVALID (-1) +#endif /* not windows */ + +#ifndef LWS_HAVE_BZERO + #ifndef bzero + #define bzero(b, len) (memset((b), '\0', (len)), (void) 0) + #endif +#endif + +#ifndef LWS_HAVE_STRERROR + #define strerror(x) "" +#endif + + +#define lws_socket_is_valid(x) (x != LWS_SOCK_INVALID) + +#include "libwebsockets.h" + +#include "tls/private.h" + +#if defined(WIN32) || defined(_WIN32) + #include <gettimeofday.h> + + #ifndef BIG_ENDIAN + #define BIG_ENDIAN 4321 /* to show byte order (taken from gcc) */ + #endif + #ifndef LITTLE_ENDIAN + #define LITTLE_ENDIAN 1234 + #endif + #ifndef BYTE_ORDER + #define BYTE_ORDER LITTLE_ENDIAN + #endif + + #undef __P + #ifndef __P + #if __STDC__ + #define __P(protos) protos + #else + #define __P(protos) () + #endif + #endif + +#else /* not windows */ + static inline int compatible_close(int fd) { return close(fd); } + + #include <sys/stat.h> + #include <sys/time.h> + + #if defined(__APPLE__) + #include <machine/endian.h> + #elif defined(__FreeBSD__) + #include <sys/endian.h> + #elif defined(__linux__) + #include <endian.h> + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__QNX__) + #include <gulliver.h> + #if defined(__LITTLEENDIAN__) + #define BYTE_ORDER __LITTLEENDIAN__ + #define LITTLE_ENDIAN __LITTLEENDIAN__ + #define BIG_ENDIAN 4321 /* to show byte order (taken from gcc); for suppres warning that BIG_ENDIAN is not defined. */ + #endif + #if defined(__BIGENDIAN__) + #define BYTE_ORDER __BIGENDIAN__ + #define LITTLE_ENDIAN 1234 /* to show byte order (taken from gcc); for suppres warning that LITTLE_ENDIAN is not defined. */ + #define BIG_ENDIAN __BIGENDIAN__ + #endif +#endif + +#if defined(__sun) && defined(__GNUC__) + + #include <arpa/nameser_compat.h> + + #if !defined (BYTE_ORDER) + #define BYTE_ORDER __BYTE_ORDER__ + #endif + + #if !defined(LITTLE_ENDIAN) + #define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ + #endif + + #if !defined(BIG_ENDIAN) + #define BIG_ENDIAN __ORDER_BIG_ENDIAN__ + #endif + +#endif /* sun + GNUC */ + +#if !defined(BYTE_ORDER) + #define BYTE_ORDER __BYTE_ORDER +#endif +#if !defined(LITTLE_ENDIAN) + #define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif +#if !defined(BIG_ENDIAN) + #define BIG_ENDIAN __BIG_ENDIAN +#endif + + +/* + * Mac OSX as well as iOS do not define the MSG_NOSIGNAL flag, + * but happily have something equivalent in the SO_NOSIGPIPE flag. + */ +#ifdef __APPLE__ +#define MSG_NOSIGNAL SO_NOSIGPIPE +#endif + +/* + * Solaris 11.X only supports POSIX 2001, MSG_NOSIGNAL appears in + * POSIX 2008. + */ +#ifdef __sun + #define MSG_NOSIGNAL 0 +#endif + +#ifdef _WIN32 + #ifndef FD_HASHTABLE_MODULUS + #define FD_HASHTABLE_MODULUS 32 + #endif +#endif + +#ifndef LWS_DEF_HEADER_LEN +#define LWS_DEF_HEADER_LEN 4096 +#endif +#ifndef LWS_DEF_HEADER_POOL +#define LWS_DEF_HEADER_POOL 4 +#endif +#ifndef LWS_MAX_PROTOCOLS +#define LWS_MAX_PROTOCOLS 5 +#endif +#ifndef LWS_MAX_EXTENSIONS_ACTIVE +#define LWS_MAX_EXTENSIONS_ACTIVE 1 +#endif +#ifndef LWS_MAX_EXT_OFFERS +#define LWS_MAX_EXT_OFFERS 8 +#endif +#ifndef SPEC_LATEST_SUPPORTED +#define SPEC_LATEST_SUPPORTED 13 +#endif +#ifndef AWAITING_TIMEOUT +#define AWAITING_TIMEOUT 20 +#endif +#ifndef CIPHERS_LIST_STRING +#define CIPHERS_LIST_STRING "DEFAULT" +#endif +#ifndef LWS_SOMAXCONN +#define LWS_SOMAXCONN SOMAXCONN +#endif + +#define MAX_WEBSOCKET_04_KEY_LEN 128 + +#ifndef SYSTEM_RANDOM_FILEPATH +#define SYSTEM_RANDOM_FILEPATH "/dev/urandom" +#endif + +#define LWS_H2_RX_SCRATCH_SIZE 512 + + + +/* + * All lws_tls...() functions must return this type, converting the + * native backend result and doing the extra work to determine which one + * as needed. + * + * Native TLS backend return codes are NOT ALLOWED outside the backend. + * + * Non-SSL mode also uses these types. + */ +enum lws_ssl_capable_status { + LWS_SSL_CAPABLE_ERROR = -1, /* it failed */ + LWS_SSL_CAPABLE_DONE = 0, /* it succeeded */ + LWS_SSL_CAPABLE_MORE_SERVICE_READ = -2, /* retry WANT_READ */ + LWS_SSL_CAPABLE_MORE_SERVICE_WRITE = -3, /* retry WANT_WRITE */ + LWS_SSL_CAPABLE_MORE_SERVICE = -4, /* general retry */ +}; + +#if defined(__clang__) +#define lws_memory_barrier() __sync_synchronize() +#elif defined(__GNUC__) +#define lws_memory_barrier() __sync_synchronize() +#else +#define lws_memory_barrier() +#endif + +/* + * + * ------ roles ------ + * + */ + +#include "roles/private.h" + +/* null-terminated array of pointers to roles lws built with */ +extern const struct lws_role_ops *available_roles[]; + +#define LWS_FOR_EVERY_AVAILABLE_ROLE_START(xx) { \ + const struct lws_role_ops **ppxx = available_roles; \ + while (*ppxx) { \ + const struct lws_role_ops *xx = *ppxx++; + +#define LWS_FOR_EVERY_AVAILABLE_ROLE_END }} + +/* + * + * ------ event_loop ops ------ + * + */ + +#include "event-libs/private.h" + +/* enums of socks version */ +enum socks_version { + SOCKS_VERSION_4 = 4, + SOCKS_VERSION_5 = 5 +}; + +/* enums of subnegotiation version */ +enum socks_subnegotiation_version { + SOCKS_SUBNEGOTIATION_VERSION_1 = 1, +}; + +/* enums of socks commands */ +enum socks_command { + SOCKS_COMMAND_CONNECT = 1, + SOCKS_COMMAND_BIND = 2, + SOCKS_COMMAND_UDP_ASSOCIATE = 3 +}; + +/* enums of socks address type */ +enum socks_atyp { + SOCKS_ATYP_IPV4 = 1, + SOCKS_ATYP_DOMAINNAME = 3, + SOCKS_ATYP_IPV6 = 4 +}; + +/* enums of socks authentication methods */ +enum socks_auth_method { + SOCKS_AUTH_NO_AUTH = 0, + SOCKS_AUTH_GSSAPI = 1, + SOCKS_AUTH_USERNAME_PASSWORD = 2 +}; + +/* enums of subnegotiation status */ +enum socks_subnegotiation_status { + SOCKS_SUBNEGOTIATION_STATUS_SUCCESS = 0, +}; + +/* enums of socks request reply */ +enum socks_request_reply { + SOCKS_REQUEST_REPLY_SUCCESS = 0, + SOCKS_REQUEST_REPLY_FAILURE_GENERAL = 1, + SOCKS_REQUEST_REPLY_CONNECTION_NOT_ALLOWED = 2, + SOCKS_REQUEST_REPLY_NETWORK_UNREACHABLE = 3, + SOCKS_REQUEST_REPLY_HOST_UNREACHABLE = 4, + SOCKS_REQUEST_REPLY_CONNECTION_REFUSED = 5, + SOCKS_REQUEST_REPLY_TTL_EXPIRED = 6, + SOCKS_REQUEST_REPLY_COMMAND_NOT_SUPPORTED = 7, + SOCKS_REQUEST_REPLY_ATYP_NOT_SUPPORTED = 8 +}; + +/* enums used to generate socks messages */ +enum socks_msg_type { + /* greeting */ + SOCKS_MSG_GREETING, + /* credential, user name and password */ + SOCKS_MSG_USERNAME_PASSWORD, + /* connect command */ + SOCKS_MSG_CONNECT +}; + +enum { + LWS_RXFLOW_ALLOW = (1 << 0), + LWS_RXFLOW_PENDING_CHANGE = (1 << 1), +}; + +struct lws_ring { + void *buf; + void (*destroy_element)(void *element); + uint32_t buflen; + uint32_t element_len; + uint32_t head; + uint32_t oldest_tail; +}; + +struct lws_protocols; +struct lws; + +struct lws_io_watcher { +#ifdef LWS_WITH_LIBEV + struct lws_io_watcher_libev ev; +#endif +#ifdef LWS_WITH_LIBUV + struct lws_io_watcher_libuv uv; +#endif +#ifdef LWS_WITH_LIBEVENT + struct lws_io_watcher_libevent event; +#endif + struct lws_context *context; + + uint8_t actual_events; +}; + +struct lws_signal_watcher { +#ifdef LWS_WITH_LIBEV + struct lws_signal_watcher_libev ev; +#endif +#ifdef LWS_WITH_LIBUV + struct lws_signal_watcher_libuv uv; +#endif +#ifdef LWS_WITH_LIBEVENT + struct lws_signal_watcher_libevent event; +#endif + struct lws_context *context; +}; + +#ifdef _WIN32 +#define LWS_FD_HASH(fd) ((fd ^ (fd >> 8) ^ (fd >> 16)) % FD_HASHTABLE_MODULUS) +struct lws_fd_hashtable { + struct lws **wsi; + int length; +}; +#endif + +struct lws_foreign_thread_pollfd { + struct lws_foreign_thread_pollfd *next; + int fd_index; + int _and; + int _or; +}; + + +#define LWS_HRTIMER_NOWAIT (0x7fffffffffffffffll) + +/* + * so we can have n connections being serviced simultaneously, + * these things need to be isolated per-thread. + */ + +struct lws_context_per_thread { +#if LWS_MAX_SMP > 1 + pthread_mutex_t lock; + pthread_mutex_t lock_stats; + pthread_t lock_owner; + const char *last_lock_reason; +#endif + + struct lws_context *context; + + /* + * usable by anything in the service code, but only if the scope + * does not last longer than the service action (since next service + * of any socket can likewise use it and overwrite) + */ + unsigned char *serv_buf; + + struct lws_dll_lws dll_head_timeout; + struct lws_dll_lws dll_head_hrtimer; + struct lws_dll_lws dll_head_buflist; /* guys with pending rxflow */ + +#if defined(LWS_WITH_TLS) + struct lws_pt_tls tls; +#endif + + struct lws_pollfd *fds; + volatile struct lws_foreign_thread_pollfd * volatile foreign_pfd_list; +#ifdef _WIN32 + WSAEVENT *events; +#endif + lws_sockfd_type dummy_pipe_fds[2]; + struct lws *pipe_wsi; + + /* --- role based members --- */ + +#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_pt_role_ws ws; +#endif +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + struct lws_pt_role_http http; +#endif + + /* --- event library based members --- */ + +#if defined(LWS_WITH_LIBEV) + struct lws_pt_eventlibs_libev ev; +#endif +#if defined(LWS_WITH_LIBUV) + struct lws_pt_eventlibs_libuv uv; +#endif +#if defined(LWS_WITH_LIBEVENT) + struct lws_pt_eventlibs_libevent event; +#endif + +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + struct lws_signal_watcher w_sigint; +#endif + + /* --- */ + + unsigned long count_conns; + unsigned int fds_count; + + volatile unsigned char inside_poll; + volatile unsigned char foreign_spinlock; + + unsigned char tid; + + unsigned char lock_depth; + unsigned char inside_service:1; + unsigned char event_loop_foreign:1; + unsigned char event_loop_destroy_processing_done:1; +}; + +struct lws_conn_stats { + unsigned long long rx, tx; + unsigned long h1_conn, h1_trans, h2_trans, ws_upg, h2_alpn, h2_subs, + h2_upg, rejected; +}; + +void +lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs); + +struct lws_timed_vh_protocol { + struct lws_timed_vh_protocol *next; + const struct lws_protocols *protocol; + time_t time; + int reason; +}; + +/* + * virtual host -related context information + * vhostwide SSL context + * vhostwide proxy + * + * hierarchy: + * + * context -> vhost -> wsi + * + * incoming connection non-SSL vhost binding: + * + * listen socket -> wsi -> select vhost after first headers + * + * incoming connection SSL vhost binding: + * + * SSL SNI -> wsi -> bind after SSL negotiation + */ + + +struct lws_vhost { +#if !defined(LWS_WITHOUT_CLIENT) + char proxy_basic_auth_token[128]; +#endif +#if LWS_MAX_SMP > 1 + pthread_mutex_t lock; +#endif + +#if defined(LWS_ROLE_H2) + struct lws_vhost_role_h2 h2; +#endif +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + struct lws_vhost_role_http http; +#endif +#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_vhost_role_ws ws; +#endif + +#if defined(LWS_WITH_SOCKS5) + char socks_proxy_address[128]; + char socks_user[96]; + char socks_password[96]; +#endif +#if defined(LWS_WITH_LIBEV) + struct lws_io_watcher w_accept; +#endif + struct lws_conn_stats conn_stats; + struct lws_context *context; + struct lws_vhost *vhost_next; + + struct lws *lserv_wsi; + const char *name; + const char *iface; + +#if !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) + int bind_iface; +#endif + const struct lws_protocols *protocols; + void **protocol_vh_privs; + const struct lws_protocol_vhost_options *pvo; + const struct lws_protocol_vhost_options *headers; + struct lws **same_vh_protocol_list; + struct lws_vhost *no_listener_vhost_list; +#if !defined(LWS_NO_CLIENT) + struct lws_dll_lws dll_active_client_conns; +#endif + +#if defined(LWS_WITH_TLS) + struct lws_vhost_tls tls; +#endif + + struct lws_timed_vh_protocol *timed_vh_protocol_list; + void *user; + + int listen_port; + +#if defined(LWS_WITH_SOCKS5) + unsigned int socks_proxy_port; +#endif + unsigned int options; + int count_protocols; + int ka_time; + int ka_probes; + int ka_interval; + int keepalive_timeout; + int timeout_secs_ah_idle; + +#ifdef LWS_WITH_ACCESS_LOG + int log_fd; +#endif + + unsigned int created_vhost_protocols:1; + unsigned int being_destroyed:1; + + unsigned char default_protocol_index; + unsigned char raw_protocol_index; +}; + +struct lws_deferred_free +{ + struct lws_deferred_free *next; + time_t deadline; + void *payload; +}; + +typedef union { +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 sa6; +#endif + struct sockaddr_in sa4; +} sockaddr46; + + +#if defined(LWS_WITH_PEER_LIMITS) +struct lws_peer { + struct lws_peer *next; + struct lws_peer *peer_wait_list; + + time_t time_created; + time_t time_closed_all; + + uint8_t addr[32]; + uint32_t hash; + uint32_t count_wsi; + uint32_t total_wsi; + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + struct lws_peer_role_http http; +#endif + + uint8_t af; +}; +#endif + +/* + * the rest is managed per-context, that includes + * + * - processwide single fd -> wsi lookup + * - contextwide headers pool + */ + +struct lws_context { + time_t last_timeout_check_s; + time_t last_ws_ping_pong_check_s; + time_t time_up; + time_t time_discontiguity; + time_t time_fixup; + const struct lws_plat_file_ops *fops; + struct lws_plat_file_ops fops_platform; + struct lws_context **pcontext_finalize; + + const struct lws_tls_ops *tls_ops; + +#if defined(LWS_WITH_HTTP2) + struct http2_settings set; +#endif +#if defined(LWS_WITH_ZIP_FOPS) + struct lws_plat_file_ops fops_zip; +#endif + struct lws_context_per_thread pt[LWS_MAX_SMP]; + struct lws_conn_stats conn_stats; +#if LWS_MAX_SMP > 1 + pthread_mutex_t lock; + int lock_depth; +#endif +#ifdef _WIN32 +/* different implementation between unix and windows */ + struct lws_fd_hashtable fd_hashtable[FD_HASHTABLE_MODULUS]; +#else + struct lws **lws_lookup; /* fd to wsi */ +#endif + struct lws_vhost *vhost_list; + struct lws_vhost *no_listener_vhost_list; + struct lws_vhost *vhost_pending_destruction_list; + struct lws_plugin *plugin_list; + struct lws_deferred_free *deferred_free_list; +#if defined(LWS_WITH_PEER_LIMITS) + struct lws_peer **pl_hash_table; + struct lws_peer *peer_wait_list; + time_t next_cull; +#endif + + void *external_baggage_free_on_destroy; + const struct lws_token_limits *token_limits; + void *user_space; + const struct lws_protocol_vhost_options *reject_service_keywords; + lws_reload_func deprecation_cb; + void (*eventlib_signal_cb)(void *event_lib_handle, int signum); + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + cap_value_t caps[4]; + char count_caps; +#endif + +#if defined(LWS_WITH_LIBEV) + struct lws_context_eventlibs_libev ev; +#endif +#if defined(LWS_WITH_LIBUV) + struct lws_context_eventlibs_libuv uv; +#endif +#if defined(LWS_WITH_LIBEVENT) + struct lws_context_eventlibs_libevent event; +#endif + struct lws_event_loop_ops *event_loop_ops; + + +#if defined(LWS_WITH_TLS) + struct lws_context_tls tls; +#endif + + char canonical_hostname[128]; + const char *server_string; + +#ifdef LWS_LATENCY + unsigned long worst_latency; + char worst_latency_info[256]; +#endif + +#if defined(LWS_WITH_STATS) + uint64_t lws_stats[LWSSTATS_SIZE]; + uint64_t last_dump; + int updated; +#endif +#if defined(LWS_WITH_ESP32) + unsigned long time_last_state_dump; + uint32_t last_free_heap; +#endif + + int max_fds; + int count_event_loop_static_asset_handles; + int started_with_parent; + int uid, gid; + + int fd_random; + + int count_wsi_allocated; + int count_cgi_spawned; + unsigned int options; + unsigned int fd_limit_per_thread; + unsigned int timeout_secs; + unsigned int pt_serv_buf_size; + int max_http_header_data; + int simultaneous_ssl_restriction; + int simultaneous_ssl; +#if defined(LWS_WITH_PEER_LIMITS) + uint32_t pl_hash_elements; /* protected by context->lock */ + uint32_t count_peers; /* protected by context->lock */ + unsigned short ip_limit_ah; + unsigned short ip_limit_wsi; +#endif + unsigned int deprecated:1; + unsigned int being_destroyed:1; + unsigned int being_destroyed1:1; + unsigned int being_destroyed2:1; + unsigned int requested_kill:1; + unsigned int protocol_init_done:1; + unsigned int doing_protocol_init:1; + unsigned int done_protocol_destroy_cb:1; + unsigned int finalize_destroy_after_internal_loops_stopped:1; + /* + * set to the Thread ID that's doing the service loop just before entry + * to poll indicates service thread likely idling in poll() + * volatile because other threads may check it as part of processing + * for pollfd event change. + */ + volatile int service_tid; + int service_tid_detected; + + short max_http_header_pool; + short count_threads; + short plugin_protocol_count; + short plugin_extension_count; + short server_string_len; + unsigned short ws_ping_pong_interval; + unsigned short deprecation_pending_listen_close_count; + + uint8_t max_fi; +}; + +int +lws_check_deferred_free(struct lws_context *context, int force); + +#define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x] +#define lws_get_vh_protocol(vh, x) vh->protocols[x] + +LWS_EXTERN void +__lws_close_free_wsi_final(struct lws *wsi); +LWS_EXTERN void +lws_libuv_closehandle(struct lws *wsi); +LWS_EXTERN int +lws_libuv_check_watcher_active(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN int +lws_plat_plugins_init(struct lws_context * context, const char * const *d); + +LWS_VISIBLE LWS_EXTERN int +lws_plat_plugins_destroy(struct lws_context * context); + +LWS_EXTERN void +lws_restart_ws_ping_pong_timer(struct lws *wsi); + +struct lws * +lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd); + +int +lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max); + +void +lws_vhost_destroy1(struct lws_vhost *vh); + +enum { + LWS_EV_READ = (1 << 0), + LWS_EV_WRITE = (1 << 1), + LWS_EV_START = (1 << 2), + LWS_EV_STOP = (1 << 3), + + LWS_EV_PREPARE_DELETION = (1 << 31), +}; + + +#if defined(LWS_WITH_ESP32) +LWS_EXTERN int +lws_find_string_in_file(const char *filename, const char *string, int stringlen); +#endif + +#ifdef LWS_WITH_IPV6 +#define LWS_IPV6_ENABLED(vh) \ + (!lws_check_opt(vh->context->options, LWS_SERVER_OPTION_DISABLE_IPV6) && \ + !lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_IPV6)) +#else +#define LWS_IPV6_ENABLED(context) (0) +#endif + +#ifdef LWS_WITH_UNIX_SOCK +#define LWS_UNIX_SOCK_ENABLED(vhost) \ + (vhost->options & LWS_SERVER_OPTION_UNIX_SOCK) +#else +#define LWS_UNIX_SOCK_ENABLED(vhost) (0) +#endif + +enum uri_path_states { + URIPS_IDLE, + URIPS_SEEN_SLASH, + URIPS_SEEN_SLASH_DOT, + URIPS_SEEN_SLASH_DOT_DOT, +}; + +enum uri_esc_states { + URIES_IDLE, + URIES_SEEN_PERCENT, + URIES_SEEN_PERCENT_H1, +}; + + +#ifndef LWS_NO_CLIENT +struct client_info_stash { + char *address; + char *path; + char *host; + char *origin; + char *protocol; + char *method; + char *iface; + char *alpn; +}; +#endif + + +signed char char_to_hex(const char c); + + +struct lws_buflist { + struct lws_buflist *next; + + size_t len; + size_t pos; + + uint8_t buf[1]; /* true length of this is set by the oversize malloc */ +}; + +#define lws_wsi_is_udp(___wsi) (!!___wsi->udp) + +#define LWS_H2_FRAME_HEADER_LENGTH 9 + + +struct lws { + /* structs */ + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + struct _lws_http_mode_related http; +#endif +#if defined(LWS_ROLE_H2) + struct _lws_h2_related h2; +#endif +#if defined(LWS_ROLE_WS) + struct _lws_websocket_related *ws; /* allocated if we upgrade to ws */ +#endif + + const struct lws_role_ops *role_ops; + lws_wsi_state_t wsistate; + lws_wsi_state_t wsistate_pre_close; + + /* lifetime members */ + +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + struct lws_io_watcher w_read; +#endif +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBEVENT) + struct lws_io_watcher w_write; +#endif + + /* pointers */ + + struct lws_context *context; + struct lws_vhost *vhost; + struct lws *parent; /* points to parent, if any */ + struct lws *child_list; /* points to first child */ + struct lws *sibling_list; /* subsequent children at same level */ + + const struct lws_protocols *protocol; + struct lws **same_vh_protocol_prev, *same_vh_protocol_next; + + struct lws_dll_lws dll_timeout; + struct lws_dll_lws dll_hrtimer; + struct lws_dll_lws dll_buflist; /* guys with pending rxflow */ + +#if defined(LWS_WITH_PEER_LIMITS) + struct lws_peer *peer; +#endif + + struct lws_udp *udp; +#ifndef LWS_NO_CLIENT + struct client_info_stash *stash; + char *client_hostname_copy; + struct lws_dll_lws dll_active_client_conns; + struct lws_dll_lws dll_client_transaction_queue_head; + struct lws_dll_lws dll_client_transaction_queue; +#endif + void *user_space; + void *opaque_parent_data; + + struct lws_buflist *buflist; + + /* truncated send handling */ + unsigned char *trunc_alloc; /* non-NULL means buffering in progress */ + +#if defined(LWS_WITH_TLS) + struct lws_lws_tls tls; +#endif + + lws_sock_file_fd_type desc; /* .filefd / .sockfd */ +#if defined(LWS_WITH_STATS) + uint64_t active_writable_req_us; +#if defined(LWS_WITH_TLS) + uint64_t accept_start_us; +#endif +#endif + + lws_usec_t pending_timer; /* hrtimer fires */ + time_t pending_timeout_set; /* second-resolution timeout start */ + +#ifdef LWS_LATENCY + unsigned long action_start; + unsigned long latency_start; +#endif + + /* ints */ +#define LWS_NO_FDS_POS (-1) + int position_in_fds_table; + unsigned int trunc_alloc_len; /* size of malloc */ + unsigned int trunc_offset; /* where we are in terms of spilling */ + unsigned int trunc_len; /* how much is buffered */ +#ifndef LWS_NO_CLIENT + int chunk_remaining; +#endif + unsigned int cache_secs; + + unsigned int hdr_parsing_completed:1; + unsigned int http2_substream:1; + unsigned int upgraded_to_http2:1; + unsigned int h2_stream_carries_ws:1; + unsigned int seen_nonpseudoheader:1; + unsigned int listener:1; + unsigned int user_space_externally_allocated:1; + unsigned int socket_is_permanently_unusable:1; + unsigned int rxflow_change_to:2; + unsigned int conn_stat_done:1; + unsigned int cache_reuse:1; + unsigned int cache_revalidate:1; + unsigned int cache_intermediaries:1; + unsigned int favoured_pollin:1; + unsigned int sending_chunked:1; + unsigned int interpreting:1; + unsigned int already_did_cce:1; + unsigned int told_user_closed:1; + unsigned int told_event_loop_closed:1; + unsigned int waiting_to_send_close_frame:1; + unsigned int close_needs_ack:1; + unsigned int ipv6:1; + unsigned int parent_carries_io:1; + unsigned int parent_pending_cb_on_writable:1; + unsigned int cgi_stdout_zero_length:1; + unsigned int seen_zero_length_recv:1; + unsigned int rxflow_will_be_applied:1; + unsigned int event_pipe:1; + unsigned int on_same_vh_list:1; + unsigned int handling_404:1; + unsigned int protocol_bind_balance:1; + + unsigned int could_have_pending:1; /* detect back-to-back writes */ + unsigned int outer_will_close:1; + +#ifdef LWS_WITH_ACCESS_LOG + unsigned int access_log_pending:1; +#endif +#ifndef LWS_NO_CLIENT + unsigned int do_ws:1; /* whether we are doing http or ws flow */ + unsigned int chunked:1; /* if the clientside connection is chunked */ + unsigned int client_rx_avail:1; + unsigned int client_http_body_pending:1; + unsigned int transaction_from_pipeline_queue:1; + unsigned int keepalive_active:1; + unsigned int keepalive_rejected:1; + unsigned int client_pipeline:1; + unsigned int client_h2_alpn:1; + unsigned int client_h2_substream:1; +#endif + +#ifdef _WIN32 + unsigned int sock_send_blocking:1; +#endif + +#ifndef LWS_NO_CLIENT + unsigned short c_port; +#endif + unsigned short pending_timeout_limit; + + /* chars */ + + char lws_rx_parse_state; /* enum lws_rx_parse_state */ + char rx_frame_type; /* enum lws_write_protocol */ + char pending_timeout; /* enum pending_timeout */ + char tsi; /* thread service index we belong to */ + char protocol_interpret_idx; + char redirects; + uint8_t rxflow_bitmap; +#ifdef LWS_WITH_CGI + char cgi_channel; /* which of stdin/out/err */ + char hdr_state; +#endif +#ifndef LWS_NO_CLIENT + char chunk_parser; /* enum lws_chunk_parser */ +#endif +#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT) + char reason_bf; /* internal writeable callback reason bitfield */ +#endif +#if defined(LWS_WITH_STATS) && defined(LWS_WITH_TLS) + char seen_rx; +#endif + uint8_t ws_over_h2_count; + /* volatile to make sure code is aware other thread can change */ + volatile char handling_pollout; + volatile char leave_pollout_active; +}; + +#define lws_is_flowcontrolled(w) (!!(wsi->rxflow_bitmap)) + +void +lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt); + +LWS_EXTERN int log_level; + +LWS_EXTERN int +lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, + const char *iface); + +#if defined(LWS_WITH_IPV6) +LWS_EXTERN unsigned long +lws_get_addr_scope(const char *ipaddr); +#endif + +LWS_EXTERN void +lws_close_free_wsi(struct lws *wsi, enum lws_close_status, const char *caller); +LWS_EXTERN void +__lws_close_free_wsi(struct lws *wsi, enum lws_close_status, const char *caller); + +LWS_EXTERN void +__lws_free_wsi(struct lws *wsi); + +LWS_EXTERN int +__remove_wsi_socket_from_fds(struct lws *wsi); +LWS_EXTERN int +lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len); + +#ifndef LWS_LATENCY +static inline void +lws_latency(struct lws_context *context, struct lws *wsi, const char *action, + int ret, int completion) { + do { + (void)context; (void)wsi; (void)action; (void)ret; + (void)completion; + } while (0); +} +static inline void +lws_latency_pre(struct lws_context *context, struct lws *wsi) { + do { (void)context; (void)wsi; } while (0); +} +#else +#define lws_latency_pre(_context, _wsi) lws_latency(_context, _wsi, NULL, 0, 0) +extern void +lws_latency(struct lws_context *context, struct lws *wsi, const char *action, + int ret, int completion); +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ws_client_rx_sm(struct lws *wsi, unsigned char c); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse(struct lws *wsi, unsigned char *buf, int *len); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse_urldecode(struct lws *wsi, uint8_t *_c); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_action(struct lws *wsi); + +LWS_EXTERN int +lws_b64_selftest(void); + +LWS_EXTERN int +lws_service_flag_pending(struct lws_context *context, int tsi); + +LWS_EXTERN int +lws_timed_callback_remove(struct lws_vhost *vh, struct lws_timed_vh_protocol *p); + +#if defined(_WIN32) +LWS_EXTERN struct lws * +wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd); + +LWS_EXTERN int +insert_wsi(struct lws_context *context, struct lws *wsi); + +LWS_EXTERN int +delete_from_fd(struct lws_context *context, lws_sockfd_type fd); +#else +#define wsi_from_fd(A,B) A->lws_lookup[B - lws_plat_socket_offset()] +#define insert_wsi(A,B) assert(A->lws_lookup[B->desc.sockfd - lws_plat_socket_offset()] == 0); A->lws_lookup[B->desc.sockfd - lws_plat_socket_offset()]=B +#define delete_from_fd(A,B) A->lws_lookup[B - lws_plat_socket_offset()]=0 +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +__insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len); + +LWS_EXTERN void +lws_remove_from_timeout_list(struct lws *wsi); + +LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_client_connect_2(struct lws *wsi); + +LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT +lws_client_reset(struct lws **wsi, int ssl, const char *address, int port, + const char *path, const char *host); + +LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi); + +LWS_EXTERN char * LWS_WARN_UNUSED_RESULT +lws_generate_client_handshake(struct lws *wsi, char *pkt); + +LWS_EXTERN int +lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd); + +LWS_EXTERN struct lws * +lws_client_connect_via_info2(struct lws *wsi); + + + +LWS_EXTERN void +lws_client_stash_destroy(struct lws *wsi); + +/* + * EXTENSIONS + */ + +#if defined(LWS_WITHOUT_EXTENSIONS) +#define lws_any_extension_handled(_a, _b, _c, _d) (0) +#define lws_ext_cb_active(_a, _b, _c, _d) (0) +#define lws_ext_cb_all_exts(_a, _b, _c, _d, _e) (0) +#define lws_issue_raw_ext_access lws_issue_raw +#define lws_context_init_extensions(_a, _b) +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_client_interpret_server_handshake(struct lws *wsi); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len); + +LWS_EXTERN void +lws_role_transition(struct lws *wsi, enum lwsi_role role, enum lwsi_state state, + struct lws_role_ops *ops); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, + enum lws_callback_reasons reason, void *user, + void *in, size_t len); + +LWS_EXTERN int +lws_plat_socket_offset(void); + +LWS_EXTERN int +lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd); + +LWS_EXTERN int +lws_plat_check_connection_error(struct lws *wsi); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_header_table_attach(struct lws *wsi, int autoservice); + +LWS_EXTERN int +lws_header_table_detach(struct lws *wsi, int autoservice); +LWS_EXTERN int +__lws_header_table_detach(struct lws *wsi, int autoservice); + +LWS_EXTERN void +lws_header_table_reset(struct lws *wsi, int autoservice); + +void +__lws_header_table_reset(struct lws *wsi, int autoservice); + +LWS_EXTERN char * LWS_WARN_UNUSED_RESULT +lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ensure_user_space(struct lws *wsi); + +LWS_EXTERN int +lws_change_pollfd(struct lws *wsi, int _and, int _or); + +#ifndef LWS_NO_SERVER + int _lws_vhost_init_server(const struct lws_context_creation_info *info, + struct lws_vhost *vhost); + LWS_EXTERN struct lws_vhost * + lws_select_vhost(struct lws_context *context, int port, const char *servername); + LWS_EXTERN int LWS_WARN_UNUSED_RESULT + lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len); + LWS_EXTERN void + lws_server_get_canonical_hostname(struct lws_context *context, + const struct lws_context_creation_info *info); +#else + #define _lws_vhost_init_server(_a, _b) (0) + #define lws_parse_ws(_a, _b, _c) (0) + #define lws_server_get_canonical_hostname(_a, _b) +#endif + +#ifndef LWS_NO_DAEMONIZE + LWS_EXTERN int get_daemonize_pid(); +#else + #define get_daemonize_pid() (0) +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +interface_to_sa(struct lws_vhost *vh, const char *ifname, + struct sockaddr_in *addr, size_t addrlen); +LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); + +#if !defined(LWS_WITH_TLS) + #define LWS_SSL_ENABLED(context) (0) + #define lws_context_init_server_ssl(_a, _b) (0) + #define lws_ssl_destroy(_a) + #define lws_context_init_alpn(_a) + #define lws_ssl_capable_read lws_ssl_capable_read_no_ssl + #define lws_ssl_capable_write lws_ssl_capable_write_no_ssl + #define lws_ssl_pending lws_ssl_pending_no_ssl + #define lws_server_socket_service_ssl(_b, _c) (0) + #define lws_ssl_close(_a) (0) + #define lws_ssl_context_destroy(_a) + #define lws_ssl_SSL_CTX_destroy(_a) + #define lws_ssl_remove_wsi_from_buffered_list(_a) + #define __lws_ssl_remove_wsi_from_buffered_list(_a) + #define lws_context_init_ssl_library(_a) + #define lws_tls_check_all_cert_lifetimes(_a) + #define lws_tls_acme_sni_cert_destroy(_a) +#endif + + +#if LWS_MAX_SMP > 1 + +static LWS_INLINE void +lws_pt_mutex_init(struct lws_context_per_thread *pt) +{ + pthread_mutex_init(&pt->lock, NULL); + pthread_mutex_init(&pt->lock_stats, NULL); +} + +static LWS_INLINE void +lws_pt_mutex_destroy(struct lws_context_per_thread *pt) +{ + pthread_mutex_destroy(&pt->lock_stats); + pthread_mutex_destroy(&pt->lock); +} + +static LWS_INLINE void +lws_pt_lock(struct lws_context_per_thread *pt, const char *reason) +{ + if (pt->lock_owner == pthread_self()) { + pt->lock_depth++; + return; + } + pthread_mutex_lock(&pt->lock); + pt->last_lock_reason = reason; + pt->lock_owner = pthread_self(); + //lwsl_notice("tid %d: lock %s\n", pt->tid, reason); +} + +static LWS_INLINE void +lws_pt_unlock(struct lws_context_per_thread *pt) +{ + if (pt->lock_depth) { + pt->lock_depth--; + return; + } + pt->last_lock_reason = "free"; + pt->lock_owner = 0; + //lwsl_notice("tid %d: unlock %s\n", pt->tid, pt->last_lock_reason); + pthread_mutex_unlock(&pt->lock); +} + +static LWS_INLINE void +lws_pt_stats_lock(struct lws_context_per_thread *pt) +{ + pthread_mutex_lock(&pt->lock_stats); +} + +static LWS_INLINE void +lws_pt_stats_unlock(struct lws_context_per_thread *pt) +{ + pthread_mutex_unlock(&pt->lock_stats); +} + +static LWS_INLINE void +lws_context_lock(struct lws_context *context) +{ + pthread_mutex_lock(&context->lock); +} + +static LWS_INLINE void +lws_context_unlock(struct lws_context *context) +{ + pthread_mutex_unlock(&context->lock); +} + +static LWS_INLINE void +lws_vhost_lock(struct lws_vhost *vhost) +{ + pthread_mutex_lock(&vhost->lock); +} + +static LWS_INLINE void +lws_vhost_unlock(struct lws_vhost *vhost) +{ + pthread_mutex_unlock(&vhost->lock); +} + + +#else +#define lws_pt_mutex_init(_a) (void)(_a) +#define lws_pt_mutex_destroy(_a) (void)(_a) +#define lws_pt_lock(_a, b) (void)(_a) +#define lws_pt_unlock(_a) (void)(_a) +#define lws_context_lock(_a) (void)(_a) +#define lws_context_unlock(_a) (void)(_a) +#define lws_vhost_lock(_a) (void)(_a) +#define lws_vhost_unlock(_a) (void)(_a) +#define lws_pt_stats_lock(_a) (void)(_a) +#define lws_pt_stats_unlock(_a) (void)(_a) +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_pending_no_ssl(struct lws *wsi); + +int +lws_tls_check_cert_lifetime(struct lws_vhost *vhost); + +int lws_jws_selftest(void); + + +#ifndef LWS_NO_CLIENT +LWS_EXTERN int lws_client_socket_service(struct lws *wsi, + struct lws_pollfd *pollfd, + struct lws *wsi_conn); +LWS_EXTERN struct lws * +lws_client_wsi_effective(struct lws *wsi); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi); +#if !defined(LWS_WITH_TLS) + #define lws_context_init_client_ssl(_a, _b) (0) +#endif +LWS_EXTERN void +lws_decode_ssl_error(void); +#else +#define lws_context_init_client_ssl(_a, _b) (0) +#endif + +LWS_EXTERN int +__lws_rx_flow_control(struct lws *wsi); + +LWS_EXTERN int +_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa); + +#ifndef LWS_NO_SERVER +LWS_EXTERN int +lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); +#else +#define lws_server_socket_service(_b, _c) (0) +#define lws_handshake_server(_a, _b, _c) (0) +#endif + +#ifdef LWS_WITH_ACCESS_LOG +LWS_EXTERN int +lws_access_log(struct lws *wsi); +LWS_EXTERN void +lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth); +#else +#define lws_access_log(_a) +#endif + +LWS_EXTERN int +lws_cgi_kill_terminated(struct lws_context_per_thread *pt); + +LWS_EXTERN void +lws_cgi_remove_and_kill(struct lws *wsi); + +int +lws_protocol_init(struct lws_context *context); + +int +lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p); + +const struct lws_http_mount * +lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len); + +/* + * custom allocator + */ +LWS_EXTERN void * +lws_realloc(void *ptr, size_t size, const char *reason); + +LWS_EXTERN void * LWS_WARN_UNUSED_RESULT +lws_zalloc(size_t size, const char *reason); + +#ifdef LWS_PLAT_OPTEE +void *lws_malloc(size_t size, const char *reason); +void lws_free(void *p); +#define lws_free_set_NULL(P) do { lws_free(P); (P) = NULL; } while(0) +#else +#define lws_malloc(S, R) lws_realloc(NULL, S, R) +#define lws_free(P) lws_realloc(P, 0, "lws_free") +#define lws_free_set_NULL(P) do { lws_realloc(P, 0, "free"); (P) = NULL; } while(0) +#endif + +int +lws_plat_pipe_create(struct lws *wsi); +int +lws_plat_pipe_signal(struct lws *wsi); +void +lws_plat_pipe_close(struct lws *wsi); +int +lws_create_event_pipes(struct lws_context *context); + +const struct lws_plat_file_ops * +lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path, + const char **vpath); + +/* lws_plat_ */ +LWS_EXTERN void +lws_plat_delete_socket_from_fds(struct lws_context *context, + struct lws *wsi, int m); +LWS_EXTERN void +lws_plat_insert_socket_into_fds(struct lws_context *context, + struct lws *wsi); +LWS_EXTERN void +lws_plat_service_periodic(struct lws_context *context); + +LWS_EXTERN int +lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi, + struct lws_pollfd *pfd); +LWS_EXTERN void +lws_add_wsi_to_draining_ext_list(struct lws *wsi); +LWS_EXTERN void +lws_remove_wsi_from_draining_ext_list(struct lws *wsi); +LWS_EXTERN int +lws_plat_context_early_init(void); +LWS_EXTERN void +lws_plat_context_early_destroy(struct lws_context *context); +LWS_EXTERN void +lws_plat_context_late_destroy(struct lws_context *context); +LWS_EXTERN int +lws_poll_listen_fd(struct lws_pollfd *fd); +LWS_EXTERN int +lws_plat_service(struct lws_context *context, int timeout_ms); +LWS_EXTERN LWS_VISIBLE int +_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi); +LWS_EXTERN int +lws_plat_init(struct lws_context *context, + const struct lws_context_creation_info *info); +LWS_EXTERN void +lws_plat_drop_app_privileges(const struct lws_context_creation_info *info); +LWS_EXTERN unsigned long long +time_in_microseconds(void); +LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT +lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_plat_inet_pton(int af, const char *src, void *dst); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len); +LWS_EXTERN int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount); + + +LWS_EXTERN void +lws_same_vh_protocol_remove(struct lws *wsi); +LWS_EXTERN void +lws_same_vh_protocol_insert(struct lws *wsi, int n); + +LWS_EXTERN int +lws_broadcast(struct lws_context *context, int reason, void *in, size_t len); + +#if defined(LWS_WITH_STATS) + void + lws_stats_atomic_bump(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t bump); + void + lws_stats_atomic_max(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t val); +#else + static inline uint64_t lws_stats_atomic_bump(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t bump) { + (void)context; (void)pt; (void)index; (void)bump; return 0; } + static inline uint64_t lws_stats_atomic_max(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t val) { + (void)context; (void)pt; (void)index; (void)val; return 0; } +#endif + +/* socks */ +void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, + ssize_t *msg_len); + +#if defined(LWS_WITH_PEER_LIMITS) +void +lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer); +int +lws_peer_confirm_ah_attach_ok(struct lws_context *context, struct lws_peer *peer); +void +lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer); +void +lws_peer_cull_peer_wait_list(struct lws_context *context); +struct lws_peer * +lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd); +void +lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer, + struct lws *wsi); +void +lws_peer_dump_from_wsi(struct lws *wsi); +#endif + + +void +__lws_remove_from_timeout_list(struct lws *wsi); + +lws_usec_t +__lws_hrtimer_service(struct lws_context_per_thread *pt); + +void +__lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); +int +__lws_change_pollfd(struct lws *wsi, int _and, int _or); + + +int +lws_callback_as_writeable(struct lws *wsi); +int +lws_buflist_aware_read(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_tokens *ebuf); +int +lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used, + int buffered); + + +char * +lws_generate_client_ws_handshake(struct lws *wsi, char *p); +int +lws_client_ws_upgrade(struct lws *wsi, const char **cce); +int +lws_create_client_ws_object(struct lws_client_connect_info *i, struct lws *wsi); +int +lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len); +int +lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn); +int +lws_tls_server_conn_alpn(struct lws *wsi); + +int +lws_ws_client_rx_sm_block(struct lws *wsi, unsigned char **buf, size_t len); +void +lws_destroy_event_pipe(struct lws *wsi); +void +lws_context_destroy2(struct lws_context *context); + +#ifdef __cplusplus +}; +#endif diff --git a/thirdparty/libwebsockets/core/service.c b/thirdparty/libwebsockets/core/service.c new file mode 100644 index 0000000000..6523058814 --- /dev/null +++ b/thirdparty/libwebsockets/core/service.c @@ -0,0 +1,987 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +int +lws_callback_as_writeable(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int n, m; + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + + n = wsi->role_ops->writeable_cb[lwsi_role_server(wsi)]; + + m = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, (enum lws_callback_reasons) n, + wsi->user_space, NULL, 0); + + return m; +} + +LWS_VISIBLE int +lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) +{ + volatile struct lws *vwsi = (volatile struct lws *)wsi; + int n; + + //lwsl_notice("%s: %p\n", __func__, wsi); + + vwsi->leave_pollout_active = 0; + vwsi->handling_pollout = 1; + /* + * if another thread wants POLLOUT on us, from here on while + * handling_pollout is set, he will only set leave_pollout_active. + * If we are going to disable POLLOUT, we will check that first. + */ + wsi->could_have_pending = 0; /* clear back-to-back write detection */ + + /* + * user callback is lowest priority to get these notifications + * actually, since other pending things cannot be disordered + * + * Priority 1: pending truncated sends are incomplete ws fragments + * If anything else sent first the protocol would be + * corrupted. + */ + + if (wsi->trunc_len) { + //lwsl_notice("%s: completing partial\n", __func__); + if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, + wsi->trunc_len) < 0) { + lwsl_info("%s signalling to close\n", __func__); + goto bail_die; + } + /* leave POLLOUT active either way */ + goto bail_ok; + } else + if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) { + wsi->socket_is_permanently_unusable = 1; + goto bail_die; /* retry closing now */ + } + +#ifdef LWS_WITH_CGI + /* + * A cgi master's wire protocol remains h1 or h2. He is just getting + * his data from his child cgis. + */ + if (wsi->http.cgi) { + /* also one shot */ + if (pollfd) + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return 1; + } + goto user_service_go_again; + } +#endif + + /* if we got here, we should have wire protocol ops set on the wsi */ + assert(wsi->role_ops); + + if (!wsi->role_ops->handle_POLLOUT) + goto bail_ok; + + switch ((wsi->role_ops->handle_POLLOUT)(wsi)) { + case LWS_HP_RET_BAIL_OK: + goto bail_ok; + case LWS_HP_RET_BAIL_DIE: + goto bail_die; + case LWS_HP_RET_USER_SERVICE: + break; + default: + assert(0); + } + + /* one shot */ + + if (wsi->parent_carries_io) { + vwsi->handling_pollout = 0; + vwsi->leave_pollout_active = 0; + + return lws_callback_as_writeable(wsi); + } + + if (pollfd) { + int eff = vwsi->leave_pollout_active; + + if (!eff) { + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + goto bail_die; + } + } + + vwsi->handling_pollout = 0; + + /* cannot get leave_pollout_active set after the above */ + if (!eff && wsi->leave_pollout_active) { + /* + * got set inbetween sampling eff and clearing + * handling_pollout, force POLLOUT on + */ + lwsl_debug("leave_pollout_active\n"); + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { + lwsl_info("failed at set pollfd\n"); + goto bail_die; + } + } + + vwsi->leave_pollout_active = 0; + } + + if (lwsi_role_client(wsi) && + !wsi->hdr_parsing_completed && + lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS && + lwsi_state(wsi) != LRS_ISSUE_HTTP_BODY + ) + goto bail_ok; + + +#ifdef LWS_WITH_CGI +user_service_go_again: +#endif + + if (wsi->role_ops->perform_user_POLLOUT) { + if (wsi->role_ops->perform_user_POLLOUT(wsi) == -1) + goto bail_die; + else + goto bail_ok; + } + + lwsl_debug("%s: %p: non mux: wsistate 0x%x, ops %s\n", __func__, wsi, + wsi->wsistate, wsi->role_ops->name); + + vwsi = (volatile struct lws *)wsi; + vwsi->leave_pollout_active = 0; + + n = lws_callback_as_writeable(wsi); + vwsi->handling_pollout = 0; + + if (vwsi->leave_pollout_active) + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + + return n; + + /* + * since these don't disable the POLLOUT, they are always doing the + * right thing for leave_pollout_active whether it was set or not. + */ + +bail_ok: + vwsi->handling_pollout = 0; + vwsi->leave_pollout_active = 0; + + return 0; + +bail_die: + vwsi->handling_pollout = 0; + vwsi->leave_pollout_active = 0; + + return -1; +} + +static int +__lws_service_timeout_check(struct lws *wsi, time_t sec) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int n = 0; + + (void)n; + + /* + * if we went beyond the allowed time, kill the + * connection + */ + if (wsi->dll_timeout.prev && + lws_compare_time_t(wsi->context, sec, wsi->pending_timeout_set) > + wsi->pending_timeout_limit) { + + if (wsi->desc.sockfd != LWS_SOCK_INVALID && + wsi->position_in_fds_table >= 0) + n = pt->fds[wsi->position_in_fds_table].events; + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_TIMEOUTS, 1); + + /* no need to log normal idle keepalive timeout */ + if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE) +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + lwsl_info("wsi %p: TIMEDOUT WAITING on %d " + "(did hdr %d, ah %p, wl %d, pfd " + "events %d) %llu vs %llu\n", + (void *)wsi, wsi->pending_timeout, + wsi->hdr_parsing_completed, wsi->http.ah, + pt->http.ah_wait_list_length, n, + (unsigned long long)sec, + (unsigned long long)wsi->pending_timeout_limit); +#if defined(LWS_WITH_CGI) + if (wsi->http.cgi) + lwsl_notice("CGI timeout: %s\n", wsi->http.cgi->summary); +#endif +#else + lwsl_info("wsi %p: TIMEDOUT WAITING on %d ", (void *)wsi, + wsi->pending_timeout); +#endif + + /* + * Since he failed a timeout, he already had a chance to do + * something and was unable to... that includes situations like + * half closed connections. So process this "failed timeout" + * close as a violent death and don't try to do protocol + * cleanup like flush partials. + */ + wsi->socket_is_permanently_unusable = 1; + if (lwsi_state(wsi) == LRS_WAITING_SSL && wsi->protocol) + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, + (void *)"Timed out waiting SSL", 21); + + __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "timeout"); + + return 1; + } + + return 0; +} + +int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + uint8_t *buffered; + size_t blen; + int ret = 0, m; + + /* his RX is flowcontrolled, don't send remaining now */ + blen = lws_buflist_next_segment_len(&wsi->buflist, &buffered); + if (blen) { + if (buf >= buffered && buf + len <= buffered + blen) { + /* rxflow while we were spilling prev rxflow */ + lwsl_info("%s: staying in rxflow buf\n", __func__); + + return 1; + } + ret = 1; + } + + /* a new rxflow, buffer it and warn caller */ + + m = lws_buflist_append_segment(&wsi->buflist, buf + n, len - n); + + if (m < 0) + return -1; + if (m) { + lwsl_debug("%s: added %p to rxflow list\n", __func__, wsi); + lws_dll_lws_add_front(&wsi->dll_buflist, &pt->dll_head_buflist); + } + + return ret; +} + +/* this is used by the platform service code to stop us waiting for network + * activity in poll() when we have something that already needs service + */ + +LWS_VISIBLE LWS_EXTERN int +lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + + /* Figure out if we really want to wait in poll() + * We only need to wait if really nothing already to do and we have + * to wait for something from network + */ +#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) + /* 1) if we know we are draining rx ext, do not wait in poll */ + if (pt->ws.rx_draining_ext_list) + return 0; +#endif + + /* 2) if we know we have non-network pending data, do not wait in poll */ + + if (pt->context->tls_ops && + pt->context->tls_ops->fake_POLLIN_for_buffered) + if (pt->context->tls_ops->fake_POLLIN_for_buffered(pt)) + return 0; + + /* 3) If there is any wsi with rxflow buffered and in a state to process + * it, we should not wait in poll + */ + + lws_start_foreach_dll(struct lws_dll_lws *, d, pt->dll_head_buflist.next) { + struct lws *wsi = lws_container_of(d, struct lws, dll_buflist); + + if (lwsi_state(wsi) != LRS_DEFERRING_ACTION) + return 0; + + } lws_end_foreach_dll(d); + + return timeout_ms; +} + +/* + * POLLIN said there is something... we must read it, and either use it; or + * if other material already in the buflist append it and return the buflist + * head material. + */ +int +lws_buflist_aware_read(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_tokens *ebuf) +{ + int n, prior = (int)lws_buflist_next_segment_len(&wsi->buflist, NULL); + + ebuf->token = (char *)pt->serv_buf; + ebuf->len = lws_ssl_capable_read(wsi, pt->serv_buf, + wsi->context->pt_serv_buf_size); + + if (ebuf->len == LWS_SSL_CAPABLE_MORE_SERVICE && prior) + goto get_from_buflist; + + if (ebuf->len <= 0) + return 0; + + /* nothing in buflist already? Then just use what we read */ + + if (!prior) + return 0; + + /* stash what we read */ + + n = lws_buflist_append_segment(&wsi->buflist, (uint8_t *)ebuf->token, + ebuf->len); + if (n < 0) + return -1; + if (n) { + lwsl_debug("%s: added %p to rxflow list\n", __func__, wsi); + lws_dll_lws_add_front(&wsi->dll_buflist, &pt->dll_head_buflist); + } + + /* get the first buflist guy in line */ + +get_from_buflist: + + ebuf->len = (int)lws_buflist_next_segment_len(&wsi->buflist, + (uint8_t **)&ebuf->token); + + return 1; /* came from buflist */ +} + +int +lws_buflist_aware_consume(struct lws *wsi, struct lws_tokens *ebuf, int used, + int buffered) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int m; + + /* it's in the buflist; we didn't use any */ + + if (!used && buffered) + return 0; + + if (used && buffered) { + m = lws_buflist_use_segment(&wsi->buflist, used); + lwsl_info("%s: draining rxflow: used %d, next %d\n", + __func__, used, m); + if (m) + return 0; + + lwsl_info("%s: removed %p from dll_buflist\n", __func__, wsi); + lws_dll_lws_remove(&wsi->dll_buflist); + + return 0; + } + + /* any remainder goes on the buflist */ + + if (used != ebuf->len) { + m = lws_buflist_append_segment(&wsi->buflist, + (uint8_t *)ebuf->token + used, + ebuf->len - used); + if (m < 0) + return 1; /* OOM */ + if (m) { + lwsl_debug("%s: added %p to rxflow list\n", __func__, wsi); + lws_dll_lws_add_front(&wsi->dll_buflist, &pt->dll_head_buflist); + } + } + + return 0; +} + +void +lws_service_do_ripe_rxflow(struct lws_context_per_thread *pt) +{ + struct lws_pollfd pfd; + + if (!pt->dll_head_buflist.next) + return; + + /* + * service all guys with pending rxflow that reached a state they can + * accept the pending data + */ + + lws_pt_lock(pt, __func__); + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + pt->dll_head_buflist.next) { + struct lws *wsi = lws_container_of(d, struct lws, dll_buflist); + + pfd.events = LWS_POLLIN; + pfd.revents = LWS_POLLIN; + pfd.fd = -1; + + lwsl_debug("%s: rxflow processing: %p 0x%x\n", __func__, wsi, + wsi->wsistate); + + if (!lws_is_flowcontrolled(wsi) && + lwsi_state(wsi) != LRS_DEFERRING_ACTION && + (wsi->role_ops->handle_POLLIN)(pt, wsi, &pfd) == + LWS_HPI_RET_PLEASE_CLOSE_ME) + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "close_and_handled"); + + } lws_end_foreach_dll_safe(d, d1); + + lws_pt_unlock(pt); +} + +/* + * guys that need POLLIN service again without waiting for network action + * can force POLLIN here if not flowcontrolled, so they will get service. + * + * Return nonzero if anybody got their POLLIN faked + */ +int +lws_service_flag_pending(struct lws_context *context, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + +#if defined(LWS_WITH_TLS) + struct lws *wsi, *wsi_next; +#endif + int forced = 0; + + lws_pt_lock(pt, __func__); + + /* + * 1) If there is any wsi with a buflist and in a state to process + * it, we should not wait in poll + */ + + lws_start_foreach_dll(struct lws_dll_lws *, d, pt->dll_head_buflist.next) { + struct lws *wsi = lws_container_of(d, struct lws, dll_buflist); + + if (lwsi_state(wsi) != LRS_DEFERRING_ACTION) { + forced = 1; + break; + } + } lws_end_foreach_dll(d); + +#if defined(LWS_ROLE_WS) + forced |= role_ops_ws.service_flag_pending(context, tsi); +#endif + +#if defined(LWS_WITH_TLS) + /* + * 2) For all guys with buffered SSL read data already saved up, if they + * are not flowcontrolled, fake their POLLIN status so they'll get + * service to use up the buffered incoming data, even though their + * network socket may have nothing + */ + wsi = pt->tls.pending_read_list; + while (wsi) { + wsi_next = wsi->tls.pending_read_list_next; + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) { + forced = 1; + /* + * he's going to get serviced now, take him off the + * list of guys with buffered SSL. If he still has some + * at the end of the service, he'll get put back on the + * list then. + */ + __lws_ssl_remove_wsi_from_buffered_list(wsi); + } + + wsi = wsi_next; + } +#endif + + lws_pt_unlock(pt); + + return forced; +} + +static int +lws_service_periodic_checks(struct lws_context *context, + struct lws_pollfd *pollfd, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + lws_sockfd_type our_fd = 0, tmp_fd; + struct lws *wsi; + int timed_out = 0; + time_t now; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + struct allocated_headers *ah; + int m; +#endif + + if (!context->protocol_init_done) + if (lws_protocol_init(context)) + return -1; + + time(&now); + + /* + * handle case that system time was uninitialized when lws started + * at boot, and got initialized a little later + */ + if (context->time_up < 1464083026 && now > 1464083026) + context->time_up = now; + + if (context->last_timeout_check_s && + now - context->last_timeout_check_s > 100) { + /* + * There has been a discontiguity. Any stored time that is + * less than context->time_discontiguity should have context-> + * time_fixup added to it. + * + * Some platforms with no RTC will experience this as a normal + * event when ntp sets their clock, but we can have started + * long before that with a 0-based unix time. + */ + + context->time_discontiguity = now; + context->time_fixup = now - context->last_timeout_check_s; + + lwsl_notice("time discontiguity: at old time %llus, " + "new time %llus: +%llus\n", + (unsigned long long)context->last_timeout_check_s, + (unsigned long long)context->time_discontiguity, + (unsigned long long)context->time_fixup); + + context->last_timeout_check_s = now - 1; + } + + if (!lws_compare_time_t(context, context->last_timeout_check_s, now)) + return 0; + + context->last_timeout_check_s = now; + +#if defined(LWS_WITH_STATS) + if (!tsi && now - context->last_dump > 10) { + lws_stats_log_dump(context); + context->last_dump = now; + } +#endif + + lws_plat_service_periodic(context); + lws_check_deferred_free(context, 0); + +#if defined(LWS_WITH_PEER_LIMITS) + lws_peer_cull_peer_wait_list(context); +#endif + + /* retire unused deprecated context */ +#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_ESP32) +#if !defined(_WIN32) + if (context->deprecated && !context->count_wsi_allocated) { + lwsl_notice("%s: ending deprecated context\n", __func__); + kill(getpid(), SIGINT); + return 0; + } +#endif +#endif + /* global timeout check once per second */ + + if (pollfd) + our_fd = pollfd->fd; + + /* + * Phase 1: check every wsi on the timeout check list + */ + + lws_pt_lock(pt, __func__); + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + context->pt[tsi].dll_head_timeout.next) { + wsi = lws_container_of(d, struct lws, dll_timeout); + tmp_fd = wsi->desc.sockfd; + if (__lws_service_timeout_check(wsi, now)) { + /* he did time out... */ + if (tmp_fd == our_fd) + /* it was the guy we came to service! */ + timed_out = 1; + /* he's gone, no need to mark as handled */ + } + } lws_end_foreach_dll_safe(d, d1); + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* + * Phase 2: double-check active ah timeouts independent of wsi + * timeout status + */ + + ah = pt->http.ah_list; + while (ah) { + int len; + char buf[256]; + const unsigned char *c; + + if (!ah->in_use || !ah->wsi || !ah->assigned || + (ah->wsi->vhost && + lws_compare_time_t(context, now, ah->assigned) < + ah->wsi->vhost->timeout_secs_ah_idle + 360)) { + ah = ah->next; + continue; + } + + /* + * a single ah session somehow got held for + * an unreasonable amount of time. + * + * Dump info on the connection... + */ + wsi = ah->wsi; + buf[0] = '\0'; +#if !defined(LWS_PLAT_OPTEE) + lws_get_peer_simple(wsi, buf, sizeof(buf)); +#else + buf[0] = '\0'; +#endif + lwsl_notice("ah excessive hold: wsi %p\n" + " peer address: %s\n" + " ah pos %u\n", + wsi, buf, ah->pos); + buf[0] = '\0'; + m = 0; + do { + c = lws_token_to_string(m); + if (!c) + break; + if (!(*c)) + break; + + len = lws_hdr_total_length(wsi, m); + if (!len || len > (int)sizeof(buf) - 1) { + m++; + continue; + } + + if (lws_hdr_copy(wsi, buf, + sizeof buf, m) > 0) { + buf[sizeof(buf) - 1] = '\0'; + + lwsl_notice(" %s = %s\n", + (const char *)c, buf); + } + m++; + } while (1); + + /* explicitly detach the ah */ + lws_header_table_detach(wsi, 0); + + /* ... and then drop the connection */ + + m = 0; + if (wsi->desc.sockfd == our_fd) { + m = timed_out; + + /* it was the guy we came to service! */ + timed_out = 1; + } + + if (!m) /* if he didn't already timeout */ + __lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "excessive ah"); + + ah = pt->http.ah_list; + } +#endif + lws_pt_unlock(pt); + +#if 0 + { + char s[300], *p = s; + + for (n = 0; n < context->count_threads; n++) + p += sprintf(p, " %7lu (%5d), ", + context->pt[n].count_conns, + context->pt[n].fds_count); + + lwsl_notice("load: %s\n", s); + } +#endif + /* + * Phase 3: vhost / protocol timer callbacks + */ + + wsi = NULL; + lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { + struct lws_timed_vh_protocol *nx; + if (v->timed_vh_protocol_list) { + lws_start_foreach_ll(struct lws_timed_vh_protocol *, + q, v->timed_vh_protocol_list) { + if (now >= q->time) { + if (!wsi) + wsi = lws_zalloc(sizeof(*wsi), "cbwsi"); + wsi->context = context; + wsi->vhost = v; + wsi->protocol = q->protocol; + lwsl_debug("timed cb: vh %s, protocol %s, reason %d\n", v->name, q->protocol->name, q->reason); + q->protocol->callback(wsi, q->reason, NULL, NULL, 0); + nx = q->next; + lws_timed_callback_remove(v, q); + q = nx; + continue; /* we pointed ourselves to the next from the now-deleted guy */ + } + } lws_end_foreach_ll(q, next); + } + } lws_end_foreach_ll(v, vhost_next); + if (wsi) + lws_free(wsi); + + /* + * Phase 4: check for unconfigured vhosts due to required + * interface missing before + */ + + lws_context_lock(context); + lws_start_foreach_llp(struct lws_vhost **, pv, + context->no_listener_vhost_list) { + struct lws_vhost *v = *pv; + lwsl_debug("deferred iface: checking if on vh %s\n", (*pv)->name); + if (_lws_vhost_init_server(NULL, *pv) == 0) { + /* became happy */ + lwsl_notice("vh %s: became connected\n", v->name); + *pv = v->no_listener_vhost_list; + v->no_listener_vhost_list = NULL; + break; + } + } lws_end_foreach_llp(pv, no_listener_vhost_list); + lws_context_unlock(context); + + /* + * Phase 5: role periodic checks + */ +#if defined(LWS_ROLE_WS) + role_ops_ws.periodic_checks(context, tsi, now); +#endif +#if defined(LWS_ROLE_CGI) + role_ops_cgi.periodic_checks(context, tsi, now); +#endif + + /* + * Phase 6: check the remaining cert lifetime daily + */ + + if (context->tls_ops && + context->tls_ops->periodic_housekeeping) + context->tls_ops->periodic_housekeeping(context, now); + + return timed_out; +} + +LWS_VISIBLE int +lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, + int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct lws *wsi; + + if (!context || context->being_destroyed1) + return -1; + + /* the socket we came to service timed out, nothing to do */ + if (lws_service_periodic_checks(context, pollfd, tsi) || !pollfd) + return 0; + + /* no, here to service a socket descriptor */ + wsi = wsi_from_fd(context, pollfd->fd); + if (!wsi) + /* not lws connection ... leave revents alone and return */ + return 0; + + /* + * so that caller can tell we handled, past here we need to + * zero down pollfd->revents after handling + */ + + /* handle session socket closed */ + + if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && + (pollfd->revents & LWS_POLLHUP)) { + wsi->socket_is_permanently_unusable = 1; + lwsl_debug("Session Socket %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + goto close_and_handled; + } + +#ifdef _WIN32 + if (pollfd->revents & LWS_POLLOUT) + wsi->sock_send_blocking = FALSE; +#endif + + if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && + (pollfd->revents & LWS_POLLHUP)) { + lwsl_debug("pollhup\n"); + wsi->socket_is_permanently_unusable = 1; + goto close_and_handled; + } + +#if defined(LWS_WITH_TLS) + if (lwsi_state(wsi) == LRS_SHUTDOWN && + lws_is_ssl(wsi) && wsi->tls.ssl) { + switch (__lws_tls_shutdown(wsi)) { + case LWS_SSL_CAPABLE_DONE: + case LWS_SSL_CAPABLE_ERROR: + goto close_and_handled; + + case LWS_SSL_CAPABLE_MORE_SERVICE_READ: + case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: + case LWS_SSL_CAPABLE_MORE_SERVICE: + goto handled; + } + } +#endif + wsi->could_have_pending = 0; /* clear back-to-back write detection */ + + /* okay, what we came here to do... */ + + /* if we got here, we should have wire protocol ops set on the wsi */ + assert(wsi->role_ops); + + // lwsl_notice("%s: %s: wsistate 0x%x\n", __func__, wsi->role_ops->name, + // wsi->wsistate); + + switch ((wsi->role_ops->handle_POLLIN)(pt, wsi, pollfd)) { + case LWS_HPI_RET_WSI_ALREADY_DIED: + return 1; + case LWS_HPI_RET_HANDLED: + break; + case LWS_HPI_RET_PLEASE_CLOSE_ME: +close_and_handled: + lwsl_debug("%p: Close and handled\n", wsi); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "close_and_handled"); +#if defined(_DEBUG) && defined(LWS_WITH_LIBUV) + /* + * confirm close has no problem being called again while + * it waits for libuv service to complete the first async + * close + */ + if (context->event_loop_ops == &event_loop_ops_uv) + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "close_and_handled uv repeat test"); +#endif + /* + * pollfd may point to something else after the close + * due to pollfd swapping scheme on delete on some platforms + * we can't clear revents now because it'd be the wrong guy's + * revents + */ + return 1; + default: + assert(0); + } +#if defined(LWS_WITH_TLS) +handled: +#endif + pollfd->revents = 0; + + lws_pt_lock(pt, __func__); + __lws_hrtimer_service(pt); + lws_pt_unlock(pt); + + return 0; +} + +LWS_VISIBLE int +lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd) +{ + return lws_service_fd_tsi(context, pollfd, 0); +} + +LWS_VISIBLE int +lws_service(struct lws_context *context, int timeout_ms) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + int n; + + if (!context) + return 1; + + pt->inside_service = 1; + + if (context->event_loop_ops->run_pt) { + /* we are configured for an event loop */ + context->event_loop_ops->run_pt(context, 0); + + pt->inside_service = 0; + + return 1; + } + n = lws_plat_service(context, timeout_ms); + + pt->inside_service = 0; + + return n; +} + +LWS_VISIBLE int +lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + int n; + + pt->inside_service = 1; + + if (context->event_loop_ops->run_pt) { + /* we are configured for an event loop */ + context->event_loop_ops->run_pt(context, tsi); + + pt->inside_service = 0; + + return 1; + } + + n = _lws_plat_service_tsi(context, timeout_ms, tsi); + + pt->inside_service = 0; + + return n; +} diff --git a/thirdparty/libwebsockets/event-libs/poll/poll.c b/thirdparty/libwebsockets/event-libs/poll/poll.c new file mode 100644 index 0000000000..09af5b15d8 --- /dev/null +++ b/thirdparty/libwebsockets/event-libs/poll/poll.c @@ -0,0 +1,43 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h if LWS_ROLE_WS + */ + +#include <core/private.h> + +struct lws_event_loop_ops event_loop_ops_poll = { + /* name */ "poll", + /* init_context */ NULL, + /* destroy_context1 */ NULL, + /* destroy_context2 */ NULL, + /* init_vhost_listen_wsi */ NULL, + /* init_pt */ NULL, + /* wsi_logical_close */ NULL, + /* check_client_connect_ok */ NULL, + /* close_handle_manually */ NULL, + /* accept */ NULL, + /* io */ NULL, + /* run */ NULL, + /* destroy_pt */ NULL, + /* destroy wsi */ NULL, + + /* periodic_events_available */ 1, +};
\ No newline at end of file diff --git a/thirdparty/libwebsockets/event-libs/poll/private.h b/thirdparty/libwebsockets/event-libs/poll/private.h new file mode 100644 index 0000000000..ca313ebfb0 --- /dev/null +++ b/thirdparty/libwebsockets/event-libs/poll/private.h @@ -0,0 +1,23 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + */ + +extern struct lws_event_loop_ops event_loop_ops_poll; diff --git a/thirdparty/libwebsockets/event-libs/private.h b/thirdparty/libwebsockets/event-libs/private.h new file mode 100644 index 0000000000..c36d39c8c2 --- /dev/null +++ b/thirdparty/libwebsockets/event-libs/private.h @@ -0,0 +1,74 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h + */ + +struct lws_event_loop_ops { + const char *name; + /* event loop-specific context init during context creation */ + int (*init_context)(struct lws_context *context, + const struct lws_context_creation_info *info); + /* called during lws_destroy_context */ + int (*destroy_context1)(struct lws_context *context); + /* called during lws_destroy_context2 */ + int (*destroy_context2)(struct lws_context *context); + /* init vhost listening wsi */ + int (*init_vhost_listen_wsi)(struct lws *wsi); + /* init the event loop for a pt */ + int (*init_pt)(struct lws_context *context, void *_loop, int tsi); + /* called at end of first phase of close_free_wsi() */ + int (*wsi_logical_close)(struct lws *wsi); + /* return nonzero if client connect not allowed */ + int (*check_client_connect_ok)(struct lws *wsi); + /* close handle manually */ + void (*close_handle_manually)(struct lws *wsi); + /* event loop accept processing */ + void (*accept)(struct lws *wsi); + /* control wsi active events */ + void (*io)(struct lws *wsi, int flags); + /* run the event loop for a pt */ + void (*run_pt)(struct lws_context *context, int tsi); + /* called before pt is destroyed */ + void (*destroy_pt)(struct lws_context *context, int tsi); + /* called just before wsi is freed */ + void (*destroy_wsi)(struct lws *wsi); + + unsigned int periodic_events_available:1; +}; + +/* bring in event libs private declarations */ + +#if defined(LWS_WITH_POLL) +#include "event-libs/poll/private.h" +#endif + +#if defined(LWS_WITH_LIBUV) +#include "event-libs/libuv/private.h" +#endif + +#if defined(LWS_WITH_LIBEVENT) +#include "event-libs/libevent/private.h" +#endif + +#if defined(LWS_WITH_LIBEV) +#include "event-libs/libev/private.h" +#endif + diff --git a/thirdparty/lws/libwebsockets.h b/thirdparty/libwebsockets/libwebsockets.h index 460c732602..7ae563d582 100644 --- a/thirdparty/lws/libwebsockets.h +++ b/thirdparty/libwebsockets/libwebsockets.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,25 +27,21 @@ #ifdef __cplusplus #include <cstddef> #include <cstdarg> -# + extern "C" { #else #include <stdarg.h> #endif +#include <string.h> +#include <stdlib.h> + #include "lws_config.h" /* * CARE: everything using cmake defines needs to be below here */ -#if defined(LWS_WITH_ESP8266) -struct sockaddr_in; -#define LWS_POSIX 0 -#else -#define LWS_POSIX 1 -#endif - #if defined(LWS_HAS_INTPTR_T) #include <stdint.h> #define lws_intptr_t intptr_t @@ -62,6 +58,7 @@ typedef unsigned long long lws_intptr_t; #include <ws2tcpip.h> #include <stddef.h> #include <basetsd.h> +#include <io.h> #ifndef _WIN32_WCE #include <fcntl.h> #else @@ -99,25 +96,18 @@ typedef unsigned long long lws_intptr_t; #define LWS_O_CREAT _O_CREAT #define LWS_O_TRUNC _O_TRUNC -#if !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER < 1900) /* Visual Studio 2015 already defines this in <stdio.h> */ -#define lws_snprintf _snprintf -#endif - #ifndef __func__ #define __func__ __FUNCTION__ #endif -#if !defined(__MINGW32__) &&(!defined(_MSC_VER) || _MSC_VER < 1900) && !defined(snprintf) -#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__) -#endif - #else /* NOT WIN32 */ #include <unistd.h> #if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) #include <sys/capability.h> #endif -#if defined(__NetBSD__) || defined(__FreeBSD__) +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__QNX__) || defined(__OpenBSD__) +#include <sys/socket.h> #include <netinet/in.h> #endif @@ -127,7 +117,7 @@ typedef unsigned long long lws_intptr_t; #define LWS_O_CREAT O_CREAT #define LWS_O_TRUNC O_TRUNC -#if !defined(LWS_WITH_ESP8266) && !defined(OPTEE_TA) && !defined(LWS_WITH_ESP32) +#if !defined(LWS_PLAT_OPTEE) && !defined(OPTEE_TA) && !defined(LWS_WITH_ESP32) #include <poll.h> #include <netdb.h> #define LWS_INVALID_FILE -1 @@ -166,7 +156,7 @@ typedef unsigned long long lws_intptr_t; #endif -#ifdef LWS_WITH_LIBEV +#if defined(LWS_WITH_LIBEV) #include <ev.h> #endif /* LWS_WITH_LIBEV */ #ifdef LWS_WITH_LIBUV @@ -175,7 +165,7 @@ typedef unsigned long long lws_intptr_t; #include <uv-version.h> #endif #endif /* LWS_WITH_LIBUV */ -#ifdef LWS_WITH_LIBEVENT +#if defined(LWS_WITH_LIBEVENT) #include <event2/event.h> #endif /* LWS_WITH_LIBEVENT */ @@ -192,13 +182,34 @@ typedef unsigned long long lws_intptr_t; #endif #endif -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL +#ifdef _WIN32 +/* + * Include user-controlled settings for windows from + * <wolfssl-root>/IDE/WIN/user_settings.h + */ +#include <IDE/WIN/user_settings.h> +#include <cyassl/ctaocrypt/settings.h> +#else +#include <cyassl/options.h> +#endif #include <cyassl/openssl/ssl.h> #include <cyassl/error-ssl.h> + #else +#ifdef _WIN32 +/* + * Include user-controlled settings for windows from + * <wolfssl-root>/IDE/WIN/user_settings.h + */ +#include <IDE/WIN/user_settings.h> +#include <wolfssl/wolfcrypt/settings.h> +#else +#include <wolfssl/options.h> +#endif #include <wolfssl/openssl/ssl.h> #include <wolfssl/error-ssl.h> #endif /* not USE_OLD_CYASSL */ @@ -210,14 +221,60 @@ typedef unsigned long long lws_intptr_t; #define MBEDTLS_CONFIG_FILE <mbedtls/esp_config.h> #endif #include <mbedtls/ssl.h> -#endif +#else #include <openssl/ssl.h> #if !defined(LWS_WITH_MBEDTLS) #include <openssl/err.h> #endif +#endif #endif /* not USE_WOLFSSL */ #endif +/* + * Helpers for pthread mutex in user code... if lws is built for + * multiple service threads, these resolve to pthread mutex + * operations. In the case LWS_MAX_SMP is 1 (the default), they + * are all NOPs and no pthread type or api is referenced. + */ + +#if LWS_MAX_SMP > 1 + +#include <pthread.h> + +#define lws_pthread_mutex(name) pthread_mutex_t name; + +static LWS_INLINE void +lws_pthread_mutex_init(pthread_mutex_t *lock) +{ + pthread_mutex_init(lock, NULL); +} + +static LWS_INLINE void +lws_pthread_mutex_destroy(pthread_mutex_t *lock) +{ + pthread_mutex_destroy(lock); +} + +static LWS_INLINE void +lws_pthread_mutex_lock(pthread_mutex_t *lock) +{ + pthread_mutex_lock(lock); +} + +static LWS_INLINE void +lws_pthread_mutex_unlock(pthread_mutex_t *lock) +{ + pthread_mutex_unlock(lock); +} + +#else +#define lws_pthread_mutex(name) +#define lws_pthread_mutex_init(_a) +#define lws_pthread_mutex_destroy(_a) +#define lws_pthread_mutex_lock(_a) +#define lws_pthread_mutex_unlock(_a) +#endif + #define CONTEXT_PORT_NO_LISTEN -1 #define CONTEXT_PORT_NO_LISTEN_SERVER -2 @@ -280,10 +337,6 @@ lwsl_timestamp(int level, char *p, int len); * active */ -#if defined(LWS_WITH_ESP8266) -#undef _DEBUG -#endif - #ifdef _DEBUG #if defined(LWS_WITH_NO_LOGS) /* notice, warn and log are always compiled in */ @@ -313,14 +366,20 @@ lwsl_timestamp(int level, char *p, int len); #endif +#define lwsl_hexdump_err(...) lwsl_hexdump_level(LLL_ERR, __VA_ARGS__) +#define lwsl_hexdump_warn(...) lwsl_hexdump_level(LLL_WARN, __VA_ARGS__) +#define lwsl_hexdump_notice(...) lwsl_hexdump_level(LLL_NOTICE, __VA_ARGS__) +#define lwsl_hexdump_info(...) lwsl_hexdump_level(LLL_INFO, __VA_ARGS__) +#define lwsl_hexdump_debug(...) lwsl_hexdump_level(LLL_DEBUG, __VA_ARGS__) + /** - * lwsl_hexdump() - helper to hexdump a buffer + * lwsl_hexdump_level() - helper to hexdump a buffer at a selected debug level * * \param level: one of LLL_ constants - * \param buf: buffer start to dump + * \param vbuf: buffer start to dump * \param len: length of buffer to dump * - * If \p level is visible, does a nice hexdump -C style dump of \p buf for + * If \p level is visible, does a nice hexdump -C style dump of \p vbuf for * \p len bytes. This can be extremely convenient while debugging. */ LWS_VISIBLE LWS_EXTERN void @@ -396,12 +455,13 @@ lwsl_visible(int level); #define lws_container_of(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) #endif - struct lws; #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #endif +typedef int64_t lws_usec_t; + /* api change list for user code to test against */ #define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_ARG @@ -422,7 +482,7 @@ struct lws; #if defined(_WIN32) typedef SOCKET lws_sockfd_type; typedef HANDLE lws_filefd_type; -#define lws_sockfd_valid(sfd) (!!sfd) + struct lws_pollfd { lws_sockfd_type fd; /**< file descriptor */ SHORT events; /**< which events to respond to */ @@ -434,77 +494,11 @@ struct lws_pollfd { #else -#if defined(LWS_WITH_ESP8266) - -#include <user_interface.h> -#include <espconn.h> - -typedef struct espconn * lws_sockfd_type; -typedef void * lws_filefd_type; -#define lws_sockfd_valid(sfd) (!!sfd) -struct pollfd { - lws_sockfd_type fd; /**< fd related to */ - short events; /**< which POLL... events to respond to */ - short revents; /**< which POLL... events occurred */ -}; -#define POLLIN 0x0001 -#define POLLPRI 0x0002 -#define POLLOUT 0x0004 -#define POLLERR 0x0008 -#define POLLHUP 0x0010 -#define POLLNVAL 0x0020 - -struct lws_vhost; - -lws_sockfd_type esp8266_create_tcp_listen_socket(struct lws_vhost *vh); -void esp8266_tcp_stream_accept(lws_sockfd_type fd, struct lws *wsi); - -#include <os_type.h> -#include <osapi.h> -#include "ets_sys.h" - -int ets_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3); -#define snprintf ets_snprintf - -typedef os_timer_t uv_timer_t; -typedef void uv_cb_t(uv_timer_t *); - -void os_timer_disarm(void *); -void os_timer_setfn(os_timer_t *, os_timer_func_t *, void *); - -void ets_timer_arm_new(os_timer_t *, int, int, int); - -//void os_timer_arm(os_timer_t *, int, int); - -#define UV_VERSION_MAJOR 1 - -#define lws_uv_getloop(a, b) (NULL) - -static inline void uv_timer_init(void *l, uv_timer_t *t) -{ - (void)l; - memset(t, 0, sizeof(*t)); - os_timer_disarm(t); -} - -static inline void uv_timer_start(uv_timer_t *t, uv_cb_t *cb, int first, int rep) -{ - os_timer_setfn(t, (os_timer_func_t *)cb, t); - /* ms, repeat */ - os_timer_arm(t, first, !!rep); -} - -static inline void uv_timer_stop(uv_timer_t *t) -{ - os_timer_disarm(t); -} - -#else #if defined(LWS_WITH_ESP32) typedef int lws_sockfd_type; typedef int lws_filefd_type; -#define lws_sockfd_valid(sfd) (sfd >= 0) + struct pollfd { lws_sockfd_type fd; /**< fd related to */ short events; /**< which POLL... events to respond to */ @@ -638,13 +632,14 @@ struct lws_esp32 { char model[16]; char group[16]; char role[16]; - char ssid[4][16]; - char password[4][32]; - char active_ssid[32]; + char ssid[4][64]; + char password[4][64]; + char active_ssid[64]; char access_pw[16]; char hostname[32]; char mac[20]; - mdns_server_t *mdns; + char le_dns[64]; + char le_email[64]; char region; char inet; char conn_ap; @@ -656,6 +651,11 @@ struct lws_esp32 { void *scan_consumer_arg; struct lws_group_member *first; int extant_group_members; + + char acme; + char upload; + + volatile char button_is_down; }; struct lws_esp32_image { @@ -702,8 +702,6 @@ extern void lws_esp32_leds_timer_cb(TimerHandle_t th); #else typedef int lws_sockfd_type; typedef int lws_filefd_type; -#define lws_sockfd_valid(sfd) (sfd >= 0) -#endif #endif #define lws_pollfd pollfd @@ -830,6 +828,8 @@ enum lws_close_status { connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified). */ + LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE = 2000, + /****** add new things just above ---^ ******/ LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY = 9999, @@ -858,37 +858,6 @@ struct lws_context; /* needed even with extensions disabled for create context */ struct lws_extension; -/*! \defgroup lwsmeta lws-meta - * - * ##lws-meta protocol - * - * The protocol wraps other muxed connections inside one tcp connection. - * - * Commands are assigned from 0x41 up (so they are valid unicode) - */ -///@{ - -enum lws_meta_commands { - LWS_META_CMD_OPEN_SUBCHANNEL = 'A', - /**< Client requests to open new subchannel - */ - LWS_META_CMD_OPEN_RESULT, - /**< Result of client request to open new subchannel */ - LWS_META_CMD_CLOSE_NOTIFY, - /**< Notification of subchannel closure */ - LWS_META_CMD_CLOSE_RQ, - /**< client requests to close a subchannel */ - LWS_META_CMD_WRITE, - /**< connection writes something to specific channel index */ - - /****** add new things just above ---^ ******/ -}; - -/* channel numbers are transported offset by 0x20 so they are valid unicode */ - -#define LWS_META_TRANSPORT_OFFSET 0x20 - -///@} /*! \defgroup usercb User Callback * @@ -908,16 +877,388 @@ struct lws_ssl_info { int ret; }; +enum lws_cert_update_state { + LWS_CUS_IDLE, + LWS_CUS_STARTING, + LWS_CUS_SUCCESS, + LWS_CUS_FAILED, + + LWS_CUS_CREATE_KEYS, + LWS_CUS_REG, + LWS_CUS_AUTH, + LWS_CUS_CHALLENGE, + LWS_CUS_CREATE_REQ, + LWS_CUS_REQ, + LWS_CUS_CONFIRM, + LWS_CUS_ISSUE, +}; + +enum { + LWS_TLS_REQ_ELEMENT_COUNTRY, + LWS_TLS_REQ_ELEMENT_STATE, + LWS_TLS_REQ_ELEMENT_LOCALITY, + LWS_TLS_REQ_ELEMENT_ORGANIZATION, + LWS_TLS_REQ_ELEMENT_COMMON_NAME, + LWS_TLS_REQ_ELEMENT_EMAIL, + + LWS_TLS_REQ_ELEMENT_COUNT, + + LWS_TLS_SET_DIR_URL = LWS_TLS_REQ_ELEMENT_COUNT, + LWS_TLS_SET_AUTH_PATH, + LWS_TLS_SET_CERT_PATH, + LWS_TLS_SET_KEY_PATH, + + LWS_TLS_TOTAL_COUNT +}; + +struct lws_acme_cert_aging_args { + struct lws_vhost *vh; + const char *element_overrides[LWS_TLS_TOTAL_COUNT]; /* NULL = use pvo */ +}; + /* * NOTE: These public enums are part of the abi. If you want to add one, * add it at where specified so existing users are unaffected. */ /** enum lws_callback_reasons - reason you're getting a protocol callback */ enum lws_callback_reasons { + + /* --------------------------------------------------------------------- + * ----- Callbacks related to wsi and protocol binding lifecycle ----- + */ + + LWS_CALLBACK_PROTOCOL_INIT = 27, + /**< One-time call per protocol, per-vhost using it, so it can + * do initial setup / allocations etc */ + + LWS_CALLBACK_PROTOCOL_DESTROY = 28, + /**< One-time call per protocol, per-vhost using it, indicating + * this protocol won't get used at all after this callback, the + * vhost is getting destroyed. Take the opportunity to + * deallocate everything that was allocated by the protocol. */ + + LWS_CALLBACK_WSI_CREATE = 29, + /**< outermost (earliest) wsi create notification to protocols[0] */ + + LWS_CALLBACK_WSI_DESTROY = 30, + /**< outermost (latest) wsi destroy notification to protocols[0] */ + + LWS_CALLBACK_HTTP_BIND_PROTOCOL = 49, + /**< By default, all HTTP handling is done in protocols[0]. + * However you can bind different protocols (by name) to + * different parts of the URL space using callback mounts. This + * callback occurs in the new protocol when a wsi is bound + * to that protocol. Any protocol allocation related to the + * http transaction processing should be created then. + * These specific callbacks are necessary because with HTTP/1.1, + * a single connection may perform at series of different + * transactions at different URLs, thus the lifetime of the + * protocol bind is just for one transaction, not connection. */ + + LWS_CALLBACK_HTTP_DROP_PROTOCOL = 50, + /**< This is called when a transaction is unbound from a protocol. + * It indicates the connection completed its transaction and may + * do something different now. Any protocol allocation related + * to the http transaction processing should be destroyed. */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to Server TLS ----- + */ + + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS = 21, + /**< if configured for + * including OpenSSL support, this callback allows your user code + * to perform extra SSL_CTX_load_verify_locations() or similar + * calls to direct OpenSSL where to find certificates the client + * can use to confirm the remote server identity. user is the + * OpenSSL SSL_CTX* */ + + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS = 22, + /**< if configured for + * including OpenSSL support, this callback allows your user code + * to load extra certificates into the server which allow it to + * verify the validity of certificates returned by clients. user + * is the server's OpenSSL SSL_CTX* and in is the lws_vhost */ + + LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION = 23, + /**< if the libwebsockets vhost was created with the option + * LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT, then this + * callback is generated during OpenSSL verification of the cert + * sent from the client. It is sent to protocol[0] callback as + * no protocol has been negotiated on the connection yet. + * Notice that the libwebsockets context and wsi are both NULL + * during this callback. See + * http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html + * to understand more detail about the OpenSSL callback that + * generates this libwebsockets callback and the meanings of the + * arguments passed. In this callback, user is the x509_ctx, + * in is the ssl pointer and len is preverify_ok + * Notice that this callback maintains libwebsocket return + * conventions, return 0 to mean the cert is OK or 1 to fail it. + * This also means that if you don't handle this callback then + * the default callback action of returning 0 allows the client + * certificates. */ + + LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY = 37, + /**< if configured for including OpenSSL support but no private key + * file has been specified (ssl_private_key_filepath is NULL), this is + * called to allow the user to set the private key directly via + * libopenssl and perform further operations if required; this might be + * useful in situations where the private key is not directly accessible + * by the OS, for example if it is stored on a smartcard. + * user is the server's OpenSSL SSL_CTX* */ + + LWS_CALLBACK_SSL_INFO = 67, + /**< SSL connections only. An event you registered an + * interest in at the vhost has occurred on a connection + * using the vhost. in is a pointer to a + * struct lws_ssl_info containing information about the + * event*/ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to Client TLS ----- + */ + + LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION = 58, + /**< Similar to LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION + * this callback is called during OpenSSL verification of the cert + * sent from the server to the client. It is sent to protocol[0] + * callback as no protocol has been negotiated on the connection yet. + * Notice that the wsi is set because lws_client_connect_via_info was + * successful. + * + * See http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html + * to understand more detail about the OpenSSL callback that + * generates this libwebsockets callback and the meanings of the + * arguments passed. In this callback, user is the x509_ctx, + * in is the ssl pointer and len is preverify_ok. + * + * THIS IS NOT RECOMMENDED BUT if a cert validation error shall be + * overruled and cert shall be accepted as ok, + * X509_STORE_CTX_set_error((X509_STORE_CTX*)user, X509_V_OK); must be + * called and return value must be 0 to mean the cert is OK; + * returning 1 will fail the cert in any case. + * + * This also means that if you don't handle this callback then + * the default callback action of returning 0 will not accept the + * certificate in case of a validation error decided by the SSL lib. + * + * This is expected and secure behaviour when validating certificates. + * + * Note: LCCSCF_ALLOW_SELFSIGNED and + * LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK still work without this + * callback being implemented. + */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to HTTP Server ----- + */ + + LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED = 19, + /**< A new client has been accepted by the ws server. This + * callback allows setting any relevant property to it. Because this + * happens immediately after the instantiation of a new client, + * there's no websocket protocol selected yet so this callback is + * issued only to protocol 0. Only wsi is defined, pointing to the + * new client, and the return value is ignored. */ + + LWS_CALLBACK_HTTP = 12, + /**< an http request has come from a client that is not + * asking to upgrade the connection to a websocket + * one. This is a chance to serve http content, + * for example, to send a script to the client + * which will then open the websockets connection. + * in points to the URI path requested and + * lws_serve_http_file() makes it very + * simple to send back a file to the client. + * Normally after sending the file you are done + * with the http connection, since the rest of the + * activity will come by websockets from the script + * that was delivered by http, so you will want to + * return 1; to close and free up the connection. */ + + LWS_CALLBACK_HTTP_BODY = 13, + /**< the next len bytes data from the http + * request body HTTP connection is now available in in. */ + + LWS_CALLBACK_HTTP_BODY_COMPLETION = 14, + /**< the expected amount of http request body has been delivered */ + + LWS_CALLBACK_HTTP_FILE_COMPLETION = 15, + /**< a file requested to be sent down http link has completed. */ + + LWS_CALLBACK_HTTP_WRITEABLE = 16, + /**< you can write more down the http protocol link now. */ + + LWS_CALLBACK_CLOSED_HTTP = 5, + /**< when a HTTP (non-websocket) session ends */ + + LWS_CALLBACK_FILTER_HTTP_CONNECTION = 18, + /**< called when the request has + * been received and parsed from the client, but the response is + * not sent yet. Return non-zero to disallow the connection. + * user is a pointer to the connection user space allocation, + * in is the URI, eg, "/" + * In your handler you can use the public APIs + * lws_hdr_total_length() / lws_hdr_copy() to access all of the + * headers using the header enums lws_token_indexes from + * libwebsockets.h to check for and read the supported header + * presence and content before deciding to allow the http + * connection to proceed or to kill the connection. */ + + LWS_CALLBACK_ADD_HEADERS = 53, + /**< This gives your user code a chance to add headers to a server + * transaction bound to your protocol. `in` points to a + * `struct lws_process_html_args` describing a buffer and length + * you can add headers into using the normal lws apis. + * + * (see LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to add headers to + * a client transaction) + * + * Only `args->p` and `args->len` are valid, and `args->p` should + * be moved on by the amount of bytes written, if any. Eg + * + * case LWS_CALLBACK_ADD_HEADERS: + * + * struct lws_process_html_args *args = + * (struct lws_process_html_args *)in; + * + * if (lws_add_http_header_by_name(wsi, + * (unsigned char *)"set-cookie:", + * (unsigned char *)cookie, cookie_len, + * (unsigned char **)&args->p, + * (unsigned char *)args->p + args->max_len)) + * return 1; + * + * break; + */ + + LWS_CALLBACK_CHECK_ACCESS_RIGHTS = 51, + /**< This gives the user code a chance to forbid an http access. + * `in` points to a `struct lws_process_html_args`, which + * describes the URL, and a bit mask describing the type of + * authentication required. If the callback returns nonzero, + * the transaction ends with HTTP_STATUS_UNAUTHORIZED. */ + + LWS_CALLBACK_PROCESS_HTML = 52, + /**< This gives your user code a chance to mangle outgoing + * HTML. `in` points to a `struct lws_process_html_args` + * which describes the buffer containing outgoing HTML. + * The buffer may grow up to `.max_len` (currently +128 + * bytes per buffer). + */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to HTTP Client ----- + */ + + LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP = 44, + /**< The HTTP client connection has succeeded, and is now + * connected to the server */ + + LWS_CALLBACK_CLOSED_CLIENT_HTTP = 45, + /**< The HTTP client connection is closing */ + + LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ = 48, + /**< This is generated by lws_http_client_read() used to drain + * incoming data. In the case the incoming data was chunked, it will + * be split into multiple smaller callbacks for each chunk block, + * removing the chunk headers. If not chunked, it will appear all in + * one callback. */ + + LWS_CALLBACK_RECEIVE_CLIENT_HTTP = 46, + /**< This simply indicates data was received on the HTTP client + * connection. It does NOT drain or provide the data. + * This exists to neatly allow a proxying type situation, + * where this incoming data will go out on another connection. + * If the outgoing connection stalls, we should stall processing + * the incoming data. So a handler for this in that case should + * simply set a flag to indicate there is incoming data ready + * and ask for a writeable callback on the outgoing connection. + * In the writable callback he can check the flag and then get + * and drain the waiting incoming data using lws_http_client_read(). + * This will use callbacks to LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ + * to get and drain the incoming data, where it should be sent + * back out on the outgoing connection. */ + LWS_CALLBACK_COMPLETED_CLIENT_HTTP = 47, + /**< The client transaction completed... at the moment this + * is the same as closing since transaction pipelining on + * client side is not yet supported. */ + + LWS_CALLBACK_CLIENT_HTTP_WRITEABLE = 57, + /**< when doing an HTTP type client connection, you can call + * lws_client_http_body_pending(wsi, 1) from + * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to get these callbacks + * sending the HTTP headers. + * + * From this callback, when you have sent everything, you should let + * lws know by calling lws_client_http_body_pending(wsi, 0) + */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to Websocket Server ----- + */ + LWS_CALLBACK_ESTABLISHED = 0, /**< (VH) after the server completes a handshake with an incoming * client. If you built the library with ssl support, in is a - * pointer to the ssl struct associated with the connection or NULL.*/ + * pointer to the ssl struct associated with the connection or NULL. + * + * b0 of len is set if the connection was made using ws-over-h2 + */ + + LWS_CALLBACK_CLOSED = 4, + /**< when the websocket session ends */ + + LWS_CALLBACK_SERVER_WRITEABLE = 11, + /**< See LWS_CALLBACK_CLIENT_WRITEABLE */ + + LWS_CALLBACK_RECEIVE = 6, + /**< data has appeared for this server endpoint from a + * remote client, it can be found at *in and is + * len bytes long */ + + LWS_CALLBACK_RECEIVE_PONG = 7, + /**< servers receive PONG packets with this callback reason */ + + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE = 38, + /**< The peer has sent an unsolicited Close WS packet. in and + * len are the optional close code (first 2 bytes, network + * order) and the optional additional information which is not + * defined in the standard, and may be a string or non human-readable + * data. + * If you return 0 lws will echo the close and then close the + * connection. If you return nonzero lws will just close the + * connection. */ + + LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION = 20, + /**< called when the handshake has + * been received and parsed from the client, but the response is + * not sent yet. Return non-zero to disallow the connection. + * user is a pointer to the connection user space allocation, + * in is the requested protocol name + * In your handler you can use the public APIs + * lws_hdr_total_length() / lws_hdr_copy() to access all of the + * headers using the header enums lws_token_indexes from + * libwebsockets.h to check for and read the supported header + * presence and content before deciding to allow the handshake + * to proceed or to kill the connection. */ + + LWS_CALLBACK_CONFIRM_EXTENSION_OKAY = 25, + /**< When the server handshake code + * sees that it does support a requested extension, before + * accepting the extension by additing to the list sent back to + * the client it gives this callback just to check that it's okay + * to use that extension. It calls back to the requested protocol + * and with in being the extension name, len is 0 and user is + * valid. Note though at this time the ESTABLISHED callback hasn't + * happened yet so if you initialize user content there, user + * content during this callback might not be useful for anything. */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to Websocket Client ----- + */ + LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 1, /**< the request client connection has been unable to complete a * handshake with the remote server. If in is non-NULL, you can @@ -962,6 +1303,7 @@ enum lws_callback_reasons { * "HS: SO_SNDBUF failed" * "HS: Rejected at CLIENT_ESTABLISHED" */ + LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH = 2, /**< this is the last chance for the client user code to examine the * http headers and decide to reject the connection. If the @@ -969,131 +1311,14 @@ enum lws_callback_reasons { * client (url, etc) it needs to copy it out at * this point since it will be destroyed before * the CLIENT_ESTABLISHED call */ + LWS_CALLBACK_CLIENT_ESTABLISHED = 3, - /**< after your client connection completed - * a handshake with the remote server */ - LWS_CALLBACK_CLOSED = 4, - /**< when the websocket session ends */ - LWS_CALLBACK_CLOSED_HTTP = 5, - /**< when a HTTP (non-websocket) session ends */ - LWS_CALLBACK_RECEIVE = 6, - /**< data has appeared for this server endpoint from a - * remote client, it can be found at *in and is - * len bytes long */ - LWS_CALLBACK_RECEIVE_PONG = 7, - /**< servers receive PONG packets with this callback reason */ - LWS_CALLBACK_CLIENT_RECEIVE = 8, - /**< data has appeared from the server for the client connection, it - * can be found at *in and is len bytes long */ - LWS_CALLBACK_CLIENT_RECEIVE_PONG = 9, - /**< clients receive PONG packets with this callback reason */ - LWS_CALLBACK_CLIENT_WRITEABLE = 10, - /**< If you call lws_callback_on_writable() on a connection, you will - * get one of these callbacks coming when the connection socket - * is able to accept another write packet without blocking. - * If it already was able to take another packet without blocking, - * you'll get this callback at the next call to the service loop - * function. Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE - * and servers get LWS_CALLBACK_SERVER_WRITEABLE. */ - LWS_CALLBACK_SERVER_WRITEABLE = 11, - /**< See LWS_CALLBACK_CLIENT_WRITEABLE */ - LWS_CALLBACK_HTTP = 12, - /**< an http request has come from a client that is not - * asking to upgrade the connection to a websocket - * one. This is a chance to serve http content, - * for example, to send a script to the client - * which will then open the websockets connection. - * in points to the URI path requested and - * lws_serve_http_file() makes it very - * simple to send back a file to the client. - * Normally after sending the file you are done - * with the http connection, since the rest of the - * activity will come by websockets from the script - * that was delivered by http, so you will want to - * return 1; to close and free up the connection. */ - LWS_CALLBACK_HTTP_BODY = 13, - /**< the next len bytes data from the http - * request body HTTP connection is now available in in. */ - LWS_CALLBACK_HTTP_BODY_COMPLETION = 14, - /**< the expected amount of http request body has been delivered */ - LWS_CALLBACK_HTTP_FILE_COMPLETION = 15, - /**< a file requested to be sent down http link has completed. */ - LWS_CALLBACK_HTTP_WRITEABLE = 16, - /**< you can write more down the http protocol link now. */ - LWS_CALLBACK_FILTER_NETWORK_CONNECTION = 17, - /**< called when a client connects to - * the server at network level; the connection is accepted but then - * passed to this callback to decide whether to hang up immediately - * or not, based on the client IP. in contains the connection - * socket's descriptor. Since the client connection information is - * not available yet, wsi still pointing to the main server socket. - * Return non-zero to terminate the connection before sending or - * receiving anything. Because this happens immediately after the - * network connection from the client, there's no websocket protocol - * selected yet so this callback is issued only to protocol 0. */ - LWS_CALLBACK_FILTER_HTTP_CONNECTION = 18, - /**< called when the request has - * been received and parsed from the client, but the response is - * not sent yet. Return non-zero to disallow the connection. - * user is a pointer to the connection user space allocation, - * in is the URI, eg, "/" - * In your handler you can use the public APIs - * lws_hdr_total_length() / lws_hdr_copy() to access all of the - * headers using the header enums lws_token_indexes from - * libwebsockets.h to check for and read the supported header - * presence and content before deciding to allow the http - * connection to proceed or to kill the connection. */ - LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED = 19, - /**< A new client just had - * been connected, accepted, and instantiated into the pool. This - * callback allows setting any relevant property to it. Because this - * happens immediately after the instantiation of a new client, - * there's no websocket protocol selected yet so this callback is - * issued only to protocol 0. Only wsi is defined, pointing to the - * new client, and the return value is ignored. */ - LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION = 20, - /**< called when the handshake has - * been received and parsed from the client, but the response is - * not sent yet. Return non-zero to disallow the connection. - * user is a pointer to the connection user space allocation, - * in is the requested protocol name - * In your handler you can use the public APIs - * lws_hdr_total_length() / lws_hdr_copy() to access all of the - * headers using the header enums lws_token_indexes from - * libwebsockets.h to check for and read the supported header - * presence and content before deciding to allow the handshake - * to proceed or to kill the connection. */ - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS = 21, - /**< if configured for - * including OpenSSL support, this callback allows your user code - * to perform extra SSL_CTX_load_verify_locations() or similar - * calls to direct OpenSSL where to find certificates the client - * can use to confirm the remote server identity. user is the - * OpenSSL SSL_CTX* */ - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS = 22, - /**< if configured for - * including OpenSSL support, this callback allows your user code - * to load extra certificates into the server which allow it to - * verify the validity of certificates returned by clients. user - * is the server's OpenSSL SSL_CTX* */ - LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION = 23, - /**< if the libwebsockets vhost was created with the option - * LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT, then this - * callback is generated during OpenSSL verification of the cert - * sent from the client. It is sent to protocol[0] callback as - * no protocol has been negotiated on the connection yet. - * Notice that the libwebsockets context and wsi are both NULL - * during this callback. See - * http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html - * to understand more detail about the OpenSSL callback that - * generates this libwebsockets callback and the meanings of the - * arguments passed. In this callback, user is the x509_ctx, - * in is the ssl pointer and len is preverify_ok - * Notice that this callback maintains libwebsocket return - * conventions, return 0 to mean the cert is OK or 1 to fail it. - * This also means that if you don't handle this callback then - * the default callback action of returning 0 allows the client - * certificates. */ + /**< after your client connection completed the websocket upgrade + * handshake with the remote server */ + + LWS_CALLBACK_CLIENT_CLOSED = 75, + /**< when a client websocket session ends */ + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER = 24, /**< this callback happens * when a client handshake is being compiled. user is NULL, @@ -1117,19 +1342,30 @@ enum lws_callback_reasons { * optional, if you don't handle it everything is fine. * * Notice the callback is coming to protocols[0] all the time, - * because there is no specific protocol negotiated yet. */ - LWS_CALLBACK_CONFIRM_EXTENSION_OKAY = 25, - /**< When the server handshake code - * sees that it does support a requested extension, before - * accepting the extension by additing to the list sent back to - * the client it gives this callback just to check that it's okay - * to use that extension. It calls back to the requested protocol - * and with in being the extension name, len is 0 and user is - * valid. Note though at this time the ESTABLISHED callback hasn't - * happened yet so if you initialize user content there, user - * content during this callback might not be useful for anything. */ + * because there is no specific protocol negotiated yet. + * + * See LWS_CALLBACK_ADD_HEADERS for adding headers to server + * transactions. + */ + + LWS_CALLBACK_CLIENT_RECEIVE = 8, + /**< data has appeared from the server for the client connection, it + * can be found at *in and is len bytes long */ + + LWS_CALLBACK_CLIENT_RECEIVE_PONG = 9, + /**< clients receive PONG packets with this callback reason */ + + LWS_CALLBACK_CLIENT_WRITEABLE = 10, + /**< If you call lws_callback_on_writable() on a connection, you will + * get one of these callbacks coming when the connection socket + * is able to accept another write packet without blocking. + * If it already was able to take another packet without blocking, + * you'll get this callback at the next call to the service loop + * function. Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE + * and servers get LWS_CALLBACK_SERVER_WRITEABLE. */ + LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED = 26, - /**< When a client + /**< When a ws client * connection is being prepared to start a handshake to a server, * each supported extension is checked with protocols[0] callback * with this reason, giving the user code a chance to suppress the @@ -1137,18 +1373,32 @@ enum lws_callback_reasons { * unhandled, by default 0 will be returned and the extension * support included in the header to the server. Notice this * callback comes to protocols[0]. */ - LWS_CALLBACK_PROTOCOL_INIT = 27, - /**< One-time call per protocol, per-vhost using it, so it can - * do initial setup / allocations etc */ - LWS_CALLBACK_PROTOCOL_DESTROY = 28, - /**< One-time call per protocol, per-vhost using it, indicating - * this protocol won't get used at all after this callback, the - * vhost is getting destroyed. Take the opportunity to - * deallocate everything that was allocated by the protocol. */ - LWS_CALLBACK_WSI_CREATE = 29, - /**< outermost (earliest) wsi create notification to protocols[0] */ - LWS_CALLBACK_WSI_DESTROY = 30, - /**< outermost (latest) wsi destroy notification to protocols[0] */ + + LWS_CALLBACK_WS_EXT_DEFAULTS = 39, + /**< Gives client connections an opportunity to adjust negotiated + * extension defaults. `user` is the extension name that was + * negotiated (eg, "permessage-deflate"). `in` points to a + * buffer and `len` is the buffer size. The user callback can + * set the buffer to a string describing options the extension + * should parse. Or just ignore for defaults. */ + + + LWS_CALLBACK_FILTER_NETWORK_CONNECTION = 17, + /**< called when a client connects to + * the server at network level; the connection is accepted but then + * passed to this callback to decide whether to hang up immediately + * or not, based on the client IP. in contains the connection + * socket's descriptor. Since the client connection information is + * not available yet, wsi still pointing to the main server socket. + * Return non-zero to terminate the connection before sending or + * receiving anything. Because this happens immediately after the + * network connection from the client, there's no websocket protocol + * selected yet so this callback is issued only to protocol 0. */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to external poll loop integration ----- + */ + LWS_CALLBACK_GET_THREAD_ID = 31, /**< lws can accept callback when writable requests from other * threads, if you implement this callback and return an opaque @@ -1171,12 +1421,14 @@ enum lws_callback_reasons { * * If you are using the internal lws polling / event loop * you can just ignore these callbacks. */ + LWS_CALLBACK_DEL_POLL_FD = 33, /**< This callback happens when a socket descriptor * needs to be removed from an external polling array. in is * again the struct lws_pollargs containing the fd member * to be removed. If you are using the internal polling * loop, you can just ignore it. */ + LWS_CALLBACK_CHANGE_MODE_POLL_FD = 34, /**< This callback happens when lws wants to modify the events for * a connection. @@ -1185,6 +1437,7 @@ enum lws_callback_reasons { * the prev_events member. * If you are using the internal polling loop, you can just ignore * it. */ + LWS_CALLBACK_LOCK_POLL = 35, /**< These allow the external poll changes driven * by lws to participate in an external thread locking @@ -1197,135 +1450,46 @@ enum lws_callback_reasons { * len == 1 allows external threads to be synchronized against * wsi lifecycle changes if it acquires the same lock for the * duration of wsi dereference from the other thread context. */ + LWS_CALLBACK_UNLOCK_POLL = 36, /**< See LWS_CALLBACK_LOCK_POLL, ignore if using lws internal poll */ - LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY = 37, - /**< if configured for including OpenSSL support but no private key - * file has been specified (ssl_private_key_filepath is NULL), this is - * called to allow the user to set the private key directly via - * libopenssl and perform further operations if required; this might be - * useful in situations where the private key is not directly accessible - * by the OS, for example if it is stored on a smartcard. - * user is the server's OpenSSL SSL_CTX* */ - LWS_CALLBACK_WS_PEER_INITIATED_CLOSE = 38, - /**< The peer has sent an unsolicited Close WS packet. in and - * len are the optional close code (first 2 bytes, network - * order) and the optional additional information which is not - * defined in the standard, and may be a string or non-human- readable data. - * If you return 0 lws will echo the close and then close the - * connection. If you return nonzero lws will just close the - * connection. */ - - LWS_CALLBACK_WS_EXT_DEFAULTS = 39, - /**< Gives client connections an opportunity to adjust negotiated - * extension defaults. `user` is the extension name that was - * negotiated (eg, "permessage-deflate"). `in` points to a - * buffer and `len` is the buffer size. The user callback can - * set the buffer to a string describing options the extension - * should parse. Or just ignore for defaults. */ + /* --------------------------------------------------------------------- + * ----- Callbacks related to CGI serving ----- + */ LWS_CALLBACK_CGI = 40, /**< CGI: CGI IO events on stdin / out / err are sent here on * protocols[0]. The provided `lws_callback_http_dummy()` * handles this and the callback should be directed there if * you use CGI. */ + LWS_CALLBACK_CGI_TERMINATED = 41, /**< CGI: The related CGI process ended, this is called before * the wsi is closed. Used to, eg, terminate chunking. * The provided `lws_callback_http_dummy()` * handles this and the callback should be directed there if * you use CGI. The child PID that terminated is in len. */ + LWS_CALLBACK_CGI_STDIN_DATA = 42, /**< CGI: Data is, to be sent to the CGI process stdin, eg from * a POST body. The provided `lws_callback_http_dummy()` * handles this and the callback should be directed there if * you use CGI. */ + LWS_CALLBACK_CGI_STDIN_COMPLETED = 43, /**< CGI: no more stdin is coming. The provided * `lws_callback_http_dummy()` handles this and the callback * should be directed there if you use CGI. */ - LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP = 44, - /**< The HTTP client connection has succeeded, and is now - * connected to the server */ - LWS_CALLBACK_CLOSED_CLIENT_HTTP = 45, - /**< The HTTP client connection is closing */ - LWS_CALLBACK_RECEIVE_CLIENT_HTTP = 46, - /**< This simply indicates data was received on the HTTP client - * connection. It does NOT drain or provide the data. - * This exists to neatly allow a proxying type situation, - * where this incoming data will go out on another connection. - * If the outgoing connection stalls, we should stall processing - * the incoming data. So a handler for this in that case should - * simply set a flag to indicate there is incoming data ready - * and ask for a writeable callback on the outgoing connection. - * In the writable callback he can check the flag and then get - * and drain the waiting incoming data using lws_http_client_read(). - * This will use callbacks to LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ - * to get and drain the incoming data, where it should be sent - * back out on the outgoing connection. */ - LWS_CALLBACK_COMPLETED_CLIENT_HTTP = 47, - /**< The client transaction completed... at the moment this - * is the same as closing since transaction pipelining on - * client side is not yet supported. */ - LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ = 48, - /**< This is generated by lws_http_client_read() used to drain - * incoming data. In the case the incoming data was chunked, - * it will be split into multiple smaller callbacks for each - * chunk block, removing the chunk headers. If not chunked, - * it will appear all in one callback. */ - LWS_CALLBACK_HTTP_BIND_PROTOCOL = 49, - /**< By default, all HTTP handling is done in protocols[0]. - * However you can bind different protocols (by name) to - * different parts of the URL space using callback mounts. This - * callback occurs in the new protocol when a wsi is bound - * to that protocol. Any protocol allocation related to the - * http transaction processing should be created then. - * These specific callbacks are necessary because with HTTP/1.1, - * a single connection may perform at series of different - * transactions at different URLs, thus the lifetime of the - * protocol bind is just for one transaction, not connection. */ - LWS_CALLBACK_HTTP_DROP_PROTOCOL = 50, - /**< This is called when a transaction is unbound from a protocol. - * It indicates the connection completed its transaction and may - * do something different now. Any protocol allocation related - * to the http transaction processing should be destroyed. */ - LWS_CALLBACK_CHECK_ACCESS_RIGHTS = 51, - /**< This gives the user code a chance to forbid an http access. - * `in` points to a `struct lws_process_html_args`, which - * describes the URL, and a bit mask describing the type of - * authentication required. If the callback returns nonzero, - * the transaction ends with HTTP_STATUS_UNAUTHORIZED. */ - LWS_CALLBACK_PROCESS_HTML = 52, - /**< This gives your user code a chance to mangle outgoing - * HTML. `in` points to a `struct lws_process_html_args` - * which describes the buffer containing outgoing HTML. - * The buffer may grow up to `.max_len` (currently +128 - * bytes per buffer). - * */ - LWS_CALLBACK_ADD_HEADERS = 53, - /**< This gives your user code a chance to add headers to a - * transaction bound to your protocol. `in` points to a - * `struct lws_process_html_args` describing a buffer and length - * you can add headers into using the normal lws apis. - * - * Only `args->p` and `args->len` are valid, and `args->p` should - * be moved on by the amount of bytes written, if any. Eg - * - * case LWS_CALLBACK_ADD_HEADERS: - * - * struct lws_process_html_args *args = - * (struct lws_process_html_args *)in; - * - * if (lws_add_http_header_by_name(wsi, - * (unsigned char *)"set-cookie:", - * (unsigned char *)cookie, cookie_len, - * (unsigned char **)&args->p, - * (unsigned char *)args->p + args->max_len)) - * return 1; - * - * break; + + LWS_CALLBACK_CGI_PROCESS_ATTACH = 70, + /**< CGI: Sent when the CGI process is spawned for the wsi. The + * len parameter is the PID of the child process */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to Generic Sessions ----- */ + LWS_CALLBACK_SESSION_INFO = 54, /**< This is only generated by user code using generic sessions. * It's used to get a `struct lws_session_info` filled in by @@ -1335,85 +1499,103 @@ enum lws_callback_reasons { LWS_CALLBACK_GS_EVENT = 55, /**< Indicates an event happened to the Generic Sessions session. * `in` contains a `struct lws_gs_event_args` describing the event. */ + LWS_CALLBACK_HTTP_PMO = 56, /**< per-mount options for this connection, called before * the normal LWS_CALLBACK_HTTP when the mount has per-mount * options. */ - LWS_CALLBACK_CLIENT_HTTP_WRITEABLE = 57, - /**< when doing an HTTP type client connection, you can call - * lws_client_http_body_pending(wsi, 1) from - * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to get these callbacks - * sending the HTTP headers. - * - * From this callback, when you have sent everything, you should let - * lws know by calling lws_client_http_body_pending(wsi, 0) - */ - LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION = 58, - /**< Similar to LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION - * this callback is called during OpenSSL verification of the cert - * sent from the server to the client. It is sent to protocol[0] - * callback as no protocol has been negotiated on the connection yet. - * Notice that the wsi is set because lws_client_connect_via_info was - * successful. - * - * See http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html - * to understand more detail about the OpenSSL callback that - * generates this libwebsockets callback and the meanings of the - * arguments passed. In this callback, user is the x509_ctx, - * in is the ssl pointer and len is preverify_ok. - * - * THIS IS NOT RECOMMENDED BUT if a cert validation error shall be - * overruled and cert shall be accepted as ok, - * X509_STORE_CTX_set_error((X509_STORE_CTX*)user, X509_V_OK); must be - * called and return value must be 0 to mean the cert is OK; - * returning 1 will fail the cert in any case. - * - * This also means that if you don't handle this callback then - * the default callback action of returning 0 will not accept the - * certificate in case of a validation error decided by the SSL lib. - * - * This is expected and secure behaviour when validating certificates. - * - * Note: LCCSCF_ALLOW_SELFSIGNED and - * LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK still work without this - * callback being implemented. + + /* --------------------------------------------------------------------- + * ----- Callbacks related to RAW sockets ----- */ + LWS_CALLBACK_RAW_RX = 59, /**< RAW mode connection RX */ + LWS_CALLBACK_RAW_CLOSE = 60, /**< RAW mode connection is closing */ + LWS_CALLBACK_RAW_WRITEABLE = 61, /**< RAW mode connection may be written */ + LWS_CALLBACK_RAW_ADOPT = 62, /**< RAW mode connection was adopted (equivalent to 'wsi created') */ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to RAW file handles ----- + */ + LWS_CALLBACK_RAW_ADOPT_FILE = 63, /**< RAW mode file was adopted (equivalent to 'wsi created') */ + LWS_CALLBACK_RAW_RX_FILE = 64, - /**< RAW mode file has something to read */ + /**< This is the indication the RAW mode file has something to read. + * This doesn't actually do the read of the file and len is always + * 0... your code should do the read having been informed there is + * something to read now. */ + LWS_CALLBACK_RAW_WRITEABLE_FILE = 65, /**< RAW mode file is writeable */ + LWS_CALLBACK_RAW_CLOSE_FILE = 66, /**< RAW mode wsi that adopted a file is closing */ - LWS_CALLBACK_SSL_INFO = 67, - /**< SSL connections only. An event you registered an - * interest in at the vhost has occurred on a connection - * using the vhost. in is a pointer to a - * struct lws_ssl_info containing information about the - * event*/ + + /* --------------------------------------------------------------------- + * ----- Callbacks related to generic wsi events ----- + */ + + LWS_CALLBACK_TIMER = 73, + /**< When the time elapsed after a call to + * lws_set_timer_usecs(wsi, usecs) is up, the wsi will get one of + * these callbacks. The deadline can be continuously extended into the + * future by later calls to lws_set_timer_usecs() before the deadline + * expires, or cancelled by lws_set_timer_usecs(wsi, -1); + * See the note on lws_set_timer_usecs() about which event loops are + * supported. */ + + LWS_CALLBACK_EVENT_WAIT_CANCELLED = 71, + /**< This is sent to every protocol of every vhost in response + * to lws_cancel_service() or lws_cancel_service_pt(). This + * callback is serialized in the lws event loop normally, even + * if the lws_cancel_service[_pt]() call was from a different + * thread. */ + + LWS_CALLBACK_CHILD_CLOSING = 69, + /**< Sent to parent to notify them a child is closing / being + * destroyed. in is the child wsi. + */ + LWS_CALLBACK_CHILD_WRITE_VIA_PARENT = 68, /**< Child has been marked with parent_carries_io attribute, so * lws_write directs the to this callback at the parent, * in is a struct lws_write_passthru containing the args * the lws_write() was called with. */ - LWS_CALLBACK_CHILD_CLOSING = 69, - /**< Sent to parent to notify them a child is closing / being - * destroyed. in is the child wsi. + + /* --------------------------------------------------------------------- + * ----- Callbacks related to TLS certificate management ----- */ - LWS_CALLBACK_CGI_PROCESS_ATTACH = 70, - /**< CGI: Sent when the CGI process is spawned for the wsi. The - * len parameter is the PID of the child process */ + + LWS_CALLBACK_VHOST_CERT_AGING = 72, + /**< When a vhost TLS cert has its expiry checked, this callback + * is broadcast to every protocol of every vhost in case the + * protocol wants to take some action with this information. + * \p in is a pointer to a struct lws_acme_cert_aging_args, + * and \p len is the number of days left before it expires, as + * a (ssize_t). In the struct lws_acme_cert_aging_args, vh + * points to the vhost the cert aging information applies to, + * and element_overrides[] is an optional way to update information + * from the pvos... NULL in an index means use the information from + * from the pvo for the cert renewal, non-NULL in the array index + * means use that pointer instead for the index. */ + + LWS_CALLBACK_VHOST_CERT_UPDATE = 74, + /**< When a vhost TLS cert is being updated, progress is + * reported to the vhost in question here, including completion + * and failure. in points to optional JSON, and len represents the + * connection state using enum lws_cert_update_state */ + /****** add new things just above ---^ ******/ @@ -1448,6 +1630,8 @@ lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, #define LWS_CB_REASON_AUX_BF__CGI_HEADERS 8 ///@} +struct lws_vhost; + /*! \defgroup generic hash * ## Generic Hash related functions * @@ -1459,7 +1643,7 @@ lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, */ ///@{ -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) #if defined(LWS_WITH_MBEDTLS) #include <mbedtls/sha1.h> @@ -1467,9 +1651,20 @@ lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, #include <mbedtls/sha512.h> #endif -#define LWS_GENHASH_TYPE_SHA1 0 -#define LWS_GENHASH_TYPE_SHA256 1 -#define LWS_GENHASH_TYPE_SHA512 2 +enum lws_genhash_types { + LWS_GENHASH_TYPE_SHA1, + LWS_GENHASH_TYPE_SHA256, + LWS_GENHASH_TYPE_SHA384, + LWS_GENHASH_TYPE_SHA512, +}; + +enum lws_genhmac_types { + LWS_GENHMAC_TYPE_SHA256, + LWS_GENHMAC_TYPE_SHA384, + LWS_GENHMAC_TYPE_SHA512, +}; + +#define LWS_GENHASH_LARGEST 64 struct lws_genhash_ctx { uint8_t type; @@ -1477,7 +1672,8 @@ struct lws_genhash_ctx { union { mbedtls_sha1_context sha1; mbedtls_sha256_context sha256; - mbedtls_sha512_context sha512; + mbedtls_sha512_context sha512; /* 384 also uses this */ + const mbedtls_md_info_t *hmac; } u; #else const EVP_MD *evp_type; @@ -1485,6 +1681,17 @@ struct lws_genhash_ctx { #endif }; +struct lws_genhmac_ctx { + uint8_t type; +#if defined(LWS_WITH_MBEDTLS) + const mbedtls_md_info_t *hmac; + mbedtls_md_context_t ctx; +#else + const EVP_MD *evp_type; + EVP_MD_CTX *ctx; +#endif +}; + /** lws_genhash_size() - get hash size in bytes * * \param type: one of LWS_GENHASH_TYPE_... @@ -1492,7 +1699,16 @@ struct lws_genhash_ctx { * Returns number of bytes in this type of hash */ LWS_VISIBLE LWS_EXTERN size_t LWS_WARN_UNUSED_RESULT -lws_genhash_size(int type); +lws_genhash_size(enum lws_genhash_types type); + +/** lws_genhmac_size() - get hash size in bytes + * + * \param type: one of LWS_GENHASH_TYPE_... + * + * Returns number of bytes in this type of hmac + */ +LWS_VISIBLE LWS_EXTERN size_t LWS_WARN_UNUSED_RESULT +lws_genhmac_size(enum lws_genhmac_types type); /** lws_genhash_init() - prepare your struct lws_genhash_ctx for use * @@ -1502,7 +1718,7 @@ lws_genhash_size(int type); * Initializes the hash context for the type you requested */ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_genhash_init(struct lws_genhash_ctx *ctx, int type); +lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type); /** lws_genhash_update() - digest len bytes of the buffer starting at in * @@ -1529,10 +1745,386 @@ lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len); LWS_VISIBLE LWS_EXTERN int lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result); +/** lws_genhmac_init() - prepare your struct lws_genhmac_ctx for use + * + * \param ctx: your struct lws_genhmac_ctx + * \param type: one of LWS_GENHMAC_TYPE_... + * \param key: pointer to the start of the HMAC key + * \param key_len: length of the HMAC key + * + * Initializes the hash context for the type you requested + * + * If the return is nonzero, it failed and there is nothing needing to be + * destroyed. + */ +int +lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, + const uint8_t *key, size_t key_len); + +/** lws_genhmac_update() - digest len bytes of the buffer starting at in + * + * \param ctx: your struct lws_genhmac_ctx + * \param in: start of the bytes to digest + * \param len: count of bytes to digest + * + * Updates the state of your hash context to reflect digesting len bytes from in + * + * If the return is nonzero, it failed and needs destroying. + */ +int +lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len); + +/** lws_genhmac_destroy() - copy out the result digest and destroy the ctx + * + * \param ctx: your struct lws_genhmac_ctx + * \param result: NULL, or where to copy the result hash + * + * Finalizes the hash and copies out the digest. Destroys any allocations such + * that ctx can safely go out of scope after calling this. + * + * NULL result is supported so that you can destroy the ctx cleanly on error + * conditions, where there is no valid result. + */ +int +lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result); +///@} + +/*! \defgroup generic RSA + * ## Generic RSA related functions + * + * Lws provides generic RSA functions that abstract the ones + * provided by whatever OpenSSL library you are linking against. + * + * It lets you use the same code if you build against mbedtls or OpenSSL + * for example. + */ +///@{ + +enum enum_jwk_tok { + JWK_KEY_E, + JWK_KEY_N, + JWK_KEY_D, + JWK_KEY_P, + JWK_KEY_Q, + JWK_KEY_DP, + JWK_KEY_DQ, + JWK_KEY_QI, + JWK_KTY, /* also serves as count of real elements */ + JWK_KEY, +}; + +#define LWS_COUNT_RSA_ELEMENTS JWK_KTY + +struct lws_genrsa_ctx { +#if defined(LWS_WITH_MBEDTLS) + mbedtls_rsa_context *ctx; +#else + BIGNUM *bn[LWS_COUNT_RSA_ELEMENTS]; + RSA *rsa; #endif +}; + +struct lws_genrsa_element { + uint8_t *buf; + uint16_t len; +}; + +struct lws_genrsa_elements { + struct lws_genrsa_element e[LWS_COUNT_RSA_ELEMENTS]; +}; + +/** lws_jwk_destroy_genrsa_elements() - Free allocations in genrsa_elements + * + * \param el: your struct lws_genrsa_elements + * + * This is a helper for user code making use of struct lws_genrsa_elements + * where the elements are allocated on the heap, it frees any non-NULL + * buf element and sets the buf to NULL. + * + * NB: lws_genrsa_public_... apis do not need this as they take care of the key + * creation and destruction themselves. + */ +LWS_VISIBLE LWS_EXTERN void +lws_jwk_destroy_genrsa_elements(struct lws_genrsa_elements *el); + +/** lws_genrsa_public_decrypt_create() - Create RSA public decrypt context + * + * \param ctx: your struct lws_genrsa_ctx + * \param el: struct prepared with key element data + * + * Creates an RSA context with a public key associated with it, formed from + * the key elements in \p el. + * + * Returns 0 for OK or nonzero for error. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el); + +/** lws_genrsa_new_keypair() - Create new RSA keypair + * + * \param context: your struct lws_context (may be used for RNG) + * \param ctx: your struct lws_genrsa_ctx + * \param el: struct to get the new key element data allocated into it + * \param bits: key size, eg, 4096 + * + * Creates a new RSA context and generates a new keypair into it, with \p bits + * bits. + * + * Returns 0 for OK or nonzero for error. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, + struct lws_genrsa_elements *el, int bits); + +/** lws_genrsa_public_decrypt() - Perform RSA public decryption + * + * \param ctx: your struct lws_genrsa_ctx + * \param in: encrypted input + * \param in_len: length of encrypted input + * \param out: decrypted output + * \param out_max: size of output buffer + * + * Performs the decryption. + * + * Returns <0 for error, or length of decrypted data. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out, size_t out_max); + +/** lws_genrsa_public_verify() - Perform RSA public verification + * + * \param ctx: your struct lws_genrsa_ctx + * \param in: unencrypted payload (usually a recomputed hash) + * \param hash_type: one of LWS_GENHASH_TYPE_ + * \param sig: pointer to the signature we received with the payload + * \param sig_len: length of the signature we are checking in bytes + * + * Returns <0 for error, or 0 if signature matches the payload + key. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_public_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, + const uint8_t *sig, size_t sig_len); + +/** lws_genrsa_public_sign() - Create RSA signature + * + * \param ctx: your struct lws_genrsa_ctx + * \param in: precomputed hash + * \param hash_type: one of LWS_GENHASH_TYPE_ + * \param sig: pointer to buffer to take signature + * \param sig_len: length of the buffer (must be >= length of key N) + * + * Returns <0 for error, or 0 for success. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_public_sign(struct lws_genrsa_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, uint8_t *sig, + size_t sig_len); + +/** lws_genrsa_public_decrypt_destroy() - Destroy RSA public decrypt context + * + * \param ctx: your struct lws_genrsa_ctx + * + * Destroys any allocations related to \p ctx. + * + * This and related APIs operate identically with OpenSSL or mbedTLS backends. + */ +LWS_VISIBLE LWS_EXTERN void +lws_genrsa_destroy(struct lws_genrsa_ctx *ctx); +/** lws_genrsa_render_pkey_asn1() - Exports public or private key to ASN1/DER + * + * \param ctx: your struct lws_genrsa_ctx + * \param _private: 0 = public part only, 1 = all parts of the key + * \param pkey_asn1: pointer to buffer to take the ASN1 + * \param pkey_asn1_len: max size of the pkey_asn1_len + * + * Returns length of pkey_asn1 written, or -1 for error. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private, + uint8_t *pkey_asn1, size_t pkey_asn1_len); ///@} +/*! \defgroup jwk JSON Web Keys + * ## JSON Web Keys API + * + * Lws provides an API to parse JSON Web Keys into a struct lws_genrsa_elements. + * + * "oct" and "RSA" type keys are supported. For "oct" keys, they are held in + * the "e" member of the struct lws_genrsa_elements. + * + * Keys elements are allocated on the heap. You must destroy the allocations + * in the struct lws_genrsa_elements by calling + * lws_jwk_destroy_genrsa_elements() when you are finished with it. + */ +///@{ + +struct lws_jwk { + char keytype[5]; /**< "oct" or "RSA" */ + struct lws_genrsa_elements el; /**< OCTet key is in el.e */ +}; + +/** lws_jwk_import() - Create a JSON Web key from the textual representation + * + * \param s: the JWK object to create + * \param in: a single JWK JSON stanza in utf-8 + * \param len: the length of the JWK JSON stanza in bytes + * + * Creates an lws_jwk struct filled with data from the JSON representation. + * "oct" and "rsa" key types are supported. + * + * For "oct" type keys, it is loaded into el.e. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jwk_import(struct lws_jwk *s, const char *in, size_t len); + +/** lws_jwk_destroy() - Destroy a JSON Web key + * + * \param s: the JWK object to destroy + * + * All allocations in the lws_jwk are destroyed + */ +LWS_VISIBLE LWS_EXTERN void +lws_jwk_destroy(struct lws_jwk *s); + +/** lws_jwk_export() - Export a JSON Web key to a textual representation + * + * \param s: the JWK object to export + * \param _private: 0 = just export public parts, 1 = export everything + * \param p: the buffer to write the exported JWK to + * \param len: the length of the buffer \p p in bytes + * + * Returns length of the used part of the buffer if OK, or -1 for error. + * + * Serializes the content of the JWK into a char buffer. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jwk_export(struct lws_jwk *s, int _private, char *p, size_t len); + +/** lws_jwk_load() - Import a JSON Web key from a file + * + * \param s: the JWK object to load into + * \param filename: filename to load from + * + * Returns 0 for OK or -1 for failure + */ +LWS_VISIBLE int +lws_jwk_load(struct lws_jwk *s, const char *filename); + +/** lws_jwk_save() - Export a JSON Web key to a file + * + * \param s: the JWK object to save from + * \param filename: filename to save to + * + * Returns 0 for OK or -1 for failure + */ +LWS_VISIBLE int +lws_jwk_save(struct lws_jwk *s, const char *filename); + +/** lws_jwk_rfc7638_fingerprint() - jwk to RFC7638 compliant fingerprint + * + * \param s: the JWK object to fingerprint + * \param digest32: buffer to take 32-byte digest + * + * Returns 0 for OK or -1 for failure + */ +LWS_VISIBLE int +lws_jwk_rfc7638_fingerprint(struct lws_jwk *s, char *digest32); +///@} + + +/*! \defgroup jws JSON Web Signature + * ## JSON Web Signature API + * + * Lws provides an API to check and create RFC7515 JSON Web Signatures + * + * SHA256/384/512 HMAC, and RSA 256/384/512 are supported. + * + * The API uses your TLS library crypto, but works exactly the same no matter + * what you TLS backend is. + */ +///@{ + +LWS_VISIBLE LWS_EXTERN int +lws_jws_confirm_sig(const char *in, size_t len, struct lws_jwk *jwk); + +/** + * lws_jws_sign_from_b64() - add b64 sig to b64 hdr + payload + * + * \param b64_hdr: protected header encoded in b64, may be NULL + * \param hdr_len: bytes in b64 coding of protected header + * \param b64_pay: payload encoded in b64 + * \param pay_len: bytes in b64 coding of payload + * \param b64_sig: buffer to write the b64 encoded signature into + * \param sig_len: max bytes we can write at b64_sig + * \param hash_type: one of LWS_GENHASH_TYPE_SHA[256|384|512] + * \param jwk: the struct lws_jwk containing the signing key + * + * This adds a b64-coded JWS signature of the b64-encoded protected header + * and b64-encoded payload, at \p b64_sig. The signature will be as large + * as the N element of the RSA key when the RSA key is used, eg, 512 bytes for + * a 4096-bit key, and then b64-encoding on top. + * + * In some special cases, there is only payload to sign and no header, in that + * case \p b64_hdr may be NULL, and only the payload will be hashed before + * signing. + * + * Returns the length of the encoded signature written to \p b64_sig, or -1. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jws_sign_from_b64(const char *b64_hdr, size_t hdr_len, const char *b64_pay, + size_t pay_len, char *b64_sig, size_t sig_len, + enum lws_genhash_types hash_type, struct lws_jwk *jwk); + +/** + * lws_jws_create_packet() - add b64 sig to b64 hdr + payload + * + * \param jwk: the struct lws_jwk containing the signing key + * \param payload: unencoded payload JSON + * \param len: length of unencoded payload JSON + * \param nonce: Nonse string to include in protected header + * \param out: buffer to take signed packet + * \param out_len: size of \p out buffer + * + * This creates a "flattened" JWS packet from the jwk and the plaintext + * payload, and signs it. The packet is written into \p out. + * + * This does the whole packet assembly and signing, calling through to + * lws_jws_sign_from_b64() as part of the process. + * + * Returns the length written to \p out, or -1. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jws_create_packet(struct lws_jwk *jwk, const char *payload, size_t len, + const char *nonce, char *out, size_t out_len); + +/** + * lws_jws_base64_enc() - encode input data into b64url data + * + * \param in: the incoming plaintext + * \param in_len: the length of the incoming plaintext in bytes + * \param out: the buffer to store the b64url encoded data to + * \param out_max: the length of \p out in bytes + * + * Returns either -1 if problems, or the number of bytes written to \p out. + */ +LWS_VISIBLE LWS_EXTERN int +lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max); +///@} +#endif + /*! \defgroup extensions Extension related functions * ##Extension releated functions * @@ -1548,27 +2140,10 @@ lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result); * add it at where specified so existing users are unaffected. */ enum lws_extension_callback_reasons { - LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT = 0, - LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT = 1, - LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT = 2, - LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT = 3, LWS_EXT_CB_CONSTRUCT = 4, LWS_EXT_CB_CLIENT_CONSTRUCT = 5, - LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE = 6, - LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION = 7, LWS_EXT_CB_DESTROY = 8, - LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING = 9, - LWS_EXT_CB_ANY_WSI_ESTABLISHED = 10, - LWS_EXT_CB_PACKET_RX_PREPARSE = 11, LWS_EXT_CB_PACKET_TX_PRESEND = 12, - LWS_EXT_CB_PACKET_TX_DO_SEND = 13, - LWS_EXT_CB_HANDSHAKE_REPLY_TX = 14, - LWS_EXT_CB_FLUSH_PENDING_TX = 15, - LWS_EXT_CB_EXTENDED_PAYLOAD_RX = 16, - LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION = 17, - LWS_EXT_CB_1HZ = 18, - LWS_EXT_CB_REQUEST_ON_WRITEABLE = 19, - LWS_EXT_CB_IS_WRITEABLE = 20, LWS_EXT_CB_PAYLOAD_TX = 21, LWS_EXT_CB_PAYLOAD_RX = 22, LWS_EXT_CB_OPTION_DEFAULT = 23, @@ -1646,18 +2221,6 @@ struct lws_ext_option_arg { * user data is deleted. This same callback is used whether you * are in client or server instantiation context. * - * LWS_EXT_CB_PACKET_RX_PREPARSE: when this extension was active on - * a connection, and a packet of data arrived at the connection, - * it is passed to this callback to give the extension a chance to - * change the data, eg, decompress it. user is pointing to the - * extension's private connection context data, in is pointing - * to an lws_tokens struct, it consists of a char * pointer called - * token, and an int called token_len. At entry, these are - * set to point to the received buffer and set to the content - * length. If the extension will grow the content, it should use - * a new buffer allocated in its private user context data and - * set the pointed-to lws_tokens members to point to its buffer. - * * LWS_EXT_CB_PACKET_TX_PRESEND: this works the same way as * LWS_EXT_CB_PACKET_RX_PREPARSE above, except it gives the * extension a chance to change websocket data just before it will @@ -1697,16 +2260,6 @@ LWS_VISIBLE LWS_EXTERN int lws_set_extension_option(struct lws *wsi, const char *ext_name, const char *opt_name, const char *opt_val); -#ifndef LWS_NO_EXTENSIONS -/* lws_get_internal_extensions() - DEPRECATED - * - * \Deprecated There is no longer a set internal extensions table. The table is provided - * by user code along with application-specific settings. See the test - * client and server for how to do. - */ -static LWS_INLINE LWS_WARN_DEPRECATED const struct lws_extension * -lws_get_internal_extensions(void) { return NULL; } - /** * lws_ext_parse_options() - deal with parsing negotiated extension options * @@ -1721,7 +2274,6 @@ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi, void *ext_user, const struct lws_ext_options *opts, const char *o, int len); -#endif /** lws_extension_callback_pm_deflate() - extension for RFC7692 * @@ -1785,8 +2337,8 @@ struct lws_protocols { * be able to consume it all without having to return to the event * loop. That is supported in lws. * - * If .tx_packet_size is 0, this also controls how much may be sent at once - * for backwards compatibility. + * If .tx_packet_size is 0, this also controls how much may be sent at + * once for backwards compatibility. */ unsigned int id; /**< ignored by lws, but useful to contain user information bound @@ -1811,8 +2363,6 @@ struct lws_protocols { * This is part of the ABI, don't needlessly break compatibility */ }; -struct lws_vhost; - /** * lws_vhost_name_to_protocol() - get vhost's protocol object from its name * @@ -1894,6 +2444,17 @@ lws_adjust_protocol_psds(struct lws *wsi, size_t new_size); LWS_VISIBLE LWS_EXTERN int lws_finalize_startup(struct lws_context *context); +/** + * lws_pvo_search() - helper to find a named pvo in a linked-list + * + * \param pvo: the first pvo in the linked-list + * \param name: the name of the pvo to return if found + * + * Returns NULL, or a pointer to the name pvo in the linked-list + */ +LWS_VISIBLE LWS_EXTERN const struct lws_protocol_vhost_options * +lws_pvo_search(const struct lws_protocol_vhost_options *pvo, const char *name); + LWS_VISIBLE LWS_EXTERN int lws_protocol_init(struct lws_context *context); @@ -2088,6 +2649,17 @@ enum lws_context_options { * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS callback, which * provides the vhost SSL_CTX * in the user parameter. */ + LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT = (1 << 25), + /**< (VH) You probably don't want this. It forces this vhost to not + * call LWS_CALLBACK_PROTOCOL_INIT on its protocols. It's used in the + * special case of a temporary vhost bound to a single protocol. + */ + LWS_SERVER_OPTION_IGNORE_MISSING_CERT = (1 << 26), + /**< (VH) Don't fail if the vhost TLS cert or key are missing, just + * continue. The vhost won't be able to serve anything, but if for + * example the ACME plugin was configured to fetch a cert, this lets + * you bootstrap your vhost from having no cert to start with. + */ /****** add new things just above ---^ ******/ }; @@ -2110,7 +2682,11 @@ struct lws_context_creation_info { /**< VHOST: Port to listen on. Use CONTEXT_PORT_NO_LISTEN to suppress * listening for a client. Use CONTEXT_PORT_NO_LISTEN_SERVER if you are * writing a server but you are using \ref sock-adopt instead of the - * built-in listener */ + * built-in listener. + * + * You can also set port to 0, in which case the kernel will pick + * a random port that is not already in use. You can find out what + * port the vhost is listening on using lws_get_vhost_listen_port() */ const char *iface; /**< VHOST: NULL to bind the listen socket to all interfaces, or the * interface name, eg, "eth2" @@ -2191,12 +2767,12 @@ struct lws_context_creation_info { int ka_interval; /**< CONTEXT: if ka_time was nonzero, how long to wait before each ka_probes * attempt */ -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) SSL_CTX *provided_client_ssl_ctx; /**< CONTEXT: If non-null, swap out libwebsockets ssl - * implementation for the one provided by provided_ssl_ctx. - * Libwebsockets no longer is responsible for freeing the context - * if this option is selected. */ + * implementation for the one provided by provided_ssl_ctx. + * Libwebsockets no longer is responsible for freeing the context + * if this option is selected. */ #else /* maintain structure layout either way */ void *provided_client_ssl_ctx; /**< dummy if ssl disabled */ #endif @@ -2207,9 +2783,10 @@ struct lws_context_creation_info { short max_http_header_pool; /**< CONTEXT: The max number of connections with http headers that * can be processed simultaneously (the corresponding memory is - * allocated for the lifetime of the context). If the pool is - * busy new incoming connections must wait for accept until one - * becomes free. */ + * allocated and deallocated dynamically as needed). If the pool is + * fully busy new incoming connections must wait for accept until one + * becomes free. 0 = allow as many ah as number of availble fds for + * the process */ unsigned int count_threads; /**< CONTEXT: how many contexts to create in an array, 0 = 1 */ @@ -2372,13 +2949,42 @@ struct lws_context_creation_info { * given here. */ uint32_t http2_settings[7]; - /**< CONTEXT: after context creation http2_settings[1] thru [6] have - * been set to the lws platform default values. - * VHOST: if http2_settings[0] is nonzero, the values given in + /**< VHOST: if http2_settings[0] is nonzero, the values given in * http2_settings[1]..[6] are used instead of the lws * platform default values. * Just leave all at 0 if you don't care. */ + const char *error_document_404; + /**< VHOST: If non-NULL, when asked to serve a non-existent file, + * lws attempts to server this url path instead. Eg, + * "/404.html" */ + const char *alpn; + /**< CONTEXT: If non-NULL, default list of advertised alpn, comma- + * separated + * + * VHOST: If non-NULL, per-vhost list of advertised alpn, comma- + * separated + */ + void **foreign_loops; + /**< CONTEXT: This is ignored if the context is not being started with + * an event loop, ie, .options has a flag like + * LWS_SERVER_OPTION_LIBUV. + * + * NULL indicates lws should start its own even loop for + * each service thread, and deal with closing the loops + * when the context is destroyed. + * + * Non-NULL means it points to an array of external + * ("foreign") event loops that are to be used in turn for + * each service thread. In the default case of 1 service + * thread, it can just point to one foreign event loop. + */ + void (*signal_cb)(void *event_lib_handle, int signum); + /**< CONTEXT: NULL: default signal handling. Otherwise this receives + * the signal handler callback. event_lib_handle is the + * native event library signal handle, eg uv_signal_t * + * for libuv. + */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility @@ -2387,8 +2993,14 @@ struct lws_context_creation_info { * members added above will see 0 (default) even if the app * was not built against the newer headers. */ + struct lws_context **pcontext; + /**< CONTEXT: if non-NULL, at the end of context destroy processing, + * the pointer pointed to by pcontext is written with NULL. You can + * use this to let foreign event loops know that lws context destruction + * is fully completed. + */ - void *_unused[8]; /**< dummy */ + void *_unused[4]; /**< dummy */ }; /** @@ -2426,7 +3038,8 @@ struct lws_context_creation_info { * one place; they're all handled in the user callback. */ LWS_VISIBLE LWS_EXTERN struct lws_context * -lws_create_context(struct lws_context_creation_info *info); +lws_create_context(const struct lws_context_creation_info *info); + /** * lws_context_destroy() - Destroy the websocket context @@ -2439,9 +3052,6 @@ lws_create_context(struct lws_context_creation_info *info); LWS_VISIBLE LWS_EXTERN void lws_context_destroy(struct lws_context *context); -LWS_VISIBLE LWS_EXTERN void -lws_context_destroy2(struct lws_context *context); - typedef int (*lws_reload_func)(void); /** @@ -2529,7 +3139,7 @@ struct lws_vhost; */ LWS_VISIBLE LWS_EXTERN struct lws_vhost * lws_create_vhost(struct lws_context *context, - struct lws_context_creation_info *info); + const struct lws_context_creation_info *info); /** * lws_vhost_destroy() - Destroy a vhost (virtual server context) @@ -2598,6 +3208,38 @@ LWS_VISIBLE LWS_EXTERN struct lws_vhost * lws_get_vhost(struct lws *wsi); /** + * lws_get_vhost_name() - returns the name of a vhost + * + * \param vhost: which vhost + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_get_vhost_name(struct lws_vhost *vhost); + +/** + * lws_get_vhost_port() - returns the port a vhost listens on, or -1 + * + * \param vhost: which vhost + */ +LWS_VISIBLE LWS_EXTERN int +lws_get_vhost_port(struct lws_vhost *vhost); + +/** + * lws_get_vhost_user() - returns the user pointer for the vhost + * + * \param vhost: which vhost + */ +LWS_VISIBLE LWS_EXTERN void * +lws_get_vhost_user(struct lws_vhost *vhost); + +/** + * lws_get_vhost_iface() - returns the binding for the vhost listen socket + * + * \param vhost: which vhost + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_get_vhost_iface(struct lws_vhost *vhost); + +/** * lws_json_dump_vhost() - describe vhost state and stats in JSON * * \param vh: the vhost @@ -2748,7 +3390,16 @@ enum lws_client_connect_ssl_connection_flags { LCCSCF_USE_SSL = (1 << 0), LCCSCF_ALLOW_SELFSIGNED = (1 << 1), LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK = (1 << 2), - LCCSCF_ALLOW_EXPIRED = (1 << 3) + LCCSCF_ALLOW_EXPIRED = (1 << 3), + + LCCSCF_PIPELINE = (1 << 16), + /**< Serialize / pipeline multiple client connections + * on a single connection where possible. + * + * HTTP/1.0: possible if Keep-Alive: yes sent by server + * HTTP/1.1: always possible... uses pipelining + * HTTP/2: always possible... uses parallel streams + * */ }; /** struct lws_client_connect_info - parameters to connect with when using @@ -2762,7 +3413,7 @@ struct lws_client_connect_info { int port; /**< remote port to connect to */ int ssl_connection; - /**< nonzero for ssl */ + /**< 0, or a combination of LCCSCF_ flags */ const char *path; /**< uri path */ const char *host; @@ -2779,7 +3430,9 @@ struct lws_client_connect_info { /**< UNUSED... provide in info.extensions at context creation time */ const char *method; /**< if non-NULL, do this http method instead of ws[s] upgrade. - * use "GET" to be a simple http client connection */ + * use "GET" to be a simple http client connection. "RAW" gets + * you a connected socket that lws itself will leave alone once + * connected. */ struct lws *parent_wsi; /**< if another wsi is responsible for this connection, give it here. * this is used to make sure if the parent closes so do any @@ -2805,6 +3458,13 @@ struct lws_client_connect_info { const char *iface; /**< NULL to allow routing on any interface, or interface name or IP * to bind the socket to */ + const char *local_protocol_name; + /**< NULL: .protocol is used both to select the local protocol handler + * to bind to and as the list of remote ws protocols we could + * accept. + * non-NULL: this protocol name is used to bind the connection to + * the local protocol handler. .protocol is used for the + * list of remote ws protocols we could accept */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility @@ -2813,6 +3473,12 @@ struct lws_client_connect_info { * members added above will see 0 (default) even if the app * was not built against the newer headers. */ + const char *alpn; + /* NULL: allow lws default ALPN list, from vhost if present or from + * list of roles built into lws + * non-NULL: require one from provided comma-separated list of alpn + * tokens + */ void *_unused[4]; /**< dummy */ }; @@ -2938,6 +3604,10 @@ lws_http_client_read(struct lws *wsi, char **buf, int *len); * \param wsi: client connection * * Returns the last server response code, eg, 200 for client http connections. + * + * You should capture this during the LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP + * callback, because after that the memory reserved for storing the related + * headers is freed and this value is lost. */ LWS_VISIBLE LWS_EXTERN unsigned int lws_http_client_http_response(struct lws *wsi); @@ -3030,15 +3700,8 @@ lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi); * on one thread * \param wsi: Cancel service on the thread this wsi is serviced by * - * This function lets a call to lws_service() waiting for a timeout - * immediately return. - * - * It works by creating a phony event and then swallowing it silently. - * - * The reason it may be needed is when waiting in poll(), changes to - * the event masks are ignored by the OS until poll() is reentered. This - * lets you halt the poll() wait and make the reentry happen immediately - * instead of having the wait out the rest of the poll timeout. + * Same as lws_cancel_service(), but targets a single service thread, the one + * the wsi belongs to. You probably want to use lws_cancel_service() instead. */ LWS_VISIBLE LWS_EXTERN void lws_cancel_service_pt(struct lws *wsi); @@ -3047,12 +3710,13 @@ lws_cancel_service_pt(struct lws *wsi); * lws_cancel_service() - Cancel wait for new pending socket activity * \param context: Websocket context * - * This function let a call to lws_service() waiting for a timeout - * immediately return. + * This function creates an immediate "synchronous interrupt" to the lws poll() + * wait or event loop. As soon as possible in the serialzed service sequencing, + * a LWS_CALLBACK_EVENT_WAIT_CANCELLED callback is sent to every protocol on + * every vhost. * - * What it basically does is provide a fake event that will be swallowed, - * so the wait in poll() is ended. That's useful because poll() doesn't - * attend to changes in POLLIN/OUT/ERR until it re-enters the wait. + * lws_cancel_service() may be called from another thread while the context + * exists, and its effect will be immediately serialized. */ LWS_VISIBLE LWS_EXTERN void lws_cancel_service(struct lws_context *context); @@ -3235,6 +3899,7 @@ struct lws_process_html_args { int len; /**< length of the original data at p */ int max_len; /**< maximum length we can grow the data to */ int final; /**< set if this is the last chunk of the file */ + int chunked; /**< 0 == unchunked, 1 == produce chunk headers (incompatible with HTTP/2) */ }; typedef const char *(*lws_process_html_state_cb)(void *data, int index); @@ -3296,12 +3961,12 @@ lws_chunked_html_process(struct lws_process_html_args *args, /** struct lws_tokens * you need these to look at headers that have been parsed if using the * LWS_CALLBACK_FILTER_CONNECTION callback. If a header from the enum - * list below is absent, .token = NULL and token_len = 0. Otherwise .token - * points to .token_len chars containing that header content. + * list below is absent, .token = NULL and len = 0. Otherwise .token + * points to .len chars containing that header content. */ struct lws_tokens { char *token; /**< pointer to start of the token */ - int token_len; /**< length of the token's value */ + int len; /**< length of the token's value */ }; /* enum lws_token_indexes @@ -3399,6 +4064,10 @@ enum lws_token_indexes { WSI_TOKEN_CONNECT = 81, WSI_TOKEN_HEAD_URI = 82, WSI_TOKEN_TE = 83, + WSI_TOKEN_REPLAY_NONCE = 84, + WSI_TOKEN_COLON_PROTOCOL = 85, + WSI_TOKEN_X_AUTH_TOKEN = 86, + /****** add new things just above ---^ ******/ /* use token storage to stash these internally, not for @@ -3411,6 +4080,7 @@ enum lws_token_indexes { _WSI_TOKEN_CLIENT_ORIGIN, _WSI_TOKEN_CLIENT_METHOD, _WSI_TOKEN_CLIENT_IFACE, + _WSI_TOKEN_CLIENT_ALPN, /* always last real token index*/ WSI_TOKEN_COUNT, @@ -3605,6 +4275,54 @@ lws_add_http_header_content_length(struct lws *wsi, LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_finalize_http_header(struct lws *wsi, unsigned char **p, unsigned char *end); + +/** + * lws_finalize_write_http_header() - Helper finializing and writing http headers + * + * \param wsi: the connection to check + * \param start: pointer to the start of headers in the buffer, eg &buf[LWS_PRE] + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Terminates the headers correctly accoring to the protocol in use (h1 / h2) + * and writes the headers. Returns nonzero for error. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_finalize_write_http_header(struct lws *wsi, unsigned char *start, + unsigned char **p, unsigned char *end); + +#define LWS_ILLEGAL_HTTP_CONTENT_LEN ((lws_filepos_t)-1ll) + +/** + * lws_add_http_common_headers() - Helper preparing common http headers + * + * \param wsi: the connection to check + * \param code: an HTTP code like 200, 404 etc (see enum http_status) + * \param content_type: the content type, like "text/html" + * \param content_len: the content length, in bytes + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Adds the initial response code, so should be called first. + * + * Code may additionally take OR'd flags: + * + * LWSAHH_FLAG_NO_SERVER_NAME: don't apply server name header this time + * + * This helper just calls public apis to simplify adding headers that are + * commonly needed. If it doesn't fit your case, or you want to add additional + * headers just call the public apis directly yourself for what you want. + * + * You can miss out the content length header by providing the constant + * LWS_ILLEGAL_HTTP_CONTENT_LEN for the content_len. + * + * It does not call lws_finalize_http_header(), to allow you to add further + * headers after calling this. You will need to call that yourself at the end. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_add_http_common_headers(struct lws *wsi, unsigned int code, + const char *content_type, lws_filepos_t content_len, + unsigned char **p, unsigned char *end); ///@} /** \defgroup form-parsing Form Parsing @@ -3787,7 +4505,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, const char *html_body); /** - * lws_http_redirect() - write http redirect into buffer + * lws_http_redirect() - write http redirect out on wsi * * \param wsi: websocket connection * \param code: HTTP response code (eg, 301) @@ -3795,6 +4513,8 @@ lws_return_http_status(struct lws *wsi, unsigned int code, * \param len: length of loc * \param p: pointer current position in buffer (updated as we write) * \param end: pointer to end of buffer + * + * Returns amount written, or < 0 indicating fatal write failure. */ LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, @@ -3845,30 +4565,29 @@ lws_sql_purify(char *escaped, const char *string, int len); */ LWS_VISIBLE LWS_EXTERN const char * lws_json_purify(char *escaped, const char *string, int len); -///@} -/*! \defgroup ev libev helpers +/** + * lws_filename_purify_inplace() - replace scary filename chars with underscore * - * ##libev helpers + * \param filename: filename to be purified * - * APIs specific to libev event loop itegration + * Replace scary characters in the filename (it should not be a path) + * with underscore, so it's safe to use. */ -///@{ - -#ifdef LWS_WITH_LIBEV -typedef void (lws_ev_signal_cb_t)(EV_P_ struct ev_signal *w, int revents); +LWS_VISIBLE LWS_EXTERN void +lws_filename_purify_inplace(char *filename); LWS_VISIBLE LWS_EXTERN int -lws_ev_sigint_cfg(struct lws_context *context, int use_ev_sigint, - lws_ev_signal_cb_t *cb); - +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len); LWS_VISIBLE LWS_EXTERN int -lws_ev_initloop(struct lws_context *context, struct ev_loop *loop, int tsi); +lws_plat_write_file(const char *filename, void *buf, int len); -LWS_VISIBLE LWS_EXTERN void -lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents); -#endif /* LWS_WITH_LIBEV */ +LWS_VISIBLE LWS_EXTERN int +lws_plat_read_file(const char *filename, void *buf, int len); +LWS_VISIBLE LWS_EXTERN int +lws_plat_recommended_rsa_bits(void); ///@} /*! \defgroup uv libuv helpers @@ -3879,60 +4598,38 @@ lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents); */ ///@{ #ifdef LWS_WITH_LIBUV -LWS_VISIBLE LWS_EXTERN int -lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint, - uv_signal_cb cb); - -LWS_VISIBLE LWS_EXTERN void -lws_libuv_run(const struct lws_context *context, int tsi); - -LWS_VISIBLE LWS_EXTERN void -lws_libuv_stop(struct lws_context *context); - -LWS_VISIBLE LWS_EXTERN void -lws_libuv_stop_without_kill(const struct lws_context *context, int tsi); - -LWS_VISIBLE LWS_EXTERN int -lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi); +/* + * Any direct libuv allocations in lws protocol handlers must participate in the + * lws reference counting scheme. Two apis are provided: + * + * - lws_libuv_static_refcount_add(handle, context) to mark the handle with + * a pointer to the context and increment the global uv object counter + * + * - lws_libuv_static_refcount_del() which should be used as the close callback + * for your own libuv objects declared in the protocol scope. + * + * Using the apis allows lws to detach itself from a libuv loop completely + * cleanly and at the moment all of its libuv objects have completed close. + */ LWS_VISIBLE LWS_EXTERN uv_loop_t * lws_uv_getloop(struct lws_context *context, int tsi); LWS_VISIBLE LWS_EXTERN void -lws_uv_sigint_cb(uv_signal_t *watcher, int signum); +lws_libuv_static_refcount_add(uv_handle_t *, struct lws_context *context); LWS_VISIBLE LWS_EXTERN void -lws_close_all_handles_in_loop(uv_loop_t *loop); -#endif /* LWS_WITH_LIBUV */ -///@} - -/*! \defgroup event libevent helpers - * - * ##libevent helpers - * - * APIs specific to libevent event loop itegration - */ -///@{ - -#ifdef LWS_WITH_LIBEVENT -typedef void (lws_event_signal_cb_t) (evutil_socket_t sock_fd, short revents, - void *ctx); - -LWS_VISIBLE LWS_EXTERN int -lws_event_sigint_cfg(struct lws_context *context, int use_event_sigint, - lws_event_signal_cb_t cb); - -LWS_VISIBLE LWS_EXTERN int -lws_event_initloop(struct lws_context *context, struct event_base *loop, - int tsi); +lws_libuv_static_refcount_del(uv_handle_t *); -LWS_VISIBLE LWS_EXTERN void -lws_event_sigint_cb(evutil_socket_t sock_fd, short revents, - void *ctx); -#endif /* LWS_WITH_LIBEVENT */ +#endif /* LWS_WITH_LIBUV */ +#if defined(LWS_WITH_ESP32) +#define lws_libuv_static_refcount_add(_a, _b) +#define lws_libuv_static_refcount_del NULL +#endif ///@} + /*! \defgroup timeout Connection timeouts APIs related to setting connection timeouts @@ -3951,7 +4648,7 @@ enum pending_timeout { PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE = 4, PENDING_TIMEOUT_AWAITING_PING = 5, PENDING_TIMEOUT_CLOSE_ACK = 6, - PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE = 7, + PENDING_TIMEOUT_UNUSED1 = 7, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE = 8, PENDING_TIMEOUT_SSL_ACCEPT = 9, PENDING_TIMEOUT_HTTP_CONTENT = 10, @@ -3970,6 +4667,9 @@ enum pending_timeout { PENDING_TIMEOUT_KILLED_BY_PARENT = 23, PENDING_TIMEOUT_CLOSE_SEND = 24, PENDING_TIMEOUT_HOLDING_AH = 25, + PENDING_TIMEOUT_UDP_IDLE = 26, + PENDING_TIMEOUT_CLIENT_CONN_IDLE = 27, + PENDING_TIMEOUT_LAGGING = 28, /****** add new things just above ---^ ******/ @@ -4003,6 +4703,60 @@ enum pending_timeout { */ LWS_VISIBLE LWS_EXTERN void lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); + +#define LWS_SET_TIMER_USEC_CANCEL ((lws_usec_t)-1ll) +#define LWS_USEC_PER_SEC (1000000ll) + +/** + * lws_set_timer_usecs() - schedules a callback on the wsi in the future + * + * \param wsi: Websocket connection instance + * \param usecs: LWS_SET_TIMER_USEC_CANCEL removes any existing scheduled + * callback, otherwise number of microseconds in the future + * the callback will occur at. + * + * NOTE: event loop support for this: + * + * default poll() loop: yes + * libuv event loop: yes + * libev: not implemented (patch welcome) + * libevent: not implemented (patch welcome) + * + * After the deadline expires, the wsi will get a callback of type + * LWS_CALLBACK_TIMER and the timer is exhausted. The deadline may be + * continuously deferred by further calls to lws_set_timer_usecs() with a later + * deadline, or cancelled by lws_set_timer_usecs(wsi, -1). + * + * If the timer should repeat, lws_set_timer_usecs() must be called again from + * LWS_CALLBACK_TIMER. + * + * Accuracy depends on the platform and the load on the event loop or system... + * all that's guaranteed is the callback will come after the requested wait + * period. + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_timer_usecs(struct lws *wsi, lws_usec_t usecs); + +/* + * lws_timed_callback_vh_protocol() - calls back a protocol on a vhost after + * the specified delay + * + * \param vh: the vhost to call back + * \param protocol: the protocol to call back + * \param reason: callback reason + * \param secs: how many seconds in the future to do the callback. Set to + * -1 to cancel the timer callback. + * + * Callback the specified protocol with a fake wsi pointing to the specified + * vhost and protocol, with the specified reason, at the specified time in the + * future. + * + * Returns 0 if OK. + */ +LWS_VISIBLE LWS_EXTERN int +lws_timed_callback_vh_protocol(struct lws_vhost *vh, + const struct lws_protocols *prot, + int reason, int secs); ///@} /*! \defgroup sending-data Sending data @@ -4011,7 +4765,7 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); */ //@{ #if !defined(LWS_SIZEOFPTR) -#define LWS_SIZEOFPTR (sizeof (void *)) +#define LWS_SIZEOFPTR ((int)sizeof (void *)) #endif #if defined(__x86_64__) @@ -4027,6 +4781,8 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); #define LWS_SEND_BUFFER_PRE_PADDING LWS_PRE #define LWS_SEND_BUFFER_POST_PADDING 0 +#define LWS_WRITE_RAW LWS_WRITE_HTTP + /* * NOTE: These public enums are part of the abi. If you want to add one, * add it at where specified so existing users are unaffected. @@ -4185,6 +4941,23 @@ lws_write(struct lws *wsi, unsigned char *buf, size_t len, /* helper for case where buffer may be const */ #define lws_write_http(wsi, buf, len) \ lws_write(wsi, (unsigned char *)(buf), len, LWS_WRITE_HTTP) + +/* helper for multi-frame ws message flags */ +static inline int +lws_write_ws_flags(int initial, int is_start, int is_end) +{ + int r; + + if (is_start) + r = initial; + else + r = LWS_WRITE_CONTINUATION; + + if (!is_end) + r |= LWS_WRITE_NO_FIN; + + return r; +} ///@} /** \defgroup callback-when-writeable Callback when writeable @@ -4324,9 +5097,31 @@ lws_callback_all_protocol_vhost_args(struct lws_vhost *vh, * - Which: connections using this protocol on same VHOST as wsi ONLY * - When: now * - What: reason + * + * This is deprecated since v2.5, use lws_callback_vhost_protocols_vhost() + * which takes the pointer to the vhost directly without using or needing the + * wsi. */ LWS_VISIBLE LWS_EXTERN int -lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len); +lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len) +LWS_WARN_DEPRECATED; + +/** + * lws_callback_vhost_protocols_vhost() - Callback all protocols enabled on a vhost + * with the given reason + * + * \param vh: vhost that will get callbacks + * \param reason: Callback reason index + * \param in: in argument to callback + * \param len: len argument to callback + * + * - Which: connections using this protocol on same VHOST as wsi ONLY + * - When: now + * - What: reason + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_vhost_protocols_vhost(struct lws_vhost *vh, int reason, void *in, + size_t len); LWS_VISIBLE LWS_EXTERN int lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, @@ -4335,11 +5130,11 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, /** * lws_get_socket_fd() - returns the socket file descriptor * - * You will not need this unless you are doing something special + * This is needed to use sendto() on UDP raw sockets * * \param wsi: Websocket connection instance */ -LWS_VISIBLE LWS_EXTERN int +LWS_VISIBLE LWS_EXTERN lws_sockfd_type lws_get_socket_fd(struct lws *wsi); /** @@ -4363,7 +5158,7 @@ lws_get_socket_fd(struct lws *wsi); * automatically, so this number reflects the situation at the peer or * intermediary dynamically. */ -LWS_VISIBLE LWS_EXTERN size_t +LWS_VISIBLE LWS_EXTERN lws_fileofs_t lws_get_peer_write_allowance(struct lws *wsi); ///@} @@ -4422,19 +5217,22 @@ lws_rx_flow_allow_all_protocol(const struct lws_context *context, /** * lws_remaining_packet_payload() - Bytes to come before "overall" - * rx packet is complete + * rx fragment is complete * \param wsi: Websocket instance (available from user callback) * - * This function is intended to be called from the callback if the - * user code is interested in "complete packets" from the client. - * libwebsockets just passes through payload as it comes and issues a buffer - * additionally when it hits a built-in limit. The LWS_CALLBACK_RECEIVE - * callback handler can use this API to find out if the buffer it has just - * been given is the last piece of a "complete packet" from the client -- - * when that is the case lws_remaining_packet_payload() will return - * 0. + * This tracks how many bytes are left in the current ws fragment, according + * to the ws length given in the fragment header. + * + * If the message was in a single fragment, and there is no compression, this + * is the same as "how much data is left to read for this message". * - * Many protocols won't care becuse their packets are always small. + * However, if the message is being sent in multiple fragments, this will + * reflect the unread amount of the current **fragment**, not the message. With + * ws, it is legal to not know the length of the message before it completes. + * + * Additionally if the message is sent via the negotiated permessage-deflate + * extension, this number only tells the amount of **compressed** data left to + * be read, since that is the only information available at the ws layer. */ LWS_VISIBLE LWS_EXTERN size_t lws_remaining_packet_payload(struct lws *wsi); @@ -4488,8 +5286,10 @@ typedef enum { LWS_ADOPT_ALLOW_SSL = 4, /* flag: if set requires LWS_ADOPT_SOCKET */ LWS_ADOPT_WS_PARENTIO = 8, /* flag: ws mode parent handles IO * if given must be only flag - * wsi put directly into ws mode - */ + * wsi put directly into ws mode */ + LWS_ADOPT_FLAG_UDP = 16, /* flag: socket is UDP */ + + LWS_ADOPT_RAW_SOCKET_UDP = LWS_ADOPT_SOCKET | LWS_ADOPT_FLAG_UDP, } lws_adoption_type; typedef union { @@ -4497,6 +5297,16 @@ typedef union { lws_filefd_type filefd; } lws_sock_file_fd_type; +#if !defined(LWS_WITH_ESP32) +struct lws_udp { + struct sockaddr sa; + socklen_t salen; + + struct sockaddr sa_pending; + socklen_t salen_pending; +}; +#endif + /* * lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor * if socket descriptor, should already have been accepted from listen socket @@ -4573,6 +5383,24 @@ lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, LWS_VISIBLE LWS_EXTERN struct lws * lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, lws_sockfd_type accept_fd, const char *readbuf, size_t len); + +#define LWS_CAUDP_BIND 1 + +/** + * lws_create_adopt_udp() - create, bind and adopt a UDP socket + * + * \param vhost: lws vhost + * \param port: UDP port to bind to, -1 means unbound + * \param flags: 0 or LWS_CAUDP_NO_BIND + * \param protocol_name: Name of protocol on vhost to bind wsi to + * \param parent_wsi: NULL or parent wsi new wsi will be a child of + * + * Either returns new wsi bound to accept_fd, or closes accept_fd and + * returns NULL, having cleaned up any new wsi pieces. + * */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, + const char *protocol_name, struct lws *parent_wsi); ///@} /** \defgroup net Network related helper APIs @@ -4624,17 +5452,31 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, */ LWS_VISIBLE LWS_EXTERN const char * lws_get_peer_simple(struct lws *wsi, char *name, int namelen); -#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) + + +#define LWS_ITOSA_NOT_EXIST -1 +#define LWS_ITOSA_NOT_USABLE -2 +#define LWS_ITOSA_USABLE 0 +#if !defined(LWS_WITH_ESP32) /** * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct * - * \param ipv6: Allow IPV6 addresses + * \param ipv6: Allow IPV6 addresses * \param ifname: Interface name or IP - * \param addr: struct sockaddr_in * to be written + * \param addr: struct sockaddr_in * to be written * \param addrlen: Length of addr * * This converts a textual network interface name to a sockaddr usable by - * other network functions + * other network functions. + * + * If the network interface doesn't exist, it will return LWS_ITOSA_NOT_EXIST. + * + * If the network interface is not usable, eg ethernet cable is removed, it + * may logically exist but not have any IP address. As such it will return + * LWS_ITOSA_NOT_USABLE. + * + * If the network interface exists and is usable, it will return + * LWS_ITOSA_USABLE. */ LWS_VISIBLE LWS_EXTERN int lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, @@ -4702,6 +5544,13 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, type it = &(start); \ while (*(it)) { +#define lws_start_foreach_llp_safe(type, it, start, nxt)\ +{ \ + type it = &(start); \ + type next; \ + while (*(it)) { \ + next = &((*(it))->nxt); \ + /** * lws_end_foreach_llp(): linkedlist pointer iterator helper end * @@ -4717,6 +5566,170 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, } \ } +#define lws_end_foreach_llp_safe(it) \ + it = next; \ + } \ +} + +#define lws_ll_fwd_insert(\ + ___new_object, /* pointer to new object */ \ + ___m_list, /* member for next list object ptr */ \ + ___list_head /* list head */ \ + ) {\ + ___new_object->___m_list = ___list_head; \ + ___list_head = ___new_object; \ + } + +#define lws_ll_fwd_remove(\ + ___type, /* type of listed object */ \ + ___m_list, /* member for next list object ptr */ \ + ___target, /* object to remove from list */ \ + ___list_head /* list head */ \ + ) { \ + lws_start_foreach_llp(___type **, ___ppss, ___list_head) { \ + if (*___ppss == ___target) { \ + *___ppss = ___target->___m_list; \ + break; \ + } \ + } lws_end_foreach_llp(___ppss, ___m_list); \ + } + +/* + * doubly linked-list + */ + +struct lws_dll { /* abstract */ + struct lws_dll *prev; + struct lws_dll *next; +}; + +/* + * these all point to the composed list objects... you have to use the + * lws_container_of() helper to recover the start of the containing struct + */ + +LWS_VISIBLE LWS_EXTERN void +lws_dll_add_front(struct lws_dll *d, struct lws_dll *phead); + +LWS_VISIBLE LWS_EXTERN void +lws_dll_remove(struct lws_dll *d); + +struct lws_dll_lws { /* typed as struct lws * */ + struct lws_dll_lws *prev; + struct lws_dll_lws *next; +}; + +#define lws_dll_is_null(___dll) (!(___dll)->prev && !(___dll)->next) + +static inline void +lws_dll_lws_add_front(struct lws_dll_lws *_a, struct lws_dll_lws *_head) +{ + lws_dll_add_front((struct lws_dll *)_a, (struct lws_dll *)_head); +} + +static inline void +lws_dll_lws_remove(struct lws_dll_lws *_a) +{ + lws_dll_remove((struct lws_dll *)_a); +} + +/* + * these are safe against the current container object getting deleted, + * since the hold his next in a temp and go to that next. ___tmp is + * the temp. + */ + +#define lws_start_foreach_dll_safe(___type, ___it, ___tmp, ___start) \ +{ \ + ___type ___it = ___start; \ + while (___it) { \ + ___type ___tmp = (___it)->next; + +#define lws_end_foreach_dll_safe(___it, ___tmp) \ + ___it = ___tmp; \ + } \ +} + +#define lws_start_foreach_dll(___type, ___it, ___start) \ +{ \ + ___type ___it = ___start; \ + while (___it) { + +#define lws_end_foreach_dll(___it) \ + ___it = (___it)->next; \ + } \ +} + +struct lws_buflist; + +/** + * lws_buflist_append_segment(): add buffer to buflist at head + * + * \param head: list head + * \param buf: buffer to stash + * \param len: length of buffer to stash + * + * Returns -1 on OOM, 1 if this was the first segment on the list, and 0 if + * it was a subsequent segment. + */ +LWS_VISIBLE LWS_EXTERN int +lws_buflist_append_segment(struct lws_buflist **head, const uint8_t *buf, + size_t len); +/** + * lws_buflist_next_segment_len(): number of bytes left in current segment + * + * \param head: list head + * \param buf: if non-NULL, *buf is written with the address of the start of + * the remaining data in the segment + * + * Returns the number of bytes left in the current segment. 0 indicates + * that the buflist is empty (there are no segments on the buflist). + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_buflist_next_segment_len(struct lws_buflist **head, uint8_t **buf); +/** + * lws_buflist_use_segment(): remove len bytes from the current segment + * + * \param head: list head + * \param len: number of bytes to mark as used + * + * If len is less than the remaining length of the current segment, the position + * in the current segment is simply advanced and it returns. + * + * If len uses up the remaining length of the current segment, then the segment + * is deleted and the list head moves to the next segment if any. + * + * Returns the number of bytes left in the current segment. 0 indicates + * that the buflist is empty (there are no segments on the buflist). + */ +LWS_VISIBLE LWS_EXTERN int +lws_buflist_use_segment(struct lws_buflist **head, size_t len); +/** + * lws_buflist_destroy_all_segments(): free all segments on the list + * + * \param head: list head + * + * This frees everything on the list unconditionally. *head is always + * NULL after this. + */ +LWS_VISIBLE LWS_EXTERN void +lws_buflist_destroy_all_segments(struct lws_buflist **head); + +void +lws_buflist_describe(struct lws_buflist **head, void *id); + +/** + * lws_ptr_diff(): helper to report distance between pointers as an int + * + * \param head: the pointer with the larger address + * \param tail: the pointer with the smaller address + * + * This helper gives you an int representing the number of bytes further + * forward the first pointer is compared to the second pointer. + */ +#define lws_ptr_diff(head, tail) \ + ((int)((char *)(head) - (char *)(tail))) + /** * lws_snprintf(): snprintf that truncates the returned length too * @@ -4732,6 +5745,19 @@ LWS_VISIBLE LWS_EXTERN int lws_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3); /** + * lws_strncpy(): strncpy that guarantees NUL on truncated copy + * + * \param dest: destination buffer + * \param src: source buffer + * \param size: bytes left in destination buffer + * + * This lets you correctly truncate buffers by concatenating lengths, if you + * reach the limit the reported length doesn't exceed the limit. + */ +LWS_VISIBLE LWS_EXTERN char * +lws_strncpy(char *dest, const char *src, size_t size); + +/** * lws_get_random(): fill a buffer with platform random data * * \param context: the lws context @@ -4797,6 +5823,25 @@ lws_set_wsi_user(struct lws *wsi, void *user); LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_parse_uri(char *p, const char **prot, const char **ads, int *port, const char **path); +/** + * lws_cmdline_option(): simple commandline parser + * + * \param argc: count of argument strings + * \param argv: argument strings + * \param val: string to find + * + * Returns NULL if the string \p val is not found in the arguments. + * + * If it is found, then it returns a pointer to the next character after \p val. + * So if \p val is "-d", then for the commandlines "myapp -d15" and + * "myapp -d 15", in both cases the return will point to the "15". + * + * In the case there is no argument, like "myapp -d", the return will + * either point to the '\\0' at the end of -d, or to the start of the + * next argument, ie, will be non-NULL. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_cmdline_option(int argc, const char **argv, const char *val); /** * lws_now_secs(): return seconds since 1970-1-1 @@ -4805,6 +5850,26 @@ LWS_VISIBLE LWS_EXTERN unsigned long lws_now_secs(void); /** + * lws_compare_time_t(): return relationship between two time_t + * + * \param context: struct lws_context + * \param t1: time_t 1 + * \param t2: time_t 2 + * + * returns <0 if t2 > t1; >0 if t1 > t2; or == 0 if t1 == t2. + * + * This is aware of clock discontiguities that may have affected either t1 or + * t2 and adapts the comparison for them. + * + * For the discontiguity detection to work, you must avoid any arithmetic on + * the times being compared. For example to have a timeout that triggers + * 15s from when it was set, store the time it was set and compare like + * `if (lws_compare_time_t(context, now, set_time) > 15)` + */ +LWS_VISIBLE LWS_EXTERN int +lws_compare_time_t(struct lws_context *context, time_t t1, time_t t2); + +/** * lws_get_context - Allow getting lws_context from a Websocket connection * instance * @@ -4817,6 +5882,18 @@ LWS_VISIBLE LWS_EXTERN struct lws_context * LWS_WARN_UNUSED_RESULT lws_get_context(const struct lws *wsi); /** + * lws_get_vhost_listen_port - Find out the port number a vhost is listening on + * + * In the case you passed 0 for the port number at context creation time, you + * can discover the port number that was actually chosen for the vhost using + * this api. + * + * \param vhost: Vhost to get listen port from + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_get_vhost_listen_port(struct lws_vhost *vhost); + +/** * lws_get_count_threads(): how many service threads the context uses * * \param context: the lws context @@ -4848,6 +5925,16 @@ LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT lws_get_child(const struct lws *wsi); /** + * lws_get_udp() - get wsi's udp struct + * + * \param wsi: lws connection + * + * Returns NULL or pointer to the wsi's UDP-specific information + */ +LWS_VISIBLE LWS_EXTERN const struct lws_udp * LWS_WARN_UNUSED_RESULT +lws_get_udp(const struct lws *wsi); + +/** * lws_parent_carries_io() - mark wsi as needing to send messages via parent * * \param wsi: child lws connection @@ -4887,14 +5974,6 @@ lws_get_close_payload(struct lws *wsi); LWS_VISIBLE LWS_EXTERN struct lws *lws_get_network_wsi(struct lws *wsi); -/* - * \deprecated DEPRECATED Note: this is not normally needed as a user api. - * It's provided in case it is - * useful when integrating with other app poll loop service code. - */ -LWS_VISIBLE LWS_EXTERN int -lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len); - /** * lws_set_allocator() - custom allocator support * @@ -4992,7 +6071,18 @@ lws_is_ssl(struct lws *wsi); LWS_VISIBLE LWS_EXTERN int lws_is_cgi(struct lws *wsi); -#ifdef LWS_OPENSSL_SUPPORT + +struct lws_wifi_scan { /* generic wlan scan item */ + struct lws_wifi_scan *next; + char ssid[32]; + int32_t rssi; /* divide by .count to get db */ + uint8_t bssid[6]; + uint8_t count; + uint8_t channel; + uint8_t authmode; +}; + +#if defined(LWS_WITH_TLS) && !defined(LWS_WITH_MBEDTLS) /** * lws_get_ssl() - Return wsi's SSL context structure * \param wsi: websocket connection @@ -5002,6 +6092,160 @@ lws_is_cgi(struct lws *wsi); LWS_VISIBLE LWS_EXTERN SSL* lws_get_ssl(struct lws *wsi); #endif + +enum lws_tls_cert_info { + LWS_TLS_CERT_INFO_VALIDITY_FROM, + /**< fills .time with the time_t the cert validity started from */ + LWS_TLS_CERT_INFO_VALIDITY_TO, + /**< fills .time with the time_t the cert validity ends at */ + LWS_TLS_CERT_INFO_COMMON_NAME, + /**< fills up to len bytes of .ns.name with the cert common name */ + LWS_TLS_CERT_INFO_ISSUER_NAME, + /**< fills up to len bytes of .ns.name with the cert issuer name */ + LWS_TLS_CERT_INFO_USAGE, + /**< fills verified with a bitfield asserting the valid uses */ + LWS_TLS_CERT_INFO_VERIFIED, + /**< fills .verified with a bool representing peer cert validity, + * call returns -1 if no cert */ + LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY, + /**< the certificate's public key, as an opaque bytestream. These + * opaque bytestreams can only be compared with each other using the + * same tls backend, ie, OpenSSL or mbedTLS. The different backends + * produce different, incompatible representations for the same cert. + */ +}; + +union lws_tls_cert_info_results { + unsigned int verified; + time_t time; + unsigned int usage; + struct { + int len; + /* KEEP LAST... notice the [64] is only there because + * name[] is not allowed in a union. The actual length of + * name[] is arbitrary and is passed into the api using the + * len parameter. Eg + * + * char big[1024]; + * union lws_tls_cert_info_results *buf = + * (union lws_tls_cert_info_results *)big; + * + * lws_tls_peer_cert_info(wsi, type, buf, sizeof(big) - + * sizeof(*buf) + sizeof(buf->ns.name)); + */ + char name[64]; + } ns; +}; + +/** + * lws_tls_peer_cert_info() - get information from the peer's TLS cert + * + * \param wsi: the connection to query + * \param type: one of LWS_TLS_CERT_INFO_ + * \param buf: pointer to union to take result + * \param len: when result is a string, the true length of buf->ns.name[] + * + * lws_tls_peer_cert_info() lets you get hold of information from the peer + * certificate. + * + * Return 0 if there is a result in \p buf, or -1 indicating there was no cert + * or another problem. + * + * This function works the same no matter if the TLS backend is OpenSSL or + * mbedTLS. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len); + +/** + * lws_tls_vhost_cert_info() - get information from the vhost's own TLS cert + * + * \param vhost: the vhost to query + * \param type: one of LWS_TLS_CERT_INFO_ + * \param buf: pointer to union to take result + * \param len: when result is a string, the true length of buf->ns.name[] + * + * lws_tls_vhost_cert_info() lets you get hold of information from the vhost + * certificate. + * + * Return 0 if there is a result in \p buf, or -1 indicating there was no cert + * or another problem. + * + * This function works the same no matter if the TLS backend is OpenSSL or + * mbedTLS. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len); + +/** + * lws_tls_acme_sni_cert_create() - creates a temp selfsigned cert + * and attaches to a vhost + * + * \param vhost: the vhost to acquire the selfsigned cert + * \param san_a: SAN written into the certificate + * \param san_b: second SAN written into the certificate + * + * + * Returns 0 if created and attached to the vhost. Returns -1 if problems and + * frees all allocations before returning. + * + * On success, any allocations are destroyed at vhost destruction automatically. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, + const char *san_b); + +/** + * lws_tls_acme_sni_csr_create() - creates a CSR and related private key PEM + * + * \param context: lws_context used for random + * \param elements: array of LWS_TLS_REQ_ELEMENT_COUNT const char * + * \param csr: buffer that will get the b64URL(ASN-1 CSR) + * \param csr_len: max length of the csr buffer + * \param privkey_pem: pointer to pointer allocated to hold the privkey_pem + * \param privkey_len: pointer to size_t set to the length of the privkey_pem + * + * Creates a CSR according to the information in \p elements, and a private + * RSA key used to sign the CSR. + * + * The outputs are the b64URL(ASN-1 CSR) into csr, and the PEM private key into + * privkey_pem. + * + * Notice that \p elements points to an array of const char *s pointing to the + * information listed in the enum above. If an entry is NULL or an empty + * string, the element is set to "none" in the CSR. + * + * Returns 0 on success or nonzero for failure. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[], + uint8_t *csr, size_t csr_len, char **privkey_pem, + size_t *privkey_len); + +/** + * lws_tls_cert_updated() - update every vhost using the given cert path + * + * \param context: our lws_context + * \param certpath: the filepath to the certificate + * \param keypath: the filepath to the private key of the certificate + * \param mem_cert: copy of the cert in memory + * \param len_mem_cert: length of the copy of the cert in memory + * \param mem_privkey: copy of the private key in memory + * \param len_mem_privkey: length of the copy of the private key in memory + * + * Checks every vhost to see if it is the using certificate described by the + * the given filepaths. If so, it attempts to update the vhost ssl_ctx to use + * the new certificate. + * + * Returns 0 on success or nonzero for failure. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_cert_updated(struct lws_context *context, const char *certpath, + const char *keypath, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t len_mem_privkey); ///@} /** \defgroup lws_ring LWS Ringbuffer APIs @@ -5226,6 +6470,65 @@ lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start, */ LWS_VISIBLE LWS_EXTERN void lws_ring_bump_head(struct lws_ring *ring, size_t bytes); + +LWS_VISIBLE LWS_EXTERN void +lws_ring_dump(struct lws_ring *ring, uint32_t *tail); + +/* + * This is a helper that combines the common pattern of needing to consume + * some ringbuffer elements, move the consumer tail on, and check if that + * has moved any ringbuffer elements out of scope, because it was the last + * consumer that had not already consumed them. + * + * Elements that go out of scope because the oldest tail is now after them + * get garbage-collected by calling the destroy_element callback on them + * defined when the ringbuffer was created. + */ + +#define lws_ring_consume_and_update_oldest_tail(\ + ___ring, /* the lws_ring object */ \ + ___type, /* type of objects with tails */ \ + ___ptail, /* ptr to tail of obj with tail doing consuming */ \ + ___count, /* count of payload objects being consumed */ \ + ___list_head, /* head of list of objects with tails */ \ + ___mtail, /* member name of tail in ___type */ \ + ___mlist /* member name of next list member ptr in ___type */ \ + ) { \ + int ___n, ___m; \ + \ + ___n = lws_ring_get_oldest_tail(___ring) == *(___ptail); \ + lws_ring_consume(___ring, ___ptail, NULL, ___count); \ + if (___n) { \ + uint32_t ___oldest; \ + ___n = 0; \ + ___oldest = *(___ptail); \ + lws_start_foreach_llp(___type **, ___ppss, ___list_head) { \ + ___m = lws_ring_get_count_waiting_elements( \ + ___ring, &(*___ppss)->tail); \ + if (___m >= ___n) { \ + ___n = ___m; \ + ___oldest = (*___ppss)->tail; \ + } \ + } lws_end_foreach_llp(___ppss, ___mlist); \ + \ + lws_ring_update_oldest_tail(___ring, ___oldest); \ + } \ +} + +/* + * This does the same as the lws_ring_consume_and_update_oldest_tail() + * helper, but for the simpler case there is only one consumer, so one + * tail, and that tail is always the oldest tail. + */ + +#define lws_ring_consume_single_tail(\ + ___ring, /* the lws_ring object */ \ + ___ptail, /* ptr to tail of obj with tail doing consuming */ \ + ___count /* count of payload objects being consumed */ \ + ) { \ + lws_ring_consume(___ring, ___ptail, NULL, ___count); \ + lws_ring_update_oldest_tail(___ring, *(___ptail)); \ +} ///@} /** \defgroup sha SHA and B64 helpers @@ -5262,16 +6565,40 @@ lws_SHA1(const unsigned char *d, size_t n, unsigned char *md); LWS_VISIBLE LWS_EXTERN int lws_b64_encode_string(const char *in, int in_len, char *out, int out_size); /** + * lws_b64_encode_string_url(): encode a string into base 64 + * + * \param in: incoming buffer + * \param in_len: length of incoming buffer + * \param out: result buffer + * \param out_size: length of result buffer + * + * Encodes a string using b64 with the "URL" variant (+ -> -, and / -> _) + */ +LWS_VISIBLE LWS_EXTERN int +lws_b64_encode_string_url(const char *in, int in_len, char *out, int out_size); +/** * lws_b64_decode_string(): decode a string from base 64 * * \param in: incoming buffer * \param out: result buffer * \param out_size: length of result buffer * - * Decodes a string using b64 + * Decodes a NUL-terminated string using b64 */ LWS_VISIBLE LWS_EXTERN int lws_b64_decode_string(const char *in, char *out, int out_size); +/** + * lws_b64_decode_string_len(): decode a string from base 64 + * + * \param in: incoming buffer + * \param in_len: length of incoming buffer + * \param out: result buffer + * \param out_size: length of result buffer + * + * Decodes a range of chars using b64 + */ +LWS_VISIBLE LWS_EXTERN int +lws_b64_decode_string_len(const char *in, int in_len, char *out, int out_size); ///@} @@ -5727,6 +7054,248 @@ lws_email_destroy(struct lws_email *email); #endif //@} + +/** \defgroup lejp JSON parser + * ##JSON parsing related functions + * \ingroup lwsapi + * + * LEJP is an extremely lightweight JSON stream parser included in lws. + */ +//@{ +struct lejp_ctx; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0])) +#endif +#define LWS_ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0])) +#define LEJP_FLAG_WS_KEEP 64 +#define LEJP_FLAG_WS_COMMENTLINE 32 + +enum lejp_states { + LEJP_IDLE = 0, + LEJP_MEMBERS = 1, + LEJP_M_P = 2, + LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3, + LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4, + LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5, + LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6, + LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7, + LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8, + LEJP_MP_DELIM = 9, + LEJP_MP_VALUE = 10, + LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11, + LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12, + LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13, + LEJP_MP_COMMA_OR_END = 14, + LEJP_MP_ARRAY_END = 15, +}; + +enum lejp_reasons { + LEJP_CONTINUE = -1, + LEJP_REJECT_IDLE_NO_BRACE = -2, + LEJP_REJECT_MEMBERS_NO_CLOSE = -3, + LEJP_REJECT_MP_NO_OPEN_QUOTE = -4, + LEJP_REJECT_MP_STRING_UNDERRUN = -5, + LEJP_REJECT_MP_ILLEGAL_CTRL = -6, + LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7, + LEJP_REJECT_ILLEGAL_HEX = -8, + LEJP_REJECT_MP_DELIM_MISSING_COLON = -9, + LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10, + LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11, + LEJP_REJECT_MP_VAL_NUM_FORMAT = -12, + LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13, + LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14, + LEJP_REJECT_MP_C_OR_E_UNDERF = -15, + LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16, + LEJP_REJECT_MP_ARRAY_END_MISSING = -17, + LEJP_REJECT_STACK_OVERFLOW = -18, + LEJP_REJECT_MP_DELIM_ISTACK = -19, + LEJP_REJECT_NUM_TOO_LONG = -20, + LEJP_REJECT_MP_C_OR_E_NEITHER = -21, + LEJP_REJECT_UNKNOWN = -22, + LEJP_REJECT_CALLBACK = -23 +}; + +#define LEJP_FLAG_CB_IS_VALUE 64 + +enum lejp_callbacks { + LEJPCB_CONSTRUCTED = 0, + LEJPCB_DESTRUCTED = 1, + + LEJPCB_START = 2, + LEJPCB_COMPLETE = 3, + LEJPCB_FAILED = 4, + + LEJPCB_PAIR_NAME = 5, + + LEJPCB_VAL_TRUE = LEJP_FLAG_CB_IS_VALUE | 6, + LEJPCB_VAL_FALSE = LEJP_FLAG_CB_IS_VALUE | 7, + LEJPCB_VAL_NULL = LEJP_FLAG_CB_IS_VALUE | 8, + LEJPCB_VAL_NUM_INT = LEJP_FLAG_CB_IS_VALUE | 9, + LEJPCB_VAL_NUM_FLOAT = LEJP_FLAG_CB_IS_VALUE | 10, + LEJPCB_VAL_STR_START = 11, /* notice handle separately */ + LEJPCB_VAL_STR_CHUNK = LEJP_FLAG_CB_IS_VALUE | 12, + LEJPCB_VAL_STR_END = LEJP_FLAG_CB_IS_VALUE | 13, + + LEJPCB_ARRAY_START = 14, + LEJPCB_ARRAY_END = 15, + + LEJPCB_OBJECT_START = 16, + LEJPCB_OBJECT_END = 17 +}; + +/** + * _lejp_callback() - User parser actions + * \param ctx: LEJP context + * \param reason: Callback reason + * + * Your user callback is associated with the context at construction time, + * and receives calls as the parsing progresses. + * + * All of the callbacks may be ignored and just return 0. + * + * The reasons it might get called, found in @reason, are: + * + * LEJPCB_CONSTRUCTED: The context was just constructed... you might want to + * perform one-time allocation for the life of the context. + * + * LEJPCB_DESTRUCTED: The context is being destructed... if you made any + * allocations at construction-time, you can free them now + * + * LEJPCB_START: Parsing is beginning at the first byte of input + * + * LEJPCB_COMPLETE: Parsing has completed successfully. You'll get a 0 or + * positive return code from lejp_parse indicating the + * amount of unused bytes left in the input buffer + * + * LEJPCB_FAILED: Parsing failed. You'll get a negative error code + * returned from lejp_parse + * + * LEJPCB_PAIR_NAME: When a "name":"value" pair has had the name parsed, + * this callback occurs. You can find the new name at + * the end of ctx->path[] + * + * LEJPCB_VAL_TRUE: The "true" value appeared + * + * LEJPCB_VAL_FALSE: The "false" value appeared + * + * LEJPCB_VAL_NULL: The "null" value appeared + * + * LEJPCB_VAL_NUM_INT: A string representing an integer is in ctx->buf + * + * LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf + * + * LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet + * + * LEJPCB_VAL_STR_CHUNK: We parsed LEJP_STRING_CHUNK -1 bytes of string data in + * ctx->buf, which is as much as we can buffer, so we are + * spilling it. If all your strings are less than + * LEJP_STRING_CHUNK - 1 bytes, you will never see this + * callback. + * + * LEJPCB_VAL_STR_END: String parsing has completed, the last chunk of the + * string is in ctx->buf. + * + * LEJPCB_ARRAY_START: An array started + * + * LEJPCB_ARRAY_END: An array ended + * + * LEJPCB_OBJECT_START: An object started + * + * LEJPCB_OBJECT_END: An object ended + */ +LWS_EXTERN signed char _lejp_callback(struct lejp_ctx *ctx, char reason); + +typedef signed char (*lejp_callback)(struct lejp_ctx *ctx, char reason); + +#ifndef LEJP_MAX_DEPTH +#define LEJP_MAX_DEPTH 12 +#endif +#ifndef LEJP_MAX_INDEX_DEPTH +#define LEJP_MAX_INDEX_DEPTH 5 +#endif +#ifndef LEJP_MAX_PATH +#define LEJP_MAX_PATH 128 +#endif +#ifndef LEJP_STRING_CHUNK +/* must be >= 30 to assemble floats */ +#define LEJP_STRING_CHUNK 255 +#endif + +enum num_flags { + LEJP_SEEN_MINUS = (1 << 0), + LEJP_SEEN_POINT = (1 << 1), + LEJP_SEEN_POST_POINT = (1 << 2), + LEJP_SEEN_EXP = (1 << 3) +}; + +struct _lejp_stack { + char s; /* lejp_state stack*/ + char p; /* path length */ + char i; /* index array length */ + char b; /* user bitfield */ +}; + +struct lejp_ctx { + + /* sorted by type for most compact alignment + * + * pointers + */ + + signed char (*callback)(struct lejp_ctx *ctx, char reason); + void *user; + const char * const *paths; + + /* arrays */ + + struct _lejp_stack st[LEJP_MAX_DEPTH]; + uint16_t i[LEJP_MAX_INDEX_DEPTH]; /* index array */ + uint16_t wild[LEJP_MAX_INDEX_DEPTH]; /* index array */ + char path[LEJP_MAX_PATH]; + char buf[LEJP_STRING_CHUNK]; + + /* int */ + + uint32_t line; + + /* short */ + + uint16_t uni; + + /* char */ + + uint8_t npos; + uint8_t dcount; + uint8_t f; + uint8_t sp; /* stack head */ + uint8_t ipos; /* index stack depth */ + uint8_t ppos; + uint8_t count_paths; + uint8_t path_match; + uint8_t path_match_len; + uint8_t wildcount; +}; + +LWS_VISIBLE LWS_EXTERN void +lejp_construct(struct lejp_ctx *ctx, + signed char (*callback)(struct lejp_ctx *ctx, char reason), + void *user, const char * const *paths, unsigned char paths_count); + +LWS_VISIBLE LWS_EXTERN void +lejp_destruct(struct lejp_ctx *ctx); + +LWS_VISIBLE LWS_EXTERN int +lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len); + +LWS_VISIBLE LWS_EXTERN void +lejp_change_callback(struct lejp_ctx *ctx, + signed char (*callback)(struct lejp_ctx *ctx, char reason)); + +LWS_VISIBLE LWS_EXTERN int +lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len); +//@} + /* * Stats are all uint64_t numbers that start at 0. * Index names here have the convention @@ -5775,9 +7344,9 @@ LWS_VISIBLE LWS_EXTERN void lws_stats_log_dump(struct lws_context *context); #else static LWS_INLINE uint64_t -lws_stats_get(struct lws_context *context, int index) { return 0; } +lws_stats_get(struct lws_context *context, int index) { (void)context; (void)index; return 0; } static LWS_INLINE void -lws_stats_log_dump(struct lws_context *context) { } +lws_stats_log_dump(struct lws_context *context) { (void)context; } #endif #ifdef __cplusplus diff --git a/thirdparty/lws/lws_config.h b/thirdparty/libwebsockets/lws_config.h index 6005d94ec6..7185a806a5 100644 --- a/thirdparty/lws/lws_config.h +++ b/thirdparty/libwebsockets/lws_config.h @@ -14,6 +14,12 @@ #define LWS_INSTALL_DATADIR "/usr/local/share" +#define LWS_ROLE_H1 +#define LWS_ROLE_WS +#define LWS_ROLE_RAW +/* #undef LWS_ROLE_H2 */ +/* #undef LWS_ROLE_CGI */ + /* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL. * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */ /* #undef USE_WOLFSSL */ @@ -25,26 +31,26 @@ #define LWS_WITH_MBEDTLS /* #undef LWS_WITH_POLARSSL */ -/* #undef LWS_WITH_ESP8266 */ /* #undef LWS_WITH_ESP32 */ /* #undef LWS_WITH_PLUGINS */ /* #undef LWS_WITH_NO_LOGS */ /* The Libwebsocket version */ -#define LWS_LIBRARY_VERSION "2.4.2" +#define LWS_LIBRARY_VERSION "3.0.0" -#define LWS_LIBRARY_VERSION_MAJOR 2 -#define LWS_LIBRARY_VERSION_MINOR 4 -#define LWS_LIBRARY_VERSION_PATCH 2 +#define LWS_LIBRARY_VERSION_MAJOR 3 +#define LWS_LIBRARY_VERSION_MINOR 0 +#define LWS_LIBRARY_VERSION_PATCH 0 /* LWS_LIBRARY_VERSION_NUMBER looks like 1005001 for e.g. version 1.5.1 */ #define LWS_LIBRARY_VERSION_NUMBER (LWS_LIBRARY_VERSION_MAJOR*1000000)+(LWS_LIBRARY_VERSION_MINOR*1000)+LWS_LIBRARY_VERSION_PATCH /* The current git commit hash that we're building from */ -#define LWS_BUILD_HASH "8964ce9db75a98e463dfafd2e89f2bc8a95ec6ed" +#define LWS_BUILD_HASH "v2.0.0-948-geaa935a8" -/* Build with OpenSSL support */ +/* Build with OpenSSL support ... alias of LWS_WITH_TLS for compatibility*/ #define LWS_OPENSSL_SUPPORT +#define LWS_WITH_TLS /* The client should load and trust CA root certs it finds in the OS */ /* #undef LWS_SSL_CLIENT_USE_OS_CA_CERTS */ @@ -53,7 +59,13 @@ /* #undef LWS_OPENSSL_CLIENT_CERTS "../share" */ /* Turn off websocket extensions */ -/* #undef LWS_NO_EXTENSIONS */ +#define LWS_WITHOUT_EXTENSIONS + +/* notice if client or server gone */ +/* #undef LWS_WITHOUT_SERVER */ +/* #undef LWS_WITHOUT_CLIENT */ + +#define LWS_WITH_POLL /* Enable libev io loop */ /* #undef LWS_WITH_LIBEV */ @@ -99,8 +111,11 @@ /* #undef LWS_HAVE_SSL_CTX_set1_param */ #define LWS_HAVE_X509_VERIFY_PARAM_set1_host /* #undef LWS_HAVE_RSA_SET0_KEY */ +/* #undef LWS_HAVE_X509_get_key_usage */ +/* #undef LWS_HAVE_SSL_CTX_get0_certificate */ /* #undef LWS_HAVE_UV_VERSION_H */ +/* #undef LWS_HAVE_PTHREAD_H */ /* CGI apis */ /* #undef LWS_WITH_CGI */ @@ -112,7 +127,7 @@ /* #undef LWS_WITH_HTTP_PROXY */ /* HTTP Ranges support */ -#define LWS_WITH_RANGES +/* #undef LWS_WITH_RANGES */ /* Http access log support */ /* #undef LWS_WITH_ACCESS_LOG */ @@ -134,7 +149,7 @@ /* #undef LWS_PLAT_OPTEE */ /* ZIP FOPS */ -#define LWS_WITH_ZIP_FOPS +/* #undef LWS_WITH_ZIP_FOPS */ #define LWS_HAVE_STDINT_H /* #undef LWS_AVOID_SIGPIPE_IGN */ @@ -151,11 +166,26 @@ /* #undef LWS_HAVE__ATOI64 */ /* #undef LWS_HAVE__STAT32I64 */ +/* #undef LWS_WITH_JWS */ +/* #undef LWS_WITH_ACME */ +/* #undef LWS_WITH_SELFTESTS */ + +#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) +#define LWS_HAVE_MALLOC_H +#endif + +#if !defined(IPHONE_ENABLED) && !defined(OSX_ENABLED) +#define LWS_HAVE_PIPE2 +#endif + /* OpenSSL various APIs */ #define LWS_HAVE_TLS_CLIENT_METHOD /* #undef LWS_HAVE_TLSV1_2_CLIENT_METHOD */ /* #undef LWS_HAVE_SSL_SET_INFO_CALLBACK */ +/* #undef LWS_HAVE_SSL_EXTRA_CHAIN_CERTS */ +/* #undef LWS_HAVE_SSL_get0_alpn_selected */ +/* #undef LWS_HAVE_SSL_set_alpn_protos */ #define LWS_HAS_INTPTR_T diff --git a/thirdparty/lws/lws_config_private.h b/thirdparty/libwebsockets/lws_config_private.h index 475d1bd3f8..9d04078fef 100644 --- a/thirdparty/lws/lws_config_private.h +++ b/thirdparty/libwebsockets/lws_config_private.h @@ -100,6 +100,8 @@ /* Define to 1 if you have the <unistd.h> header file. */ #define LWS_HAVE_UNISTD_H +#define LWS_HAVE_TCP_USER_TIMEOUT + /* Define to 1 if you have the `vfork' function. */ #define LWS_HAVE_VFORK diff --git a/thirdparty/lws/misc/base64-decode.c b/thirdparty/libwebsockets/misc/base64-decode.c index c8f11d21b8..64b84d78fa 100644 --- a/thirdparty/lws/misc/base64-decode.c +++ b/thirdparty/libwebsockets/misc/base64-decode.c @@ -40,15 +40,18 @@ #include <stdio.h> #include <string.h> -#include "private-libwebsockets.h" +#include "core/private.h" -static const char encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +static const char encode_orig[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char encode_url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789-_"; static const char decode[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW" "$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; -LWS_VISIBLE int -lws_b64_encode_string(const char *in, int in_len, char *out, int out_size) +static int +_lws_b64_encode_string(const char *encode, const char *in, int in_len, + char *out, int out_size) { unsigned char triple[3]; int i; @@ -89,26 +92,47 @@ lws_b64_encode_string(const char *in, int in_len, char *out, int out_size) return done; } +LWS_VISIBLE int +lws_b64_encode_string(const char *in, int in_len, char *out, int out_size) +{ + return _lws_b64_encode_string(encode_orig, in, in_len, out, out_size); +} + +LWS_VISIBLE int +lws_b64_encode_string_url(const char *in, int in_len, char *out, int out_size) +{ + return _lws_b64_encode_string(encode_url, in, in_len, out, out_size); +} + /* * returns length of decoded string in out, or -1 if out was too small * according to out_size + * + * Only reads up to in_len chars, otherwise if in_len is -1 on entry reads until + * the first NUL in the input. */ -LWS_VISIBLE int -lws_b64_decode_string(const char *in, char *out, int out_size) +static int +_lws_b64_decode_string(const char *in, int in_len, char *out, int out_size) { int len, i, c = 0, done = 0; unsigned char v, quad[4]; - while (*in) { + while (in_len && *in) { len = 0; - for (i = 0; i < 4 && *in; i++) { + for (i = 0; i < 4 && in_len && *in; i++) { v = 0; c = 0; - while (*in && !v) { + while (in_len && *in && !v) { c = v = *in++; + in_len--; + /* support the url base64 variant too */ + if (v == '-') + c = v = '+'; + if (v == '_') + c = v = '/'; v = (v < 43 || v > 122) ? 0 : decode[v - 43]; if (v) v = (v == '$') ? 0 : v - 61; @@ -131,7 +155,7 @@ lws_b64_decode_string(const char *in, char *out, int out_size) * bytes." (wikipedia) */ - if (!*in && c == '=') + if ((!in_len || !*in) && c == '=') len--; if (len >= 2) @@ -152,6 +176,18 @@ lws_b64_decode_string(const char *in, char *out, int out_size) return done; } +LWS_VISIBLE int +lws_b64_decode_string(const char *in, char *out, int out_size) +{ + return _lws_b64_decode_string(in, -1, out, out_size); +} + +LWS_VISIBLE int +lws_b64_decode_string_len(const char *in, int in_len, char *out, int out_size) +{ + return _lws_b64_decode_string(in, in_len, out, out_size); +} + #if 0 int lws_b64_selftest(void) diff --git a/thirdparty/lws/misc/getifaddrs.c b/thirdparty/libwebsockets/misc/getifaddrs.c index 4f42ab4595..735b899f48 100644 --- a/thirdparty/lws/misc/getifaddrs.c +++ b/thirdparty/libwebsockets/misc/getifaddrs.c @@ -43,7 +43,7 @@ #include <string.h> #include <sys/ioctl.h> #include <unistd.h> -#include "private-libwebsockets.h" +#include "core/private.h" #ifdef LWS_HAVE_SYS_SOCKIO_H #include <sys/sockio.h> diff --git a/thirdparty/lws/misc/getifaddrs.h b/thirdparty/libwebsockets/misc/getifaddrs.h index d26670c082..d26670c082 100644 --- a/thirdparty/lws/misc/getifaddrs.h +++ b/thirdparty/libwebsockets/misc/getifaddrs.h diff --git a/thirdparty/lws/misc/lejp.c b/thirdparty/libwebsockets/misc/lejp.c index 38efa8b122..00d350f81e 100644 --- a/thirdparty/lws/misc/lejp.c +++ b/thirdparty/libwebsockets/misc/lejp.c @@ -1,14 +1,26 @@ /* * Lightweight Embedded JSON Parser * - * Copyright (C) 2013 Andy Green <andy@warmcat.com> - * This code is licensed under LGPL 2.1 - * http://www.gnu.org/licenses/lgpl-2.1.html + * Copyright (C) 2013-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 */ +#include <libwebsockets.h> #include <string.h> -#include "lejp.h" - #include <stdio.h> /** @@ -148,7 +160,8 @@ lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len) n = ctx->wild[wildcard]; - while (--len && n < ctx->ppos && (n == ctx->wild[wildcard] || ctx->path[n] != '.')) + while (--len && n < ctx->ppos && + (n == ctx->wild[wildcard] || ctx->path[n] != '.')) *dest++ = ctx->path[n++]; *dest = '\0'; @@ -186,7 +199,6 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) while (len--) { c = *json++; - s = ctx->st[ctx->sp].s; /* skip whitespace unless we should care */ @@ -411,6 +423,26 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) } goto add_stack_level; + case ']': + /* pop */ + ctx->sp--; + if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) { + ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY; + goto reject; + } + /* drop the path [n] bit */ + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + goto array_end; + case 't': /* true */ ctx->uni = 0; ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; @@ -582,8 +614,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) goto reject; } /* drop the path [n] bit */ - ctx->ppos = ctx->st[ctx->sp - 1].p; - ctx->ipos = ctx->st[ctx->sp - 1].i; + if (ctx->sp) { + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + } ctx->path[ctx->ppos] = '\0'; if (ctx->path_match && ctx->ppos <= ctx->path_match_len) @@ -609,8 +643,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) } /* pop */ ctx->sp--; - ctx->ppos = ctx->st[ctx->sp - 1].p; - ctx->ipos = ctx->st[ctx->sp - 1].i; + if (ctx->sp) { + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + } ctx->path[ctx->ppos] = '\0'; if (ctx->path_match && ctx->ppos <= ctx->path_match_len) @@ -631,6 +667,7 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) goto reject; case LEJP_MP_ARRAY_END: +array_end: ctx->path[ctx->ppos] = '\0'; if (c == ',') { /* increment this stack level's index */ diff --git a/thirdparty/lws/misc/sha-1.c b/thirdparty/libwebsockets/misc/sha-1.c index 50205a0100..2e4db52693 100644 --- a/thirdparty/lws/misc/sha-1.c +++ b/thirdparty/libwebsockets/misc/sha-1.c @@ -32,7 +32,7 @@ * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org> */ -#include "private-libwebsockets.h" +#include "core/private.h" #ifdef LWS_HAVE_SYS_TYPES_H #include <sys/types.h> diff --git a/thirdparty/lws/plat/lws-plat-unix.c b/thirdparty/libwebsockets/plat/lws-plat-unix.c index a51e67bb81..bacc6af647 100644 --- a/thirdparty/lws/plat/lws-plat-unix.c +++ b/thirdparty/libwebsockets/plat/lws-plat-unix.c @@ -19,7 +19,8 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#define _GNU_SOURCE +#include "core/private.h" #include <pwd.h> #include <grp.h> @@ -29,6 +30,56 @@ #endif #include <dirent.h> +int +lws_plat_socket_offset(void) +{ + return 0; +} + +int +lws_plat_pipe_create(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + +#if defined(LWS_HAVE_PIPE2) + return pipe2(pt->dummy_pipe_fds, O_NONBLOCK); +#else + return pipe(pt->dummy_pipe_fds); +#endif +} + +int +lws_plat_pipe_signal(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char buf = 0; + int n; + + n = write(pt->dummy_pipe_fds[1], &buf, 1); + + return n != 1; +} + +void +lws_plat_pipe_close(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (pt->dummy_pipe_fds[0] && pt->dummy_pipe_fds[0] != -1) + close(pt->dummy_pipe_fds[0]); + if (pt->dummy_pipe_fds[1] && pt->dummy_pipe_fds[1] != -1) + close(pt->dummy_pipe_fds[1]); + + pt->dummy_pipe_fds[0] = pt->dummy_pipe_fds[1] = -1; +} + +#ifdef __QNX__ +# include "netinet/tcp_var.h" +# define TCP_KEEPINTVL TCPCTL_KEEPINTVL +# define TCP_KEEPIDLE TCPCTL_KEEPIDLE +# define TCP_KEEPCNT TCPCTL_KEEPCNT +#endif + unsigned long long time_in_microseconds(void) { struct timeval tv; @@ -52,6 +103,10 @@ lws_send_pipe_choked(struct lws *wsi) #if defined(LWS_WITH_HTTP2) wsi_eff = lws_get_network_wsi(wsi); #endif + + /* the fact we checked implies we avoided back-to-back writes */ + wsi_eff->could_have_pending = 0; + /* treat the fact we got a truncated send pending as if we're choked */ if (wsi_eff->trunc_len) return 1; @@ -77,29 +132,6 @@ lws_poll_listen_fd(struct lws_pollfd *fd) return poll(fd, 1, 0); } -LWS_VISIBLE void -lws_cancel_service_pt(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - char buf = 0; - - if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1) - lwsl_err("Cannot write to dummy pipe"); -} - -LWS_VISIBLE void -lws_cancel_service(struct lws_context *context) -{ - struct lws_context_per_thread *pt = &context->pt[0]; - char buf = 0, m = context->count_threads; - - while (m--) { - if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1) - lwsl_err("Cannot write to dummy pipe"); - pt++; - } -} - LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line) { int syslog_level = LOG_DEBUG; @@ -124,9 +156,10 @@ LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line) LWS_VISIBLE LWS_EXTERN int _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) { + volatile struct lws_foreign_thread_pollfd *ftp, *next; + volatile struct lws_context_per_thread *vpt; struct lws_context_per_thread *pt; int n = -1, m, c; - char buf; /* stay dead once we are dead */ @@ -134,15 +167,15 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) return 1; pt = &context->pt[tsi]; + vpt = (volatile struct lws_context_per_thread *)pt; lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1); if (timeout_ms < 0) goto faked_service; - lws_libev_run(context, tsi); - lws_libuv_run(context, tsi); - lws_libevent_run(context, tsi); + if (context->event_loop_ops->run_pt) + context->event_loop_ops->run_pt(context, tsi); if (!context->service_tid_detected) { struct lws _lws; @@ -169,15 +202,73 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) timeout_ms = 0; } + if (timeout_ms) { + lws_pt_lock(pt, __func__); + /* don't stay in poll wait longer than next hr timeout */ + lws_usec_t t = __lws_hrtimer_service(pt); + if ((lws_usec_t)timeout_ms * 1000 > t) + timeout_ms = t / 1000; + lws_pt_unlock(pt); + } + + vpt->inside_poll = 1; + lws_memory_barrier(); n = poll(pt->fds, pt->fds_count, timeout_ms); + vpt->inside_poll = 0; + lws_memory_barrier(); -#ifdef LWS_OPENSSL_SUPPORT - if (!n && !pt->rx_draining_ext_list && - !lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) { -#else - if (!pt->rx_draining_ext_list && !n) /* poll timeout */ { + /* Collision will be rare and brief. Just spin until it completes */ + while (vpt->foreign_spinlock) + ; + + /* + * At this point we are not inside a foreign thread pollfd change, + * and we have marked ourselves as outside the poll() wait. So we + * are the only guys that can modify the lws_foreign_thread_pollfd + * list on the pt. Drain the list and apply the changes to the + * affected pollfds in the correct order. + */ + + lws_pt_lock(pt, __func__); + + ftp = vpt->foreign_pfd_list; + //lwsl_notice("cleared list %p\n", ftp); + while (ftp) { + struct lws *wsi; + struct lws_pollfd *pfd; + + next = ftp->next; + pfd = &vpt->fds[ftp->fd_index]; + if (lws_socket_is_valid(pfd->fd)) { + wsi = wsi_from_fd(context, pfd->fd); + if (wsi) + __lws_change_pollfd(wsi, ftp->_and, ftp->_or); + } + lws_free((void *)ftp); + ftp = next; + } + vpt->foreign_pfd_list = NULL; + lws_memory_barrier(); + + /* we have come out of a poll wait... check the hrtimer list */ + + __lws_hrtimer_service(pt); + + lws_pt_unlock(pt); + + m = 0; +#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS) + m |= !!pt->ws.rx_draining_ext_list; #endif + + if (pt->context->tls_ops && + pt->context->tls_ops->fake_POLLIN_for_buffered) + m |= pt->context->tls_ops->fake_POLLIN_for_buffered(pt); + + if (!m && !n) { /* nothing to do */ lws_service_fd_tsi(context, NULL, tsi); + lws_service_do_ripe_rxflow(pt); + return 0; } @@ -194,18 +285,12 @@ faked_service: c = n; /* any socket with events to service? */ - for (n = 0; n < pt->fds_count && c; n++) { + for (n = 0; n < (int)pt->fds_count && c; n++) { if (!pt->fds[n].revents) continue; c--; - if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) { - if (read(pt->fds[n].fd, &buf, 1) != 1) - lwsl_err("Cannot read from dummy pipe."); - continue; - } - m = lws_service_fd_tsi(context, &pt->fds[n], tsi); if (m < 0) return -1; @@ -214,6 +299,8 @@ faked_service: n--; } + lws_service_do_ripe_rxflow(pt); + return 0; } @@ -262,6 +349,14 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd) */ #else /* set the keepalive conditions we want on it too */ + +#if defined(LWS_HAVE_TCP_USER_TIMEOUT) + optval = 1000 * (vhost->ka_time + + (vhost->ka_interval * vhost->ka_probes)); + if (setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, + (const void *)&optval, optlen) < 0) + return 1; +#endif optval = vhost->ka_time; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, (const void *)&optval, optlen) < 0) @@ -292,7 +387,7 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd) /* Disable Nagle */ optval = 1; -#if defined (__sun) +#if defined (__sun) || defined(__QNX__) if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0) return 1; #elif !defined(__APPLE__) && \ @@ -317,7 +412,7 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd) #if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) static void -_lws_plat_apply_caps(int mode, cap_value_t *cv, int count) +_lws_plat_apply_caps(int mode, const cap_value_t *cv, int count) { cap_t caps; @@ -334,7 +429,7 @@ _lws_plat_apply_caps(int mode, cap_value_t *cv, int count) #endif LWS_VISIBLE void -lws_plat_drop_app_privileges(struct lws_context_creation_info *info) +lws_plat_drop_app_privileges(const struct lws_context_creation_info *info) { #if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) int n; @@ -449,8 +544,8 @@ lws_plat_plugins_init(struct lws_context * context, const char * const *d) } plugin->list = context->plugin_list; context->plugin_list = plugin; - strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1); - plugin->name[sizeof(plugin->name) - 1] = '\0'; + lws_strncpy(plugin->name, namelist[i]->d_name, + sizeof(plugin->name)); plugin->l = l; plugin->caps = lcaps; context->plugin_protocol_count += lcaps.count_protocols; @@ -543,9 +638,6 @@ lws_plat_context_early_destroy(struct lws_context *context) LWS_VISIBLE void lws_plat_context_late_destroy(struct lws_context *context) { - struct lws_context_per_thread *pt = &context->pt[0]; - int m = context->count_threads; - #ifdef LWS_WITH_PLUGINS if (context->plugin_list) lws_plat_plugins_destroy(context); @@ -554,13 +646,6 @@ lws_plat_context_late_destroy(struct lws_context *context) if (context->lws_lookup) lws_free(context->lws_lookup); - while (m--) { - if (pt->dummy_pipe_fds[0]) - close(pt->dummy_pipe_fds[0]); - if (pt->dummy_pipe_fds[1]) - close(pt->dummy_pipe_fds[1]); - pt++; - } if (!context->fd_random) lwsl_err("ZERO RANDOM FD\n"); if (context->fd_random != LWS_INVALID_FILE) @@ -573,7 +658,7 @@ LWS_VISIBLE int lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, size_t addrlen) { - int rc = -1; + int rc = LWS_ITOSA_NOT_EXIST; struct ifaddrs *ifr; struct ifaddrs *ifc; @@ -586,12 +671,19 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, if (!ifc->ifa_addr) continue; - lwsl_info(" interface %s vs %s\n", ifc->ifa_name, ifname); + lwsl_debug(" interface %s vs %s (fam %d) ipv6 %d\n", ifc->ifa_name, ifname, ifc->ifa_addr->sa_family, ipv6); if (strcmp(ifc->ifa_name, ifname)) continue; switch (ifc->ifa_addr->sa_family) { +#if defined(AF_PACKET) + case AF_PACKET: + /* interface exists but is not usable */ + rc = LWS_ITOSA_NOT_USABLE; + continue; +#endif + case AF_INET: #ifdef LWS_WITH_IPV6 if (ipv6) { @@ -619,20 +711,20 @@ lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, default: continue; } - rc = 0; + rc = LWS_ITOSA_USABLE; } freeifaddrs(ifr); - if (rc == -1) { + if (rc) { /* check if bind to IP address */ #ifdef LWS_WITH_IPV6 if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) - rc = 0; + rc = LWS_ITOSA_USABLE; else #endif if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1) - rc = 0; + rc = LWS_ITOSA_USABLE; } return rc; @@ -643,9 +735,8 @@ lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi) { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ); - lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ); - lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ); + if (context->event_loop_ops->io) + context->event_loop_ops->io(wsi, LWS_EV_START | LWS_EV_READ); pt->fds[pt->fds_count++].revents = 0; } @@ -656,9 +747,9 @@ lws_plat_delete_socket_from_fds(struct lws_context *context, { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); - lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); - lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); + if (context->event_loop_ops->io) + context->event_loop_ops->io(wsi, + LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); pt->fds_count--; } @@ -739,7 +830,8 @@ _lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset) { lws_fileofs_t r; - if (offset > 0 && offset > fop_fd->len - fop_fd->pos) + if (offset > 0 && + offset > (lws_fileofs_t)fop_fd->len - (lws_fileofs_t)fop_fd->pos) offset = fop_fd->len - fop_fd->pos; if ((lws_fileofs_t)fop_fd->pos + offset < 0) @@ -793,13 +885,11 @@ _lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount, return 0; } - LWS_VISIBLE int lws_plat_init(struct lws_context *context, - struct lws_context_creation_info *info) + const struct lws_context_creation_info *info) { - struct lws_context_per_thread *pt = &context->pt[0]; - int n = context->count_threads, fd; + int fd; /* master context has the global fd lookup array */ context->lws_lookup = lws_zalloc(sizeof(struct lws *) * @@ -821,26 +911,6 @@ lws_plat_init(struct lws_context *context, return 1; } - if (!lws_libev_init_fd_table(context) && - !lws_libuv_init_fd_table(context) && - !lws_libevent_init_fd_table(context)) { - /* otherwise libev/uv/event handled it instead */ - - while (n--) { - if (pipe(pt->dummy_pipe_fds)) { - lwsl_err("Unable to create pipe\n"); - return 1; - } - - /* use the read end of pipe as first item */ - pt->fds[0].fd = pt->dummy_pipe_fds[0]; - pt->fds[0].events = LWS_POLLIN; - pt->fds[0].revents = 0; - pt->fds_count = 1; - pt++; - } - } - #ifdef LWS_WITH_PLUGINS if (info->plugin_dirs) lws_plat_plugins_init(context, info->plugin_dirs); @@ -848,3 +918,52 @@ lws_plat_init(struct lws_context *context, return 0; } + +LWS_VISIBLE int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + int n; + + n = write(fd, buf, len); + + fsync(fd); + lseek(fd, 0, SEEK_SET); + + return n != len; +} + +LWS_VISIBLE int +lws_plat_write_file(const char *filename, void *buf, int len) +{ + int m, fd; + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + if (fd == -1) + return 1; + + m = write(fd, buf, len); + close(fd); + + return m != len; +} + +LWS_VISIBLE int +lws_plat_read_file(const char *filename, void *buf, int len) +{ + int n, fd = open(filename, O_RDONLY); + if (fd == -1) + return -1; + + n = read(fd, buf, len); + close(fd); + + return n; +} + +LWS_VISIBLE int +lws_plat_recommended_rsa_bits(void) +{ + return 4096; +} diff --git a/thirdparty/lws/plat/lws-plat-win.c b/thirdparty/libwebsockets/plat/lws-plat-win.c index f5b178ce85..948db62896 100644 --- a/thirdparty/lws/plat/lws-plat-win.c +++ b/thirdparty/libwebsockets/plat/lws-plat-win.c @@ -1,7 +1,34 @@ #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS #define _WINSOCK_DEPRECATED_NO_WARNINGS #endif -#include "private-libwebsockets.h" +#include "core/private.h" + +int +lws_plat_socket_offset(void) +{ + return 0; +} + +int +lws_plat_pipe_create(struct lws *wsi) +{ + return 1; +} + +int +lws_plat_pipe_signal(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + WSASetEvent(pt->events[0]); /* trigger the cancel event */ + + return 0; +} + +void +lws_plat_pipe_close(struct lws *wsi) +{ +} unsigned long long time_in_microseconds() @@ -19,9 +46,10 @@ time_in_microseconds() #endif /* - * As per Windows documentation for FILETIME, copy the resulting FILETIME structure to a - * ULARGE_INTEGER structure using memcpy (using memcpy instead of direct assignment can - * prevent alignment faults on 64-bit Windows). + * As per Windows documentation for FILETIME, copy the resulting + * FILETIME structure to a ULARGE_INTEGER structure using memcpy + * (using memcpy instead of direct assignment can prevent alignment + * faults on 64-bit Windows). */ memcpy(&datetime, &filetime, sizeof(datetime)); @@ -81,7 +109,7 @@ delete_from_fd(struct lws_context *context, lws_sockfd_type fd) if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) { while (n < context->fd_hashtable[h].length) { context->fd_hashtable[h].wsi[n] = - context->fd_hashtable[h].wsi[n + 1]; + context->fd_hashtable[h].wsi[n + 1]; n++; } context->fd_hashtable[h].length--; @@ -94,8 +122,8 @@ delete_from_fd(struct lws_context *context, lws_sockfd_type fd) return 1; } -LWS_VISIBLE int lws_get_random(struct lws_context *context, - void *buf, int len) +LWS_VISIBLE int +lws_get_random(struct lws_context *context, void *buf, int len) { int n; char *p = (char *)buf; @@ -106,16 +134,25 @@ LWS_VISIBLE int lws_get_random(struct lws_context *context, return n; } -LWS_VISIBLE int lws_send_pipe_choked(struct lws *wsi) -{ +LWS_VISIBLE int +lws_send_pipe_choked(struct lws *wsi) +{ struct lws *wsi_eff = wsi; + +#if defined(LWS_WITH_HTTP2) + wsi_eff = lws_get_network_wsi(wsi); +#endif + /* the fact we checked implies we avoided back-to-back writes */ + wsi_eff->could_have_pending = 0; + /* treat the fact we got a truncated send pending as if we're choked */ - if (wsi->trunc_len) + if (wsi_eff->trunc_len) return 1; - return (int)wsi->sock_send_blocking; + return (int)wsi_eff->sock_send_blocking; } -LWS_VISIBLE int lws_poll_listen_fd(struct lws_pollfd *fd) +LWS_VISIBLE int +lws_poll_listen_fd(struct lws_pollfd *fd) { fd_set readfds; struct timeval tv = { 0, 0 }; @@ -125,29 +162,11 @@ LWS_VISIBLE int lws_poll_listen_fd(struct lws_pollfd *fd) FD_ZERO(&readfds); FD_SET(fd->fd, &readfds); - return select(fd->fd + 1, &readfds, NULL, NULL, &tv); + return select(((int)fd->fd) + 1, &readfds, NULL, NULL, &tv); } LWS_VISIBLE void -lws_cancel_service(struct lws_context *context) -{ - struct lws_context_per_thread *pt = &context->pt[0]; - int n = context->count_threads; - - while (n--) { - WSASetEvent(pt->events[0]); - pt++; - } -} - -LWS_VISIBLE void -lws_cancel_service_pt(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - WSASetEvent(pt->events[0]); -} - -LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line) +lwsl_emit_syslog(int level, const char *line) { lwsl_emit_stderr(level, line); } @@ -182,9 +201,8 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) context->service_tid_detected = 1; } - if (timeout_ms < 0) - { - if (lws_service_flag_pending(context, tsi)) { + if (timeout_ms < 0) { + if (lws_service_flag_pending(context, tsi)) { /* any socket with events to service? */ for (n = 0; n < (int)pt->fds_count; n++) { if (!pt->fds[n].revents) @@ -201,6 +219,9 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) return 0; } + if (context->event_loop_ops->run_pt) + context->event_loop_ops->run_pt(context, tsi); + for (i = 0; i < pt->fds_count; ++i) { pfd = &pt->fds[i]; @@ -220,6 +241,9 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) if (n) i--; + /* + * any wsi has truncated, force him signalled + */ if (wsi->trunc_len) WSASetEvent(pt->events[0]); } @@ -236,29 +260,44 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) timeout_ms = 0; } - ev = WSAWaitForMultipleEvents( 1, pt->events , FALSE, timeout_ms, FALSE); + if (timeout_ms) { + lws_pt_lock(pt, __func__); + /* don't stay in poll wait longer than next hr timeout */ + lws_usec_t t = __lws_hrtimer_service(pt); + + if ((lws_usec_t)timeout_ms * 1000 > t) + timeout_ms = (int)(t / 1000); + lws_pt_unlock(pt); + } + + ev = WSAWaitForMultipleEvents(1, pt->events, FALSE, timeout_ms, FALSE); if (ev == WSA_WAIT_EVENT_0) { - unsigned int eIdx; + unsigned int eIdx, err; WSAResetEvent(pt->events[0]); + if (pt->context->tls_ops && + pt->context->tls_ops->fake_POLLIN_for_buffered) + pt->context->tls_ops->fake_POLLIN_for_buffered(pt); + for (eIdx = 0; eIdx < pt->fds_count; ++eIdx) { - if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, 0, &networkevents) == SOCKET_ERROR) { - lwsl_err("WSAEnumNetworkEvents() failed with error %d\n", LWS_ERRNO); + if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, 0, + &networkevents) == SOCKET_ERROR) { + lwsl_err("WSAEnumNetworkEvents() failed " + "with error %d\n", LWS_ERRNO); return -1; } pfd = &pt->fds[eIdx]; pfd->revents = (short)networkevents.lNetworkEvents; + err = networkevents.iErrorCode[FD_CONNECT_BIT]; + if ((networkevents.lNetworkEvents & FD_CONNECT) && - networkevents.iErrorCode[FD_CONNECT_BIT] && - networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EALREADY && - networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EINPROGRESS && - networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EWOULDBLOCK && - networkevents.iErrorCode[FD_CONNECT_BIT] != WSAEINVAL) { - lwsl_debug("Unable to connect errno=%d\n", - networkevents.iErrorCode[FD_CONNECT_BIT]); + err && err != LWS_EALREADY && + err != LWS_EINPROGRESS && err != LWS_EWOULDBLOCK && + err != WSAEINVAL) { + lwsl_debug("Unable to connect errno=%d\n", err); pfd->revents |= LWS_POLLHUP; } @@ -269,21 +308,19 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) } /* if something closed, retry this slot */ if (pfd->revents & LWS_POLLHUP) - --eIdx; + --eIdx; - if( pfd->revents != 0 ) { + if (pfd->revents) lws_service_fd_tsi(context, pfd, tsi); - - } } } context->service_tid = 0; - if (ev == WSA_WAIT_TIMEOUT) { + if (ev == WSA_WAIT_TIMEOUT) lws_service_fd(context, NULL); - } - return 0;; + + return 0; } LWS_VISIBLE int @@ -309,7 +346,7 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd) /* enable keepalive on this socket */ optval = 1; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, - (const char *)&optval, optlen) < 0) + (const char *)&optval, optlen) < 0) return 1; alive.onoff = TRUE; @@ -317,7 +354,7 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd) alive.keepaliveinterval = vhost->ka_interval; if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), - NULL, 0, &dwBytesRet, NULL, NULL)) + NULL, 0, &dwBytesRet, NULL, NULL)) return 1; } @@ -343,7 +380,7 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd) } LWS_VISIBLE void -lws_plat_drop_app_privileges(struct lws_context_creation_info *info) +lws_plat_drop_app_privileges(const struct lws_context_creation_info *info) { } @@ -406,7 +443,7 @@ lws_interface_to_sa(int ipv6, if (ipv6) { if (lws_plat_inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) { - return 0; + return LWS_ITOSA_USABLE; } } #endif @@ -420,11 +457,11 @@ lws_interface_to_sa(int ipv6, } if (address == INADDR_NONE) - return -1; + return LWS_ITOSA_NOT_EXIST; - addr->sin_addr.s_addr = (lws_intptr_t)address; + addr->sin_addr.s_addr = (unsigned long)(lws_intptr_t)address; - return 0; + return LWS_ITOSA_USABLE; } LWS_VISIBLE void @@ -542,7 +579,7 @@ LWS_VISIBLE int lws_plat_inet_pton(int af, const char *src, void *dst) { WCHAR *buffer; - DWORD bufferlen = strlen(src) + 1; + DWORD bufferlen = (int)strlen(src) + 1; BOOL ok = FALSE; buffer = lws_malloc(bufferlen * 2, "inet_pton"); @@ -692,7 +729,7 @@ _lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount, LWS_VISIBLE int lws_plat_init(struct lws_context *context, - struct lws_context_creation_info *info) + const struct lws_context_creation_info *info) { struct lws_context_per_thread *pt = &context->pt[0]; int i, n = context->count_threads; @@ -715,7 +752,7 @@ lws_plat_init(struct lws_context *context, } pt->fds_count = 0; - pt->events[0] = WSACreateEvent(); + pt->events[0] = WSACreateEvent(); /* the cancel event */ pt++; } @@ -743,3 +780,50 @@ int fork(void) exit(0); } +LWS_VISIBLE int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + int n; + + n = write(fd, buf, len); + + lseek(fd, 0, SEEK_SET); + + return n != len; +} + +LWS_VISIBLE int +lws_plat_write_file(const char *filename, void *buf, int len) +{ + int m, fd; + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + if (fd == -1) + return -1; + + m = write(fd, buf, len); + close(fd); + + return m != len; +} + +LWS_VISIBLE int +lws_plat_read_file(const char *filename, void *buf, int len) +{ + int n, fd = open(filename, O_RDONLY); + if (fd == -1) + return -1; + + n = read(fd, buf, len); + close(fd); + + return n; +} + +LWS_VISIBLE int +lws_plat_recommended_rsa_bits(void) +{ + return 4096; +} diff --git a/thirdparty/libwebsockets/roles/h1/ops-h1.c b/thirdparty/libwebsockets/roles/h1/ops-h1.c new file mode 100644 index 0000000000..d3b16f4d1f --- /dev/null +++ b/thirdparty/libwebsockets/roles/h1/ops-h1.c @@ -0,0 +1,687 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + + +/* + * We have to take care about parsing because the headers may be split + * into multiple fragments. They may contain unknown headers with arbitrary + * argument lengths. So, we parse using a single-character at a time state + * machine that is completely independent of packet size. + * + * Returns <0 for error or length of chars consumed from buf (up to len) + */ + +int +lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len) +{ + unsigned char *last_char, *oldbuf = buf; + lws_filepos_t body_chunk_len; + size_t n; + + // lwsl_notice("%s: h1 path: wsi state 0x%x\n", __func__, lwsi_state(wsi)); + + switch (lwsi_state(wsi)) { + + case LRS_ISSUING_FILE: + return 0; + + case LRS_ESTABLISHED: + + if (lwsi_role_ws(wsi)) + goto ws_mode; + + if (lwsi_role_client(wsi)) + break; + + wsi->hdr_parsing_completed = 0; + + /* fallthru */ + + case LRS_HEADERS: + if (!wsi->http.ah) { + lwsl_err("%s: LRS_HEADERS: NULL ah\n", __func__); + assert(0); + } + lwsl_parser("issuing %d bytes to parser\n", (int)len); +#if defined(LWS_ROLE_WS) && !defined(LWS_NO_CLIENT) + if (lws_ws_handshake_client(wsi, &buf, (size_t)len)) + goto bail; +#endif + last_char = buf; + if (lws_handshake_server(wsi, &buf, (size_t)len)) + /* Handshake indicates this session is done. */ + goto bail; + + /* we might have transitioned to RAW */ + if (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file) + /* we gave the read buffer to RAW handler already */ + goto read_ok; + + /* + * It's possible that we've exhausted our data already, or + * rx flow control has stopped us dealing with this early, + * but lws_handshake_server doesn't update len for us. + * Figure out how much was read, so that we can proceed + * appropriately: + */ + len -= (buf - last_char); +// lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len); + + if (!wsi->hdr_parsing_completed) + /* More header content on the way */ + goto read_ok; + + switch (lwsi_state(wsi)) { + case LRS_ESTABLISHED: + case LRS_HEADERS: + goto read_ok; + case LRS_ISSUING_FILE: + goto read_ok; + case LRS_BODY: + wsi->http.rx_content_remain = + wsi->http.rx_content_length; + if (wsi->http.rx_content_remain) + goto http_postbody; + + /* there is no POST content */ + goto postbody_completion; + default: + break; + } + break; + + case LRS_BODY: +http_postbody: + lwsl_debug("%s: http post body: remain %d\n", __func__, + (int)wsi->http.rx_content_remain); + while (len && wsi->http.rx_content_remain) { + /* Copy as much as possible, up to the limit of: + * what we have in the read buffer (len) + * remaining portion of the POST body (content_remain) + */ + body_chunk_len = min(wsi->http.rx_content_remain, len); + wsi->http.rx_content_remain -= body_chunk_len; + len -= body_chunk_len; +#ifdef LWS_WITH_CGI + if (wsi->http.cgi) { + struct lws_cgi_args args; + + args.ch = LWS_STDIN; + args.stdwsi = &wsi->http.cgi->stdwsi[0]; + args.data = buf; + args.len = body_chunk_len; + + /* returns how much used */ + n = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_CGI_STDIN_DATA, + wsi->user_space, + (void *)&args, 0); + if ((int)n < 0) + goto bail; + } else { +#endif + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY, wsi->user_space, + buf, (size_t)body_chunk_len); + if (n) + goto bail; + n = (size_t)body_chunk_len; +#ifdef LWS_WITH_CGI + } +#endif + buf += n; + + if (wsi->http.rx_content_remain) { + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + wsi->context->timeout_secs); + break; + } + /* he sent all the content in time */ +postbody_completion: +#ifdef LWS_WITH_CGI + /* + * If we're running a cgi, we can't let him off the + * hook just because he sent his POST data + */ + if (wsi->http.cgi) + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, + wsi->context->timeout_secs); + else +#endif + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); +#ifdef LWS_WITH_CGI + if (!wsi->http.cgi) +#endif + { + lwsl_info("HTTP_BODY_COMPLETION: %p (%s)\n", + wsi, wsi->protocol->name); + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY_COMPLETION, + wsi->user_space, NULL, 0); + if (n) + goto bail; + + if (wsi->http2_substream) + lwsi_set_state(wsi, LRS_ESTABLISHED); + } + + break; + } + break; + + case LRS_AWAITING_CLOSE_ACK: + case LRS_WAITING_TO_SEND_CLOSE: + case LRS_SHUTDOWN: + +ws_mode: +#if !defined(LWS_NO_CLIENT) && defined(LWS_ROLE_WS) + // lwsl_notice("%s: ws_mode\n", __func__); + if (lws_ws_handshake_client(wsi, &buf, (size_t)len)) + goto bail; +#endif +#if defined(LWS_ROLE_WS) + if (lwsi_role_ws(wsi) && lwsi_role_server(wsi) && + /* + * for h2 we are on the swsi + */ + lws_parse_ws(wsi, &buf, (size_t)len) < 0) { + lwsl_info("%s: lws_parse_ws bailed\n", __func__); + goto bail; + } +#endif + // lwsl_notice("%s: ws_mode: buf moved on by %d\n", __func__, + // lws_ptr_diff(buf, oldbuf)); + break; + + case LRS_DEFERRING_ACTION: + lwsl_debug("%s: LRS_DEFERRING_ACTION\n", __func__); + break; + + case LRS_SSL_ACK_PENDING: + break; + + case LRS_DEAD_SOCKET: + lwsl_err("%s: Unhandled state LRS_DEAD_SOCKET\n", __func__); + goto bail; + // assert(0); + /* fallthru */ + + default: + lwsl_err("%s: Unhandled state %d\n", __func__, lwsi_state(wsi)); + assert(0); + goto bail; + } + +read_ok: + /* Nothing more to do for now */ +// lwsl_info("%s: %p: read_ok, used %ld (len %d, state %d)\n", __func__, +// wsi, (long)(buf - oldbuf), (int)len, wsi->state); + + return lws_ptr_diff(buf, oldbuf); + +bail: + /* + * h2 / h2-ws calls us recursively in + * + * lws_read_h1()-> + * lws_h2_parser()-> + * lws_read_h1() + * + * pattern, having stripped the h2 framing in the middle. + * + * When taking down the whole connection, make sure that only the + * outer lws_read() does the wsi close. + */ + if (!wsi->outer_will_close) + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "lws_read_h1 bail"); + + return -1; +} +#if !defined(LWS_NO_SERVER) +static int +lws_h1_server_socket_service(struct lws *wsi, struct lws_pollfd *pollfd) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_tokens ebuf; + int n, buffered; + + if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) + goto try_pollout; + + /* any incoming data ready? */ + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) + goto try_pollout; + + /* + * If we previously just did POLLIN when IN and OUT were signaled + * (because POLLIN processing may have used up the POLLOUT), don't let + * that happen twice in a row... next time we see the situation favour + * POLLOUT + */ + + if (wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT)) { + // lwsl_notice("favouring pollout\n"); + wsi->favoured_pollin = 0; + goto try_pollout; + } + + /* + * We haven't processed that the tunnel is set up yet, so + * defer reading + */ + + if (lwsi_state(wsi) == LRS_SSL_ACK_PENDING) + return LWS_HPI_RET_HANDLED; + + /* these states imply we MUST have an ah attached */ + + if ((lwsi_state(wsi) == LRS_ESTABLISHED || + lwsi_state(wsi) == LRS_ISSUING_FILE || + lwsi_state(wsi) == LRS_HEADERS || + lwsi_state(wsi) == LRS_BODY)) { + + if (!wsi->http.ah && lws_header_table_attach(wsi, 0)) { + lwsl_info("%s: wsi %p: ah not available\n", __func__, wsi); + goto try_pollout; + } + + /* + * We got here because there was specifically POLLIN... + * regardless of our buflist state, we need to get it, + * and either use it, or append to the buflist and use + * buflist head material. + * + * We will not notice a connection close until the buflist is + * exhausted and we tried to do a read of some kind. + */ + + buffered = lws_buflist_aware_read(pt, wsi, &ebuf); + switch (ebuf.len) { + case 0: + lwsl_info("%s: read 0 len a\n", __func__); + wsi->seen_zero_length_recv = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* + * autobahn requires us to win the race between close + * and draining the extensions + */ + if (wsi->ws && + (wsi->ws->rx_draining_ext || wsi->ws->tx_draining_ext)) + goto try_pollout; +#endif + /* + * normally, we respond to close with logically closing + * our side immediately + */ + goto fail; + + case LWS_SSL_CAPABLE_ERROR: + goto fail; + case LWS_SSL_CAPABLE_MORE_SERVICE: + goto try_pollout; + } + + /* just ignore incoming if waiting for close */ + if (lwsi_state(wsi) == LRS_FLUSHING_BEFORE_CLOSE) { + lwsl_notice("%s: just ignoring\n", __func__); + goto try_pollout; + } + + if (lwsi_state(wsi) == LRS_ISSUING_FILE) { + // lwsl_notice("stashing: wsi %p: bd %d\n", wsi, buffered); + if (lws_buflist_aware_consume(wsi, &ebuf, 0, buffered)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + + goto try_pollout; + } + + /* + * Otherwise give it to whoever wants it according to the + * connection state + */ +#if defined(LWS_ROLE_H2) + if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) + n = lws_read_h2(wsi, (uint8_t *)ebuf.token, ebuf.len); + else +#endif + n = lws_read_h1(wsi, (uint8_t *)ebuf.token, ebuf.len); + if (n < 0) /* we closed wsi */ + return LWS_HPI_RET_WSI_ALREADY_DIED; + + lwsl_debug("%s: consumed %d\n", __func__, n); + + if (lws_buflist_aware_consume(wsi, &ebuf, n, buffered)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + + /* + * during the parsing our role changed to something non-http, + * so the ah has no further meaning + */ + + if (wsi->http.ah && + !lwsi_role_h1(wsi) && + !lwsi_role_h2(wsi) && + !lwsi_role_cgi(wsi)) + lws_header_table_detach(wsi, 0); + + /* + * He may have used up the writability above, if we will defer + * POLLOUT processing in favour of POLLIN, note it + */ + + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; + + return LWS_HPI_RET_HANDLED; + } + + /* + * He may have used up the writability above, if we will defer POLLOUT + * processing in favour of POLLIN, note it + */ + + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; + +try_pollout: + + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + /* one shot */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_notice("%s a\n", __func__); + goto fail; + } + + /* clear back-to-back write detection */ + wsi->could_have_pending = 0; + + if (lwsi_state(wsi) == LRS_DEFERRING_ACTION) { + lwsl_debug("%s: LRS_DEFERRING_ACTION now writable\n", __func__); + + lwsi_set_state(wsi, LRS_ESTABLISHED); + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + goto fail; + } + } + + if (!wsi->hdr_parsing_completed) + return LWS_HPI_RET_HANDLED; + + if (lwsi_state(wsi) != LRS_ISSUING_FILE) { + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + + n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_HTTP_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + + return LWS_HPI_RET_HANDLED; + } + + /* >0 == completion, <0 == error + * + * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when + * it's done. That's the case even if we just completed the + * send, so wait for that. + */ + n = lws_serve_http_file_fragment(wsi); + if (n < 0) + goto fail; + + return LWS_HPI_RET_HANDLED; + + +fail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, + "server socket svc fail"); + + return LWS_HPI_RET_WSI_ALREADY_DIED; +} +#endif + +static int +rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + +// lwsl_notice("%s: %p: wsistate 0x%x %s, revents 0x%x\n", __func__, wsi, +// wsi->wsistate, wsi->role_ops->name, pollfd->revents); + +#ifdef LWS_WITH_CGI + if (wsi->http.cgi && (pollfd->revents & LWS_POLLOUT)) { + if (lws_handle_POLLOUT_event(wsi, pollfd)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + + return LWS_HPI_RET_HANDLED; + } +#endif + + if (lws_is_flowcontrolled(wsi)) + /* We cannot deal with any kind of new RX because we are + * RX-flowcontrolled. + */ + return LWS_HPI_RET_HANDLED; + +#if !defined(LWS_NO_SERVER) + if (!lwsi_role_client(wsi)) { + int n; + + lwsl_debug("%s: %p: wsistate 0x%x\n", __func__, wsi, wsi->wsistate); + n = lws_h1_server_socket_service(wsi, pollfd); + if (n != LWS_HPI_RET_HANDLED) + return n; + if (lwsi_state(wsi) != LRS_SSL_INIT) + if (lws_server_socket_service_ssl(wsi, LWS_SOCK_INVALID)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + + return LWS_HPI_RET_HANDLED; + } +#endif + +#ifndef LWS_NO_CLIENT + if ((pollfd->revents & LWS_POLLIN) && + wsi->hdr_parsing_completed && !wsi->told_user_closed) { + + /* + * In SSL mode we get POLLIN notification about + * encrypted data in. + * + * But that is not necessarily related to decrypted + * data out becoming available; in may need to perform + * other in or out before that happens. + * + * simply mark ourselves as having readable data + * and turn off our POLLIN + */ + wsi->client_rx_avail = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + + //lwsl_notice("calling back %s\n", wsi->protocol->name); + + /* let user code know, he'll usually ask for writeable + * callback and drain / re-enable it there + */ + if (user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + lwsl_info("RECEIVE_CLIENT_HTTP closed it\n"); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + return LWS_HPI_RET_HANDLED; + } +#endif + +// if (lwsi_state(wsi) == LRS_ESTABLISHED) +// return LWS_HPI_RET_HANDLED; + +#if !defined(LWS_NO_CLIENT) + if ((pollfd->revents & LWS_POLLOUT) && + lws_handle_POLLOUT_event(wsi, pollfd)) { + lwsl_debug("POLLOUT event closed it\n"); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + if (lws_client_socket_service(wsi, pollfd, NULL)) + return LWS_HPI_RET_WSI_ALREADY_DIED; +#endif + + return LWS_HPI_RET_HANDLED; +} + +int rops_handle_POLLOUT_h1(struct lws *wsi) +{ + if (lwsi_state(wsi) == LRS_ISSUE_HTTP_BODY) + return LWS_HP_RET_USER_SERVICE; + + if (lwsi_role_client(wsi)) + return LWS_HP_RET_USER_SERVICE; + + return LWS_HP_RET_BAIL_OK; +} + +static int +rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ +#if 0 + /* if not in a state to send stuff, then just send nothing */ + + if ((lwsi_state(wsi) != LRS_RETURNED_CLOSE && + lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE && + lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK)) { + //assert(0); + lwsl_debug("binning %d %d\n", lwsi_state(wsi), *wp); + return 0; + } +#endif + + return lws_issue_raw(wsi, (unsigned char *)buf, len); +} + +static int +rops_alpn_negotiated_h1(struct lws *wsi, const char *alpn) +{ + lwsl_debug("%s: client %d\n", __func__, lwsi_role_client(wsi)); +#if !defined(LWS_NO_CLIENT) + if (lwsi_role_client(wsi)) { + /* + * If alpn asserts it is http/1.1, server support for KA is + * mandatory. + * + * Knowing this lets us proceed with sending pipelined headers + * before we received the first response headers. + */ + wsi->keepalive_active = 1; + } +#endif + + return 0; +} + +static int +rops_destroy_role_h1(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct allocated_headers *ah; + + /* we may not have an ah, but may be on the waiting list... */ + lwsl_info("%s: ah det due to close\n", __func__); + __lws_header_table_detach(wsi, 0); + + ah = pt->http.ah_list; + + while (ah) { + if (ah->in_use && ah->wsi == wsi) { + lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi); + ah->in_use = 0; + ah->wsi = NULL; + pt->http.ah_count_in_use--; + break; + } + ah = ah->next; + } + + return 0; +} + +struct lws_role_ops role_ops_h1 = { + /* role name */ "h1", + /* alpn id */ "http/1.1", + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_h1, + /* handle_POLLOUT */ rops_handle_POLLOUT_h1, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ rops_write_role_protocol_h1, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ rops_alpn_negotiated_h1, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ rops_destroy_role_h1, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_HTTP_WRITEABLE, + LWS_CALLBACK_HTTP_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLOSED_CLIENT_HTTP, + LWS_CALLBACK_CLOSED_HTTP }, + /* file_handle */ 0, +}; diff --git a/thirdparty/libwebsockets/roles/h1/private.h b/thirdparty/libwebsockets/roles/h1/private.h new file mode 100644 index 0000000000..3f53954d33 --- /dev/null +++ b/thirdparty/libwebsockets/roles/h1/private.h @@ -0,0 +1,27 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h if LWS_ROLE_H1 + * + * Most of the h1 business is defined in the h1 / h2 common roles/http dir + */ + +extern struct lws_role_ops role_ops_h1; +#define lwsi_role_h1(wsi) (wsi->role_ops == &role_ops_h1) diff --git a/thirdparty/lws/client/client-handshake.c b/thirdparty/libwebsockets/roles/http/client/client-handshake.c index c2720d9283..4830fc9eca 100644 --- a/thirdparty/lws/client/client-handshake.c +++ b/thirdparty/libwebsockets/roles/http/client/client-handshake.c @@ -1,4 +1,4 @@ -#include "private-libwebsockets.h" +#include "core/private.h" static int lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) @@ -20,7 +20,6 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) { hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_CANONNAME; } return getaddrinfo(ads, NULL, &hints, result); @@ -29,15 +28,20 @@ lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) struct lws * lws_client_connect_2(struct lws *wsi) { - sockaddr46 sa46; - struct addrinfo *result; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + const char *adsin; + struct lws *wsi_piggyback = NULL; struct lws_pollfd pfd; - const char *cce = "", *iface; - int n, port; ssize_t plen = 0; +#endif + struct addrinfo *result; const char *ads; + sockaddr46 sa46; + int n, port; + const char *cce = "", *iface; + const char *meth = NULL; #ifdef LWS_WITH_IPV6 char ipv6only = lws_check_opt(wsi->vhost->options, LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | @@ -48,24 +52,148 @@ lws_client_connect_2(struct lws *wsi) #endif #endif - lwsl_client("%s\n", __func__); + lwsl_client("%s: %p\n", __func__, wsi); - if (!wsi->u.hdr.ah) { +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + if (!wsi->http.ah) { cce = "ah was NULL at cc2"; lwsl_err("%s\n", cce); goto oom4; } + /* we can only piggyback GET or POST */ + + meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (meth && strcmp(meth, "GET") && strcmp(meth, "POST")) + goto create_new_conn; + + /* we only pipeline connections that said it was okay */ + + if (!wsi->client_pipeline) + goto create_new_conn; + + /* + * let's take a look first and see if there are any already-active + * client connections we can piggy-back on. + */ + + adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + + lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */ + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->vhost->dll_active_client_conns.next) { + struct lws *w = lws_container_of(d, struct lws, + dll_active_client_conns); + + lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin, + w->client_hostname_copy, wsi->c_port, w->c_port); + + if (w != wsi && w->client_hostname_copy && + !strcmp(adsin, w->client_hostname_copy) && +#if defined(LWS_WITH_TLS) + (wsi->tls.use_ssl & LCCSCF_USE_SSL) == + (w->tls.use_ssl & LCCSCF_USE_SSL) && +#endif + wsi->c_port == w->c_port) { + + /* someone else is already connected to the right guy */ + + /* do we know for a fact pipelining won't fly? */ + if (w->keepalive_rejected) { + lwsl_info("defeating pipelining due to no " + "keepalive on server\n"); + lws_vhost_unlock(wsi->vhost); /* } ---------- */ + goto create_new_conn; + } +#if defined (LWS_WITH_HTTP2) + /* + * h2: in usable state already: just use it without + * going through the queue + */ + if (w->client_h2_alpn && + (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS || + lwsi_state(w) == LRS_ESTABLISHED)) { + + lwsl_info("%s: just join h2 directly\n", + __func__); + + wsi->client_h2_alpn = 1; + lws_wsi_h2_adopt(w, wsi); + lws_vhost_unlock(wsi->vhost); /* } ---------- */ + + return wsi; + } +#endif + + lwsl_info("applying %p to txn queue on %p (wsistate 0x%x)\n", + wsi, w, w->wsistate); + /* + * ...let's add ourselves to his transaction queue... + * we are adding ourselves at the HEAD + */ + lws_dll_lws_add_front(&wsi->dll_client_transaction_queue, + &w->dll_client_transaction_queue_head); + + /* + * h1: pipeline our headers out on him, + * and wait for our turn at client transaction_complete + * to take over parsing the rx. + */ + + wsi_piggyback = w; + + lws_vhost_unlock(wsi->vhost); /* } ---------- */ + goto send_hs; + } + + } lws_end_foreach_dll_safe(d, d1); + + lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */ + +create_new_conn: +#endif + + /* + * clients who will create their own fresh connection keep a copy of + * the hostname they originally connected to, in case other connections + * want to use it too + */ + + if (!wsi->client_hostname_copy) + wsi->client_hostname_copy = + strdup(lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS)); + + /* + * If we made our own connection, and we're doing a method that can take + * a pipeline, we are an "active client connection". + * + * Add ourselves to the vhost list of those so that others can + * piggyback on our transaction queue + */ + + if (meth && (!strcmp(meth, "GET") || !strcmp(meth, "POST")) && + lws_dll_is_null(&wsi->dll_client_transaction_queue) && + lws_dll_is_null(&wsi->dll_active_client_conns)) { + lws_vhost_lock(wsi->vhost); + lws_dll_lws_add_front(&wsi->dll_active_client_conns, + &wsi->vhost->dll_active_client_conns); + lws_vhost_unlock(wsi->vhost); + } + /* * start off allowing ipv6 on connection if vhost allows it */ wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* Decide what it is we need to connect to: * * Priority 1: connect to http proxy */ - if (wsi->vhost->http_proxy_port) { + if (wsi->vhost->http.http_proxy_port) { plen = sprintf((char *)pt->serv_buf, "CONNECT %s:%u HTTP/1.0\x0d\x0a" "User-agent: libwebsockets\x0d\x0a", @@ -78,8 +206,11 @@ lws_client_connect_2(struct lws *wsi) wsi->vhost->proxy_basic_auth_token); plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); - ads = wsi->vhost->http_proxy_address; - port = wsi->vhost->http_proxy_port; + ads = wsi->vhost->http.http_proxy_address; + port = wsi->vhost->http.http_proxy_port; +#else + if (0) { +#endif #if defined(LWS_WITH_SOCKS5) @@ -104,12 +235,14 @@ lws_client_connect_2(struct lws *wsi) * to whatever we decided to connect to */ - lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads); + lwsl_info("%s: %p: address %s\n", __func__, wsi, ads); n = lws_getaddrinfo46(wsi, ads, &result); #ifdef LWS_WITH_IPV6 if (wsi->ipv6) { + struct sockaddr_in6 *sa6 = + ((struct sockaddr_in6 *)result->ai_addr); if (n) { /* lws_getaddrinfo46 failed, there is no usable result */ @@ -138,11 +271,10 @@ lws_client_connect_2(struct lws *wsi) break; case AF_INET6: - memcpy(&sa46.sa6.sin6_addr, - &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, + memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr, sizeof(struct in6_addr)); - sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id; - sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo; + sa46.sa6.sin6_scope_id = sa6->sin6_scope_id; + sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo; break; default: lwsl_err("Unknown address family\n"); @@ -211,14 +343,11 @@ lws_client_connect_2(struct lws *wsi) if (!lws_socket_is_valid(wsi->desc.sockfd)) { -#if defined(LWS_WITH_LIBUV) - if (LWS_LIBUV_ENABLED(context)) - if (lws_libuv_check_watcher_active(wsi)) { - lwsl_warn("Waiting for libuv watcher to close\n"); - cce = "waiting for libuv watcher to close"; - goto oom4; - } -#endif + if (wsi->context->event_loop_ops->check_client_connect_ok && + wsi->context->event_loop_ops->check_client_connect_ok(wsi)) { + cce = "waiting for event loop watcher to close"; + goto oom4; + } #ifdef LWS_WITH_IPV6 if (wsi->ipv6) @@ -240,13 +369,12 @@ lws_client_connect_2(struct lws *wsi) goto oom4; } - wsi->mode = LWSCM_WSCL_WAITING_CONNECT; + lwsi_set_state(wsi, LRS_WAITING_CONNECT); - lws_libev_accept(wsi, wsi->desc); - lws_libuv_accept(wsi, wsi->desc); - lws_libevent_accept(wsi, wsi->desc); + if (wsi->context->event_loop_ops->accept) + wsi->context->event_loop_ops->accept(wsi); - if (insert_wsi_socket_into_fds(context, wsi)) { + if (__insert_wsi_socket_into_fds(wsi->context, wsi)) { compatible_close(wsi->desc.sockfd); cce = "insert wsi failed"; goto oom4; @@ -328,10 +456,11 @@ lws_client_connect_2(struct lws *wsi) lwsl_client("connected\n"); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* we are connected to server, or proxy */ /* http proxy */ - if (wsi->vhost->http_proxy_port) { + if (wsi->vhost->http.http_proxy_port) { /* * OK from now on we talk via the proxy, so connect to that @@ -340,11 +469,11 @@ lws_client_connect_2(struct lws *wsi) * leaving old string/frag there but unreferenced) */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, - wsi->vhost->http_proxy_address)) + wsi->vhost->http.http_proxy_address)) goto failed; - wsi->c_port = wsi->vhost->http_proxy_port; + wsi->c_port = wsi->vhost->http.http_proxy_port; - n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, (int)plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to proxy socket\n"); @@ -355,10 +484,11 @@ lws_client_connect_2(struct lws *wsi) lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, AWAITING_TIMEOUT); - wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY; + lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY); return wsi; } +#endif #if defined(LWS_WITH_SOCKS5) /* socks proxy */ else if (wsi->vhost->socks_proxy_port) { @@ -373,72 +503,105 @@ lws_client_connect_2(struct lws *wsi) lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, AWAITING_TIMEOUT); - wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY; + lwsi_set_state(wsi, LRS_WAITING_SOCKS_GREETING_REPLY); return wsi; } #endif +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +send_hs: - /* - * provoke service to issue the handshake directly - * we need to do it this way because in the proxy case, this is the - * next state and executed only if and when we get a good proxy - * response inside the state machine... but notice in SSL case this - * may not have sent anything yet with 0 return, and won't until some - * many retries from main loop. To stop that becoming endless, - * cover with a timeout. - */ + if (wsi_piggyback && + !lws_dll_is_null(&wsi->dll_client_transaction_queue)) { + /* + * We are pipelining on an already-established connection... + * we can skip tls establishment. + */ + + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); + + /* + * we can't send our headers directly, because they have to + * be sent when the parent is writeable. The parent will check + * for anybody on his client transaction queue that is in + * LRS_H1C_ISSUE_HANDSHAKE2, and let them write. + * + * If we are trying to do this too early, before the master + * connection has written his own headers, then it will just + * wait in the queue until it's possible to send them. + */ + lws_callback_on_writable(wsi_piggyback); + lwsl_info("%s: wsi %p: waiting to send headers (parent state %x)\n", + __func__, wsi, lwsi_state(wsi_piggyback)); + } else { + lwsl_info("%s: wsi %p: client creating own connection\n", + __func__, wsi); - lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, - AWAITING_TIMEOUT); + /* we are making our own connection */ + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE); + + /* + * provoke service to issue the handshake directly. + * + * we need to do it this way because in the proxy case, this is + * the next state and executed only if and when we get a good + * proxy response inside the state machine... but notice in + * SSL case this may not have sent anything yet with 0 return, + * and won't until many retries from main loop. To stop that + * becoming endless, cover with a timeout. + */ - wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE; - pfd.fd = wsi->desc.sockfd; - pfd.events = LWS_POLLIN; - pfd.revents = LWS_POLLIN; + lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, + AWAITING_TIMEOUT); - n = lws_service_fd(context, &pfd); - if (n < 0) { - cce = "first service failed"; - goto failed; - } - if (n) /* returns 1 on failure after closing wsi */ - return NULL; + pfd.fd = wsi->desc.sockfd; + pfd.events = LWS_POLLIN; + pfd.revents = LWS_POLLIN; + n = lws_service_fd(context, &pfd); + if (n < 0) { + cce = "first service failed"; + goto failed; + } + if (n) /* returns 1 on failure after closing wsi */ + return NULL; + } +#endif return wsi; oom4: - /* we're closing, losing some rx is OK */ - lws_header_table_force_to_detachable_state(wsi); - - if (wsi->mode == LWSCM_HTTP_CLIENT || - wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED || - wsi->mode == LWSCM_WSCL_WAITING_CONNECT) { - wsi->vhost->protocols[0].callback(wsi, + if (lwsi_role_client(wsi) && lwsi_state_est(wsi)) { + wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; } /* take care that we might be inserted in fds already */ - if (wsi->position_in_fds_table != -1) + if (wsi->position_in_fds_table != LWS_NO_FDS_POS) goto failed1; lws_remove_from_timeout_list(wsi); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) lws_header_table_detach(wsi, 0); +#endif + lws_client_stash_destroy(wsi); + lws_free_set_NULL(wsi->client_hostname_copy); lws_free(wsi); return NULL; failed: - wsi->vhost->protocols[0].callback(wsi, + wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; failed1: - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2"); return NULL; } +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /** * lws_client_reset() - retarget a connected wsi to start over with a new connection (ie, redirect) * this only works if still in HTTP, ie, not upgraded yet @@ -452,7 +615,8 @@ LWS_VISIBLE struct lws * lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, const char *path, const char *host) { - char origin[300] = "", protocol[300] = "", method[32] = "", iface[16] = "", *p; + char origin[300] = "", protocol[300] = "", method[32] = "", + iface[16] = "", alpn[32] = "", *p; struct lws *wsi = *pwsi; if (wsi->redirects == 3) { @@ -463,49 +627,42 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN); if (p) - strncpy(origin, p, sizeof(origin) - 1); + lws_strncpy(origin, p, sizeof(origin)); p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); if (p) - strncpy(protocol, p, sizeof(protocol) - 1); + lws_strncpy(protocol, p, sizeof(protocol)); p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (p) - strncpy(method, p, sizeof(method) - 1); + lws_strncpy(method, p, sizeof(method)); p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); if (p) - strncpy(method, p, sizeof(iface) - 1); + lws_strncpy(iface, p, sizeof(iface)); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ALPN); + if (p) + lws_strncpy(alpn, p, sizeof(alpn)); lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n", address, port, path, ssl); /* close the connection by hand */ -#ifdef LWS_OPENSSL_SUPPORT +#if defined(LWS_WITH_TLS) lws_ssl_close(wsi); #endif -#ifdef LWS_WITH_LIBUV - if (LWS_LIBUV_ENABLED(wsi->context)) { - lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi); - /* - * libuv has to do his own close handle processing asynchronously - * but once it starts we can do everything else synchronously, - * including trash wsi->desc.sockfd since it took a copy. - * - * When it completes it will call compatible_close() - */ - lws_libuv_closehandle_manually(wsi); - } else -#else - compatible_close(wsi->desc.sockfd); -#endif + if (wsi->context->event_loop_ops->close_handle_manually) + wsi->context->event_loop_ops->close_handle_manually(wsi); + else + compatible_close(wsi->desc.sockfd); - remove_wsi_socket_from_fds(wsi); + __remove_wsi_socket_from_fds(wsi); -#ifdef LWS_OPENSSL_SUPPORT - wsi->use_ssl = ssl; +#if defined(LWS_WITH_TLS) + wsi->tls.use_ssl = ssl; #else if (ssl) { lwsl_err("%s: not configured for ssl\n", __func__); @@ -514,12 +671,12 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, #endif wsi->desc.sockfd = LWS_SOCK_INVALID; - wsi->state = LWSS_CLIENT_UNCONNECTED; + lwsi_set_state(wsi, LRS_UNCONNECTED); wsi->protocol = NULL; wsi->pending_timeout = NO_PENDING_TIMEOUT; wsi->c_port = port; wsi->hdr_parsing_completed = 0; - _lws_header_table_reset(wsi->u.hdr.ah); + _lws_header_table_reset(wsi->http.ah); if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address)) return NULL; @@ -544,6 +701,10 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, iface)) return NULL; + if (alpn[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN, + alpn)) + return NULL; origin[0] = '/'; strncpy(&origin[1], path, sizeof(origin) - 2); @@ -683,26 +844,68 @@ html_parser_cb(const hubbub_token *token, void *pw) } #endif +#endif + +static char * +lws_strdup(const char *s) +{ + char *d = lws_malloc(strlen(s) + 1, "strdup"); + + if (d) + strcpy(d, s); + + return d; +} + +void +lws_client_stash_destroy(struct lws *wsi) +{ + if (!wsi || !wsi->stash) + return; + + lws_free_set_NULL(wsi->stash->address); + lws_free_set_NULL(wsi->stash->path); + lws_free_set_NULL(wsi->stash->host); + lws_free_set_NULL(wsi->stash->origin); + lws_free_set_NULL(wsi->stash->protocol); + lws_free_set_NULL(wsi->stash->method); + lws_free_set_NULL(wsi->stash->iface); + lws_free_set_NULL(wsi->stash->alpn); + + lws_free_set_NULL(wsi->stash); +} + LWS_VISIBLE struct lws * lws_client_connect_via_info(struct lws_client_connect_info *i) { struct lws *wsi; - int v = SPEC_LATEST_SUPPORTED; const struct lws_protocols *p; + const char *local = i->protocol; if (i->context->requested_kill) return NULL; if (!i->context->protocol_init_done) lws_protocol_init(i->context); + /* + * If we have .local_protocol_name, use it to select the + * local protocol handler to bind to. Otherwise use .protocol if + * http[s]. + */ + if (i->local_protocol_name) + local = i->local_protocol_name; wsi = lws_zalloc(sizeof(struct lws), "client wsi"); if (wsi == NULL) goto bail; wsi->context = i->context; +#if defined(LWS_ROLE_H1) /* assert the mode and union status (hdr) clearly */ - lws_union_transition(wsi, LWSCM_HTTP_CLIENT); + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_h1); +#else + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_raw_skt); +#endif wsi->desc.sockfd = LWS_SOCK_INVALID; /* 1) fill up the wsi with stuff from the connect_info as far as it @@ -710,26 +913,52 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) * not even be able to get ahold of an ah at this point. */ - /* -1 means just use latest supported */ - if (i->ietf_version_or_minus_one != -1 && i->ietf_version_or_minus_one) - v = i->ietf_version_or_minus_one; + if (!i->method) /* ie, ws */ +#if defined(LWS_ROLE_WS) + if (lws_create_client_ws_object(i, wsi)) + return NULL; +#else + return NULL; +#endif - wsi->ietf_spec_revision = v; wsi->user_space = NULL; - wsi->state = LWSS_CLIENT_UNCONNECTED; wsi->pending_timeout = NO_PENDING_TIMEOUT; - wsi->position_in_fds_table = -1; + wsi->position_in_fds_table = LWS_NO_FDS_POS; wsi->c_port = i->port; wsi->vhost = i->vhost; if (!wsi->vhost) wsi->vhost = i->context->vhost_list; + if (!wsi->vhost) { + lwsl_err("At least one vhost in the context is required\n"); + + goto bail; + } + wsi->protocol = &wsi->vhost->protocols[0]; + wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE); - /* for http[s] connection, allow protocol selection by name */ + /* reasonable place to start */ + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, +#if defined(LWS_ROLE_H1) + &role_ops_h1); +#else + &role_ops_raw_skt); +#endif - if (i->method && i->vhost && i->protocol) { - p = lws_vhost_name_to_protocol(i->vhost, i->protocol); + /* + * 1) for http[s] connection, allow protocol selection by name + * 2) for ws[s], if local_protocol_name given also use it for + * local protocol binding... this defeats the server + * protocol negotiation if so + * + * Otherwise leave at protocols[0]... the server will tell us + * which protocol we are associated with since we can give it a + * list. + */ + if (/*(i->method || i->local_protocol_name) && */local) { + lwsl_info("binding to %s\n", local); + p = lws_vhost_name_to_protocol(wsi->vhost, local); if (p) wsi->protocol = p; } @@ -745,10 +974,10 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) if (lws_ensure_user_space(wsi)) goto bail; -#ifdef LWS_OPENSSL_SUPPORT - wsi->use_ssl = i->ssl_connection; +#if defined(LWS_WITH_TLS) + wsi->tls.use_ssl = i->ssl_connection; #else - if (i->ssl_connection) { + if (i->ssl_connection & LCCSCF_USE_SSL) { lwsl_err("libwebsockets not configured for ssl\n"); goto bail; } @@ -760,47 +989,63 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) * things pointed to have gone out of scope. */ - wsi->u.hdr.stash = lws_malloc(sizeof(*wsi->u.hdr.stash), "client stash"); - if (!wsi->u.hdr.stash) { + wsi->stash = lws_zalloc(sizeof(*wsi->stash), "client stash"); + if (!wsi->stash) { lwsl_err("%s: OOM\n", __func__); - goto bail; + goto bail1; } - wsi->u.hdr.stash->origin[0] = '\0'; - wsi->u.hdr.stash->protocol[0] = '\0'; - wsi->u.hdr.stash->method[0] = '\0'; - wsi->u.hdr.stash->iface[0] = '\0'; - - strncpy(wsi->u.hdr.stash->address, i->address, - sizeof(wsi->u.hdr.stash->address) - 1); - strncpy(wsi->u.hdr.stash->path, i->path, - sizeof(wsi->u.hdr.stash->path) - 1); - strncpy(wsi->u.hdr.stash->host, i->host, - sizeof(wsi->u.hdr.stash->host) - 1); - if (i->origin) - strncpy(wsi->u.hdr.stash->origin, i->origin, - sizeof(wsi->u.hdr.stash->origin) - 1); - if (i->protocol) - strncpy(wsi->u.hdr.stash->protocol, i->protocol, - sizeof(wsi->u.hdr.stash->protocol) - 1); - if (i->method) - strncpy(wsi->u.hdr.stash->method, i->method, - sizeof(wsi->u.hdr.stash->method) - 1); - if (i->iface) - strncpy(wsi->u.hdr.stash->iface, i->iface, - sizeof(wsi->u.hdr.stash->iface) - 1); - - wsi->u.hdr.stash->address[sizeof(wsi->u.hdr.stash->address) - 1] = '\0'; - wsi->u.hdr.stash->path[sizeof(wsi->u.hdr.stash->path) - 1] = '\0'; - wsi->u.hdr.stash->host[sizeof(wsi->u.hdr.stash->host) - 1] = '\0'; - wsi->u.hdr.stash->origin[sizeof(wsi->u.hdr.stash->origin) - 1] = '\0'; - wsi->u.hdr.stash->protocol[sizeof(wsi->u.hdr.stash->protocol) - 1] = '\0'; - wsi->u.hdr.stash->method[sizeof(wsi->u.hdr.stash->method) - 1] = '\0'; - wsi->u.hdr.stash->iface[sizeof(wsi->u.hdr.stash->iface) - 1] = '\0'; + wsi->stash->address = lws_strdup(i->address); + wsi->stash->path = lws_strdup(i->path); + wsi->stash->host = lws_strdup(i->host); + + if (!wsi->stash->address || !wsi->stash->path || !wsi->stash->host) + goto bail1; + + if (i->origin) { + wsi->stash->origin = lws_strdup(i->origin); + if (!wsi->stash->origin) + goto bail1; + } + if (i->protocol) { + wsi->stash->protocol = lws_strdup(i->protocol); + if (!wsi->stash->protocol) + goto bail1; + } + if (i->method) { + wsi->stash->method = lws_strdup(i->method); + if (!wsi->stash->method) + goto bail1; + } + if (i->iface) { + wsi->stash->iface = lws_strdup(i->iface); + if (!wsi->stash->iface) + goto bail1; + } + /* + * For ws, default to http/1.1 only. If i->alpn is set, defer to + * whatever he has set in there (eg, "h2"). + * + * The problem is he has to commit to h2 before he can find out if the + * server has the SETTINGS for ws-over-h2 enabled; if not then ws is + * not possible on that connection. So we only try it if he + * assertively said to use h2 alpn. + */ + if (!i->method && !i->alpn) { + wsi->stash->alpn = lws_strdup("http/1.1"); + if (!wsi->stash->alpn) + goto bail1; + } else + if (i->alpn) { + wsi->stash->alpn = lws_strdup(i->alpn); + if (!wsi->stash->alpn) + goto bail1; + } if (i->pwsi) *i->pwsi = wsi; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* if we went on the waiting list, no probs just return the wsi * when we get the ah, now or later, he will call * lws_client_connect_via_info2() below. @@ -810,9 +1055,11 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) * if we failed here, the connection is already closed * and freed. */ - goto bail1; + goto bail2; } +#endif + if (i->parent_wsi) { lwsl_info("%s: created child %p of parent %p\n", __func__, wsi, i->parent_wsi); @@ -822,17 +1069,21 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) } #ifdef LWS_WITH_HTTP_PROXY if (i->uri_replace_to) - wsi->rw = lws_rewrite_create(wsi, html_parser_cb, + wsi->http.rw = lws_rewrite_create(wsi, html_parser_cb, i->uri_replace_from, i->uri_replace_to); #endif return wsi; +bail1: + lws_client_stash_destroy(wsi); + bail: lws_free(wsi); - -bail1: +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +bail2: +#endif if (i->pwsi) *i->pwsi = NULL; @@ -842,28 +1093,27 @@ bail1: struct lws * lws_client_connect_via_info2(struct lws *wsi) { - struct client_info_stash *stash = wsi->u.hdr.stash; + struct client_info_stash *stash = wsi->stash; if (!stash) return wsi; /* * we're not necessarily in a position to action these right away, - * stash them... we only need during connect phase so u.hdr is fine + * stash them... we only need during connect phase so into a temp + * allocated stash */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, stash->address)) goto bail1; - /* these only need u.hdr lifetime as well */ - if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path)) goto bail1; if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host)) goto bail1; - if (stash->origin[0]) + if (stash->origin) if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN, stash->origin)) goto bail1; @@ -871,45 +1121,28 @@ lws_client_connect_via_info2(struct lws *wsi) * this is a list of protocols we tell the server we're okay with * stash it for later when we compare server response with it */ - if (stash->protocol[0]) + if (stash->protocol) if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, stash->protocol)) goto bail1; - if (stash->method[0]) + if (stash->method) if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD, stash->method)) goto bail1; - if (stash->iface[0]) + if (stash->iface) if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, stash->iface)) goto bail1; + if (stash->alpn) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN, + stash->alpn)) + goto bail1; #if defined(LWS_WITH_SOCKS5) if (!wsi->vhost->socks_proxy_port) - lws_free_set_NULL(wsi->u.hdr.stash); + lws_client_stash_destroy(wsi); #endif - /* - * Check with each extension if it is able to route and proxy this - * connection for us. For example, an extension like x-google-mux - * can handle this and then we don't need an actual socket for this - * connection. - */ - - if (lws_ext_cb_all_exts(wsi->context, wsi, - LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION, - (void *)stash->address, - wsi->c_port) > 0) { - lwsl_client("lws_client_connect: ext handling conn\n"); - - lws_set_timeout(wsi, - PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE, - AWAITING_TIMEOUT); - - wsi->mode = LWSCM_WSCL_WAITING_EXTENSION_CONNECT; - return wsi; - } - lwsl_client("lws_client_connect: direct conn\n"); wsi->context->count_wsi_allocated++; return lws_client_connect_2(wsi); @@ -917,7 +1150,7 @@ lws_client_connect_via_info2(struct lws *wsi) bail1: #if defined(LWS_WITH_SOCKS5) if (!wsi->vhost->socks_proxy_port) - lws_free_set_NULL(wsi->u.hdr.stash); + lws_free_set_NULL(wsi->stash); #endif return NULL; @@ -1003,14 +1236,14 @@ void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, /* length of the user name */ pt->serv_buf[len++] = n; /* user name */ - strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user, - context->pt_serv_buf_size - len); + lws_strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user, + context->pt_serv_buf_size - len + 1); len += n; /* length of the password */ pt->serv_buf[len++] = passwd_len; /* password */ - strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password, - context->pt_serv_buf_size - len); + lws_strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password, + context->pt_serv_buf_size - len + 1); len += passwd_len; break; @@ -1029,9 +1262,9 @@ void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, n = len++; /* the address we tell SOCKS proxy to connect to */ - strncpy((char *)&(pt->serv_buf[len]), wsi->u.hdr.stash->address, - context->pt_serv_buf_size - len); - len += strlen(wsi->u.hdr.stash->address); + lws_strncpy((char *)&(pt->serv_buf[len]), wsi->stash->address, + context->pt_serv_buf_size - len + 1); + len += strlen(wsi->stash->address); net_num = htons(wsi->c_port); /* the port we tell SOCKS proxy to connect to */ @@ -1039,7 +1272,7 @@ void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, pt->serv_buf[len++] = p[1]; /* the length of the address, excluding port */ - pt->serv_buf[n] = strlen(wsi->u.hdr.stash->address); + pt->serv_buf[n] = strlen(wsi->stash->address); break; default: diff --git a/thirdparty/libwebsockets/roles/http/client/client.c b/thirdparty/libwebsockets/roles/http/client/client.c new file mode 100644 index 0000000000..ce42dc6cd3 --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/client/client.c @@ -0,0 +1,1231 @@ +/* + * libwebsockets - lib/client/client.c + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +LWS_VISIBLE LWS_EXTERN void +lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) +{ + wsi->client_http_body_pending = !!something_left_to_send; +} + +/* + * return self, or queued client wsi we are acting on behalf of + * + * That is the TAIL of the queue (new queue elements are added at the HEAD) + */ + +struct lws * +lws_client_wsi_effective(struct lws *wsi) +{ + struct lws_dll_lws *tail = NULL; + + if (!wsi->transaction_from_pipeline_queue || + !wsi->dll_client_transaction_queue_head.next) + return wsi; + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + tail = d; + } lws_end_foreach_dll_safe(d, d1); + + return lws_container_of(tail, struct lws, + dll_client_transaction_queue); +} + +/* + * return self or the guy we are queued under + * + * REQUIRES VHOST LOCK HELD + */ + +static struct lws * +_lws_client_wsi_master(struct lws *wsi) +{ + struct lws *wsi_eff = wsi; + struct lws_dll_lws *d; + + d = wsi->dll_client_transaction_queue.prev; + while (d) { + wsi_eff = lws_container_of(d, struct lws, + dll_client_transaction_queue_head); + + d = d->prev; + } + + return wsi_eff; +} + +int +lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, + struct lws *wsi_conn) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + struct lws *w; +#if defined(LWS_WITH_TLS) + char ebuf[128]; +#endif + const char *cce = NULL; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + ssize_t len = 0; + unsigned char c; +#endif + char *sb = p; + int n = 0; +#if defined(LWS_WITH_SOCKS5) + char conn_mode = 0, pending_timeout = 0; +#endif + + if ((pollfd->revents & LWS_POLLOUT) && + wsi->keepalive_active && + wsi->dll_client_transaction_queue_head.next) { + struct lws *wfound = NULL; + + lwsl_debug("%s: pollout HANDSHAKE2\n", __func__); + + /* + * We have a transaction queued that wants to pipeline. + * + * We have to allow it to send headers strictly in the order + * that it was queued, ie, tail-first. + */ + lws_vhost_lock(wsi->vhost); + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + struct lws *w = lws_container_of(d, struct lws, + dll_client_transaction_queue); + + lwsl_debug("%s: %p states 0x%x\n", __func__, w, w->wsistate); + if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2) + wfound = w; + } lws_end_foreach_dll_safe(d, d1); + + if (wfound) { + /* + * pollfd has the master sockfd in it... we + * need to use that in HANDSHAKE2 to understand + * which wsi to actually write on + */ + lws_client_socket_service(wfound, pollfd, wsi); + lws_callback_on_writable(wsi); + } else + lwsl_debug("%s: didn't find anything in txn q in HS2\n", + __func__); + + lws_vhost_unlock(wsi->vhost); + + return 0; + } + + switch (lwsi_state(wsi)) { + + case LRS_WAITING_CONNECT: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + */ + + if (!lws_client_connect_2(wsi)) { + /* closed */ + lwsl_client("closed\n"); + return -1; + } + + /* either still pending connection, or changed mode */ + return 0; + +#if defined(LWS_WITH_SOCKS5) + /* SOCKS Greeting Reply */ + case LRS_WAITING_SOCKS_GREETING_REPLY: + case LRS_WAITING_SOCKS_AUTH_REPLY: + case LRS_WAITING_SOCKS_CONNECT_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & LWS_POLLHUP) { + lwsl_warn("SOCKS connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("SOCKS read EAGAIN, retrying\n"); + return 0; + } + lwsl_err("ERROR reading from SOCKS socket\n"); + goto bail3; + } + + switch (lwsi_state(wsi)) { + + case LRS_WAITING_SOCKS_GREETING_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5) + goto socks_reply_fail; + + if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { + lwsl_client("SOCKS GR: No Auth Method\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = + PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; + goto socks_send; + } + + if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { + lwsl_client("SOCKS GR: User/Pw Method\n"); + socks_generate_msg(wsi, + SOCKS_MSG_USERNAME_PASSWORD, + &len); + conn_mode = LRS_WAITING_SOCKS_AUTH_REPLY; + pending_timeout = + PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; + goto socks_send; + } + goto socks_reply_fail; + + case LRS_WAITING_SOCKS_AUTH_REPLY: + if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 || + pt->serv_buf[1] != SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) + goto socks_reply_fail; + + lwsl_client("SOCKS password OK, sending connect\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = + PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; +socks_send: + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing to socks proxy\n"); + goto bail3; + } + + lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); + lwsi_set_state(wsi, conn_mode); + break; + +socks_reply_fail: + lwsl_notice("socks reply: v%d, err %d\n", + pt->serv_buf[0], pt->serv_buf[1]); + goto bail3; + + case LRS_WAITING_SOCKS_CONNECT_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5 || + pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS) + goto socks_reply_fail; + + lwsl_client("socks connect OK\n"); + + /* free stash since we are done with it */ + lws_client_stash_destroy(wsi); + if (lws_hdr_simple_create(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS, + wsi->vhost->socks_proxy_address)) + goto bail3; + + wsi->c_port = wsi->vhost->socks_proxy_port; + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + goto start_ws_handshake; + } + break; +#endif + + case LRS_WAITING_PROXY_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & LWS_POLLHUP) { + + lwsl_warn("Proxy connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("Proxy read EAGAIN... retrying\n"); + return 0; + } + lwsl_err("ERROR reading from proxy socket\n"); + goto bail3; + } + + pt->serv_buf[13] = '\0'; + if (strcmp(sb, "HTTP/1.0 200 ") && + strcmp(sb, "HTTP/1.1 200 ")) { + lwsl_err("ERROR proxy: %s\n", sb); + goto bail3; + } + + /* clear his proxy connection timeout */ + + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* fallthru */ + + case LRS_H1C_ISSUE_HANDSHAKE: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + * + * take care of our lws_callback_on_writable + * happening at a time when there's no real connection yet + */ +#if defined(LWS_WITH_SOCKS5) +start_ws_handshake: +#endif + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) + return -1; + +#if defined(LWS_WITH_TLS) + /* we can retry this... just cook the SSL BIO the first time */ + + if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !wsi->tls.ssl && + lws_ssl_client_bio_create(wsi) < 0) { + cce = "bio_create failed"; + goto bail3; + } + + if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { + n = lws_ssl_client_connect1(wsi); + if (!n) + return 0; + if (n < 0) { + cce = "lws_ssl_client_connect1 failed"; + goto bail3; + } + } else + wsi->tls.ssl = NULL; + + /* fallthru */ + + case LRS_WAITING_SSL: + + if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { + n = lws_ssl_client_connect2(wsi, ebuf, sizeof(ebuf)); + if (!n) + return 0; + if (n < 0) { + cce = ebuf; + goto bail3; + } + } else + wsi->tls.ssl = NULL; +#endif +#if defined (LWS_WITH_HTTP2) + if (wsi->client_h2_alpn) { + /* + * We connected to the server and set up tls, and + * negotiated "h2". + * + * So this is it, we are an h2 master client connection + * now, not an h1 client connection. + */ + lws_tls_server_conn_alpn(wsi); + + /* send the H2 preface to legitimize the connection */ + if (lws_h2_issue_preface(wsi)) { + cce = "error sending h2 preface"; + goto bail3; + } + + break; + } +#endif + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, + context->timeout_secs); + + /* fallthru */ + + case LRS_H1C_ISSUE_HANDSHAKE2: + p = lws_generate_client_handshake(wsi, p); + if (p == NULL) { + if (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file) + return 0; + + lwsl_err("Failed to generate handshake for client\n"); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "chs"); + return 0; + } + + /* send our request to the server */ + lws_latency_pre(context, wsi); + + w = _lws_client_wsi_master(wsi); + lwsl_info("%s: HANDSHAKE2: %p: sending headers on %p (wsistate 0x%x 0x%x)\n", + __func__, wsi, w, wsi->wsistate, w->wsistate); + + n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb)); + lws_latency(context, wsi, "send lws_issue_raw", n, + n == p - sb); + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + lwsl_debug("ERROR writing to client socket\n"); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cws"); + return 0; + case LWS_SSL_CAPABLE_MORE_SERVICE: + lws_callback_on_writable(wsi); + break; + } + + if (wsi->client_http_body_pending) { + lwsi_set_state(wsi, LRS_ISSUE_HTTP_BODY); + lws_set_timeout(wsi, + PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, + context->timeout_secs); + /* user code must ask for writable callback */ + break; + } + + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + wsi->hdr_parsing_completed = 0; + + if (lwsi_state(w) == LRS_IDLING) { + lwsi_set_state(w, LRS_WAITING_SERVER_REPLY); + w->hdr_parsing_completed = 0; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + w->http.ah->parser_state = WSI_TOKEN_NAME_PART; + w->http.ah->lextable_pos = 0; + /* If we're (re)starting on headers, need other implied init */ + wsi->http.ah->ues = URIES_IDLE; +#endif + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + wsi->context->timeout_secs); + + lws_callback_on_writable(w); + + goto client_http_body_sent; + + case LRS_ISSUE_HTTP_BODY: + if (wsi->client_http_body_pending) { + //lws_set_timeout(wsi, + // PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, + // context->timeout_secs); + /* user code must ask for writable callback */ + break; + } +client_http_body_sent: +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* prepare ourselves to do the parsing */ + wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; + wsi->http.ah->lextable_pos = 0; +#endif + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + context->timeout_secs); + break; + + case LRS_WAITING_SERVER_REPLY: + /* + * handle server hanging up on us... + * but if there is POLLIN waiting, handle that first + */ + if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == + LWS_POLLHUP) { + + lwsl_debug("Server connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + cce = "Peer hung up"; + goto bail3; + } + + if (!(pollfd->revents & LWS_POLLIN)) + break; + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* interpret the server response + * + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= + * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== + * Sec-WebSocket-Protocol: chat + * + * we have to take some care here to only take from the + * socket bytewise. The browser may (and has been seen to + * in the case that onopen() performs websocket traffic) + * coalesce both handshake response and websocket traffic + * in one packet, since at that point the connection is + * definitively ready from browser pov. + */ + len = 1; + while (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE && + len > 0) { + int plen = 1; + + n = lws_ssl_capable_read(wsi, &c, 1); + lws_latency(context, wsi, "send lws_issue_raw", n, + n == 1); + switch (n) { + case 0: + case LWS_SSL_CAPABLE_ERROR: + cce = "read failed"; + goto bail3; + case LWS_SSL_CAPABLE_MORE_SERVICE: + return 0; + } + + if (lws_parse(wsi, &c, &plen)) { + lwsl_warn("problems parsing header\n"); + goto bail3; + } + } + + /* + * hs may also be coming in multiple packets, there is a 5-sec + * libwebsocket timeout still active here too, so if parsing did + * not complete just wait for next packet coming in this state + */ + if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE) + break; + +#endif + + /* + * otherwise deal with the handshake. If there's any + * packet traffic already arrived we'll trigger poll() again + * right away and deal with it that way + */ + return lws_client_interpret_server_handshake(wsi); + +bail3: + lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); + if (cce) + lwsl_info("reason: %s\n", cce); + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, cce ? strlen(cce) : 0); + wsi->already_did_cce = 1; + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cbail3"); + return -1; + + default: + break; + } + + return 0; +} + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + +int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi) +{ + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + lwsl_info("%s: wsi: %p, wsi_eff: %p\n", __func__, wsi, wsi_eff); + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, + wsi_eff, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi_eff->user_space, NULL, 0)) { + lwsl_debug("%s: Completed call returned nonzero (role 0x%x)\n", + __func__, lwsi_role(wsi_eff)); + return -1; + } + + /* + * Are we constitutionally capable of having a queue, ie, we are on + * the "active client connections" list? + * + * If not, that's it for us. + */ + + if (lws_dll_is_null(&wsi->dll_active_client_conns)) + return -1; + + /* if this was a queued guy, close him and remove from queue */ + + if (wsi->transaction_from_pipeline_queue) { + lwsl_debug("closing queued wsi %p\n", wsi_eff); + /* so the close doesn't trigger a CCE */ + wsi_eff->already_did_cce = 1; + __lws_close_free_wsi(wsi_eff, + LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE, + "queued client done"); + } + + /* after the first one, they can only be coming from the queue */ + wsi->transaction_from_pipeline_queue = 1; + + wsi->http.rx_content_length = 0; + wsi->hdr_parsing_completed = 0; + + /* is there a new tail after removing that one? */ + wsi_eff = lws_client_wsi_effective(wsi); + + /* + * Do we have something pipelined waiting? + * it's OK if he hasn't managed to send his headers yet... he's next + * in line to do that... + */ + if (wsi_eff == wsi) { + /* + * Nothing pipelined... we should hang around a bit + * in case something turns up... + */ + lwsl_info("%s: nothing pipelined waiting\n", __func__); + lwsi_set_state(wsi, LRS_IDLING); + + lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5); + + return 0; + } + + /* + * H1: we can serialize the queued guys into the same ah + * H2: everybody needs their own ah until their own STREAM_END + */ + + /* otherwise set ourselves up ready to go again */ + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + + wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; + wsi->http.ah->lextable_pos = 0; + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + wsi->context->timeout_secs); + + /* If we're (re)starting on headers, need other implied init */ + wsi->http.ah->ues = URIES_IDLE; + + lwsl_info("%s: %p: new queued transaction as %p\n", __func__, wsi, wsi_eff); + lws_callback_on_writable(wsi); + + return 0; +} + +LWS_VISIBLE LWS_EXTERN unsigned int +lws_http_client_http_response(struct lws *wsi) +{ + if (!wsi->http.ah) + return 0; + + return wsi->http.ah->http_response; +} +#endif +#if defined(LWS_PLAT_OPTEE) +char * +strrchr(const char *s, int c) +{ + char *hit = NULL; + + while (*s) + if (*(s++) == (char)c) + hit = (char *)s - 1; + + return hit; +} + +#define atoll atoi +#endif + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +int +lws_client_interpret_server_handshake(struct lws *wsi) +{ + int n, port = 0, ssl = 0; + int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; + const char *prot, *ads = NULL, *path, *cce = NULL; + struct allocated_headers *ah = NULL; + struct lws *w = lws_client_wsi_effective(wsi); + char *p, *q; + char new_path[300]; + + lws_client_stash_destroy(wsi); + + ah = wsi->http.ah; + if (!wsi->do_ws) { + /* we are being an http client... + */ +#if defined(LWS_ROLE_H2) + if (wsi->client_h2_alpn || wsi->client_h2_substream) { + lwsl_debug("%s: %p: transitioning to h2 client\n", __func__, wsi); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_ESTABLISHED, &role_ops_h2); + } else +#endif + { +#if defined(LWS_ROLE_H1) + { + lwsl_debug("%s: %p: transitioning to h1 client\n", __func__, wsi); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_ESTABLISHED, &role_ops_h1); + } +#else + return -1; +#endif + } + + wsi->http.ah = ah; + ah->http_response = 0; + } + + /* + * well, what the server sent looked reasonable for syntax. + * Now let's confirm it sent all the necessary headers + * + * http (non-ws) client will expect something like this + * + * HTTP/1.0.200 + * server:.libwebsockets + * content-type:.text/html + * content-length:.17703 + * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 + */ + + wsi->http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; + if (!wsi->client_h2_substream) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); + if (wsi->do_ws && !p) { + lwsl_info("no URI\n"); + cce = "HS: URI missing"; + goto bail3; + } + if (!p) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); + wsi->http.connection_type = HTTP_CONNECTION_CLOSE; + } + if (!p) { + cce = "HS: URI missing"; + lwsl_info("no URI\n"); + goto bail3; + } + } else { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_STATUS); + if (!p) { + cce = "HS: :status missing"; + lwsl_info("no status\n"); + goto bail3; + } + } + n = atoi(p); + if (ah) + ah->http_response = n; + + if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); + if (!p) { + cce = "HS: Redirect code but no Location"; + goto bail3; + } + + /* Relative reference absolute path */ + if (p[0] == '/') { +#if defined(LWS_WITH_TLS) + ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; +#endif + ads = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + /* +1 as lws_client_reset expects leading / omitted */ + path = p + 1; + } + /* Absolute (Full) URI */ + else if (strchr(p, ':')) { + if (lws_parse_uri(p, &prot, &ads, &port, &path)) { + cce = "HS: URI did not parse"; + goto bail3; + } + + if (!strcmp(prot, "wss") || !strcmp(prot, "https")) + ssl = 1; + } + /* Relative reference relative path */ + else { + /* This doesn't try to calculate an absolute path, + * that will be left to the server */ +#if defined(LWS_WITH_TLS) + ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; +#endif + ads = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + /* +1 as lws_client_reset expects leading / omitted */ + path = new_path + 1; + lws_strncpy(new_path, lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_URI), sizeof(new_path)); + q = strrchr(new_path, '/'); + if (q) + lws_strncpy(q + 1, p, sizeof(new_path) - + (q - new_path)); + else + path = p; + } + +#if defined(LWS_WITH_TLS) + if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !ssl) { + cce = "HS: Redirect attempted SSL downgrade"; + goto bail3; + } +#endif + + if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) { + /* there are two ways to fail out with NULL return... + * simple, early problem where the wsi is intact, or + * we went through with the reconnect attempt and the + * wsi is already closed. In the latter case, the wsi + * has beet set to NULL additionally. + */ + lwsl_err("Redirect failed\n"); + cce = "HS: Redirect failed"; + if (wsi) + goto bail3; + + return 1; + } + return 0; + } + + if (!wsi->do_ws) { + + /* if h1 KA is allowed, enable the queued pipeline guys */ + + if (!wsi->client_h2_alpn && !wsi->client_h2_substream && w == wsi) { /* ie, coming to this for the first time */ + if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) + wsi->keepalive_active = 1; + else { + /* + * Ugh... now the main http connection has seen + * both sides, we learn the server doesn't + * support keepalive. + * + * That means any guys queued on us are going + * to have to be restarted from connect2 with + * their own connections. + */ + + /* + * stick around telling any new guys they can't + * pipeline to this server + */ + wsi->keepalive_rejected = 1; + + lws_vhost_lock(wsi->vhost); + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + struct lws *ww = lws_container_of(d, struct lws, + dll_client_transaction_queue); + + /* remove him from our queue */ + lws_dll_lws_remove(&ww->dll_client_transaction_queue); + /* give up on pipelining */ + ww->client_pipeline = 0; + + /* go back to "trying to connect" state */ + lws_role_transition(ww, LWSIFR_CLIENT, + LRS_UNCONNECTED, +#if defined(LWS_ROLE_H1) + &role_ops_h1); +#else +#if defined (LWS_ROLE_H2) + &role_ops_h2); +#else + &role_ops_raw); +#endif +#endif + ww->user_space = NULL; + } lws_end_foreach_dll_safe(d, d1); + lws_vhost_unlock(wsi->vhost); + } + } + +#ifdef LWS_WITH_HTTP_PROXY + wsi->http.perform_rewrite = 0; + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { + if (!strncmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE), + "text/html", 9)) + wsi->http.perform_rewrite = 1; + } +#endif + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) { + lwsl_err("Problem allocating wsi user mem\n"); + cce = "HS: OOM"; + goto bail2; + } + + /* he may choose to send us stuff in chunked transfer-coding */ + wsi->chunked = 0; + wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ + if (lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { + wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING), + "chunked"); + /* first thing is hex, after payload there is crlf */ + wsi->chunk_parser = ELCP_HEX; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + wsi->http.rx_content_length = + atoll(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH)); + lwsl_info("%s: incoming content length %llu\n", + __func__, (unsigned long long) + wsi->http.rx_content_length); + wsi->http.rx_content_remain = + wsi->http.rx_content_length; + } else /* can't do 1.1 without a content length or chunked */ + if (!wsi->chunked) + wsi->http.connection_type = + HTTP_CONNECTION_CLOSE; + + /* + * we seem to be good to go, give client last chance to check + * headers and OK it + */ + if (wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, + wsi->user_space, NULL, 0)) { + + cce = "HS: disallowed by client filter"; + goto bail2; + } + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* call him back to inform him he is up */ + if (wsi->protocol->callback(wsi, + LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + cce = "HS: disallowed at ESTABLISHED"; + goto bail3; + } + + /* + * for pipelining, master needs to keep his ah... guys who + * queued on him can drop it now though. + */ + + if (w != wsi) + /* free up parsing allocations for queued guy */ + lws_header_table_detach(w, 0); + + lwsl_info("%s: client connection up\n", __func__); + + return 0; + } + +#if defined(LWS_ROLE_WS) + switch (lws_client_ws_upgrade(wsi, &cce)) { + case 2: + goto bail2; + case 3: + goto bail3; + } + + return 0; +#endif + +bail3: + close_reason = LWS_CLOSE_STATUS_NOSTATUS; + +bail2: + if (wsi->protocol) { + n = 0; + if (cce) + n = (int)strlen(cce); + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, + (unsigned int)n); + } + wsi->already_did_cce = 1; + + lwsl_info("closing connection due to bail2 connection error\n"); + + /* closing will free up his parsing allocations */ + lws_close_free_wsi(wsi, close_reason, "c hs interp"); + + return 1; +} +#endif + +char * +lws_generate_client_handshake(struct lws *wsi, char *pkt) +{ + char *p = pkt; + const char *meth; + const char *pp = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + + meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (!meth) { + meth = "GET"; + wsi->do_ws = 1; + } else { + wsi->do_ws = 0; + } + + if (!strcmp(meth, "RAW")) { + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_notice("client transition to raw\n"); + + if (pp) { + const struct lws_protocols *pr; + + pr = lws_vhost_name_to_protocol(wsi->vhost, pp); + + if (!pr) { + lwsl_err("protocol %s not enabled on vhost\n", + pp); + return NULL; + } + + lws_bind_protocol(wsi, pr); + } + + if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + return NULL; + + lws_role_transition(wsi, 0, LRS_ESTABLISHED, &role_ops_raw_skt); + lws_header_table_detach(wsi, 1); + + return NULL; + } + + /* + * 04 example client handshake + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Sec-WebSocket-Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 4 + */ + + p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); + + p += sprintf(p, "Pragma: no-cache\x0d\x0a" + "Cache-Control: no-cache\x0d\x0a"); + + p += sprintf(p, "Host: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { + if (lws_check_opt(wsi->context->options, + LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) + p += sprintf(p, "Origin: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_ORIGIN)); + else + p += sprintf(p, "Origin: http://%s\x0d\x0a", + lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_ORIGIN)); + } +#if defined(LWS_ROLE_WS) + if (wsi->do_ws) + p = lws_generate_client_ws_handshake(wsi, p); +#endif + + /* give userland a chance to append, eg, cookies */ + + if (wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + wsi->user_space, &p, + (pkt + wsi->context->pt_serv_buf_size) - p - 12)) + return NULL; + + p += sprintf(p, "\x0d\x0a"); + + return p; +} + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + +LWS_VISIBLE int +lws_http_client_read(struct lws *wsi, char **buf, int *len) +{ + int rlen, n; + + rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); + *len = 0; + + // lwsl_notice("%s: rlen %d\n", __func__, rlen); + + /* allow the source to signal he has data again next time */ + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + if (rlen == LWS_SSL_CAPABLE_ERROR) { + lwsl_notice("%s: SSL capable error\n", __func__); + return -1; + } + + if (rlen == 0) + return -1; + + if (rlen < 0) + return 0; + + *len = rlen; + wsi->client_rx_avail = 0; + + /* + * server may insist on transfer-encoding: chunked, + * so http client must deal with it + */ +spin_chunks: + while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { + switch (wsi->chunk_parser) { + case ELCP_HEX: + if ((*buf)[0] == '\x0d') { + wsi->chunk_parser = ELCP_CR; + break; + } + n = char_to_hex((*buf)[0]); + if (n < 0) { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_remaining <<= 4; + wsi->chunk_remaining |= n; + break; + case ELCP_CR: + if ((*buf)[0] != '\x0a') { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_parser = ELCP_CONTENT; + lwsl_info("chunk %d\n", wsi->chunk_remaining); + if (wsi->chunk_remaining) + break; + lwsl_info("final chunk\n"); + goto completed; + + case ELCP_CONTENT: + break; + + case ELCP_POST_CR: + if ((*buf)[0] != '\x0d') { + lwsl_debug("chunking failure\n"); + + return -1; + } + + wsi->chunk_parser = ELCP_POST_LF; + break; + + case ELCP_POST_LF: + if ((*buf)[0] != '\x0a') + return -1; + + wsi->chunk_parser = ELCP_HEX; + wsi->chunk_remaining = 0; + break; + } + (*buf)++; + (*len)--; + } + + if (wsi->chunked && !wsi->chunk_remaining) + return 0; + + if (wsi->http.rx_content_remain && + wsi->http.rx_content_remain < (unsigned int)*len) + n = (int)wsi->http.rx_content_remain; + else + n = *len; + + if (wsi->chunked && wsi->chunk_remaining && + wsi->chunk_remaining < n) + n = wsi->chunk_remaining; + +#ifdef LWS_WITH_HTTP_PROXY + /* hubbub */ + if (wsi->http.perform_rewrite) + lws_rewrite_parse(wsi->http.rw, (unsigned char *)*buf, n); + else +#endif + { + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, + wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi_eff->user_space, *buf, n)) { + lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n", + __func__); + + return -1; + } + } + + if (wsi->chunked && wsi->chunk_remaining) { + (*buf) += n; + wsi->chunk_remaining -= n; + *len -= n; + } + + if (wsi->chunked && !wsi->chunk_remaining) + wsi->chunk_parser = ELCP_POST_CR; + + if (wsi->chunked && *len) + goto spin_chunks; + + if (wsi->chunked) + return 0; + + /* if we know the content length, decrement the content remaining */ + if (wsi->http.rx_content_length > 0) + wsi->http.rx_content_remain -= n; + + // lwsl_notice("rx_content_remain %lld, rx_content_length %lld\n", + // wsi->http.rx_content_remain, wsi->http.rx_content_length); + + if (wsi->http.rx_content_remain || !wsi->http.rx_content_length) + return 0; + +completed: + + if (lws_http_transaction_completed_client(wsi)) { + lwsl_notice("%s: transaction completed says -1\n", __func__); + return -1; + } + + return 0; +} + +#endif
\ No newline at end of file diff --git a/thirdparty/lws/header.c b/thirdparty/libwebsockets/roles/http/header.c index e2562cd6ea..99e56f7564 100644 --- a/thirdparty/lws/header.c +++ b/thirdparty/libwebsockets/roles/http/header.c @@ -19,12 +19,12 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" - +#include "core/private.h" #include "lextable-strings.h" -const unsigned char *lws_token_to_string(enum lws_token_indexes token) +const unsigned char * +lws_token_to_string(enum lws_token_indexes token) { if ((unsigned int)token >= ARRAY_SIZE(set)) return NULL; @@ -38,7 +38,7 @@ lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, unsigned char **p, unsigned char *end) { #ifdef LWS_WITH_HTTP2 - if (wsi->mode == LWSCM_HTTP2_SERVING) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return lws_add_http2_header_by_name(wsi, name, value, length, p, end); #else @@ -66,7 +66,7 @@ int lws_finalize_http_header(struct lws *wsi, unsigned char **p, unsigned char *end) { #ifdef LWS_WITH_HTTP2 - if (wsi->mode == LWSCM_HTTP2_SERVING) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return 0; #else (void)wsi; @@ -80,19 +80,39 @@ int lws_finalize_http_header(struct lws *wsi, unsigned char **p, } int +lws_finalize_write_http_header(struct lws *wsi, unsigned char *start, + unsigned char **pp, unsigned char *end) +{ + unsigned char *p; + int len; + + if (lws_finalize_http_header(wsi, pp, end)) + return 1; + + p = *pp; + len = lws_ptr_diff(p, start); + + if (lws_write(wsi, start, len, LWS_WRITE_HTTP_HEADERS) != len) + return 1; + + return 0; +} + +int lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, const unsigned char *value, int length, unsigned char **p, unsigned char *end) { const unsigned char *name; #ifdef LWS_WITH_HTTP2 - if (wsi->mode == LWSCM_HTTP2_SERVING) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return lws_add_http2_header_by_token(wsi, token, value, length, p, end); #endif name = lws_token_to_string(token); if (!name) return 1; + return lws_add_http_header_by_name(wsi, name, value, length, p, end); } @@ -107,8 +127,31 @@ int lws_add_http_header_content_length(struct lws *wsi, if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b, n, p, end)) return 1; - wsi->u.http.tx_content_length = content_length; - wsi->u.http.tx_content_remain = content_length; + wsi->http.tx_content_length = content_length; + wsi->http.tx_content_remain = content_length; + + lwsl_info("%s: wsi %p: tx_content_length/remain %llu\n", __func__, + wsi, (unsigned long long)content_length); + + return 0; +} + +int +lws_add_http_common_headers(struct lws *wsi, unsigned int code, + const char *content_type, lws_filepos_t content_len, + unsigned char **p, unsigned char *end) +{ + if (lws_add_http_header_status(wsi, code, p, end)) + return 1; + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)content_type, + (int)strlen(content_type), p, end)) + return 1; + + if (content_len != LWS_ILLEGAL_HTTP_CONTENT_LEN && + lws_add_http_header_content_length(wsi, content_len, p, end)) + return 1; return 0; } @@ -157,11 +200,11 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code, int n; #ifdef LWS_WITH_ACCESS_LOG - wsi->access_log.response = code; + wsi->http.access_log.response = code; #endif #ifdef LWS_WITH_HTTP2 - if (wsi->mode == LWSCM_HTTP2_SERVING) + if (lwsi_role_h2(wsi) || lwsi_role_h2_ENCAPSULATION(wsi)) return lws_add_http2_header_status(wsi, code, p, end); #endif if (code >= 400 && code < (400 + ARRAY_SIZE(err400))) @@ -171,18 +214,16 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code, if (code == 100) description = "Continue"; - if (code == 200) description = "OK"; - if (code == 304) description = "Not Modified"; else if (code >= 300 && code < 400) description = "Redirect"; - if (wsi->u.http.request_version < ARRAY_SIZE(hver)) - p1 = hver[wsi->u.http.request_version]; + if (wsi->http.request_version < ARRAY_SIZE(hver)) + p1 = hver[wsi->http.request_version]; else p1 = hver[0]; @@ -196,7 +237,7 @@ lws_add_http_header_status(struct lws *wsi, unsigned int _code, if (lws_add_http_header_by_name(wsi, (const unsigned char *)headers->name, (unsigned char *)headers->value, - strlen(headers->value), p, end)) + (int)strlen(headers->value), p, end)) return 1; headers = headers->next; @@ -231,6 +272,26 @@ lws_return_http_status(struct lws *wsi, unsigned int code, int n = 0, m = 0, len; char slen[20]; + if (!wsi->vhost) { + lwsl_err("%s: wsi not bound to vhost\n", __func__); + + return 1; + } +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + if (!wsi->handling_404 && + wsi->vhost->http.error_document_404 && + code == HTTP_STATUS_NOT_FOUND) + /* we should do a redirect, and do the 404 there */ + if (lws_http_redirect(wsi, HTTP_STATUS_FOUND, + (uint8_t *)wsi->vhost->http.error_document_404, + (int)strlen(wsi->vhost->http.error_document_404), + &p, end) > 0) + return 0; +#endif + + /* if the redirect failed, just do a simple status */ + p = start; + if (!html_body) html_body = ""; @@ -242,12 +303,11 @@ lws_return_http_status(struct lws *wsi, unsigned int code, &p, end)) return 1; - len = 35 + strlen(html_body) + sprintf(slen, "%d", code); + len = 35 + (int)strlen(html_body) + sprintf(slen, "%d", code); n = sprintf(slen, "%d", len); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, - (unsigned char *)slen, n, - &p, end)) + (unsigned char *)slen, n, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) @@ -270,7 +330,7 @@ lws_return_http_status(struct lws *wsi, unsigned int code, * Solve it by writing the headers now... */ m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); - if (m != (int)(p - start)) + if (m != lws_ptr_diff(p, start)) return 1; /* @@ -281,15 +341,15 @@ lws_return_http_status(struct lws *wsi, unsigned int code, len = sprintf((char *)body, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); - wsi->u.http.tx_content_length = len; - wsi->u.http.tx_content_remain = len; + wsi->http.tx_content_length = len; + wsi->http.tx_content_remain = len; - wsi->u.h2.pending_status_body = lws_malloc(len + LWS_PRE + 1, + wsi->h2.pending_status_body = lws_malloc(len + LWS_PRE + 1, "pending status body"); - if (!wsi->u.h2.pending_status_body) + if (!wsi->h2.pending_status_body) return -1; - strcpy(wsi->u.h2.pending_status_body + LWS_PRE, + strcpy(wsi->h2.pending_status_body + LWS_PRE, (const char *)body); lws_callback_on_writable(wsi); @@ -305,15 +365,12 @@ lws_return_http_status(struct lws *wsi, unsigned int code, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); - n = (int)(p - start); - + n = lws_ptr_diff(p, start); m = lws_write(wsi, start, n, LWS_WRITE_HTTP); if (m != n) return 1; } - lwsl_notice("%s: return\n", __func__); - return m != n; } @@ -322,34 +379,29 @@ lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, unsigned char **p, unsigned char *end) { unsigned char *start = *p; - int n; if (lws_add_http_header_status(wsi, code, p, end)) return -1; - if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_LOCATION, - loc, len, p, end)) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, loc, len, + p, end)) return -1; /* * if we're going with http/1.1 and keepalive, we have to give fake * content metadata so the client knows we completed the transaction and * it can do the redirect... */ - if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_CONTENT_TYPE, - (unsigned char *)"text/html", 9, - p, end)) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/html", 9, p, + end)) return -1; - if (lws_add_http_header_by_token(wsi, - WSI_TOKEN_HTTP_CONTENT_LENGTH, - (unsigned char *)"0", 1, p, end)) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, + (unsigned char *)"0", 1, p, end)) return -1; if (lws_finalize_http_header(wsi, p, end)) return -1; - n = lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); - - return n; + return lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS | + LWS_WRITE_H2_STREAM_END); } diff --git a/thirdparty/lws/lextable-strings.h b/thirdparty/libwebsockets/roles/http/lextable-strings.h index ab42c3e476..631f5cb600 100644 --- a/thirdparty/lws/lextable-strings.h +++ b/thirdparty/libwebsockets/roles/http/lextable-strings.h @@ -98,6 +98,10 @@ STORE_IN_ROM static const char * const set[] = { "connect ", "head ", "te:", /* http/2 wants it to reject it */ + "replay-nonce:", /* ACME */ + ":protocol", /* defined in mcmanus-httpbis-h2-ws-02 */ + + "x-auth-token:", "", /* not matchable */ diff --git a/thirdparty/libwebsockets/roles/http/lextable.h b/thirdparty/libwebsockets/roles/http/lextable.h new file mode 100644 index 0000000000..9a8063b157 --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/lextable.h @@ -0,0 +1,838 @@ +/* pos 0000: 0 */ 0x67 /* 'g' */, 0x40, 0x00 /* (to 0x0040 state 1) */, + 0x70 /* 'p' */, 0x42, 0x00 /* (to 0x0045 state 5) */, + 0x6F /* 'o' */, 0x51, 0x00 /* (to 0x0057 state 10) */, + 0x68 /* 'h' */, 0x5D, 0x00 /* (to 0x0066 state 18) */, + 0x63 /* 'c' */, 0x69, 0x00 /* (to 0x0075 state 23) */, + 0x75 /* 'u' */, 0x8A, 0x00 /* (to 0x0099 state 34) */, + 0x73 /* 's' */, 0xA0, 0x00 /* (to 0x00B2 state 48) */, + 0x0D /* '.' */, 0xD9, 0x00 /* (to 0x00EE state 68) */, + 0x61 /* 'a' */, 0x31, 0x01 /* (to 0x0149 state 129) */, + 0x69 /* 'i' */, 0x70, 0x01 /* (to 0x018B state 163) */, + 0x64 /* 'd' */, 0x19, 0x02 /* (to 0x0237 state 265) */, + 0x72 /* 'r' */, 0x22, 0x02 /* (to 0x0243 state 270) */, + 0x3A /* ':' */, 0x56, 0x02 /* (to 0x027A state 299) */, + 0x65 /* 'e' */, 0xE8, 0x02 /* (to 0x030F state 409) */, + 0x66 /* 'f' */, 0x04, 0x03 /* (to 0x032E state 425) */, + 0x6C /* 'l' */, 0x26, 0x03 /* (to 0x0353 state 458) */, + 0x6D /* 'm' */, 0x49, 0x03 /* (to 0x0379 state 484) */, + 0x74 /* 't' */, 0xB8, 0x03 /* (to 0x03EB state 578) */, + 0x76 /* 'v' */, 0xD9, 0x03 /* (to 0x040F state 606) */, + 0x77 /* 'w' */, 0xE6, 0x03 /* (to 0x041F state 614) */, + 0x78 /* 'x' */, 0x0D, 0x04 /* (to 0x0449 state 650) */, + 0x08, /* fail */ +/* pos 0040: 1 */ 0xE5 /* 'e' -> */, +/* pos 0041: 2 */ 0xF4 /* 't' -> */, +/* pos 0042: 3 */ 0xA0 /* ' ' -> */, +/* pos 0043: 4 */ 0x00, 0x00 /* - terminal marker 0 - */, +/* pos 0045: 5 */ 0x6F /* 'o' */, 0x0D, 0x00 /* (to 0x0052 state 6) */, + 0x72 /* 'r' */, 0x95, 0x01 /* (to 0x01DD state 211) */, + 0x61 /* 'a' */, 0xE6, 0x03 /* (to 0x0431 state 631) */, + 0x75 /* 'u' */, 0xE8, 0x03 /* (to 0x0436 state 635) */, + 0x08, /* fail */ +/* pos 0052: 6 */ 0xF3 /* 's' -> */, +/* pos 0053: 7 */ 0xF4 /* 't' -> */, +/* pos 0054: 8 */ 0xA0 /* ' ' -> */, +/* pos 0055: 9 */ 0x00, 0x01 /* - terminal marker 1 - */, +/* pos 0057: 10 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x005E state 11) */, + 0x72 /* 'r' */, 0x51, 0x00 /* (to 0x00AB state 42) */, + 0x08, /* fail */ +/* pos 005e: 11 */ 0xF4 /* 't' -> */, +/* pos 005f: 12 */ 0xE9 /* 'i' -> */, +/* pos 0060: 13 */ 0xEF /* 'o' -> */, +/* pos 0061: 14 */ 0xEE /* 'n' -> */, +/* pos 0062: 15 */ 0xF3 /* 's' -> */, +/* pos 0063: 16 */ 0xA0 /* ' ' -> */, +/* pos 0064: 17 */ 0x00, 0x02 /* - terminal marker 2 - */, +/* pos 0066: 18 */ 0x6F /* 'o' */, 0x0A, 0x00 /* (to 0x0070 state 19) */, + 0x74 /* 't' */, 0xBF, 0x00 /* (to 0x0128 state 110) */, + 0x65 /* 'e' */, 0x04, 0x04 /* (to 0x0470 state 676) */, + 0x08, /* fail */ +/* pos 0070: 19 */ 0xF3 /* 's' -> */, +/* pos 0071: 20 */ 0xF4 /* 't' -> */, +/* pos 0072: 21 */ 0xBA /* ':' -> */, +/* pos 0073: 22 */ 0x00, 0x03 /* - terminal marker 3 - */, +/* pos 0075: 23 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x007C state 24) */, + 0x61 /* 'a' */, 0x72, 0x01 /* (to 0x01EA state 217) */, + 0x08, /* fail */ +/* pos 007c: 24 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x0083 state 25) */, + 0x6F /* 'o' */, 0x87, 0x01 /* (to 0x0206 state 243) */, + 0x08, /* fail */ +/* pos 0083: 25 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x008A state 26) */, + 0x74 /* 't' */, 0x86, 0x01 /* (to 0x020C state 248) */, + 0x08, /* fail */ +/* pos 008a: 26 */ 0xE5 /* 'e' -> */, +/* pos 008b: 27 */ 0xE3 /* 'c' -> */, +/* pos 008c: 28 */ 0xF4 /* 't' -> */, +/* pos 008d: 29 */ 0x69 /* 'i' */, 0x07, 0x00 /* (to 0x0094 state 30) */, + 0x20 /* ' ' */, 0xDE, 0x03 /* (to 0x046E state 675) */, + 0x08, /* fail */ +/* pos 0094: 30 */ 0xEF /* 'o' -> */, +/* pos 0095: 31 */ 0xEE /* 'n' -> */, +/* pos 0096: 32 */ 0xBA /* ':' -> */, +/* pos 0097: 33 */ 0x00, 0x04 /* - terminal marker 4 - */, +/* pos 0099: 34 */ 0x70 /* 'p' */, 0x0A, 0x00 /* (to 0x00A3 state 35) */, + 0x73 /* 's' */, 0x68, 0x03 /* (to 0x0404 state 596) */, + 0x72 /* 'r' */, 0xA0, 0x03 /* (to 0x043F state 642) */, + 0x08, /* fail */ +/* pos 00a3: 35 */ 0xE7 /* 'g' -> */, +/* pos 00a4: 36 */ 0xF2 /* 'r' -> */, +/* pos 00a5: 37 */ 0xE1 /* 'a' -> */, +/* pos 00a6: 38 */ 0xE4 /* 'd' -> */, +/* pos 00a7: 39 */ 0xE5 /* 'e' -> */, +/* pos 00a8: 40 */ 0xBA /* ':' -> */, +/* pos 00a9: 41 */ 0x00, 0x05 /* - terminal marker 5 - */, +/* pos 00ab: 42 */ 0xE9 /* 'i' -> */, +/* pos 00ac: 43 */ 0xE7 /* 'g' -> */, +/* pos 00ad: 44 */ 0xE9 /* 'i' -> */, +/* pos 00ae: 45 */ 0xEE /* 'n' -> */, +/* pos 00af: 46 */ 0xBA /* ':' -> */, +/* pos 00b0: 47 */ 0x00, 0x06 /* - terminal marker 6 - */, +/* pos 00b2: 48 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x00B9 state 49) */, + 0x74 /* 't' */, 0x1C, 0x03 /* (to 0x03D1 state 553) */, + 0x08, /* fail */ +/* pos 00b9: 49 */ 0x63 /* 'c' */, 0x0A, 0x00 /* (to 0x00C3 state 50) */, + 0x72 /* 'r' */, 0x05, 0x03 /* (to 0x03C1 state 539) */, + 0x74 /* 't' */, 0x08, 0x03 /* (to 0x03C7 state 544) */, + 0x08, /* fail */ +/* pos 00c3: 50 */ 0xAD /* '-' -> */, +/* pos 00c4: 51 */ 0xF7 /* 'w' -> */, +/* pos 00c5: 52 */ 0xE5 /* 'e' -> */, +/* pos 00c6: 53 */ 0xE2 /* 'b' -> */, +/* pos 00c7: 54 */ 0xF3 /* 's' -> */, +/* pos 00c8: 55 */ 0xEF /* 'o' -> */, +/* pos 00c9: 56 */ 0xE3 /* 'c' -> */, +/* pos 00ca: 57 */ 0xEB /* 'k' -> */, +/* pos 00cb: 58 */ 0xE5 /* 'e' -> */, +/* pos 00cc: 59 */ 0xF4 /* 't' -> */, +/* pos 00cd: 60 */ 0xAD /* '-' -> */, +/* pos 00ce: 61 */ 0x64 /* 'd' */, 0x19, 0x00 /* (to 0x00E7 state 62) */, + 0x65 /* 'e' */, 0x20, 0x00 /* (to 0x00F1 state 70) */, + 0x6B /* 'k' */, 0x29, 0x00 /* (to 0x00FD state 81) */, + 0x70 /* 'p' */, 0x38, 0x00 /* (to 0x010F state 88) */, + 0x61 /* 'a' */, 0x3F, 0x00 /* (to 0x0119 state 97) */, + 0x6E /* 'n' */, 0x44, 0x00 /* (to 0x0121 state 104) */, + 0x76 /* 'v' */, 0x89, 0x01 /* (to 0x0269 state 284) */, + 0x6F /* 'o' */, 0x8F, 0x01 /* (to 0x0272 state 292) */, + 0x08, /* fail */ +/* pos 00e7: 62 */ 0xF2 /* 'r' -> */, +/* pos 00e8: 63 */ 0xE1 /* 'a' -> */, +/* pos 00e9: 64 */ 0xE6 /* 'f' -> */, +/* pos 00ea: 65 */ 0xF4 /* 't' -> */, +/* pos 00eb: 66 */ 0xBA /* ':' -> */, +/* pos 00ec: 67 */ 0x00, 0x07 /* - terminal marker 7 - */, +/* pos 00ee: 68 */ 0x8A /* '.' -> */, +/* pos 00ef: 69 */ 0x00, 0x08 /* - terminal marker 8 - */, +/* pos 00f1: 70 */ 0xF8 /* 'x' -> */, +/* pos 00f2: 71 */ 0xF4 /* 't' -> */, +/* pos 00f3: 72 */ 0xE5 /* 'e' -> */, +/* pos 00f4: 73 */ 0xEE /* 'n' -> */, +/* pos 00f5: 74 */ 0xF3 /* 's' -> */, +/* pos 00f6: 75 */ 0xE9 /* 'i' -> */, +/* pos 00f7: 76 */ 0xEF /* 'o' -> */, +/* pos 00f8: 77 */ 0xEE /* 'n' -> */, +/* pos 00f9: 78 */ 0xF3 /* 's' -> */, +/* pos 00fa: 79 */ 0xBA /* ':' -> */, +/* pos 00fb: 80 */ 0x00, 0x09 /* - terminal marker 9 - */, +/* pos 00fd: 81 */ 0xE5 /* 'e' -> */, +/* pos 00fe: 82 */ 0xF9 /* 'y' -> */, +/* pos 00ff: 83 */ 0x31 /* '1' */, 0x0A, 0x00 /* (to 0x0109 state 84) */, + 0x32 /* '2' */, 0x0A, 0x00 /* (to 0x010C state 86) */, + 0x3A /* ':' */, 0x62, 0x01 /* (to 0x0267 state 283) */, + 0x08, /* fail */ +/* pos 0109: 84 */ 0xBA /* ':' -> */, +/* pos 010a: 85 */ 0x00, 0x0A /* - terminal marker 10 - */, +/* pos 010c: 86 */ 0xBA /* ':' -> */, +/* pos 010d: 87 */ 0x00, 0x0B /* - terminal marker 11 - */, +/* pos 010f: 88 */ 0xF2 /* 'r' -> */, +/* pos 0110: 89 */ 0xEF /* 'o' -> */, +/* pos 0111: 90 */ 0xF4 /* 't' -> */, +/* pos 0112: 91 */ 0xEF /* 'o' -> */, +/* pos 0113: 92 */ 0xE3 /* 'c' -> */, +/* pos 0114: 93 */ 0xEF /* 'o' -> */, +/* pos 0115: 94 */ 0xEC /* 'l' -> */, +/* pos 0116: 95 */ 0xBA /* ':' -> */, +/* pos 0117: 96 */ 0x00, 0x0C /* - terminal marker 12 - */, +/* pos 0119: 97 */ 0xE3 /* 'c' -> */, +/* pos 011a: 98 */ 0xE3 /* 'c' -> */, +/* pos 011b: 99 */ 0xE5 /* 'e' -> */, +/* pos 011c: 100 */ 0xF0 /* 'p' -> */, +/* pos 011d: 101 */ 0xF4 /* 't' -> */, +/* pos 011e: 102 */ 0xBA /* ':' -> */, +/* pos 011f: 103 */ 0x00, 0x0D /* - terminal marker 13 - */, +/* pos 0121: 104 */ 0xEF /* 'o' -> */, +/* pos 0122: 105 */ 0xEE /* 'n' -> */, +/* pos 0123: 106 */ 0xE3 /* 'c' -> */, +/* pos 0124: 107 */ 0xE5 /* 'e' -> */, +/* pos 0125: 108 */ 0xBA /* ':' -> */, +/* pos 0126: 109 */ 0x00, 0x0E /* - terminal marker 14 - */, +/* pos 0128: 110 */ 0xF4 /* 't' -> */, +/* pos 0129: 111 */ 0xF0 /* 'p' -> */, +/* pos 012a: 112 */ 0x2F /* '/' */, 0x07, 0x00 /* (to 0x0131 state 113) */, + 0x32 /* '2' */, 0x10, 0x00 /* (to 0x013D state 118) */, + 0x08, /* fail */ +/* pos 0131: 113 */ 0xB1 /* '1' -> */, +/* pos 0132: 114 */ 0xAE /* '.' -> */, +/* pos 0133: 115 */ 0x31 /* '1' */, 0x07, 0x00 /* (to 0x013A state 116) */, + 0x30 /* '0' */, 0x27, 0x03 /* (to 0x045D state 660) */, + 0x08, /* fail */ +/* pos 013a: 116 */ 0xA0 /* ' ' -> */, +/* pos 013b: 117 */ 0x00, 0x0F /* - terminal marker 15 - */, +/* pos 013d: 118 */ 0xAD /* '-' -> */, +/* pos 013e: 119 */ 0xF3 /* 's' -> */, +/* pos 013f: 120 */ 0xE5 /* 'e' -> */, +/* pos 0140: 121 */ 0xF4 /* 't' -> */, +/* pos 0141: 122 */ 0xF4 /* 't' -> */, +/* pos 0142: 123 */ 0xE9 /* 'i' -> */, +/* pos 0143: 124 */ 0xEE /* 'n' -> */, +/* pos 0144: 125 */ 0xE7 /* 'g' -> */, +/* pos 0145: 126 */ 0xF3 /* 's' -> */, +/* pos 0146: 127 */ 0xBA /* ':' -> */, +/* pos 0147: 128 */ 0x00, 0x10 /* - terminal marker 16 - */, +/* pos 0149: 129 */ 0x63 /* 'c' */, 0x0D, 0x00 /* (to 0x0156 state 130) */, + 0x75 /* 'u' */, 0xAC, 0x00 /* (to 0x01F8 state 230) */, + 0x67 /* 'g' */, 0x86, 0x01 /* (to 0x02D5 state 358) */, + 0x6C /* 'l' */, 0x87, 0x01 /* (to 0x02D9 state 361) */, + 0x08, /* fail */ +/* pos 0156: 130 */ 0xE3 /* 'c' -> */, +/* pos 0157: 131 */ 0xE5 /* 'e' -> */, +/* pos 0158: 132 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x015F state 133) */, + 0x73 /* 's' */, 0x0E, 0x00 /* (to 0x0169 state 136) */, + 0x08, /* fail */ +/* pos 015f: 133 */ 0xF4 /* 't' -> */, +/* pos 0160: 134 */ 0x3A /* ':' */, 0x07, 0x00 /* (to 0x0167 state 135) */, + 0x2D /* '-' */, 0x59, 0x00 /* (to 0x01BC state 192) */, + 0x08, /* fail */ +/* pos 0167: 135 */ 0x00, 0x11 /* - terminal marker 17 - */, +/* pos 0169: 136 */ 0xF3 /* 's' -> */, +/* pos 016a: 137 */ 0xAD /* '-' -> */, +/* pos 016b: 138 */ 0xE3 /* 'c' -> */, +/* pos 016c: 139 */ 0xEF /* 'o' -> */, +/* pos 016d: 140 */ 0xEE /* 'n' -> */, +/* pos 016e: 141 */ 0xF4 /* 't' -> */, +/* pos 016f: 142 */ 0xF2 /* 'r' -> */, +/* pos 0170: 143 */ 0xEF /* 'o' -> */, +/* pos 0171: 144 */ 0xEC /* 'l' -> */, +/* pos 0172: 145 */ 0xAD /* '-' -> */, +/* pos 0173: 146 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x017A state 147) */, + 0x61 /* 'a' */, 0x51, 0x01 /* (to 0x02C7 state 345) */, + 0x08, /* fail */ +/* pos 017a: 147 */ 0xE5 /* 'e' -> */, +/* pos 017b: 148 */ 0xF1 /* 'q' -> */, +/* pos 017c: 149 */ 0xF5 /* 'u' -> */, +/* pos 017d: 150 */ 0xE5 /* 'e' -> */, +/* pos 017e: 151 */ 0xF3 /* 's' -> */, +/* pos 017f: 152 */ 0xF4 /* 't' -> */, +/* pos 0180: 153 */ 0xAD /* '-' -> */, +/* pos 0181: 154 */ 0xE8 /* 'h' -> */, +/* pos 0182: 155 */ 0xE5 /* 'e' -> */, +/* pos 0183: 156 */ 0xE1 /* 'a' -> */, +/* pos 0184: 157 */ 0xE4 /* 'd' -> */, +/* pos 0185: 158 */ 0xE5 /* 'e' -> */, +/* pos 0186: 159 */ 0xF2 /* 'r' -> */, +/* pos 0187: 160 */ 0xF3 /* 's' -> */, +/* pos 0188: 161 */ 0xBA /* ':' -> */, +/* pos 0189: 162 */ 0x00, 0x12 /* - terminal marker 18 - */, +/* pos 018b: 163 */ 0xE6 /* 'f' -> */, +/* pos 018c: 164 */ 0xAD /* '-' -> */, +/* pos 018d: 165 */ 0x6D /* 'm' */, 0x0D, 0x00 /* (to 0x019A state 166) */, + 0x6E /* 'n' */, 0x20, 0x00 /* (to 0x01B0 state 181) */, + 0x72 /* 'r' */, 0xA7, 0x01 /* (to 0x033A state 435) */, + 0x75 /* 'u' */, 0xAB, 0x01 /* (to 0x0341 state 441) */, + 0x08, /* fail */ +/* pos 019a: 166 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x01A1 state 167) */, + 0x61 /* 'a' */, 0x97, 0x01 /* (to 0x0334 state 430) */, + 0x08, /* fail */ +/* pos 01a1: 167 */ 0xE4 /* 'd' -> */, +/* pos 01a2: 168 */ 0xE9 /* 'i' -> */, +/* pos 01a3: 169 */ 0xE6 /* 'f' -> */, +/* pos 01a4: 170 */ 0xE9 /* 'i' -> */, +/* pos 01a5: 171 */ 0xE5 /* 'e' -> */, +/* pos 01a6: 172 */ 0xE4 /* 'd' -> */, +/* pos 01a7: 173 */ 0xAD /* '-' -> */, +/* pos 01a8: 174 */ 0xF3 /* 's' -> */, +/* pos 01a9: 175 */ 0xE9 /* 'i' -> */, +/* pos 01aa: 176 */ 0xEE /* 'n' -> */, +/* pos 01ab: 177 */ 0xE3 /* 'c' -> */, +/* pos 01ac: 178 */ 0xE5 /* 'e' -> */, +/* pos 01ad: 179 */ 0xBA /* ':' -> */, +/* pos 01ae: 180 */ 0x00, 0x13 /* - terminal marker 19 - */, +/* pos 01b0: 181 */ 0xEF /* 'o' -> */, +/* pos 01b1: 182 */ 0xEE /* 'n' -> */, +/* pos 01b2: 183 */ 0xE5 /* 'e' -> */, +/* pos 01b3: 184 */ 0xAD /* '-' -> */, +/* pos 01b4: 185 */ 0xED /* 'm' -> */, +/* pos 01b5: 186 */ 0xE1 /* 'a' -> */, +/* pos 01b6: 187 */ 0xF4 /* 't' -> */, +/* pos 01b7: 188 */ 0xE3 /* 'c' -> */, +/* pos 01b8: 189 */ 0xE8 /* 'h' -> */, +/* pos 01b9: 190 */ 0xBA /* ':' -> */, +/* pos 01ba: 191 */ 0x00, 0x14 /* - terminal marker 20 - */, +/* pos 01bc: 192 */ 0x65 /* 'e' */, 0x0D, 0x00 /* (to 0x01C9 state 193) */, + 0x6C /* 'l' */, 0x14, 0x00 /* (to 0x01D3 state 202) */, + 0x63 /* 'c' */, 0xF4, 0x00 /* (to 0x02B6 state 330) */, + 0x72 /* 'r' */, 0xFA, 0x00 /* (to 0x02BF state 338) */, + 0x08, /* fail */ +/* pos 01c9: 193 */ 0xEE /* 'n' -> */, +/* pos 01ca: 194 */ 0xE3 /* 'c' -> */, +/* pos 01cb: 195 */ 0xEF /* 'o' -> */, +/* pos 01cc: 196 */ 0xE4 /* 'd' -> */, +/* pos 01cd: 197 */ 0xE9 /* 'i' -> */, +/* pos 01ce: 198 */ 0xEE /* 'n' -> */, +/* pos 01cf: 199 */ 0xE7 /* 'g' -> */, +/* pos 01d0: 200 */ 0xBA /* ':' -> */, +/* pos 01d1: 201 */ 0x00, 0x15 /* - terminal marker 21 - */, +/* pos 01d3: 202 */ 0xE1 /* 'a' -> */, +/* pos 01d4: 203 */ 0xEE /* 'n' -> */, +/* pos 01d5: 204 */ 0xE7 /* 'g' -> */, +/* pos 01d6: 205 */ 0xF5 /* 'u' -> */, +/* pos 01d7: 206 */ 0xE1 /* 'a' -> */, +/* pos 01d8: 207 */ 0xE7 /* 'g' -> */, +/* pos 01d9: 208 */ 0xE5 /* 'e' -> */, +/* pos 01da: 209 */ 0xBA /* ':' -> */, +/* pos 01db: 210 */ 0x00, 0x16 /* - terminal marker 22 - */, +/* pos 01dd: 211 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x01E4 state 212) */, + 0x6F /* 'o' */, 0xA7, 0x01 /* (to 0x0387 state 497) */, + 0x08, /* fail */ +/* pos 01e4: 212 */ 0xE7 /* 'g' -> */, +/* pos 01e5: 213 */ 0xED /* 'm' -> */, +/* pos 01e6: 214 */ 0xE1 /* 'a' -> */, +/* pos 01e7: 215 */ 0xBA /* ':' -> */, +/* pos 01e8: 216 */ 0x00, 0x17 /* - terminal marker 23 - */, +/* pos 01ea: 217 */ 0xE3 /* 'c' -> */, +/* pos 01eb: 218 */ 0xE8 /* 'h' -> */, +/* pos 01ec: 219 */ 0xE5 /* 'e' -> */, +/* pos 01ed: 220 */ 0xAD /* '-' -> */, +/* pos 01ee: 221 */ 0xE3 /* 'c' -> */, +/* pos 01ef: 222 */ 0xEF /* 'o' -> */, +/* pos 01f0: 223 */ 0xEE /* 'n' -> */, +/* pos 01f1: 224 */ 0xF4 /* 't' -> */, +/* pos 01f2: 225 */ 0xF2 /* 'r' -> */, +/* pos 01f3: 226 */ 0xEF /* 'o' -> */, +/* pos 01f4: 227 */ 0xEC /* 'l' -> */, +/* pos 01f5: 228 */ 0xBA /* ':' -> */, +/* pos 01f6: 229 */ 0x00, 0x18 /* - terminal marker 24 - */, +/* pos 01f8: 230 */ 0xF4 /* 't' -> */, +/* pos 01f9: 231 */ 0xE8 /* 'h' -> */, +/* pos 01fa: 232 */ 0xEF /* 'o' -> */, +/* pos 01fb: 233 */ 0xF2 /* 'r' -> */, +/* pos 01fc: 234 */ 0xE9 /* 'i' -> */, +/* pos 01fd: 235 */ 0xFA /* 'z' -> */, +/* pos 01fe: 236 */ 0xE1 /* 'a' -> */, +/* pos 01ff: 237 */ 0xF4 /* 't' -> */, +/* pos 0200: 238 */ 0xE9 /* 'i' -> */, +/* pos 0201: 239 */ 0xEF /* 'o' -> */, +/* pos 0202: 240 */ 0xEE /* 'n' -> */, +/* pos 0203: 241 */ 0xBA /* ':' -> */, +/* pos 0204: 242 */ 0x00, 0x19 /* - terminal marker 25 - */, +/* pos 0206: 243 */ 0xEB /* 'k' -> */, +/* pos 0207: 244 */ 0xE9 /* 'i' -> */, +/* pos 0208: 245 */ 0xE5 /* 'e' -> */, +/* pos 0209: 246 */ 0xBA /* ':' -> */, +/* pos 020a: 247 */ 0x00, 0x1A /* - terminal marker 26 - */, +/* pos 020c: 248 */ 0xE5 /* 'e' -> */, +/* pos 020d: 249 */ 0xEE /* 'n' -> */, +/* pos 020e: 250 */ 0xF4 /* 't' -> */, +/* pos 020f: 251 */ 0xAD /* '-' -> */, +/* pos 0210: 252 */ 0x6C /* 'l' */, 0x10, 0x00 /* (to 0x0220 state 253) */, + 0x74 /* 't' */, 0x1E, 0x00 /* (to 0x0231 state 260) */, + 0x64 /* 'd' */, 0xC9, 0x00 /* (to 0x02DF state 366) */, + 0x65 /* 'e' */, 0xD3, 0x00 /* (to 0x02EC state 378) */, + 0x72 /* 'r' */, 0xEC, 0x00 /* (to 0x0308 state 403) */, + 0x08, /* fail */ +/* pos 0220: 253 */ 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x022A state 254) */, + 0x61 /* 'a' */, 0xD3, 0x00 /* (to 0x02F6 state 387) */, + 0x6F /* 'o' */, 0xD9, 0x00 /* (to 0x02FF state 395) */, + 0x08, /* fail */ +/* pos 022a: 254 */ 0xEE /* 'n' -> */, +/* pos 022b: 255 */ 0xE7 /* 'g' -> */, +/* pos 022c: 256 */ 0xF4 /* 't' -> */, +/* pos 022d: 257 */ 0xE8 /* 'h' -> */, +/* pos 022e: 258 */ 0xBA /* ':' -> */, +/* pos 022f: 259 */ 0x00, 0x1B /* - terminal marker 27 - */, +/* pos 0231: 260 */ 0xF9 /* 'y' -> */, +/* pos 0232: 261 */ 0xF0 /* 'p' -> */, +/* pos 0233: 262 */ 0xE5 /* 'e' -> */, +/* pos 0234: 263 */ 0xBA /* ':' -> */, +/* pos 0235: 264 */ 0x00, 0x1C /* - terminal marker 28 - */, +/* pos 0237: 265 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x023E state 266) */, + 0x65 /* 'e' */, 0xFF, 0x01 /* (to 0x0439 state 637) */, + 0x08, /* fail */ +/* pos 023e: 266 */ 0xF4 /* 't' -> */, +/* pos 023f: 267 */ 0xE5 /* 'e' -> */, +/* pos 0240: 268 */ 0xBA /* ':' -> */, +/* pos 0241: 269 */ 0x00, 0x1D /* - terminal marker 29 - */, +/* pos 0243: 270 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x024A state 271) */, + 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x0250 state 276) */, + 0x08, /* fail */ +/* pos 024a: 271 */ 0xEE /* 'n' -> */, +/* pos 024b: 272 */ 0xE7 /* 'g' -> */, +/* pos 024c: 273 */ 0xE5 /* 'e' -> */, +/* pos 024d: 274 */ 0xBA /* ':' -> */, +/* pos 024e: 275 */ 0x00, 0x1E /* - terminal marker 30 - */, +/* pos 0250: 276 */ 0x66 /* 'f' */, 0x0A, 0x00 /* (to 0x025A state 277) */, + 0x74 /* 't' */, 0x63, 0x01 /* (to 0x03B6 state 529) */, + 0x70 /* 'p' */, 0x22, 0x02 /* (to 0x0478 state 682) */, + 0x08, /* fail */ +/* pos 025a: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0261 state 278) */, + 0x72 /* 'r' */, 0x53, 0x01 /* (to 0x03B0 state 524) */, + 0x08, /* fail */ +/* pos 0261: 278 */ 0xF2 /* 'r' -> */, +/* pos 0262: 279 */ 0xE5 /* 'e' -> */, +/* pos 0263: 280 */ 0xF2 /* 'r' -> */, +/* pos 0264: 281 */ 0xBA /* ':' -> */, +/* pos 0265: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, +/* pos 0267: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, +/* pos 0269: 284 */ 0xE5 /* 'e' -> */, +/* pos 026a: 285 */ 0xF2 /* 'r' -> */, +/* pos 026b: 286 */ 0xF3 /* 's' -> */, +/* pos 026c: 287 */ 0xE9 /* 'i' -> */, +/* pos 026d: 288 */ 0xEF /* 'o' -> */, +/* pos 026e: 289 */ 0xEE /* 'n' -> */, +/* pos 026f: 290 */ 0xBA /* ':' -> */, +/* pos 0270: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, +/* pos 0272: 292 */ 0xF2 /* 'r' -> */, +/* pos 0273: 293 */ 0xE9 /* 'i' -> */, +/* pos 0274: 294 */ 0xE7 /* 'g' -> */, +/* pos 0275: 295 */ 0xE9 /* 'i' -> */, +/* pos 0276: 296 */ 0xEE /* 'n' -> */, +/* pos 0277: 297 */ 0xBA /* ':' -> */, +/* pos 0278: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, +/* pos 027a: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0287 state 300) */, + 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x0291 state 309) */, + 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0298 state 315) */, + 0x73 /* 's' */, 0x20, 0x00 /* (to 0x02A3 state 319) */, + 0x08, /* fail */ +/* pos 0287: 300 */ 0xF5 /* 'u' -> */, +/* pos 0288: 301 */ 0xF4 /* 't' -> */, +/* pos 0289: 302 */ 0xE8 /* 'h' -> */, +/* pos 028a: 303 */ 0xEF /* 'o' -> */, +/* pos 028b: 304 */ 0xF2 /* 'r' -> */, +/* pos 028c: 305 */ 0xE9 /* 'i' -> */, +/* pos 028d: 306 */ 0xF4 /* 't' -> */, +/* pos 028e: 307 */ 0xF9 /* 'y' -> */, +/* pos 028f: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, +/* pos 0291: 309 */ 0xE5 /* 'e' -> */, +/* pos 0292: 310 */ 0xF4 /* 't' -> */, +/* pos 0293: 311 */ 0xE8 /* 'h' -> */, +/* pos 0294: 312 */ 0xEF /* 'o' -> */, +/* pos 0295: 313 */ 0xE4 /* 'd' -> */, +/* pos 0296: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, +/* pos 0298: 315 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x029F state 316) */, + 0x72 /* 'r' */, 0xE9, 0x01 /* (to 0x0484 state 693) */, + 0x08, /* fail */ +/* pos 029f: 316 */ 0xF4 /* 't' -> */, +/* pos 02a0: 317 */ 0xE8 /* 'h' -> */, +/* pos 02a1: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, +/* pos 02a3: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x02AA state 320) */, + 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02B0 state 325) */, + 0x08, /* fail */ +/* pos 02aa: 320 */ 0xE8 /* 'h' -> */, +/* pos 02ab: 321 */ 0xE5 /* 'e' -> */, +/* pos 02ac: 322 */ 0xED /* 'm' -> */, +/* pos 02ad: 323 */ 0xE5 /* 'e' -> */, +/* pos 02ae: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, +/* pos 02b0: 325 */ 0xE1 /* 'a' -> */, +/* pos 02b1: 326 */ 0xF4 /* 't' -> */, +/* pos 02b2: 327 */ 0xF5 /* 'u' -> */, +/* pos 02b3: 328 */ 0xF3 /* 's' -> */, +/* pos 02b4: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, +/* pos 02b6: 330 */ 0xE8 /* 'h' -> */, +/* pos 02b7: 331 */ 0xE1 /* 'a' -> */, +/* pos 02b8: 332 */ 0xF2 /* 'r' -> */, +/* pos 02b9: 333 */ 0xF3 /* 's' -> */, +/* pos 02ba: 334 */ 0xE5 /* 'e' -> */, +/* pos 02bb: 335 */ 0xF4 /* 't' -> */, +/* pos 02bc: 336 */ 0xBA /* ':' -> */, +/* pos 02bd: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, +/* pos 02bf: 338 */ 0xE1 /* 'a' -> */, +/* pos 02c0: 339 */ 0xEE /* 'n' -> */, +/* pos 02c1: 340 */ 0xE7 /* 'g' -> */, +/* pos 02c2: 341 */ 0xE5 /* 'e' -> */, +/* pos 02c3: 342 */ 0xF3 /* 's' -> */, +/* pos 02c4: 343 */ 0xBA /* ':' -> */, +/* pos 02c5: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, +/* pos 02c7: 345 */ 0xEC /* 'l' -> */, +/* pos 02c8: 346 */ 0xEC /* 'l' -> */, +/* pos 02c9: 347 */ 0xEF /* 'o' -> */, +/* pos 02ca: 348 */ 0xF7 /* 'w' -> */, +/* pos 02cb: 349 */ 0xAD /* '-' -> */, +/* pos 02cc: 350 */ 0xEF /* 'o' -> */, +/* pos 02cd: 351 */ 0xF2 /* 'r' -> */, +/* pos 02ce: 352 */ 0xE9 /* 'i' -> */, +/* pos 02cf: 353 */ 0xE7 /* 'g' -> */, +/* pos 02d0: 354 */ 0xE9 /* 'i' -> */, +/* pos 02d1: 355 */ 0xEE /* 'n' -> */, +/* pos 02d2: 356 */ 0xBA /* ':' -> */, +/* pos 02d3: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, +/* pos 02d5: 358 */ 0xE5 /* 'e' -> */, +/* pos 02d6: 359 */ 0xBA /* ':' -> */, +/* pos 02d7: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, +/* pos 02d9: 361 */ 0xEC /* 'l' -> */, +/* pos 02da: 362 */ 0xEF /* 'o' -> */, +/* pos 02db: 363 */ 0xF7 /* 'w' -> */, +/* pos 02dc: 364 */ 0xBA /* ':' -> */, +/* pos 02dd: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, +/* pos 02df: 366 */ 0xE9 /* 'i' -> */, +/* pos 02e0: 367 */ 0xF3 /* 's' -> */, +/* pos 02e1: 368 */ 0xF0 /* 'p' -> */, +/* pos 02e2: 369 */ 0xEF /* 'o' -> */, +/* pos 02e3: 370 */ 0xF3 /* 's' -> */, +/* pos 02e4: 371 */ 0xE9 /* 'i' -> */, +/* pos 02e5: 372 */ 0xF4 /* 't' -> */, +/* pos 02e6: 373 */ 0xE9 /* 'i' -> */, +/* pos 02e7: 374 */ 0xEF /* 'o' -> */, +/* pos 02e8: 375 */ 0xEE /* 'n' -> */, +/* pos 02e9: 376 */ 0xBA /* ':' -> */, +/* pos 02ea: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, +/* pos 02ec: 378 */ 0xEE /* 'n' -> */, +/* pos 02ed: 379 */ 0xE3 /* 'c' -> */, +/* pos 02ee: 380 */ 0xEF /* 'o' -> */, +/* pos 02ef: 381 */ 0xE4 /* 'd' -> */, +/* pos 02f0: 382 */ 0xE9 /* 'i' -> */, +/* pos 02f1: 383 */ 0xEE /* 'n' -> */, +/* pos 02f2: 384 */ 0xE7 /* 'g' -> */, +/* pos 02f3: 385 */ 0xBA /* ':' -> */, +/* pos 02f4: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, +/* pos 02f6: 387 */ 0xEE /* 'n' -> */, +/* pos 02f7: 388 */ 0xE7 /* 'g' -> */, +/* pos 02f8: 389 */ 0xF5 /* 'u' -> */, +/* pos 02f9: 390 */ 0xE1 /* 'a' -> */, +/* pos 02fa: 391 */ 0xE7 /* 'g' -> */, +/* pos 02fb: 392 */ 0xE5 /* 'e' -> */, +/* pos 02fc: 393 */ 0xBA /* ':' -> */, +/* pos 02fd: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, +/* pos 02ff: 395 */ 0xE3 /* 'c' -> */, +/* pos 0300: 396 */ 0xE1 /* 'a' -> */, +/* pos 0301: 397 */ 0xF4 /* 't' -> */, +/* pos 0302: 398 */ 0xE9 /* 'i' -> */, +/* pos 0303: 399 */ 0xEF /* 'o' -> */, +/* pos 0304: 400 */ 0xEE /* 'n' -> */, +/* pos 0305: 401 */ 0xBA /* ':' -> */, +/* pos 0306: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, +/* pos 0308: 403 */ 0xE1 /* 'a' -> */, +/* pos 0309: 404 */ 0xEE /* 'n' -> */, +/* pos 030a: 405 */ 0xE7 /* 'g' -> */, +/* pos 030b: 406 */ 0xE5 /* 'e' -> */, +/* pos 030c: 407 */ 0xBA /* ':' -> */, +/* pos 030d: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, +/* pos 030f: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x0316 state 410) */, + 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x031B state 414) */, + 0x08, /* fail */ +/* pos 0316: 410 */ 0xE1 /* 'a' -> */, +/* pos 0317: 411 */ 0xE7 /* 'g' -> */, +/* pos 0318: 412 */ 0xBA /* ':' -> */, +/* pos 0319: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, +/* pos 031b: 414 */ 0xF0 /* 'p' -> */, +/* pos 031c: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0323 state 416) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0328 state 420) */, + 0x08, /* fail */ +/* pos 0323: 416 */ 0xE3 /* 'c' -> */, +/* pos 0324: 417 */ 0xF4 /* 't' -> */, +/* pos 0325: 418 */ 0xBA /* ':' -> */, +/* pos 0326: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, +/* pos 0328: 420 */ 0xF2 /* 'r' -> */, +/* pos 0329: 421 */ 0xE5 /* 'e' -> */, +/* pos 032a: 422 */ 0xF3 /* 's' -> */, +/* pos 032b: 423 */ 0xBA /* ':' -> */, +/* pos 032c: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, +/* pos 032e: 425 */ 0xF2 /* 'r' -> */, +/* pos 032f: 426 */ 0xEF /* 'o' -> */, +/* pos 0330: 427 */ 0xED /* 'm' -> */, +/* pos 0331: 428 */ 0xBA /* ':' -> */, +/* pos 0332: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, +/* pos 0334: 430 */ 0xF4 /* 't' -> */, +/* pos 0335: 431 */ 0xE3 /* 'c' -> */, +/* pos 0336: 432 */ 0xE8 /* 'h' -> */, +/* pos 0337: 433 */ 0xBA /* ':' -> */, +/* pos 0338: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, +/* pos 033a: 435 */ 0xE1 /* 'a' -> */, +/* pos 033b: 436 */ 0xEE /* 'n' -> */, +/* pos 033c: 437 */ 0xE7 /* 'g' -> */, +/* pos 033d: 438 */ 0xE5 /* 'e' -> */, +/* pos 033e: 439 */ 0xBA /* ':' -> */, +/* pos 033f: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, +/* pos 0341: 441 */ 0xEE /* 'n' -> */, +/* pos 0342: 442 */ 0xED /* 'm' -> */, +/* pos 0343: 443 */ 0xEF /* 'o' -> */, +/* pos 0344: 444 */ 0xE4 /* 'd' -> */, +/* pos 0345: 445 */ 0xE9 /* 'i' -> */, +/* pos 0346: 446 */ 0xE6 /* 'f' -> */, +/* pos 0347: 447 */ 0xE9 /* 'i' -> */, +/* pos 0348: 448 */ 0xE5 /* 'e' -> */, +/* pos 0349: 449 */ 0xE4 /* 'd' -> */, +/* pos 034a: 450 */ 0xAD /* '-' -> */, +/* pos 034b: 451 */ 0xF3 /* 's' -> */, +/* pos 034c: 452 */ 0xE9 /* 'i' -> */, +/* pos 034d: 453 */ 0xEE /* 'n' -> */, +/* pos 034e: 454 */ 0xE3 /* 'c' -> */, +/* pos 034f: 455 */ 0xE5 /* 'e' -> */, +/* pos 0350: 456 */ 0xBA /* ':' -> */, +/* pos 0351: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, +/* pos 0353: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x035D state 459) */, + 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x036B state 472) */, + 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x0370 state 476) */, + 0x08, /* fail */ +/* pos 035d: 459 */ 0xF3 /* 's' -> */, +/* pos 035e: 460 */ 0xF4 /* 't' -> */, +/* pos 035f: 461 */ 0xAD /* '-' -> */, +/* pos 0360: 462 */ 0xED /* 'm' -> */, +/* pos 0361: 463 */ 0xEF /* 'o' -> */, +/* pos 0362: 464 */ 0xE4 /* 'd' -> */, +/* pos 0363: 465 */ 0xE9 /* 'i' -> */, +/* pos 0364: 466 */ 0xE6 /* 'f' -> */, +/* pos 0365: 467 */ 0xE9 /* 'i' -> */, +/* pos 0366: 468 */ 0xE5 /* 'e' -> */, +/* pos 0367: 469 */ 0xE4 /* 'd' -> */, +/* pos 0368: 470 */ 0xBA /* ':' -> */, +/* pos 0369: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, +/* pos 036b: 472 */ 0xEE /* 'n' -> */, +/* pos 036c: 473 */ 0xEB /* 'k' -> */, +/* pos 036d: 474 */ 0xBA /* ':' -> */, +/* pos 036e: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, +/* pos 0370: 476 */ 0xE3 /* 'c' -> */, +/* pos 0371: 477 */ 0xE1 /* 'a' -> */, +/* pos 0372: 478 */ 0xF4 /* 't' -> */, +/* pos 0373: 479 */ 0xE9 /* 'i' -> */, +/* pos 0374: 480 */ 0xEF /* 'o' -> */, +/* pos 0375: 481 */ 0xEE /* 'n' -> */, +/* pos 0376: 482 */ 0xBA /* ':' -> */, +/* pos 0377: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, +/* pos 0379: 484 */ 0xE1 /* 'a' -> */, +/* pos 037a: 485 */ 0xF8 /* 'x' -> */, +/* pos 037b: 486 */ 0xAD /* '-' -> */, +/* pos 037c: 487 */ 0xE6 /* 'f' -> */, +/* pos 037d: 488 */ 0xEF /* 'o' -> */, +/* pos 037e: 489 */ 0xF2 /* 'r' -> */, +/* pos 037f: 490 */ 0xF7 /* 'w' -> */, +/* pos 0380: 491 */ 0xE1 /* 'a' -> */, +/* pos 0381: 492 */ 0xF2 /* 'r' -> */, +/* pos 0382: 493 */ 0xE4 /* 'd' -> */, +/* pos 0383: 494 */ 0xF3 /* 's' -> */, +/* pos 0384: 495 */ 0xBA /* ':' -> */, +/* pos 0385: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, +/* pos 0387: 497 */ 0xF8 /* 'x' -> */, +/* pos 0388: 498 */ 0xF9 /* 'y' -> */, +/* pos 0389: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x0390 state 500) */, + 0x20 /* ' ' */, 0xBB, 0x00 /* (to 0x0447 state 649) */, + 0x08, /* fail */ +/* pos 0390: 500 */ 0xE1 /* 'a' -> */, +/* pos 0391: 501 */ 0xF5 /* 'u' -> */, +/* pos 0392: 502 */ 0xF4 /* 't' -> */, +/* pos 0393: 503 */ 0xE8 /* 'h' -> */, +/* pos 0394: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x039B state 505) */, + 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x03A5 state 514) */, + 0x08, /* fail */ +/* pos 039b: 505 */ 0xEE /* 'n' -> */, +/* pos 039c: 506 */ 0xF4 /* 't' -> */, +/* pos 039d: 507 */ 0xE9 /* 'i' -> */, +/* pos 039e: 508 */ 0xE3 /* 'c' -> */, +/* pos 039f: 509 */ 0xE1 /* 'a' -> */, +/* pos 03a0: 510 */ 0xF4 /* 't' -> */, +/* pos 03a1: 511 */ 0xE5 /* 'e' -> */, +/* pos 03a2: 512 */ 0xBA /* ':' -> */, +/* pos 03a3: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, +/* pos 03a5: 514 */ 0xF2 /* 'r' -> */, +/* pos 03a6: 515 */ 0xE9 /* 'i' -> */, +/* pos 03a7: 516 */ 0xFA /* 'z' -> */, +/* pos 03a8: 517 */ 0xE1 /* 'a' -> */, +/* pos 03a9: 518 */ 0xF4 /* 't' -> */, +/* pos 03aa: 519 */ 0xE9 /* 'i' -> */, +/* pos 03ab: 520 */ 0xEF /* 'o' -> */, +/* pos 03ac: 521 */ 0xEE /* 'n' -> */, +/* pos 03ad: 522 */ 0xBA /* ':' -> */, +/* pos 03ae: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, +/* pos 03b0: 524 */ 0xE5 /* 'e' -> */, +/* pos 03b1: 525 */ 0xF3 /* 's' -> */, +/* pos 03b2: 526 */ 0xE8 /* 'h' -> */, +/* pos 03b3: 527 */ 0xBA /* ':' -> */, +/* pos 03b4: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, +/* pos 03b6: 529 */ 0xF2 /* 'r' -> */, +/* pos 03b7: 530 */ 0xF9 /* 'y' -> */, +/* pos 03b8: 531 */ 0xAD /* '-' -> */, +/* pos 03b9: 532 */ 0xE1 /* 'a' -> */, +/* pos 03ba: 533 */ 0xE6 /* 'f' -> */, +/* pos 03bb: 534 */ 0xF4 /* 't' -> */, +/* pos 03bc: 535 */ 0xE5 /* 'e' -> */, +/* pos 03bd: 536 */ 0xF2 /* 'r' -> */, +/* pos 03be: 537 */ 0xBA /* ':' -> */, +/* pos 03bf: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, +/* pos 03c1: 539 */ 0xF6 /* 'v' -> */, +/* pos 03c2: 540 */ 0xE5 /* 'e' -> */, +/* pos 03c3: 541 */ 0xF2 /* 'r' -> */, +/* pos 03c4: 542 */ 0xBA /* ':' -> */, +/* pos 03c5: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, +/* pos 03c7: 544 */ 0xAD /* '-' -> */, +/* pos 03c8: 545 */ 0xE3 /* 'c' -> */, +/* pos 03c9: 546 */ 0xEF /* 'o' -> */, +/* pos 03ca: 547 */ 0xEF /* 'o' -> */, +/* pos 03cb: 548 */ 0xEB /* 'k' -> */, +/* pos 03cc: 549 */ 0xE9 /* 'i' -> */, +/* pos 03cd: 550 */ 0xE5 /* 'e' -> */, +/* pos 03ce: 551 */ 0xBA /* ':' -> */, +/* pos 03cf: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, +/* pos 03d1: 553 */ 0xF2 /* 'r' -> */, +/* pos 03d2: 554 */ 0xE9 /* 'i' -> */, +/* pos 03d3: 555 */ 0xE3 /* 'c' -> */, +/* pos 03d4: 556 */ 0xF4 /* 't' -> */, +/* pos 03d5: 557 */ 0xAD /* '-' -> */, +/* pos 03d6: 558 */ 0xF4 /* 't' -> */, +/* pos 03d7: 559 */ 0xF2 /* 'r' -> */, +/* pos 03d8: 560 */ 0xE1 /* 'a' -> */, +/* pos 03d9: 561 */ 0xEE /* 'n' -> */, +/* pos 03da: 562 */ 0xF3 /* 's' -> */, +/* pos 03db: 563 */ 0xF0 /* 'p' -> */, +/* pos 03dc: 564 */ 0xEF /* 'o' -> */, +/* pos 03dd: 565 */ 0xF2 /* 'r' -> */, +/* pos 03de: 566 */ 0xF4 /* 't' -> */, +/* pos 03df: 567 */ 0xAD /* '-' -> */, +/* pos 03e0: 568 */ 0xF3 /* 's' -> */, +/* pos 03e1: 569 */ 0xE5 /* 'e' -> */, +/* pos 03e2: 570 */ 0xE3 /* 'c' -> */, +/* pos 03e3: 571 */ 0xF5 /* 'u' -> */, +/* pos 03e4: 572 */ 0xF2 /* 'r' -> */, +/* pos 03e5: 573 */ 0xE9 /* 'i' -> */, +/* pos 03e6: 574 */ 0xF4 /* 't' -> */, +/* pos 03e7: 575 */ 0xF9 /* 'y' -> */, +/* pos 03e8: 576 */ 0xBA /* ':' -> */, +/* pos 03e9: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, +/* pos 03eb: 578 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x03F2 state 579) */, + 0x65 /* 'e' */, 0x87, 0x00 /* (to 0x0475 state 680) */, + 0x08, /* fail */ +/* pos 03f2: 579 */ 0xE1 /* 'a' -> */, +/* pos 03f3: 580 */ 0xEE /* 'n' -> */, +/* pos 03f4: 581 */ 0xF3 /* 's' -> */, +/* pos 03f5: 582 */ 0xE6 /* 'f' -> */, +/* pos 03f6: 583 */ 0xE5 /* 'e' -> */, +/* pos 03f7: 584 */ 0xF2 /* 'r' -> */, +/* pos 03f8: 585 */ 0xAD /* '-' -> */, +/* pos 03f9: 586 */ 0xE5 /* 'e' -> */, +/* pos 03fa: 587 */ 0xEE /* 'n' -> */, +/* pos 03fb: 588 */ 0xE3 /* 'c' -> */, +/* pos 03fc: 589 */ 0xEF /* 'o' -> */, +/* pos 03fd: 590 */ 0xE4 /* 'd' -> */, +/* pos 03fe: 591 */ 0xE9 /* 'i' -> */, +/* pos 03ff: 592 */ 0xEE /* 'n' -> */, +/* pos 0400: 593 */ 0xE7 /* 'g' -> */, +/* pos 0401: 594 */ 0xBA /* ':' -> */, +/* pos 0402: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, +/* pos 0404: 596 */ 0xE5 /* 'e' -> */, +/* pos 0405: 597 */ 0xF2 /* 'r' -> */, +/* pos 0406: 598 */ 0xAD /* '-' -> */, +/* pos 0407: 599 */ 0xE1 /* 'a' -> */, +/* pos 0408: 600 */ 0xE7 /* 'g' -> */, +/* pos 0409: 601 */ 0xE5 /* 'e' -> */, +/* pos 040a: 602 */ 0xEE /* 'n' -> */, +/* pos 040b: 603 */ 0xF4 /* 't' -> */, +/* pos 040c: 604 */ 0xBA /* ':' -> */, +/* pos 040d: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, +/* pos 040f: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x0416 state 607) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x041B state 611) */, + 0x08, /* fail */ +/* pos 0416: 607 */ 0xF2 /* 'r' -> */, +/* pos 0417: 608 */ 0xF9 /* 'y' -> */, +/* pos 0418: 609 */ 0xBA /* ':' -> */, +/* pos 0419: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, +/* pos 041b: 611 */ 0xE1 /* 'a' -> */, +/* pos 041c: 612 */ 0xBA /* ':' -> */, +/* pos 041d: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, +/* pos 041f: 614 */ 0xF7 /* 'w' -> */, +/* pos 0420: 615 */ 0xF7 /* 'w' -> */, +/* pos 0421: 616 */ 0xAD /* '-' -> */, +/* pos 0422: 617 */ 0xE1 /* 'a' -> */, +/* pos 0423: 618 */ 0xF5 /* 'u' -> */, +/* pos 0424: 619 */ 0xF4 /* 't' -> */, +/* pos 0425: 620 */ 0xE8 /* 'h' -> */, +/* pos 0426: 621 */ 0xE5 /* 'e' -> */, +/* pos 0427: 622 */ 0xEE /* 'n' -> */, +/* pos 0428: 623 */ 0xF4 /* 't' -> */, +/* pos 0429: 624 */ 0xE9 /* 'i' -> */, +/* pos 042a: 625 */ 0xE3 /* 'c' -> */, +/* pos 042b: 626 */ 0xE1 /* 'a' -> */, +/* pos 042c: 627 */ 0xF4 /* 't' -> */, +/* pos 042d: 628 */ 0xE5 /* 'e' -> */, +/* pos 042e: 629 */ 0xBA /* ':' -> */, +/* pos 042f: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, +/* pos 0431: 631 */ 0xF4 /* 't' -> */, +/* pos 0432: 632 */ 0xE3 /* 'c' -> */, +/* pos 0433: 633 */ 0xE8 /* 'h' -> */, +/* pos 0434: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, +/* pos 0436: 635 */ 0xF4 /* 't' -> */, +/* pos 0437: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, +/* pos 0439: 637 */ 0xEC /* 'l' -> */, +/* pos 043a: 638 */ 0xE5 /* 'e' -> */, +/* pos 043b: 639 */ 0xF4 /* 't' -> */, +/* pos 043c: 640 */ 0xE5 /* 'e' -> */, +/* pos 043d: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, +/* pos 043f: 642 */ 0xE9 /* 'i' -> */, +/* pos 0440: 643 */ 0xAD /* '-' -> */, +/* pos 0441: 644 */ 0xE1 /* 'a' -> */, +/* pos 0442: 645 */ 0xF2 /* 'r' -> */, +/* pos 0443: 646 */ 0xE7 /* 'g' -> */, +/* pos 0444: 647 */ 0xF3 /* 's' -> */, +/* pos 0445: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, +/* pos 0447: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, +/* pos 0449: 650 */ 0xAD /* '-' -> */, +/* pos 044a: 651 */ 0x72 /* 'r' */, 0x0A, 0x00 /* (to 0x0454 state 652) */, + 0x66 /* 'f' */, 0x13, 0x00 /* (to 0x0460 state 662) */, + 0x61 /* 'a' */, 0x3C, 0x00 /* (to 0x048C state 700) */, + 0x08, /* fail */ +/* pos 0454: 652 */ 0xE5 /* 'e' -> */, +/* pos 0455: 653 */ 0xE1 /* 'a' -> */, +/* pos 0456: 654 */ 0xEC /* 'l' -> */, +/* pos 0457: 655 */ 0xAD /* '-' -> */, +/* pos 0458: 656 */ 0xE9 /* 'i' -> */, +/* pos 0459: 657 */ 0xF0 /* 'p' -> */, +/* pos 045a: 658 */ 0xBA /* ':' -> */, +/* pos 045b: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, +/* pos 045d: 660 */ 0xA0 /* ' ' -> */, +/* pos 045e: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, +/* pos 0460: 662 */ 0xEF /* 'o' -> */, +/* pos 0461: 663 */ 0xF2 /* 'r' -> */, +/* pos 0462: 664 */ 0xF7 /* 'w' -> */, +/* pos 0463: 665 */ 0xE1 /* 'a' -> */, +/* pos 0464: 666 */ 0xF2 /* 'r' -> */, +/* pos 0465: 667 */ 0xE4 /* 'd' -> */, +/* pos 0466: 668 */ 0xE5 /* 'e' -> */, +/* pos 0467: 669 */ 0xE4 /* 'd' -> */, +/* pos 0468: 670 */ 0xAD /* '-' -> */, +/* pos 0469: 671 */ 0xE6 /* 'f' -> */, +/* pos 046a: 672 */ 0xEF /* 'o' -> */, +/* pos 046b: 673 */ 0xF2 /* 'r' -> */, +/* pos 046c: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, +/* pos 046e: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, +/* pos 0470: 676 */ 0xE1 /* 'a' -> */, +/* pos 0471: 677 */ 0xE4 /* 'd' -> */, +/* pos 0472: 678 */ 0xA0 /* ' ' -> */, +/* pos 0473: 679 */ 0x00, 0x52 /* - terminal marker 82 - */, +/* pos 0475: 680 */ 0xBA /* ':' -> */, +/* pos 0476: 681 */ 0x00, 0x53 /* - terminal marker 83 - */, +/* pos 0478: 682 */ 0xEC /* 'l' -> */, +/* pos 0479: 683 */ 0xE1 /* 'a' -> */, +/* pos 047a: 684 */ 0xF9 /* 'y' -> */, +/* pos 047b: 685 */ 0xAD /* '-' -> */, +/* pos 047c: 686 */ 0xEE /* 'n' -> */, +/* pos 047d: 687 */ 0xEF /* 'o' -> */, +/* pos 047e: 688 */ 0xEE /* 'n' -> */, +/* pos 047f: 689 */ 0xE3 /* 'c' -> */, +/* pos 0480: 690 */ 0xE5 /* 'e' -> */, +/* pos 0481: 691 */ 0xBA /* ':' -> */, +/* pos 0482: 692 */ 0x00, 0x54 /* - terminal marker 84 - */, +/* pos 0484: 693 */ 0xEF /* 'o' -> */, +/* pos 0485: 694 */ 0xF4 /* 't' -> */, +/* pos 0486: 695 */ 0xEF /* 'o' -> */, +/* pos 0487: 696 */ 0xE3 /* 'c' -> */, +/* pos 0488: 697 */ 0xEF /* 'o' -> */, +/* pos 0489: 698 */ 0xEC /* 'l' -> */, +/* pos 048a: 699 */ 0x00, 0x55 /* - terminal marker 85 - */, +/* pos 048c: 700 */ 0xF5 /* 'u' -> */, +/* pos 048d: 701 */ 0xF4 /* 't' -> */, +/* pos 048e: 702 */ 0xE8 /* 'h' -> */, +/* pos 048f: 703 */ 0xAD /* '-' -> */, +/* pos 0490: 704 */ 0xF4 /* 't' -> */, +/* pos 0491: 705 */ 0xEF /* 'o' -> */, +/* pos 0492: 706 */ 0xEB /* 'k' -> */, +/* pos 0493: 707 */ 0xE5 /* 'e' -> */, +/* pos 0494: 708 */ 0xEE /* 'n' -> */, +/* pos 0495: 709 */ 0xBA /* ':' -> */, +/* pos 0496: 710 */ 0x00, 0x56 /* - terminal marker 86 - */, +/* total size 1176 bytes */ diff --git a/thirdparty/libwebsockets/roles/http/private.h b/thirdparty/libwebsockets/roles/http/private.h new file mode 100644 index 0000000000..2aa7a92f75 --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/private.h @@ -0,0 +1,257 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h if either H1 or H2 roles are + * enabled + */ + +#if defined(LWS_WITH_HTTP_PROXY) + #include <hubbub/hubbub.h> + #include <hubbub/parser.h> + #endif + +#define lwsi_role_http(wsi) (lwsi_role_h1(wsi) || lwsi_role_h2(wsi)) + +enum http_version { + HTTP_VERSION_1_0, + HTTP_VERSION_1_1, + HTTP_VERSION_2 +}; + +enum http_connection_type { + HTTP_CONNECTION_CLOSE, + HTTP_CONNECTION_KEEP_ALIVE +}; + +/* + * This is totally opaque to code using the library. It's exported as a + * forward-reference pointer-only declaration; the user can use the pointer with + * other APIs to get information out of it. + */ + +#if defined(LWS_WITH_ESP32) +typedef uint16_t ah_data_idx_t; +#else +typedef uint32_t ah_data_idx_t; +#endif + +struct lws_fragments { + ah_data_idx_t offset; + uint16_t len; + uint8_t nfrag; /* which ah->frag[] continues this content, or 0 */ + uint8_t flags; /* only http2 cares */ +}; + +#if defined(LWS_WITH_RANGES) +enum range_states { + LWSRS_NO_ACTIVE_RANGE, + LWSRS_BYTES_EQ, + LWSRS_FIRST, + LWSRS_STARTING, + LWSRS_ENDING, + LWSRS_COMPLETED, + LWSRS_SYNTAX, +}; + +struct lws_range_parsing { + unsigned long long start, end, extent, agg, budget; + const char buf[128]; + int pos; + enum range_states state; + char start_valid, end_valid, ctr, count_ranges, did_try, inside, send_ctr; +}; + +int +lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp, + unsigned long long extent); +int +lws_ranges_next(struct lws_range_parsing *rp); +void +lws_ranges_reset(struct lws_range_parsing *rp); +#endif + +/* + * these are assigned from a pool held in the context. + * Both client and server mode uses them for http header analysis + */ + +struct allocated_headers { + struct allocated_headers *next; /* linked list */ + struct lws *wsi; /* owner */ + char *data; /* prepared by context init to point to dedicated storage */ + ah_data_idx_t data_length; + /* + * the randomly ordered fragments, indexed by frag_index and + * lws_fragments->nfrag for continuation. + */ + struct lws_fragments frags[WSI_TOKEN_COUNT]; + time_t assigned; + /* + * for each recognized token, frag_index says which frag[] his data + * starts in (0 means the token did not appear) + * the actual header data gets dumped as it comes in, into data[] + */ + uint8_t frag_index[WSI_TOKEN_COUNT]; + +#ifndef LWS_NO_CLIENT + char initial_handshake_hash_base64[30]; +#endif + + uint32_t pos; + uint32_t http_response; + uint32_t current_token_limit; + int hdr_token_idx; + + int16_t lextable_pos; + + uint8_t in_use; + uint8_t nfrag; + char /*enum uri_path_states */ ups; + char /*enum uri_esc_states */ ues; + + char esc_stash; + char post_literal_equal; + uint8_t /* enum lws_token_indexes */ parser_state; +}; + + + +#if defined(LWS_WITH_HTTP_PROXY) +struct lws_rewrite { + hubbub_parser *parser; + hubbub_parser_optparams params; + const char *from, *to; + int from_len, to_len; + unsigned char *p, *end; + struct lws *wsi; +}; +static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len) +{ + if ((int)s->len != len) + return 1; + + return strncmp((const char *)s->ptr, p, len); +} +typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw); +LWS_EXTERN struct lws_rewrite * +lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to); +LWS_EXTERN void +lws_rewrite_destroy(struct lws_rewrite *r); +LWS_EXTERN int +lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len); +#endif + +struct lws_pt_role_http { + struct allocated_headers *ah_list; + struct lws *ah_wait_list; +#ifdef LWS_WITH_CGI + struct lws_cgi *cgi_list; +#endif + int ah_wait_list_length; + uint32_t ah_pool_length; + + int ah_count_in_use; +}; + +struct lws_peer_role_http { + uint32_t count_ah; + uint32_t total_ah; +}; + +struct lws_vhost_role_http { + char http_proxy_address[128]; + const struct lws_http_mount *mount_list; + const char *error_document_404; + unsigned int http_proxy_port; +}; + +#ifdef LWS_WITH_ACCESS_LOG +struct lws_access_log { + char *header_log; + char *user_agent; + char *referrer; + unsigned long sent; + int response; +}; +#endif + +struct _lws_http_mode_related { + struct lws *new_wsi_list; + +#if defined(LWS_WITH_HTTP_PROXY) + struct lws_rewrite *rw; +#endif + struct allocated_headers *ah; + struct lws *ah_wait_list; + + lws_filepos_t filepos; + lws_filepos_t filelen; + lws_fop_fd_t fop_fd; + +#if defined(LWS_WITH_RANGES) + struct lws_range_parsing range; + char multipart_content_type[64]; +#endif + +#ifdef LWS_WITH_ACCESS_LOG + struct lws_access_log access_log; +#endif +#ifdef LWS_WITH_CGI + struct lws_cgi *cgi; /* wsi being cgi master have one of these */ +#endif + + enum http_version request_version; + enum http_connection_type connection_type; + lws_filepos_t tx_content_length; + lws_filepos_t tx_content_remain; + lws_filepos_t rx_content_length; + lws_filepos_t rx_content_remain; + +#if defined(LWS_WITH_HTTP_PROXY) + unsigned int perform_rewrite:1; +#endif +}; + + +#ifndef LWS_NO_CLIENT +enum lws_chunk_parser { + ELCP_HEX, + ELCP_CR, + ELCP_CONTENT, + ELCP_POST_CR, + ELCP_POST_LF, +}; +#endif + +enum lws_parse_urldecode_results { + LPUR_CONTINUE, + LPUR_SWALLOW, + LPUR_FORBID, + LPUR_EXCESSIVE, +}; + +int +lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len); + +void +_lws_header_table_reset(struct allocated_headers *ah); + +LWS_EXTERN int +_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah); diff --git a/thirdparty/libwebsockets/roles/http/server/access-log.c b/thirdparty/libwebsockets/roles/http/server/access-log.c new file mode 100644 index 0000000000..0e75309d7a --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/server/access-log.c @@ -0,0 +1,182 @@ +/* + * libwebsockets - server access log handling + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +/* + * Produce Apache-compatible log string for wsi, like this: + * + * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800] + * "GET /aep-screen.png HTTP/1.1" + * 200 152987 "https://libwebsockets.org/index.html" + * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36" + * + */ + +extern const char * const method_names[]; + +static const char * const hver[] = { + "HTTP/1.0", "HTTP/1.1", "HTTP/2" +}; + +void +lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth) +{ +#ifdef LWS_WITH_IPV6 + char ads[INET6_ADDRSTRLEN]; +#else + char ads[INET_ADDRSTRLEN]; +#endif + char da[64]; + const char *pa, *me; + struct tm *tmp; + time_t t = time(NULL); + int l = 256, m; + + if (!wsi->vhost) + return; + + /* only worry about preparing it if we store it */ + if (wsi->vhost->log_fd == (int)LWS_INVALID_FILE) + return; + + if (wsi->access_log_pending) + lws_access_log(wsi); + + wsi->http.access_log.header_log = lws_malloc(l, "access log"); + if (wsi->http.access_log.header_log) { + + tmp = localtime(&t); + if (tmp) + strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp); + else + strcpy(da, "01/Jan/1970:00:00:00 +0000"); + + pa = lws_get_peer_simple(wsi, ads, sizeof(ads)); + if (!pa) + pa = "(unknown)"; + + if (wsi->http2_substream) + me = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD); + else + me = method_names[meth]; + if (!me) + me = "(null)"; + + lws_snprintf(wsi->http.access_log.header_log, l, + "%s - - [%s] \"%s %s %s\"", + pa, da, me, uri_ptr, + hver[wsi->http.request_version]); + + l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT); + if (l) { + wsi->http.access_log.user_agent = lws_malloc(l + 2, "access log"); + if (!wsi->http.access_log.user_agent) { + lwsl_err("OOM getting user agent\n"); + lws_free_set_NULL(wsi->http.access_log.header_log); + return; + } + + lws_hdr_copy(wsi, wsi->http.access_log.user_agent, + l + 1, WSI_TOKEN_HTTP_USER_AGENT); + + for (m = 0; m < l; m++) + if (wsi->http.access_log.user_agent[m] == '\"') + wsi->http.access_log.user_agent[m] = '\''; + } + l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER); + if (l) { + wsi->http.access_log.referrer = lws_malloc(l + 2, "referrer"); + if (!wsi->http.access_log.referrer) { + lwsl_err("OOM getting user agent\n"); + lws_free_set_NULL(wsi->http.access_log.user_agent); + lws_free_set_NULL(wsi->http.access_log.header_log); + return; + } + lws_hdr_copy(wsi, wsi->http.access_log.referrer, + l + 1, WSI_TOKEN_HTTP_REFERER); + + for (m = 0; m < l; m++) + if (wsi->http.access_log.referrer[m] == '\"') + wsi->http.access_log.referrer[m] = '\''; + } + wsi->access_log_pending = 1; + } +} + + +int +lws_access_log(struct lws *wsi) +{ + char *p = wsi->http.access_log.user_agent, ass[512], + *p1 = wsi->http.access_log.referrer; + int l; + + if (!wsi->vhost) + return 0; + + if (wsi->vhost->log_fd == (int)LWS_INVALID_FILE) + return 0; + + if (!wsi->access_log_pending) + return 0; + + if (!wsi->http.access_log.header_log) + return 0; + + if (!p) + p = ""; + + if (!p1) + p1 = ""; + + /* + * We do this in two parts to restrict an oversize referrer such that + * we will always have space left to append an empty useragent, while + * maintaining the structure of the log text + */ + l = lws_snprintf(ass, sizeof(ass) - 7, "%s %d %lu \"%s", + wsi->http.access_log.header_log, + wsi->http.access_log.response, wsi->http.access_log.sent, p1); + if (strlen(p) > sizeof(ass) - 6 - l) + p[sizeof(ass) - 6 - l] = '\0'; + l += lws_snprintf(ass + l, sizeof(ass) - 1 - l, "\" \"%s\"\n", p); + + if (write(wsi->vhost->log_fd, ass, l) != l) + lwsl_err("Failed to write log\n"); + + if (wsi->http.access_log.header_log) { + lws_free(wsi->http.access_log.header_log); + wsi->http.access_log.header_log = NULL; + } + if (wsi->http.access_log.user_agent) { + lws_free(wsi->http.access_log.user_agent); + wsi->http.access_log.user_agent = NULL; + } + if (wsi->http.access_log.referrer) { + lws_free(wsi->http.access_log.referrer); + wsi->http.access_log.referrer = NULL; + } + wsi->access_log_pending = 0; + + return 0; +} + diff --git a/thirdparty/lws/server/fops-zip.c b/thirdparty/libwebsockets/roles/http/server/fops-zip.c index 2b254f67af..4db83ce621 100644 --- a/thirdparty/lws/server/fops-zip.c +++ b/thirdparty/libwebsockets/roles/http/server/fops-zip.c @@ -51,7 +51,7 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include "core/private.h" #include <zlib.h> @@ -241,13 +241,13 @@ lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len) if (priv->hdr.filename_len != len) goto next; - if (len >= sizeof(buf) - 1) + if (len >= (int)sizeof(buf) - 1) return LWS_FZ_ERR_NAME_TOO_LONG; if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd, &amount, buf, len)) return LWS_FZ_ERR_NAME_READ; - if (amount != len) + if ((int)amount != len) return LWS_FZ_ERR_NAME_READ; buf[len] = '\0'; @@ -348,9 +348,8 @@ lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path, m = sizeof(rp) - 1; if ((vpath - vfs_path - 1) < m) - m = vpath - vfs_path - 1; - strncpy(rp, vfs_path, m); - rp[m] = '\0'; + m = lws_ptr_diff(vpath, vfs_path) - 1; + lws_strncpy(rp, vfs_path, m + 1); /* open the zip file itself using the incoming fops, not fops_zip */ @@ -363,7 +362,7 @@ lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path, if (*vpath == '/') vpath++; - m = lws_fops_zip_scan(priv, vpath, strlen(vpath)); + m = lws_fops_zip_scan(priv, vpath, (int)strlen(vpath)); if (m) { lwsl_err("unable to find record matching '%s' %d\n", vpath, m); goto bail2; @@ -565,7 +564,7 @@ spin: switch (ret) { case Z_NEED_DICT: ret = Z_DATA_ERROR; - /* and fall through */ + /* fallthru */ case Z_DATA_ERROR: case Z_MEM_ERROR: diff --git a/thirdparty/lws/server/lejp-conf.c b/thirdparty/libwebsockets/roles/http/server/lejp-conf.c index c2b684c278..e9ce854cfc 100644 --- a/thirdparty/lws/server/lejp-conf.c +++ b/thirdparty/libwebsockets/roles/http/server/lejp-conf.c @@ -19,8 +19,7 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" -#include "../misc/lejp.h" +#include "core/private.h" #ifndef _WIN32 /* this is needed for Travis CI */ @@ -40,6 +39,7 @@ static const char * const paths_global[] = { "global.timeout-secs", "global.reject-service-keywords[].*", "global.reject-service-keywords[]", + "global.default-alpn", }; enum lejp_global_paths { @@ -52,7 +52,8 @@ enum lejp_global_paths { LWJPGP_PINGPONG_SECS, LWJPGP_TIMEOUT_SECS, LWJPGP_REJECT_SERVICE_KEYWORDS_NAME, - LWJPGP_REJECT_SERVICE_KEYWORDS + LWJPGP_REJECT_SERVICE_KEYWORDS, + LWJPGP_DEFAULT_ALPN, }; static const char * const paths_vhosts[] = { @@ -100,6 +101,10 @@ static const char * const paths_vhosts[] = { "vhosts[].client-ssl-ca", "vhosts[].client-ssl-ciphers", "vhosts[].onlyraw", + "vhosts[].client-cert-required", + "vhosts[].ignore-missing-cert", + "vhosts[].error-document-404", + "vhosts[].alpn", }; enum lejp_vhost_paths { @@ -147,6 +152,10 @@ enum lejp_vhost_paths { LEJPVP_CLIENT_SSL_CA, LEJPVP_CLIENT_CIPHERS, LEJPVP_FLAG_ONLYRAW, + LEJPVP_FLAG_CLIENT_CERT_REQUIRED, + LEJPVP_IGNORE_MISSING_CERT, + LEJPVP_ERROR_DOCUMENT_404, + LEJPVP_ALPN, }; static const char * const parser_errs[] = { @@ -216,7 +225,7 @@ arg_to_bool(const char *s) if (n) return 1; - for (n = 0; n < ARRAY_SIZE(on); n++) + for (n = 0; n < (int)ARRAY_SIZE(on); n++) if (!strcasecmp(s, on[n])) return 1; @@ -285,6 +294,10 @@ lejp_globals_cb(struct lejp_ctx *ctx, char reason) a->info->timeout_secs = atoi(ctx->buf); return 0; + case LWJPGP_DEFAULT_ALPN: + a->info->alpn = a->p; + break; + default: return 0; } @@ -312,20 +325,39 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) #endif if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) { + uint32_t i[4]; + const char *ss; + /* set the defaults for this vhost */ a->valid = 1; a->head = NULL; a->last = NULL; - a->info->port = 0; - a->info->iface = NULL; + + i[0] = a->info->count_threads; + i[1] = a->info->options & ( + LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME | + LWS_SERVER_OPTION_LIBUV | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN | + LWS_SERVER_OPTION_LIBEVENT | + LWS_SERVER_OPTION_LIBEV + ); + ss = a->info->server_string; + i[2] = a->info->ws_ping_pong_interval; + i[3] = a->info->timeout_secs; + + memset(a->info, 0, sizeof(*a->info)); + + a->info->count_threads = i[0]; + a->info->options = i[1]; + a->info->server_string = ss; + a->info->ws_ping_pong_interval = i[2]; + a->info->timeout_secs = i[3]; + a->info->protocols = a->protocols; a->info->extensions = a->extensions; - a->info->ssl_cert_filepath = NULL; - a->info->ssl_private_key_filepath = NULL; - a->info->ssl_ca_filepath = NULL; - a->info->client_ssl_cert_filepath = NULL; - a->info->client_ssl_private_key_filepath = NULL; - a->info->client_ssl_ca_filepath = NULL; +#if defined(LWS_WITH_TLS) a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES256-GCM-SHA384:" "DHE-RSA-AES256-GCM-SHA384:" @@ -339,7 +371,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) "!DHE-RSA-AES256-SHA256:" "!AES256-GCM-SHA384:" "!AES256-SHA256"; - a->info->timeout_secs = 5; +#endif a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" "ECDHE-RSA-AES256-GCM-SHA384:" "DHE-RSA-AES256-GCM-SHA384:" @@ -353,13 +385,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) "!DHE-RSA-AES256-SHA256:" "!AES256-GCM-SHA384:" "!AES256-SHA256"; - a->info->pvo = NULL; - a->info->headers = NULL; a->info->keepalive_timeout = 5; - a->info->log_filepath = NULL; - a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK | - LWS_SERVER_OPTION_STS | LWS_SERVER_OPTION_ONLY_RAW); - a->enable_client_ssl = 0; } if (reason == LEJPCB_OBJECT_START && @@ -379,7 +405,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->pvo->next = a->info->pvo; a->info->pvo = a->pvo; a->pvo->name = a->p; - lwsl_notice(" adding protocol %s\n", a->p); + lwsl_info(" adding protocol %s\n", a->p); a->p += n; a->pvo->value = a->p; a->pvo->options = NULL; @@ -431,6 +457,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) } a->any_vhosts = 1; +#if defined(LWS_WITH_TLS) if (a->enable_client_ssl) { const char *cert_filepath = a->info->client_ssl_cert_filepath; const char *private_key_filepath = a->info->client_ssl_private_key_filepath; @@ -444,6 +471,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; lws_init_vhost_client_ssl(a->info, vhost); } +#endif return 0; } @@ -474,7 +502,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) if (a->last) a->last->mount_next = m; - for (n = 0; n < ARRAY_SIZE(mount_protocols); n++) + for (n = 0; n < (int)ARRAY_SIZE(mount_protocols); n++) if (!strncmp(a->m.origin, mount_protocols[n], strlen(mount_protocols[n]))) { lwsl_info("----%s\n", a->m.origin); @@ -484,7 +512,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) break; } - if (n == ARRAY_SIZE(mount_protocols)) { + if (n == (int)ARRAY_SIZE(mount_protocols)) { lwsl_err("unsupported protocol:// %s\n", a->m.origin); return 1; } @@ -573,9 +601,11 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_KEEPALIVE_TIMEOUT: a->info->keepalive_timeout = atoi(ctx->buf); return 0; +#if defined(LWS_WITH_TLS) case LEJPVP_CLIENT_CIPHERS: a->info->client_ssl_cipher_list = a->p; break; +#endif case LEJPVP_CIPHERS: a->info->ssl_cipher_list = a->p; break; @@ -651,6 +681,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_ENABLE_CLIENT_SSL: a->enable_client_ssl = arg_to_bool(ctx->buf); return 0; +#if defined(LWS_WITH_TLS) case LEJPVP_CLIENT_SSL_KEY: a->info->client_ssl_private_key_filepath = a->p; break; @@ -660,6 +691,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_CLIENT_SSL_CA: a->info->client_ssl_ca_filepath = a->p; break; +#endif case LEJPVP_NOIPV6: if (arg_to_bool(ctx->buf)) @@ -683,6 +715,24 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->info->options &= ~(LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); return 0; + case LEJPVP_FLAG_CLIENT_CERT_REQUIRED: + if (arg_to_bool(ctx->buf)) + a->info->options |= + LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; + return 0; + + case LEJPVP_IGNORE_MISSING_CERT: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_IGNORE_MISSING_CERT; + else + a->info->options &= ~(LWS_SERVER_OPTION_IGNORE_MISSING_CERT); + + return 0; + + case LEJPVP_ERROR_DOCUMENT_404: + a->info->error_document_404 = a->p; + break; + case LEJPVP_SSL_OPTION_SET: a->info->ssl_options_set |= atol(ctx->buf); return 0; @@ -690,6 +740,10 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->info->ssl_options_clear |= atol(ctx->buf); return 0; + case LEJPVP_ALPN: + a->info->alpn = a->p; + break; + default: return 0; } @@ -701,7 +755,7 @@ dostring: n = p1 - p; if (n > a->end - a->p) n = a->end - a->p; - strncpy(a->p, p, n); + lws_strncpy(a->p, p, n + 1); a->p += n; a->p += lws_snprintf(a->p, a->end - a->p, "%s", LWS_INSTALL_DATADIR); p += n + strlen(ESC_INSTALL_DATADIR); diff --git a/thirdparty/libwebsockets/roles/http/server/parsers.c b/thirdparty/libwebsockets/roles/http/server/parsers.c new file mode 100644 index 0000000000..cb022e362b --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/server/parsers.c @@ -0,0 +1,1139 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +static const unsigned char lextable[] = { + #include "../lextable.h" +}; + +#define FAIL_CHAR 0x08 + +static struct allocated_headers * +_lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size) +{ + struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct"); + + if (!ah) + return NULL; + + ah->data = lws_malloc(data_size, "ah data"); + if (!ah->data) { + lws_free(ah); + + return NULL; + } + ah->next = pt->http.ah_list; + pt->http.ah_list = ah; + ah->data_length = data_size; + pt->http.ah_pool_length++; + + lwsl_info("%s: created ah %p (size %d): pool length %d\n", __func__, + ah, (int)data_size, pt->http.ah_pool_length); + + return ah; +} + +int +_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah) +{ + lws_start_foreach_llp(struct allocated_headers **, a, pt->http.ah_list) { + if ((*a) == ah) { + *a = ah->next; + pt->http.ah_pool_length--; + lwsl_info("%s: freed ah %p : pool length %d\n", + __func__, ah, pt->http.ah_pool_length); + if (ah->data) + lws_free(ah->data); + lws_free(ah); + + return 0; + } + } lws_end_foreach_llp(a, next); + + return 1; +} + +void +_lws_header_table_reset(struct allocated_headers *ah) +{ + /* init the ah to reflect no headers or data have appeared yet */ + memset(ah->frag_index, 0, sizeof(ah->frag_index)); + memset(ah->frags, 0, sizeof(ah->frags)); + ah->nfrag = 0; + ah->pos = 0; + ah->http_response = 0; + ah->parser_state = WSI_TOKEN_NAME_PART; + ah->lextable_pos = 0; +} + +// doesn't scrub the ah rxbuffer by default, parent must do if needed + +void +__lws_header_table_reset(struct lws *wsi, int autoservice) +{ + struct allocated_headers *ah = wsi->http.ah; + struct lws_context_per_thread *pt; + struct lws_pollfd *pfd; + + /* if we have the idea we're resetting 'our' ah, must be bound to one */ + assert(ah); + /* ah also concurs with ownership */ + assert(ah->wsi == wsi); + + _lws_header_table_reset(ah); + + /* since we will restart the ah, our new headers are not completed */ + wsi->hdr_parsing_completed = 0; + + /* while we hold the ah, keep a timeout on the wsi */ + __lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH, + wsi->vhost->timeout_secs_ah_idle); + + time(&ah->assigned); + + if (wsi->position_in_fds_table != LWS_NO_FDS_POS && + lws_buflist_next_segment_len(&wsi->buflist, NULL) && + autoservice) { + lwsl_debug("%s: service on readbuf ah\n", __func__); + + pt = &wsi->context->pt[(int)wsi->tsi]; + /* + * Unlike a normal connect, we have the headers already + * (or the first part of them anyway) + */ + pfd = &pt->fds[wsi->position_in_fds_table]; + pfd->revents |= LWS_POLLIN; + lwsl_err("%s: calling service\n", __func__); + lws_service_fd_tsi(wsi->context, pfd, wsi->tsi); + } +} + +void +lws_header_table_reset(struct lws *wsi, int autoservice) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt, __func__); + + __lws_header_table_reset(wsi, autoservice); + + lws_pt_unlock(pt); +} + +static void +_lws_header_ensure_we_are_on_waiting_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_pollargs pa; + struct lws **pwsi = &pt->http.ah_wait_list; + + while (*pwsi) { + if (*pwsi == wsi) + return; + pwsi = &(*pwsi)->http.ah_wait_list; + } + + lwsl_info("%s: wsi: %p\n", __func__, wsi); + wsi->http.ah_wait_list = pt->http.ah_wait_list; + pt->http.ah_wait_list = wsi; + pt->http.ah_wait_list_length++; + + /* we cannot accept input then */ + + _lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa); +} + +static int +__lws_remove_from_ah_waiting_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws **pwsi =&pt->http.ah_wait_list; + + while (*pwsi) { + if (*pwsi == wsi) { + lwsl_info("%s: wsi %p\n", __func__, wsi); + /* point prev guy to our next */ + *pwsi = wsi->http.ah_wait_list; + /* we shouldn't point anywhere now */ + wsi->http.ah_wait_list = NULL; + pt->http.ah_wait_list_length--; + + return 1; + } + pwsi = &(*pwsi)->http.ah_wait_list; + } + + return 0; +} + +int LWS_WARN_UNUSED_RESULT +lws_header_table_attach(struct lws *wsi, int autoservice) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_pollargs pa; + int n; + + lwsl_info("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__, + (void *)wsi, (void *)wsi->http.ah, wsi->tsi, + pt->http.ah_count_in_use); + + lws_pt_lock(pt, __func__); + + /* if we are already bound to one, just clear it down */ + if (wsi->http.ah) { + lwsl_info("%s: cleardown\n", __func__); + goto reset; + } + + n = pt->http.ah_count_in_use == context->max_http_header_pool; +#if defined(LWS_WITH_PEER_LIMITS) + if (!n) { + n = lws_peer_confirm_ah_attach_ok(context, wsi->peer); + if (n) + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1); + } +#endif + if (n) { + /* + * Pool is either all busy, or we don't want to give this + * particular guy an ah right now... + * + * Make sure we are on the waiting list, and return that we + * weren't able to provide the ah + */ + _lws_header_ensure_we_are_on_waiting_list(wsi); + + goto bail; + } + + __lws_remove_from_ah_waiting_list(wsi); + + wsi->http.ah = _lws_create_ah(pt, context->max_http_header_data); + if (!wsi->http.ah) { /* we could not create an ah */ + _lws_header_ensure_we_are_on_waiting_list(wsi); + + goto bail; + } + + wsi->http.ah->in_use = 1; + wsi->http.ah->wsi = wsi; /* mark our owner */ + pt->http.ah_count_in_use++; + +#if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) + lws_context_lock(context); /* <====================================== */ + if (wsi->peer) + wsi->peer->http.count_ah++; + lws_context_unlock(context); /* ====================================> */ +#endif + + _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); + + lwsl_info("%s: did attach wsi %p: ah %p: count %d (on exit)\n", __func__, + (void *)wsi, (void *)wsi->http.ah, pt->http.ah_count_in_use); + +reset: + __lws_header_table_reset(wsi, autoservice); + + lws_pt_unlock(pt); + +#ifndef LWS_NO_CLIENT + if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) + if (!lws_client_connect_via_info2(wsi)) + /* our client connect has failed, the wsi + * has been closed + */ + return -1; +#endif + + return 0; + +bail: + lws_pt_unlock(pt); + + return 1; +} + +int __lws_header_table_detach(struct lws *wsi, int autoservice) +{ + struct lws_context *context = wsi->context; + struct allocated_headers *ah = wsi->http.ah; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_pollargs pa; + struct lws **pwsi, **pwsi_eligible; + time_t now; + + __lws_remove_from_ah_waiting_list(wsi); + + if (!ah) + return 0; + + lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__, + (void *)wsi, (void *)ah, wsi->tsi, + pt->http.ah_count_in_use); + + /* we did have an ah attached */ + time(&now); + if (ah->assigned && now - ah->assigned > 3) { + /* + * we're detaching the ah, but it was held an + * unreasonably long time + */ + lwsl_debug("%s: wsi %p: ah held %ds, role/state 0x%x 0x%x," + "\n", __func__, wsi, (int)(now - ah->assigned), + lwsi_role(wsi), lwsi_state(wsi)); + } + + ah->assigned = 0; + + /* if we think we're detaching one, there should be one in use */ + assert(pt->http.ah_count_in_use > 0); + /* and this specific one should have been in use */ + assert(ah->in_use); + memset(&wsi->http.ah, 0, sizeof(wsi->http.ah)); + +#if defined(LWS_WITH_PEER_LIMITS) + if (ah->wsi) + lws_peer_track_ah_detach(context, wsi->peer); +#endif + ah->wsi = NULL; /* no owner */ + + pwsi = &pt->http.ah_wait_list; + + /* oh there is nobody on the waiting list... leave the ah unattached */ + if (!*pwsi) + goto nobody_usable_waiting; + + /* + * at least one wsi on the same tsi is waiting, give it to oldest guy + * who is allowed to take it (if any) + */ + lwsl_info("pt wait list %p\n", *pwsi); + wsi = NULL; + pwsi_eligible = NULL; + + while (*pwsi) { +#if defined(LWS_WITH_PEER_LIMITS) + /* are we willing to give this guy an ah? */ + if (!lws_peer_confirm_ah_attach_ok(context, (*pwsi)->peer)) +#endif + { + wsi = *pwsi; + pwsi_eligible = pwsi; + } +#if defined(LWS_WITH_PEER_LIMITS) + else + if (!(*pwsi)->http.ah_wait_list) + lws_stats_atomic_bump(context, pt, + LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1); +#endif + pwsi = &(*pwsi)->http.ah_wait_list; + } + + if (!wsi) /* everybody waiting already has too many ah... */ + goto nobody_usable_waiting; + + lwsl_info("%s: transferring ah to last eligible wsi in wait list %p (wsistate 0x%x)\n", __func__, wsi, wsi->wsistate); + + wsi->http.ah = ah; + ah->wsi = wsi; /* new owner */ + + __lws_header_table_reset(wsi, autoservice); +#if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) + lws_context_lock(context); /* <====================================== */ + if (wsi->peer) + wsi->peer->http.count_ah++; + lws_context_unlock(context); /* ====================================> */ +#endif + + /* clients acquire the ah and then insert themselves in fds table... */ + if (wsi->position_in_fds_table != LWS_NO_FDS_POS) { + lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi); + + /* he has been stuck waiting for an ah, but now his wait is + * over, let him progress */ + + _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); + } + + /* point prev guy to next guy in list instead */ + *pwsi_eligible = wsi->http.ah_wait_list; + /* the guy who got one is out of the list */ + wsi->http.ah_wait_list = NULL; + pt->http.ah_wait_list_length--; + +#ifndef LWS_NO_CLIENT + if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) { + lws_pt_unlock(pt); + + if (!lws_client_connect_via_info2(wsi)) { + /* our client connect has failed, the wsi + * has been closed + */ + + return -1; + } + return 0; + } +#endif + + assert(!!pt->http.ah_wait_list_length == !!(lws_intptr_t)pt->http.ah_wait_list); +bail: + lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__, + (void *)wsi, (void *)ah, pt->tid, pt->http.ah_count_in_use); + + return 0; + +nobody_usable_waiting: + lwsl_info("%s: nobody usable waiting\n", __func__); + _lws_destroy_ah(pt, ah); + pt->http.ah_count_in_use--; + + goto bail; +} + +int lws_header_table_detach(struct lws *wsi, int autoservice) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n; + + lws_pt_lock(pt, __func__); + n = __lws_header_table_detach(wsi, autoservice); + lws_pt_unlock(pt); + + return n; +} + +LWS_VISIBLE int +lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx) +{ + int n; + + if (!wsi->http.ah) + return 0; + + n = wsi->http.ah->frag_index[h]; + if (!n) + return 0; + do { + if (!frag_idx) + return wsi->http.ah->frags[n].len; + n = wsi->http.ah->frags[n].nfrag; + } while (frag_idx-- && n); + + return 0; +} + +LWS_VISIBLE int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h) +{ + int n; + int len = 0; + + if (!wsi->http.ah) + return 0; + + n = wsi->http.ah->frag_index[h]; + if (!n) + return 0; + do { + len += wsi->http.ah->frags[n].len; + n = wsi->http.ah->frags[n].nfrag; + } while (n); + + return len; +} + +LWS_VISIBLE int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len, + enum lws_token_indexes h, int frag_idx) +{ + int n = 0; + int f; + + if (!wsi->http.ah) + return -1; + + f = wsi->http.ah->frag_index[h]; + + if (!f) + return -1; + + while (n < frag_idx) { + f = wsi->http.ah->frags[f].nfrag; + if (!f) + return -1; + n++; + } + + if (wsi->http.ah->frags[f].len >= len) + return -1; + + memcpy(dst, wsi->http.ah->data + wsi->http.ah->frags[f].offset, + wsi->http.ah->frags[f].len); + dst[wsi->http.ah->frags[f].len] = '\0'; + + return wsi->http.ah->frags[f].len; +} + +LWS_VISIBLE int lws_hdr_copy(struct lws *wsi, char *dst, int len, + enum lws_token_indexes h) +{ + int toklen = lws_hdr_total_length(wsi, h); + int n; + + if (toklen >= len) + return -1; + + if (!wsi->http.ah) + return -1; + + n = wsi->http.ah->frag_index[h]; + if (!n) + return 0; + + do { + if (wsi->http.ah->frags[n].len >= len) + return -1; + strncpy(dst, &wsi->http.ah->data[wsi->http.ah->frags[n].offset], + wsi->http.ah->frags[n].len); + dst += wsi->http.ah->frags[n].len; + len -= wsi->http.ah->frags[n].len; + n = wsi->http.ah->frags[n].nfrag; + } while (n); + *dst = '\0'; + + return toklen; +} + +char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h) +{ + int n; + + n = wsi->http.ah->frag_index[h]; + if (!n) + return NULL; + + return wsi->http.ah->data + wsi->http.ah->frags[n].offset; +} + +static int LWS_WARN_UNUSED_RESULT +lws_pos_in_bounds(struct lws *wsi) +{ + if (wsi->http.ah->pos < + (unsigned int)wsi->context->max_http_header_data) + return 0; + + if ((int)wsi->http.ah->pos == wsi->context->max_http_header_data) { + lwsl_err("Ran out of header data space\n"); + return 1; + } + + /* + * with these tests everywhere, it should never be able to exceed + * the limit, only meet it + */ + lwsl_err("%s: pos %d, limit %d\n", __func__, wsi->http.ah->pos, + wsi->context->max_http_header_data); + assert(0); + + return 1; +} + +int LWS_WARN_UNUSED_RESULT +lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s) +{ + wsi->http.ah->nfrag++; + if (wsi->http.ah->nfrag == ARRAY_SIZE(wsi->http.ah->frags)) { + lwsl_warn("More hdr frags than we can deal with, dropping\n"); + return -1; + } + + wsi->http.ah->frag_index[h] = wsi->http.ah->nfrag; + + wsi->http.ah->frags[wsi->http.ah->nfrag].offset = wsi->http.ah->pos; + wsi->http.ah->frags[wsi->http.ah->nfrag].len = 0; + wsi->http.ah->frags[wsi->http.ah->nfrag].nfrag = 0; + + do { + if (lws_pos_in_bounds(wsi)) + return -1; + + wsi->http.ah->data[wsi->http.ah->pos++] = *s; + if (*s) + wsi->http.ah->frags[wsi->http.ah->nfrag].len++; + } while (*s++); + + return 0; +} + +static int LWS_WARN_UNUSED_RESULT +issue_char(struct lws *wsi, unsigned char c) +{ + unsigned short frag_len; + + if (lws_pos_in_bounds(wsi)) + return -1; + + frag_len = wsi->http.ah->frags[wsi->http.ah->nfrag].len; + /* + * If we haven't hit the token limit, just copy the character into + * the header + */ + if (frag_len < wsi->http.ah->current_token_limit) { + wsi->http.ah->data[wsi->http.ah->pos++] = c; + if (c) + wsi->http.ah->frags[wsi->http.ah->nfrag].len++; + return 0; + } + + /* Insert a null character when we *hit* the limit: */ + if (frag_len == wsi->http.ah->current_token_limit) { + if (lws_pos_in_bounds(wsi)) + return -1; + + wsi->http.ah->data[wsi->http.ah->pos++] = '\0'; + lwsl_warn("header %i exceeds limit %d\n", + wsi->http.ah->parser_state, + wsi->http.ah->current_token_limit); + } + + return 1; +} + +int +lws_parse_urldecode(struct lws *wsi, uint8_t *_c) +{ + struct allocated_headers *ah = wsi->http.ah; + unsigned int enc = 0; + uint8_t c = *_c; + + // lwsl_notice("ah->ups %d\n", ah->ups); + + /* + * PRIORITY 1 + * special URI processing... convert %xx + */ + switch (ah->ues) { + case URIES_IDLE: + if (c == '%') { + ah->ues = URIES_SEEN_PERCENT; + goto swallow; + } + break; + case URIES_SEEN_PERCENT: + if (char_to_hex(c) < 0) + /* illegal post-% char */ + goto forbid; + + ah->esc_stash = c; + ah->ues = URIES_SEEN_PERCENT_H1; + goto swallow; + + case URIES_SEEN_PERCENT_H1: + if (char_to_hex(c) < 0) + /* illegal post-% char */ + goto forbid; + + *_c = (char_to_hex(ah->esc_stash) << 4) | + char_to_hex(c); + c = *_c; + enc = 1; + ah->ues = URIES_IDLE; + break; + } + + /* + * PRIORITY 2 + * special URI processing... + * convert /.. or /... or /../ etc to / + * convert /./ to / + * convert // or /// etc to / + * leave /.dir or whatever alone + */ + + switch (ah->ups) { + case URIPS_IDLE: + if (!c) + return -1; + /* genuine delimiter */ + if ((c == '&' || c == ';') && !enc) { + if (issue_char(wsi, c) < 0) + return -1; + /* swallow the terminator */ + ah->frags[ah->nfrag].len--; + /* link to next fragment */ + ah->frags[ah->nfrag].nfrag = ah->nfrag + 1; + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + /* start next fragment after the & */ + ah->post_literal_equal = 0; + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + goto swallow; + } + /* uriencoded = in the name part, disallow */ + if (c == '=' && enc && + ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] && + !ah->post_literal_equal) { + c = '_'; + *_c =c; + } + + /* after the real =, we don't care how many = */ + if (c == '=' && !enc) + ah->post_literal_equal = 1; + + /* + to space */ + if (c == '+' && !enc) { + c = ' '; + *_c = c; + } + /* issue the first / always */ + if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) + ah->ups = URIPS_SEEN_SLASH; + break; + case URIPS_SEEN_SLASH: + /* swallow subsequent slashes */ + if (c == '/') + goto swallow; + /* track and swallow the first . after / */ + if (c == '.') { + ah->ups = URIPS_SEEN_SLASH_DOT; + goto swallow; + } + ah->ups = URIPS_IDLE; + break; + case URIPS_SEEN_SLASH_DOT: + /* swallow second . */ + if (c == '.') { + ah->ups = URIPS_SEEN_SLASH_DOT_DOT; + goto swallow; + } + /* change /./ to / */ + if (c == '/') { + ah->ups = URIPS_SEEN_SLASH; + goto swallow; + } + /* it was like /.dir ... regurgitate the . */ + ah->ups = URIPS_IDLE; + if (issue_char(wsi, '.') < 0) + return -1; + break; + + case URIPS_SEEN_SLASH_DOT_DOT: + + /* /../ or /..[End of URI] --> backup to last / */ + if (c == '/' || c == '?') { + /* + * back up one dir level if possible + * safe against header fragmentation because + * the method URI can only be in 1 fragment + */ + if (ah->frags[ah->nfrag].len > 2) { + ah->pos--; + ah->frags[ah->nfrag].len--; + do { + ah->pos--; + ah->frags[ah->nfrag].len--; + } while (ah->frags[ah->nfrag].len > 1 && + ah->data[ah->pos] != '/'); + } + ah->ups = URIPS_SEEN_SLASH; + if (ah->frags[ah->nfrag].len > 1) + break; + goto swallow; + } + + /* /..[^/] ... regurgitate and allow */ + + if (issue_char(wsi, '.') < 0) + return -1; + if (issue_char(wsi, '.') < 0) + return -1; + ah->ups = URIPS_IDLE; + break; + } + + if (c == '?' && !enc && + !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI args */ + if (ah->ues != URIES_IDLE) + goto forbid; + + /* seal off uri header */ + if (issue_char(wsi, '\0') < 0) + return -1; + + /* move to using WSI_TOKEN_HTTP_URI_ARGS */ + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + + ah->post_literal_equal = 0; + ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; + ah->ups = URIPS_IDLE; + goto swallow; + } + + return LPUR_CONTINUE; + +swallow: + return LPUR_SWALLOW; + +forbid: + return LPUR_FORBID; + +excessive: + return LPUR_EXCESSIVE; +} + +static const unsigned char methods[] = { + WSI_TOKEN_GET_URI, + WSI_TOKEN_POST_URI, + WSI_TOKEN_OPTIONS_URI, + WSI_TOKEN_PUT_URI, + WSI_TOKEN_PATCH_URI, + WSI_TOKEN_DELETE_URI, + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, +}; + +/* + * possible returns:, -1 fail, 0 ok or 2, transition to raw + */ + +int LWS_WARN_UNUSED_RESULT +lws_parse(struct lws *wsi, unsigned char *buf, int *len) +{ + struct allocated_headers *ah = wsi->http.ah; + struct lws_context *context = wsi->context; + unsigned int n, m; + unsigned char c; + int r, pos; + + assert(wsi->http.ah); + + do { + (*len)--; + c = *buf++; + + switch (ah->parser_state) { + default: + + lwsl_parser("WSI_TOK_(%d) '%c'\n", ah->parser_state, c); + + /* collect into malloc'd buffers */ + /* optional initial space swallow */ + if (!ah->frags[ah->frag_index[ah->parser_state]].len && + c == ' ') + break; + + for (m = 0; m < ARRAY_SIZE(methods); m++) + if (ah->parser_state == methods[m]) + break; + if (m == ARRAY_SIZE(methods)) + /* it was not any of the methods */ + goto check_eol; + + /* special URI processing... end at space */ + + if (c == ' ') { + /* enforce starting with / */ + if (!ah->frags[ah->nfrag].len) + if (issue_char(wsi, '/') < 0) + return -1; + + if (ah->ups == URIPS_SEEN_SLASH_DOT_DOT) { + /* + * back up one dir level if possible + * safe against header fragmentation because + * the method URI can only be in 1 fragment + */ + if (ah->frags[ah->nfrag].len > 2) { + ah->pos--; + ah->frags[ah->nfrag].len--; + do { + ah->pos--; + ah->frags[ah->nfrag].len--; + } while (ah->frags[ah->nfrag].len > 1 && + ah->data[ah->pos] != '/'); + } + } + + /* begin parsing HTTP version: */ + if (issue_char(wsi, '\0') < 0) + return -1; + ah->parser_state = WSI_TOKEN_HTTP; + goto start_fragment; + } + + r = lws_parse_urldecode(wsi, &c); + switch (r) { + case LPUR_CONTINUE: + break; + case LPUR_SWALLOW: + goto swallow; + case LPUR_FORBID: + goto forbid; + case LPUR_EXCESSIVE: + goto excessive; + default: + return -1; + } +check_eol: + /* bail at EOL */ + if (ah->parser_state != WSI_TOKEN_CHALLENGE && + c == '\x0d') { + if (ah->ues != URIES_IDLE) + goto forbid; + + c = '\0'; + ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + lwsl_parser("*\n"); + } + + n = issue_char(wsi, c); + if ((int)n < 0) + return -1; + if (n > 0) + ah->parser_state = WSI_TOKEN_SKIPPING; + +swallow: + /* per-protocol end of headers management */ + + if (ah->parser_state == WSI_TOKEN_CHALLENGE) + goto set_parsing_complete; + break; + + /* collecting and checking a name part */ + case WSI_TOKEN_NAME_PART: + lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X (role=0x%x) " + "wsi->lextable_pos=%d\n", c, c, lwsi_role(wsi), + ah->lextable_pos); + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + pos = ah->lextable_pos; + + while (1) { + if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */ + if ((lextable[pos] & 0x7f) != c) { +nope: + ah->lextable_pos = -1; + break; + } + /* fall thru */ + pos++; + if (lextable[pos] == FAIL_CHAR) + goto nope; + + ah->lextable_pos = pos; + break; + } + + if (lextable[pos] == FAIL_CHAR) + goto nope; + + /* b7 = 0, end or 3-byte */ + if (lextable[pos] < FAIL_CHAR) { /* terminal marker */ + ah->lextable_pos = pos; + break; + } + + if (lextable[pos] == c) { /* goto */ + ah->lextable_pos = pos + (lextable[pos + 1]) + + (lextable[pos + 2] << 8); + break; + } + + /* fall thru goto */ + pos += 3; + /* continue */ + } + + /* + * If it's h1, server needs to look out for unknown + * methods... + */ + if (ah->lextable_pos < 0 && lwsi_role_h1(wsi) && + lwsi_role_server(wsi)) { + /* this is not a header we know about */ + for (m = 0; m < ARRAY_SIZE(methods); m++) + if (ah->frag_index[methods[m]]) { + /* + * already had the method, no idea what + * this crap from the client is, ignore + */ + ah->parser_state = WSI_TOKEN_SKIPPING; + break; + } + /* + * hm it's an unknown http method from a client in fact, + * it cannot be valid http + */ + if (m == ARRAY_SIZE(methods)) { + /* + * are we set up to accept raw in these cases? + */ + if (lws_check_opt(wsi->vhost->options, + LWS_SERVER_OPTION_FALLBACK_TO_RAW)) + return 2; /* transition to raw */ + + lwsl_info("Unknown method - dropping\n"); + goto forbid; + } + break; + } + /* + * ...otherwise for a client, let him ignore unknown headers + * coming from the server + */ + if (ah->lextable_pos < 0) { + ah->parser_state = WSI_TOKEN_SKIPPING; + break; + } + + if (lextable[ah->lextable_pos] < FAIL_CHAR) { + /* terminal state */ + + n = ((unsigned int)lextable[ah->lextable_pos] << 8) | + lextable[ah->lextable_pos + 1]; + + lwsl_parser("known hdr %d\n", n); + for (m = 0; m < ARRAY_SIZE(methods); m++) + if (n == methods[m] && + ah->frag_index[methods[m]]) { + lwsl_warn("Duplicated method\n"); + return -1; + } + + /* + * WSORIGIN is protocol equiv to ORIGIN, + * JWebSocket likes to send it, map to ORIGIN + */ + if (n == WSI_TOKEN_SWORIGIN) + n = WSI_TOKEN_ORIGIN; + + ah->parser_state = (enum lws_token_indexes) + (WSI_TOKEN_GET_URI + n); + ah->ups = URIPS_IDLE; + + if (context->token_limits) + ah->current_token_limit = context-> + token_limits->token_limit[ + ah->parser_state]; + else + ah->current_token_limit = + wsi->context->max_http_header_data; + + if (ah->parser_state == WSI_TOKEN_CHALLENGE) + goto set_parsing_complete; + + goto start_fragment; + } + break; + +start_fragment: + ah->nfrag++; +excessive: + if (ah->nfrag == ARRAY_SIZE(ah->frags)) { + lwsl_warn("More hdr frags than we can deal with\n"); + return -1; + } + + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + ah->frags[ah->nfrag].flags = 2; + + n = ah->frag_index[ah->parser_state]; + if (!n) { /* first fragment */ + ah->frag_index[ah->parser_state] = ah->nfrag; + ah->hdr_token_idx = ah->parser_state; + break; + } + /* continuation */ + while (ah->frags[n].nfrag) + n = ah->frags[n].nfrag; + ah->frags[n].nfrag = ah->nfrag; + + if (issue_char(wsi, ' ') < 0) + return -1; + break; + + /* skipping arg part of a name we didn't recognize */ + case WSI_TOKEN_SKIPPING: + lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c); + + if (c == '\x0d') + ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + break; + + case WSI_TOKEN_SKIPPING_SAW_CR: + lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c); + if (ah->ues != URIES_IDLE) + goto forbid; + if (c == '\x0a') { + ah->parser_state = WSI_TOKEN_NAME_PART; + ah->lextable_pos = 0; + } else + ah->parser_state = WSI_TOKEN_SKIPPING; + break; + /* we're done, ignore anything else */ + + case WSI_PARSING_COMPLETE: + lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c); + break; + } + + } while (*len); + + return 0; + +set_parsing_complete: + if (ah->ues != URIES_IDLE) + goto forbid; + if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { + if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION)) + wsi->rx_frame_type = /* temp for ws version index */ + atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION)); + + lwsl_parser("v%02d hdrs done\n", wsi->rx_frame_type); + } + ah->parser_state = WSI_PARSING_COMPLETE; + wsi->hdr_parsing_completed = 1; + + return 0; + +forbid: + lwsl_notice(" forbidding on uri sanitation\n"); + lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); + + return -1; +} + diff --git a/thirdparty/lws/server/server.c b/thirdparty/libwebsockets/roles/http/server/server.c index db05954257..350af3cd7e 100644 --- a/thirdparty/lws/server/server.c +++ b/thirdparty/libwebsockets/roles/http/server/server.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,7 +19,7 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include "core/private.h" const char * const method_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", @@ -28,42 +28,46 @@ const char * const method_names[] = { #endif }; -#if defined (LWS_WITH_ESP8266) -#undef memcpy -void *memcpy(void *dest, const void *src, size_t n) -{ - return ets_memcpy(dest, src, n); -} -#endif +/* + * return 0: all done + * 1: nonfatal error + * <0: fatal error + * + * REQUIRES CONTEXT LOCK HELD + */ int -lws_context_init_server(struct lws_context_creation_info *info, - struct lws_vhost *vhost) +_lws_vhost_init_server(const struct lws_context_creation_info *info, + struct lws_vhost *vhost) { -#if LWS_POSIX int n, opt = 1, limit = 1; -#endif lws_sockfd_type sockfd; struct lws_vhost *vh; struct lws *wsi; - int m = 0; + int m = 0, is; (void)method_names; (void)opt; + + if (info) { + vhost->iface = info->iface; + vhost->listen_port = info->port; + } + /* set up our external listening socket we serve on */ - if (info->port == CONTEXT_PORT_NO_LISTEN || - info->port == CONTEXT_PORT_NO_LISTEN_SERVER) + if (vhost->listen_port == CONTEXT_PORT_NO_LISTEN || + vhost->listen_port == CONTEXT_PORT_NO_LISTEN_SERVER) return 0; vh = vhost->context->vhost_list; while (vh) { - if (vh->listen_port == info->port) { - if ((!info->iface && !vh->iface) || - (info->iface && vh->iface && - !strcmp(info->iface, vh->iface))) { - vhost->listen_port = info->port; - vhost->iface = info->iface; + if (vh->listen_port == vhost->listen_port) { + if (((!vhost->iface && !vh->iface) || + (vhost->iface && vh->iface && + !strcmp(vhost->iface, vh->iface))) && + vh->lserv_wsi + ) { lwsl_notice(" using listen skt from vhost %s\n", vh->name); return 0; @@ -72,7 +76,59 @@ lws_context_init_server(struct lws_context_creation_info *info, vh = vh->vhost_next; } -#if LWS_POSIX + if (vhost->iface) { + /* + * let's check before we do anything else about the disposition + * of the interface he wants to bind to... + */ + is = lws_socket_bind(vhost, LWS_SOCK_INVALID, vhost->listen_port, vhost->iface); + lwsl_debug("initial if check says %d\n", is); +deal: + + lws_start_foreach_llp(struct lws_vhost **, pv, + vhost->context->no_listener_vhost_list) { + if (is >= LWS_ITOSA_USABLE && *pv == vhost) { + /* on the list and shouldn't be: remove it */ + lwsl_debug("deferred iface: removing vh %s\n", (*pv)->name); + *pv = vhost->no_listener_vhost_list; + vhost->no_listener_vhost_list = NULL; + goto done_list; + } + if (is < LWS_ITOSA_USABLE && *pv == vhost) + goto done_list; + } lws_end_foreach_llp(pv, no_listener_vhost_list); + + /* not on the list... */ + + if (is < LWS_ITOSA_USABLE) { + + /* ... but needs to be: so add it */ + + lwsl_debug("deferred iface: adding vh %s\n", vhost->name); + vhost->no_listener_vhost_list = vhost->context->no_listener_vhost_list; + vhost->context->no_listener_vhost_list = vhost; + } + +done_list: + + switch (is) { + default: + break; + case LWS_ITOSA_NOT_EXIST: + /* can't add it */ + if (info) /* first time */ + lwsl_err("VH %s: iface %s port %d DOESN'T EXIST\n", + vhost->name, vhost->iface, vhost->listen_port); + return 1; + case LWS_ITOSA_NOT_USABLE: + /* can't add it */ + if (info) /* first time */ + lwsl_err("VH %s: iface %s port %d NOT USABLE\n", + vhost->name, vhost->iface, vhost->listen_port); + return 1; + } + } + (void)n; #if defined(__linux__) limit = vhost->context->count_threads; @@ -80,159 +136,150 @@ lws_context_init_server(struct lws_context_creation_info *info, for (m = 0; m < limit; m++) { #ifdef LWS_WITH_UNIX_SOCK - if (LWS_UNIX_SOCK_ENABLED(vhost)) - sockfd = socket(AF_UNIX, SOCK_STREAM, 0); - else + if (LWS_UNIX_SOCK_ENABLED(vhost)) + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + else #endif #ifdef LWS_WITH_IPV6 - if (LWS_IPV6_ENABLED(vhost)) - sockfd = socket(AF_INET6, SOCK_STREAM, 0); - else + if (LWS_IPV6_ENABLED(vhost)) + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else #endif - sockfd = socket(AF_INET, SOCK_STREAM, 0); + sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd == -1) { -#else -#if defined(LWS_WITH_ESP8266) - sockfd = esp8266_create_tcp_listen_socket(vhost); - if (!lws_sockfd_valid(sockfd)) { -#endif + if (sockfd == LWS_SOCK_INVALID) { + lwsl_err("ERROR opening socket\n"); + return 1; + } +#if !defined(LWS_WITH_ESP32) +#if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE) + /* + * only accept that we are the only listener on the port + * https://msdn.microsoft.com/zh-tw/library/ + * windows/desktop/ms740621(v=vs.85).aspx + * + * for lws, to match Linux, we default to exclusive listen + */ + if (!lws_check_opt(vhost->options, + LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) { + if (setsockopt(sockfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (const void *)&opt, sizeof(opt)) < 0) { + lwsl_err("reuseaddr failed\n"); + compatible_close(sockfd); + return -1; + } + } else #endif - lwsl_err("ERROR opening socket\n"); - return 1; - } -#if LWS_POSIX && !defined(LWS_WITH_ESP32) -#if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE) - /* - * only accept that we are the only listener on the port - * https://msdn.microsoft.com/zh-tw/library/ - * windows/desktop/ms740621(v=vs.85).aspx - * - * for lws, to match Linux, we default to exclusive listen - */ - if (!lws_check_opt(vhost->options, - LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) { - if (setsockopt(sockfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + /* + * allow us to restart even if old sockets in TIME_WAIT + */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) < 0) { lwsl_err("reuseaddr failed\n"); compatible_close(sockfd); - return 1; + return -1; } - } else -#endif - - /* - * allow us to restart even if old sockets in TIME_WAIT - */ - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, - (const void *)&opt, sizeof(opt)) < 0) { - lwsl_err("reuseaddr failed\n"); - compatible_close(sockfd); - return 1; - } #if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) - if (LWS_IPV6_ENABLED(vhost)) { - if (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) { + if (LWS_IPV6_ENABLED(vhost) && + vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) { int value = (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE) ? 1 : 0; if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const void*)&value, sizeof(value)) < 0) { compatible_close(sockfd); - return 1; + return -1; } } - } #endif #if defined(__linux__) && defined(SO_REUSEPORT) - n = lws_check_opt(vhost->options, LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE); + /* keep coverity happy */ #if LWS_MAX_SMP > 1 - n = 1; + n = 1; +#else + n = lws_check_opt(vhost->options, + LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE); #endif - - if (n) - if (vhost->context->count_threads > 1) + if (n && vhost->context->count_threads > 1) if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&opt, sizeof(opt)) < 0) { compatible_close(sockfd); - return 1; + return -1; } #endif #endif - lws_plat_set_socket_options(vhost, sockfd); + lws_plat_set_socket_options(vhost, sockfd); -#if LWS_POSIX - n = lws_socket_bind(vhost, sockfd, info->port, info->iface); - if (n < 0) - goto bail; - info->port = n; -#endif - vhost->listen_port = info->port; - vhost->iface = info->iface; + is = lws_socket_bind(vhost, sockfd, vhost->listen_port, vhost->iface); + /* + * There is a race where the network device may come up and then + * go away and fail here. So correctly handle unexpected failure + * here despite we earlier confirmed it. + */ + if (is < 0) { + lwsl_info("%s: lws_socket_bind says %d\n", __func__, is); + compatible_close(sockfd); + goto deal; + } + vhost->listen_port = is; - wsi = lws_zalloc(sizeof(struct lws), "listen wsi"); - if (wsi == NULL) { - lwsl_err("Out of mem\n"); - goto bail; - } - wsi->context = vhost->context; - wsi->desc.sockfd = sockfd; - wsi->mode = LWSCM_SERVER_LISTENER; - wsi->protocol = vhost->protocols; - wsi->tsi = m; - wsi->vhost = vhost; - wsi->listener = 1; - -#ifdef LWS_WITH_LIBUV - if (LWS_LIBUV_ENABLED(vhost->context)) - lws_uv_initvhost(vhost, wsi); -#endif + lwsl_debug("%s: lws_socket_bind says %d\n", __func__, is); - if (insert_wsi_socket_into_fds(vhost->context, wsi)) - goto bail; + wsi = lws_zalloc(sizeof(struct lws), "listen wsi"); + if (wsi == NULL) { + lwsl_err("Out of mem\n"); + goto bail; + } + wsi->context = vhost->context; + wsi->desc.sockfd = sockfd; + lws_role_transition(wsi, 0, LRS_UNCONNECTED, &role_ops_listen); + wsi->protocol = vhost->protocols; + wsi->tsi = m; + wsi->vhost = vhost; + wsi->listener = 1; - vhost->context->count_wsi_allocated++; - vhost->lserv_wsi = wsi; + if (wsi->context->event_loop_ops->init_vhost_listen_wsi) + wsi->context->event_loop_ops->init_vhost_listen_wsi(wsi); -#if LWS_POSIX - n = listen(wsi->desc.sockfd, LWS_SOMAXCONN); - if (n < 0) { - lwsl_err("listen failed with error %d\n", LWS_ERRNO); - vhost->lserv_wsi = NULL; - vhost->context->count_wsi_allocated--; - remove_wsi_socket_from_fds(wsi); - goto bail; - } + if (__insert_wsi_socket_into_fds(vhost->context, wsi)) { + lwsl_notice("inserting wsi socket into fds failed\n"); + goto bail; + } + + vhost->context->count_wsi_allocated++; + vhost->lserv_wsi = wsi; + + n = listen(wsi->desc.sockfd, LWS_SOMAXCONN); + if (n < 0) { + lwsl_err("listen failed with error %d\n", LWS_ERRNO); + vhost->lserv_wsi = NULL; + vhost->context->count_wsi_allocated--; + __remove_wsi_socket_from_fds(wsi); + goto bail; + } } /* for each thread able to independently listen */ -#else -#if defined(LWS_WITH_ESP8266) - esp8266_tcp_stream_bind(wsi->desc.sockfd, info->port, wsi); -#endif -#endif - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { + + if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { #ifdef LWS_WITH_UNIX_SOCK if (LWS_UNIX_SOCK_ENABLED(vhost)) - lwsl_info(" Listening on \"%s\"\n", info->iface); + lwsl_info(" Listening on \"%s\"\n", vhost->iface); else #endif - lwsl_info(" Listening on port %d\n", info->port); + lwsl_info(" Listening on port %d\n", vhost->listen_port); } + // info->port = vhost->listen_port; + return 0; bail: compatible_close(sockfd); - return 1; + return -1; } -#if defined(LWS_WITH_ESP8266) -#undef strchr -#define strchr ets_strchr -#endif - struct lws_vhost * lws_select_vhost(struct lws_context *context, int port, const char *servername) { @@ -240,11 +287,11 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) const char *p; int n, m, colon; - n = strlen(servername); + n = (int)strlen(servername); colon = n; p = strchr(servername, ':'); if (p) - colon = p - servername; + colon = lws_ptr_diff(p, servername); /* Priotity 1: first try exact matches */ @@ -266,7 +313,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) */ vhost = context->vhost_list; while (vhost) { - m = strlen(vhost->name); + m = (int)strlen(vhost->name); if (port == vhost->listen_port && m <= (colon - 2) && servername[colon - m - 1] == '.' && @@ -283,8 +330,8 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) vhost = context->vhost_list; while (vhost) { if (port == vhost->listen_port) { - lwsl_info("vhost match to %s based on port %d\n", - vhost->name, port); + lwsl_info("%s: vhost match to %s based on port %d\n", + __func__, vhost->name, port); return vhost; } vhost = vhost->vhost_next; @@ -298,7 +345,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) LWS_VISIBLE LWS_EXTERN const char * lws_get_mimetype(const char *file, const struct lws_http_mount *m) { - int n = strlen(file); + int n = (int)strlen(file); const struct lws_protocol_vhost_options *pvo = NULL; if (m) @@ -388,7 +435,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, const struct lws_protocol_vhost_options *pvo = m->interpret; struct lws_process_html_args args; const char *mimetype; -#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) +#if !defined(_WIN32_WCE) const struct lws_plat_file_ops *fops; const char *vpath; lws_fop_flags_t fflags = LWS_O_RDONLY; @@ -402,14 +449,24 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, char path[256], sym[512]; unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p; unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE; -#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(WIN32) && !defined(LWS_WITH_ESP32) size_t len; #endif int n; + wsi->handling_404 = 0; + if (!wsi->vhost) + return -1; + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + if (wsi->vhost->http.error_document_404 && + !strcmp(uri, wsi->vhost->http.error_document_404)) + wsi->handling_404 = 1; +#endif + lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri); -#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) +#if !defined(_WIN32_WCE) fflags |= lws_vfs_prepare_flags(wsi); @@ -417,13 +474,14 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, spin++; fops = lws_vfs_select_fops(wsi->context->fops, path, &vpath); - if (wsi->u.http.fop_fd) - lws_vfs_file_close(&wsi->u.http.fop_fd); + if (wsi->http.fop_fd) + lws_vfs_file_close(&wsi->http.fop_fd); - wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, + wsi->http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, path, vpath, &fflags); - if (!wsi->u.http.fop_fd) { - lwsl_err("Unable to open '%s'\n", path); + if (!wsi->http.fop_fd) { + lwsl_info("%s: Unable to open '%s': errno %d\n", + __func__, path, errno); return -1; } @@ -435,7 +493,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, break; #endif #if !defined(WIN32) - if (fstat(wsi->u.http.fop_fd->fd, &st)) { + if (fstat(wsi->http.fop_fd->fd, &st)) { lwsl_info("unable to stat %s\n", path); goto bail; } @@ -453,10 +511,10 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, #endif #endif - wsi->u.http.fop_fd->mod_time = (uint32_t)st.st_mtime; + wsi->http.fop_fd->mod_time = (uint32_t)st.st_mtime; fflags |= LWS_FOP_FLAG_MOD_TIME_VALID; -#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(WIN32) && !defined(LWS_WITH_ESP32) if ((S_IFMT & st.st_mode) == S_IFLNK) { len = readlink(path, sym, sizeof(sym) - 1); if (len) { @@ -480,15 +538,15 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, lwsl_err("symlink loop %s \n", path); n = sprintf(sym, "%08llX%08lX", - (unsigned long long)lws_vfs_get_length(wsi->u.http.fop_fd), - (unsigned long)lws_vfs_get_mod_time(wsi->u.http.fop_fd)); + (unsigned long long)lws_vfs_get_length(wsi->http.fop_fd), + (unsigned long)lws_vfs_get_mod_time(wsi->http.fop_fd)); /* disable ranges if IF_RANGE token invalid */ if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_RANGE)) if (strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_RANGE))) /* differs - defeat Range: */ - wsi->u.http.ah->frag_index[WSI_TOKEN_HTTP_RANGE] = 0; + wsi->http.ah->frag_index[WSI_TOKEN_HTTP_RANGE] = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH)) { /* @@ -523,7 +581,7 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, return -1; } - lws_vfs_file_close(&wsi->u.http.fop_fd); + lws_vfs_file_close(&wsi->http.fop_fd); return lws_http_transaction_completed(wsi); } @@ -549,10 +607,12 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, * a protocol */ while (pvo) { - n = strlen(path); + n = (int)strlen(path); if (n > (int)strlen(pvo->name) && !strcmp(&path[n - strlen(pvo->name)], pvo->name)) { - wsi->sending_chunked = 1; + wsi->interpreting = 1; + if (!wsi->http2_substream) + wsi->sending_chunked = 1; wsi->protocol_interpret_idx = (char)(lws_intptr_t)pvo->value; lwsl_info("want %s interpreted by %s\n", path, @@ -574,14 +634,15 @@ lws_http_serve(struct lws *wsi, char *uri, const char *origin, if (lws_bind_protocol(wsi, pp)) return 1; args.p = (char *)p; - args.max_len = end - p; + args.max_len = lws_ptr_diff(end, p); if (pp->callback(wsi, LWS_CALLBACK_ADD_HEADERS, wsi->user_space, &args, 0)) return -1; p = (unsigned char *)args.p; } - n = lws_serve_http_file(wsi, path, mimetype, (char *)start, p - start); + n = lws_serve_http_file(wsi, path, mimetype, (char *)start, + lws_ptr_diff(p, start)); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ @@ -592,13 +653,14 @@ bail: return -1; } +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) const struct lws_http_mount * lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) { const struct lws_http_mount *hm, *hit = NULL; int best = 0; - hm = wsi->vhost->mount_list; + hm = wsi->vhost->http.mount_list; while (hm) { if (uri_len >= hm->mountpoint_len && !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) && @@ -623,9 +685,9 @@ lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) return hit; } +#endif -#if LWS_POSIX - +#if !defined(LWS_WITH_ESP32) static int lws_find_string_in_file(const char *filename, const char *string, int stringlen) { @@ -635,7 +697,7 @@ lws_find_string_in_file(const char *filename, const char *string, int stringlen) fd = open(filename, O_RDONLY); if (fd < 0) { lwsl_err("can't open auth file: %s\n", filename); - return 1; + return 0; } while (1) { @@ -669,6 +731,7 @@ lws_find_string_in_file(const char *filename, const char *string, int stringlen) return hit; } +#endif static int lws_unauthorised_basic_auth(struct lws *wsi) @@ -702,8 +765,6 @@ lws_unauthorised_basic_auth(struct lws *wsi) } -#endif - int lws_clean_url(char *p) { if (p[0] == 'h' && p[1] == 't' && p[2] == 't' && p[3] == 'p') { @@ -732,7 +793,6 @@ int lws_clean_url(char *p) return 0; } - static const unsigned char methods[] = { WSI_TOKEN_GET_URI, WSI_TOKEN_POST_URI, @@ -752,7 +812,7 @@ lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len) { int n, count = 0; - for (n = 0; n < ARRAY_SIZE(methods); n++) + for (n = 0; n < (int)ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) count++; if (!count) { @@ -767,7 +827,7 @@ lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len) return -1; } - for (n = 0; n < ARRAY_SIZE(methods); n++) + for (n = 0; n < (int)ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) { *puri_ptr = lws_hdr_simple_ptr(wsi, methods[n]); *puri_len = lws_hdr_total_length(wsi, methods[n]); @@ -797,7 +857,7 @@ lws_http_action(struct lws *wsi) }; meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); - if (meth < 0 || meth >= ARRAY_SIZE(method_names)) + if (meth < 0 || meth >= (int)ARRAY_SIZE(method_names)) goto bail_nuke_ah; /* we insist on absolute paths */ @@ -811,26 +871,36 @@ lws_http_action(struct lws *wsi) lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], meth, uri_ptr); + if (wsi->role_ops && wsi->role_ops->check_upgrades) + switch (wsi->role_ops->check_upgrades(wsi)) { + case LWS_UPG_RET_DONE: + return 0; + case LWS_UPG_RET_CONTINUE: + break; + case LWS_UPG_RET_BAIL: + goto bail_nuke_ah; + } + if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; /* HTTP header had a content length? */ - wsi->u.http.rx_content_length = 0; + wsi->http.rx_content_length = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI)) - wsi->u.http.rx_content_length = 100 * 1024 * 1024; + wsi->http.rx_content_length = 100 * 1024 * 1024; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lws_hdr_copy(wsi, content_length_str, sizeof(content_length_str) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH); - wsi->u.http.rx_content_length = atoll(content_length_str); + wsi->http.rx_content_length = atoll(content_length_str); } if (wsi->http2_substream) { - wsi->u.http.request_version = HTTP_VERSION_2; + wsi->http.request_version = HTTP_VERSION_2; } else { /* http_version? Default to 1.0, override with token: */ request_version = HTTP_VERSION_1_0; @@ -845,7 +915,7 @@ lws_http_action(struct lws *wsi) http_version_str[7] == '1') request_version = HTTP_VERSION_1_1; } - wsi->u.http.request_version = request_version; + wsi->http.request_version = request_version; /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ if (request_version == HTTP_VERSION_1_1) @@ -865,7 +935,7 @@ lws_http_action(struct lws *wsi) if (!strcasecmp(http_conn_str, "close")) connection_type = HTTP_CONNECTION_CLOSE; } - wsi->u.http.connection_type = connection_type; + wsi->http.connection_type = connection_type; } n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION, @@ -881,8 +951,8 @@ lws_http_action(struct lws *wsi) */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); -#ifdef LWS_OPENSSL_SUPPORT - if (wsi->redirect_to_https) { +#ifdef LWS_WITH_TLS + if (wsi->tls.redirect_to_https) { /* * we accepted http:// only so we could redirect to * https://, so issue the redirect. Create the redirection @@ -921,6 +991,8 @@ lws_http_action(struct lws *wsi) if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0])) return 1; + lwsi_set_state(wsi, LRS_DOING_TRANSACTION); + n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, wsi->user_space, uri_ptr, uri_len); @@ -989,7 +1061,6 @@ lws_http_action(struct lws *wsi) return lws_http_transaction_completed(wsi); } -#if LWS_POSIX /* basic auth? */ if (hit->basic_auth_login_file) { @@ -1029,11 +1100,10 @@ lws_http_action(struct lws *wsi) return lws_unauthorised_basic_auth(wsi); } - lwsl_notice("basic auth accepted\n"); + lwsl_info("basic auth accepted\n"); /* accept the auth */ } -#endif #if defined(LWS_WITH_HTTP_PROXY) /* @@ -1064,7 +1134,7 @@ lws_http_action(struct lws *wsi) else n = pslash - hit->origin; - if (n >= sizeof(ads) - 2) + if (n >= (int)sizeof(ads) - 2) n = sizeof(ads) - 2; memcpy(ads, hit->origin, n); @@ -1104,9 +1174,10 @@ lws_http_action(struct lws *wsi) i.uri_replace_from = hit->origin; i.uri_replace_to = hit->mountpoint; - lwsl_notice("proxying to %s port %d url %s, ssl %d, from %s, to %s\n", - i.address, i.port, i.path, i.ssl_connection, - i.uri_replace_from, i.uri_replace_to); + lwsl_notice("proxying to %s port %d url %s, ssl %d, " + "from %s, to %s\n", + i.address, i.port, i.path, i.ssl_connection, + i.uri_replace_from, i.uri_replace_to); if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); @@ -1144,8 +1215,10 @@ lws_http_action(struct lws *wsi) args.len = uri_len; args.max_len = hit->auth_mask; args.final = 0; /* used to signal callback dealt with it */ + args.chunked = 0; - n = wsi->protocol->callback(wsi, LWS_CALLBACK_CHECK_ACCESS_RIGHTS, + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_CHECK_ACCESS_RIGHTS, wsi->user_space, &args, 0); if (n) { lws_return_http_status(wsi, HTTP_STATUS_UNAUTHORIZED, @@ -1195,7 +1268,7 @@ lws_http_action(struct lws *wsi) } #endif - n = strlen(s); + n = (int)strlen(s); if (s[0] == '\0' || (n == 1 && s[n - 1] == '/')) s = (char *)hit->def; if (!s) @@ -1206,14 +1279,17 @@ lws_http_action(struct lws *wsi) wsi->cache_revalidate = hit->cache_revalidate; wsi->cache_intermediaries = hit->cache_intermediaries; - n = lws_http_serve(wsi, s, hit->origin, hit); + n = 1; + if (hit->origin_protocol == LWSMPRO_FILE) + n = lws_http_serve(wsi, s, hit->origin, hit); if (n) { /* - * lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); + * lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); */ if (hit->protocol) { - const struct lws_protocols *pp = lws_vhost_name_to_protocol( - wsi->vhost, hit->protocol); + const struct lws_protocols *pp = + lws_vhost_name_to_protocol( + wsi->vhost, hit->protocol); if (lws_bind_protocol(wsi, pp)) return 1; @@ -1245,125 +1321,90 @@ deal_body: * In any case, return 0 and let lws_read decide how to * proceed based on state */ - if (wsi->state != LWSS_HTTP_ISSUING_FILE) { + if (lwsi_state(wsi) != LRS_ISSUING_FILE) { /* Prepare to read body if we have a content length: */ - lwsl_debug("wsi->u.http.rx_content_length %lld %d %d\n", - (long long)wsi->u.http.rx_content_length, + lwsl_debug("wsi->http.rx_content_length %lld %d %d\n", + (long long)wsi->http.rx_content_length, wsi->upgraded_to_http2, wsi->http2_substream); - if (wsi->u.http.rx_content_length > 0) { - lwsl_notice("%s: %p: LWSS_HTTP_BODY state set\n", - __func__, wsi); - wsi->state = LWSS_HTTP_BODY; - wsi->u.http.rx_content_remain = - wsi->u.http.rx_content_length; + if (wsi->http.rx_content_length > 0) { + struct lws_tokens ebuf; + int m; + + lwsi_set_state(wsi, LRS_BODY); + lwsl_info("%s: %p: LRS_BODY state set (0x%x)\n", + __func__, wsi, wsi->wsistate); + wsi->http.rx_content_remain = + wsi->http.rx_content_length; + + /* + * At this point we have transitioned from deferred + * action to expecting BODY on the stream wsi, if it's + * in a bundle like h2. So if the stream wsi has its + * own buflist, we need to deal with that first. + */ + + while (1) { + ebuf.len = (int)lws_buflist_next_segment_len( + &wsi->buflist, (uint8_t **)&ebuf.token); + if (!ebuf.len) + break; + lwsl_notice("%s: consuming %d\n", __func__, (int)ebuf.len); + m = lws_read_h1(wsi, (uint8_t *)ebuf.token, ebuf.len); + if (m < 0) + return -1; + + if (lws_buflist_aware_consume(wsi, &ebuf, m, 1)) + return -1; + } } } return 0; bail_nuke_ah: - /* we're closing, losing some rx is OK */ - lws_header_table_force_to_detachable_state(wsi); lws_header_table_detach(wsi, 1); return 1; -#if LWS_POSIX transaction_result_n: lws_return_http_status(wsi, n, NULL); return lws_http_transaction_completed(wsi); -#endif -} - -static int -lws_server_init_wsi_for_ws(struct lws *wsi) -{ - int n; - - wsi->state = LWSS_ESTABLISHED; - lws_restart_ws_ping_pong_timer(wsi); - - /* - * create the frame buffer for this connection according to the - * size mentioned in the protocol definition. If 0 there, use - * a big default for compatibility - */ - - n = wsi->protocol->rx_buffer_size; - if (!n) - n = wsi->context->pt_serv_buf_size; - n += LWS_PRE; - wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf"); - if (!wsi->u.ws.rx_ubuf) { - lwsl_err("Out of Mem allocating rx buffer %d\n", n); - return 1; - } - wsi->u.ws.rx_ubuf_alloc = n; - lwsl_debug("Allocating RX buffer %d\n", n); - -#if LWS_POSIX && !defined(LWS_WITH_ESP32) - if (!wsi->parent_carries_io) - if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, - (const char *)&n, sizeof n)) { - lwsl_warn("Failed to set SNDBUF to %d", n); - return 1; - } -#endif - - /* notify user code that we're ready to roll */ - - if (wsi->protocol->callback) - if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, - wsi->user_space, -#ifdef LWS_OPENSSL_SUPPORT - wsi->ssl, -#else - NULL, -#endif - 0)) - return 1; - - return 0; } int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { - int protocol_len, n = 0, hit, non_space_char_found = 0, m; struct lws_context *context = lws_get_context(wsi); - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - struct _lws_header_related hdr; - struct allocated_headers *ah; unsigned char *obuf = *buf; - char protocol_list[128]; - char protocol_name[64]; +#if defined(LWS_WITH_HTTP2) + char tbuf[128], *p; +#endif size_t olen = len; - char *p; + int n = 0, m, i; if (len >= 10000000) { lwsl_err("%s: assert: len %ld\n", __func__, (long)len); assert(0); } - if (!wsi->u.hdr.ah) { + if (!wsi->http.ah) { lwsl_err("%s: assert: NULL ah\n", __func__); assert(0); } - lwsl_hexdump(*buf, len); - - while (len--) { - wsi->more_rx_waiting = !!len; - - if (wsi->mode != LWSCM_HTTP_SERVING && - wsi->mode != LWSCM_HTTP2_SERVING && - wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED) { - lwsl_err("%s: bad wsi mode %d\n", __func__, wsi->mode); + while (len) { + if (!lwsi_role_server(wsi) || !lwsi_role_http(wsi)) { + lwsl_err("%s: bad wsi role 0x%x\n", __func__, + lwsi_role(wsi)); goto bail_nuke_ah; } - m = lws_parse(wsi, *(*buf)++); + i = (int)len; + m = lws_parse(wsi, *buf, &i); + lwsl_info("%s: parsed count %d\n", __func__, (int)len - i); + (*buf) += (int)len - i; + len = i; if (m) { if (m == 2) { /* @@ -1384,8 +1425,8 @@ raw_transition: wsi->user_space, NULL, 0)) goto bail_nuke_ah; - lws_header_table_force_to_detachable_state(wsi); - lws_union_transition(wsi, LWSCM_RAW); + lws_role_transition(wsi, 0, LRS_ESTABLISHED, + &role_ops_raw_skt); lws_header_table_detach(wsi, 1); if (m == 2 && (wsi->protocol->callback)(wsi, @@ -1399,12 +1440,10 @@ raw_transition: goto bail_nuke_ah; } - if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) + if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE) continue; lwsl_parser("%s: lws_parse sees parsing complete\n", __func__); - lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, - wsi->more_rx_waiting); /* select vhost */ @@ -1418,7 +1457,7 @@ raw_transition: } else lwsl_info("no host\n"); - if (wsi->mode != LWSCM_HTTP2_SERVING) { + if (!lwsi_role_h2(wsi) || !lwsi_role_server(wsi)) { wsi->vhost->conn_stats.h1_trans++; if (!wsi->conn_stat_done) { wsi->vhost->conn_stats.h1_conn++; @@ -1435,38 +1474,39 @@ raw_transition: if (lws_hdr_copy(wsi, ua, sizeof(ua) - 1, WSI_TOKEN_HTTP_USER_AGENT) > 0) { - ua[sizeof(ua) - 1] = '\0'; - while (rej) { - if (strstr(ua, rej->name)) { #ifdef LWS_WITH_ACCESS_LOG - char *uri_ptr = NULL; - int meth, uri_len; + char *uri_ptr = NULL; + int meth, uri_len; #endif + ua[sizeof(ua) - 1] = '\0'; + while (rej) { + if (!strstr(ua, rej->name)) { + rej = rej->next; + continue; + } - msg = strchr(rej->value, ' '); - if (msg) - msg++; - lws_return_http_status(wsi, - atoi(rej->value), msg); + msg = strchr(rej->value, ' '); + if (msg) + msg++; + lws_return_http_status(wsi, + atoi(rej->value), msg); #ifdef LWS_WITH_ACCESS_LOG - meth = lws_http_get_uri_and_method(wsi, - &uri_ptr, &uri_len); - if (meth >= 0) - lws_prepare_access_log_info(wsi, + meth = lws_http_get_uri_and_method(wsi, + &uri_ptr, &uri_len); + if (meth >= 0) + lws_prepare_access_log_info(wsi, uri_ptr, meth); - /* wsi close will do the log */ + /* wsi close will do the log */ #endif - wsi->vhost->conn_stats.rejected++; - /* - * We don't want anything from - * this rejected guy. Follow - * the close flow, not the - * transaction complete flow. - */ - goto bail_nuke_ah; - } - rej = rej->next; + wsi->vhost->conn_stats.rejected++; + /* + * We don't want anything from + * this rejected guy. Follow + * the close flow, not the + * transaction complete flow. + */ + goto bail_nuke_ah; } } } @@ -1478,20 +1518,24 @@ raw_transition: goto raw_transition; } - wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; + lwsi_set_state(wsi, LRS_PRE_WS_SERVING_ACCEPT); lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* is this websocket protocol or normal http 1.0? */ if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { - if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), + if (!strcasecmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_UPGRADE), "websocket")) { +#if defined(LWS_ROLE_WS) wsi->vhost->conn_stats.ws_upg++; lwsl_info("Upgrade to ws\n"); goto upgrade_ws; +#endif } -#ifdef LWS_WITH_HTTP2 - if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), +#if defined(LWS_WITH_HTTP2) + if (!strcasecmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_UPGRADE), "h2c")) { wsi->vhost->conn_stats.h2_upg++; lwsl_info("Upgrade to h2c\n"); @@ -1505,23 +1549,19 @@ raw_transition: /* no upgrade ack... he remained as HTTP */ - lwsl_info("No upgrade\n"); - ah = wsi->u.hdr.ah; + lwsl_info("%s: %p: No upgrade\n", __func__, wsi); - lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED); - wsi->state = LWSS_HTTP; - wsi->u.http.fop_fd = NULL; + lwsi_set_state(wsi, LRS_ESTABLISHED); + wsi->http.fop_fd = NULL; - /* expose it at the same offset as u.hdr */ - wsi->u.http.ah = ah; lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, - (void *)wsi->u.hdr.ah); + (void *)wsi->http.ah); n = lws_http_action(wsi); return n; -#ifdef LWS_WITH_HTTP2 +#if defined(LWS_WITH_HTTP2) upgrade_h2c: if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { lwsl_info("missing http2_settings\n"); @@ -1532,8 +1572,7 @@ upgrade_h2c: p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); /* convert the peer's HTTP-Settings */ - n = lws_b64_decode_string(p, protocol_list, - sizeof(protocol_list)); + n = lws_b64_decode_string(p, tbuf, sizeof(tbuf)); if (n < 0) { lwsl_parser("HTTP2_SETTINGS too long\n"); return 1; @@ -1541,16 +1580,10 @@ upgrade_h2c: /* adopt the header info */ - ah = wsi->u.hdr.ah; - - lws_union_transition(wsi, LWSCM_HTTP2_SERVING); - - /* http2 union member has http union struct at start */ - wsi->u.http.ah = ah; - - if (!wsi->u.h2.h2n) { - wsi->u.h2.h2n = lws_zalloc(sizeof(*wsi->u.h2.h2n), "h2n"); - if (!wsi->u.h2.h2n) + if (!wsi->h2.h2n) { + wsi->h2.h2n = lws_zalloc(sizeof(*wsi->h2.h2n), + "h2n"); + if (!wsi->h2.h2n) return 1; } @@ -1558,195 +1591,39 @@ upgrade_h2c: /* HTTP2 union */ - lws_h2_settings(wsi, &wsi->u.h2.h2n->set, - (unsigned char *)protocol_list, n); + lws_h2_settings(wsi, &wsi->h2.h2n->set, (unsigned char *)tbuf, n); - lws_hpack_dynamic_size(wsi, wsi->u.h2.h2n->set.s[ + lws_hpack_dynamic_size(wsi, wsi->h2.h2n->set.s[ H2SET_HEADER_TABLE_SIZE]); - strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Upgrade: h2c\x0d\x0a\x0d\x0a"); - n = lws_issue_raw(wsi, (unsigned char *)protocol_list, - strlen(protocol_list)); - if (n != strlen(protocol_list)) { + strcpy(tbuf, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Upgrade: h2c\x0d\x0a\x0d\x0a"); + m = (int)strlen(tbuf); + n = lws_issue_raw(wsi, (unsigned char *)tbuf, m); + if (n != m) { lwsl_debug("http2 switch: ERROR writing to socket\n"); return 1; } - wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE; + lwsi_set_state(wsi, LRS_H2_AWAIT_PREFACE); + wsi->upgraded_to_http2 = 1; return 0; #endif - +#if defined(LWS_ROLE_WS) upgrade_ws: - if (!wsi->protocol) - lwsl_err("NULL protocol at lws_read\n"); - - /* - * It's websocket - * - * Select the first protocol we support from the list - * the client sent us. - * - * Copy it to remove header fragmentation - */ - - if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, - WSI_TOKEN_PROTOCOL) < 0) { - lwsl_err("protocol list too long"); - goto bail_nuke_ah; - } - - protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); - protocol_list[protocol_len] = '\0'; - p = protocol_list; - hit = 0; - - while (*p && !hit) { - n = 0; - non_space_char_found = 0; - while (n < sizeof(protocol_name) - 1 && - *p && *p != ',') { - /* ignore leading spaces */ - if (!non_space_char_found && *p == ' ') { - n++; - continue; - } - non_space_char_found = 1; - protocol_name[n++] = *p++; - } - protocol_name[n] = '\0'; - if (*p) - p++; - - lwsl_info("checking %s\n", protocol_name); - - n = 0; - while (wsi->vhost->protocols[n].callback) { - lwsl_info("try %s\n", - wsi->vhost->protocols[n].name); - - if (wsi->vhost->protocols[n].name && - !strcmp(wsi->vhost->protocols[n].name, - protocol_name)) { - wsi->protocol = &wsi->vhost->protocols[n]; - hit = 1; - break; - } - - n++; - } - } - - /* we didn't find a protocol he wanted? */ - - if (!hit) { - if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { - lwsl_info("No protocol from \"%s\" supported\n", - protocol_list); - goto bail_nuke_ah; - } - /* - * some clients only have one protocol and - * do not send the protocol list header... - * allow it and match to the vhost's default - * protocol (which itself defaults to zero) - */ - lwsl_info("defaulting to prot handler %d\n", - wsi->vhost->default_protocol_index); - n = wsi->vhost->default_protocol_index; - wsi->protocol = &wsi->vhost->protocols[ - (int)wsi->vhost->default_protocol_index]; - } - - /* allocate wsi->user storage */ - if (lws_ensure_user_space(wsi)) - goto bail_nuke_ah; - - /* - * Give the user code a chance to study the request and - * have the opportunity to deny it - */ - if ((wsi->protocol->callback)(wsi, - LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, - wsi->user_space, - lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { - lwsl_warn("User code denied connection\n"); - goto bail_nuke_ah; - } - - /* - * Perform the handshake according to the protocol version the - * client announced - */ - - switch (wsi->ietf_spec_revision) { - case 13: - lwsl_parser("lws_parse calling handshake_04\n"); - if (handshake_0405(context, wsi)) { - lwsl_info("hs0405 has failed the connection\n"); - goto bail_nuke_ah; - } - break; - - default: - lwsl_info("Unknown client spec version %d\n", - wsi->ietf_spec_revision); + if (lws_process_ws_upgrade(wsi)) goto bail_nuke_ah; - } - - lws_same_vh_protocol_insert(wsi, n); - - /* we are upgrading to ws, so http/1.1 and keepalive + - * pipelined header considerations about keeping the ah around - * no longer apply. However it's common for the first ws - * protocol data to have been coalesced with the browser - * upgrade request and to already be in the ah rx buffer. - */ - - lwsl_info("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", - __func__, wsi, wsi->u.hdr.ah->rxpos, - wsi->u.hdr.ah->rxlen); - lws_pt_lock(pt); - hdr = wsi->u.hdr; - - lws_union_transition(wsi, LWSCM_WS_SERVING); - /* - * first service is WS mode will notice this, use the RX and - * then detach the ah (caution: we are not in u.hdr union - * mode any more then... ah_temp member is at start the same - * though) - * - * Because rxpos/rxlen shows something in the ah, we will get - * service guaranteed next time around the event loop - * - * All union members begin with hdr, so we can use it even - * though we transitioned to ws union mode (the ah detach - * code uses it anyway). - */ - wsi->u.hdr = hdr; - lws_pt_unlock(pt); - - lws_server_init_wsi_for_ws(wsi); - lwsl_parser("accepted v%02d connection\n", - wsi->ietf_spec_revision); - - /* !!! drop ah unreservedly after ESTABLISHED */ - if (!wsi->more_rx_waiting) { - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 1); - } return 0; +#endif } /* while all chars are handled */ return 0; bail_nuke_ah: /* drop the header info */ - /* we're closing, losing some rx is OK */ - lws_header_table_force_to_detachable_state(wsi); lws_header_table_detach(wsi, 1); return 1; @@ -1772,10 +1649,13 @@ lws_get_idlest_tsi(struct lws_context *context) } struct lws * -lws_create_new_server_wsi(struct lws_vhost *vhost) +lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi) { struct lws *new_wsi; - int n = lws_get_idlest_tsi(vhost->context); + int n = fixed_tsi; + + if (n < 0) + n = lws_get_idlest_tsi(vhost->context); if (n < 0) { lwsl_err("no space for new conn\n"); @@ -1799,12 +1679,11 @@ lws_create_new_server_wsi(struct lws_vhost *vhost) /* initialize the instance struct */ - new_wsi->state = LWSS_HTTP; - new_wsi->mode = LWSCM_HTTP_SERVING; + lwsi_set_state(new_wsi, LRS_UNCONNECTED); new_wsi->hdr_parsing_completed = 0; -#ifdef LWS_OPENSSL_SUPPORT - new_wsi->use_ssl = LWS_SSL_ENABLED(vhost); +#ifdef LWS_WITH_TLS + new_wsi->tls.use_ssl = LWS_SSL_ENABLED(vhost); #endif /* @@ -1815,9 +1694,8 @@ lws_create_new_server_wsi(struct lws_vhost *vhost) */ new_wsi->protocol = vhost->protocols; new_wsi->user_space = NULL; - new_wsi->ietf_spec_revision = 0; new_wsi->desc.sockfd = LWS_SOCK_INVALID; - new_wsi->position_in_fds_table = -1; + new_wsi->position_in_fds_table = LWS_NO_FDS_POS; vhost->context->count_wsi_allocated++; @@ -1845,7 +1723,6 @@ lws_http_transaction_completed(struct lws *wsi) return 0; } - lwsl_debug("%s: wsi %p\n", __func__, wsi); /* if we can't go back to accept new headers, drop the connection */ if (wsi->http2_substream) return 0; @@ -1853,22 +1730,28 @@ lws_http_transaction_completed(struct lws *wsi) if (wsi->seen_zero_length_recv) return 1; - if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { - lwsl_info("%s: %p: close connection\n", __func__, wsi); + if (wsi->http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { + lwsl_notice("%s: %p: close connection\n", __func__, wsi); return 1; } if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0])) return 1; - /* otherwise set ourselves up ready to go again */ - wsi->state = LWSS_HTTP; - wsi->mode = LWSCM_HTTP_SERVING; - wsi->u.http.tx_content_length = 0; - wsi->u.http.tx_content_remain = 0; + /* + * otherwise set ourselves up ready to go again, but because we have no + * idea about the wsi writability, we make put it in a holding state + * until we can verify POLLOUT. The part of this that confirms POLLOUT + * with no partials is in lws_server_socket_service() below. + */ + lwsl_debug("%s: %p: setting DEF_ACT from 0x%x\n", __func__, + wsi, wsi->wsistate); + lwsi_set_state(wsi, LRS_DEFERRING_ACTION); + wsi->http.tx_content_length = 0; + wsi->http.tx_content_remain = 0; wsi->hdr_parsing_completed = 0; #ifdef LWS_WITH_ACCESS_LOG - wsi->access_log.sent = 0; + wsi->http.access_log.sent = 0; #endif if (wsi->vhost->keepalive_timeout) @@ -1887,21 +1770,20 @@ lws_http_transaction_completed(struct lws *wsi) * that is already at least the start of another header set, simply * reset the existing header table and keep it. */ - if (wsi->u.hdr.ah) { - lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, - wsi->more_rx_waiting); - - if (!wsi->more_rx_waiting) { - lws_header_table_force_to_detachable_state(wsi); + if (wsi->http.ah) { + // lws_buflist_describe(&wsi->buflist, wsi); + if (!lws_buflist_next_segment_len(&wsi->buflist, NULL)) { + lwsl_info("%s: %p: nothing in buflist so detaching ah\n", + __func__, wsi); lws_header_table_detach(wsi, 1); -#ifdef LWS_OPENSSL_SUPPORT +#ifdef LWS_WITH_TLS /* * additionally... if we are hogging an SSL instance * with no pending pipelined headers (or ah now), and * SSL is scarce, drop this connection without waiting */ - if (wsi->vhost->use_ssl && + if (wsi->vhost->tls.use_ssl && wsi->context->simultaneous_ssl_restriction && wsi->context->simultaneous_ssl == wsi->context->simultaneous_ssl_restriction) { @@ -1911,7 +1793,9 @@ lws_http_transaction_completed(struct lws *wsi) } #endif } else { - lws_header_table_reset(wsi, 1); + lwsl_info("%s: %p: resetting and keeping ah as pipeline\n", + __func__, wsi); + lws_header_table_reset(wsi, 0); /* * If we kept the ah, we should restrict the amount * of time we are willing to keep it. Otherwise it @@ -1921,12 +1805,18 @@ lws_http_transaction_completed(struct lws *wsi) lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH, wsi->vhost->keepalive_timeout); } - } + /* If we're (re)starting on headers, need other implied init */ + if (wsi->http.ah) + wsi->http.ah->ues = URIES_IDLE; - /* If we're (re)starting on headers, need other implied init */ - wsi->u.hdr.ues = URIES_IDLE; + //lwsi_set_state(wsi, LRS_ESTABLISHED); + } else + if (lws_buflist_next_segment_len(&wsi->buflist, NULL)) + if (lws_header_table_attach(wsi, 0)) + lwsl_debug("acquired ah\n"); lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); + lws_callback_on_writable(wsi); return 0; } @@ -1949,11 +1839,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, if (type & LWS_ADOPT_SOCKET && !(type & LWS_ADOPT_WS_PARENTIO)) { peer = lws_get_or_create_peer(vh, fd.sockfd); - if (!peer) { - lwsl_err("OOM creating peer\n"); - return NULL; - } - if (context->ip_limit_wsi && + if (peer && context->ip_limit_wsi && peer->count_wsi >= context->ip_limit_wsi) { lwsl_notice("Peer reached wsi limit %d\n", context->ip_limit_wsi); @@ -1964,7 +1850,10 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, } #endif - new_wsi = lws_create_new_server_wsi(vh); + n = -1; + if (parent) + n = parent->tsi; + new_wsi = lws_create_new_server_wsi(vh, n); if (!new_wsi) { if (type & LWS_ADOPT_SOCKET && !(type & LWS_ADOPT_WS_PARENTIO)) compatible_close(fd.sockfd); @@ -2000,28 +1889,51 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, lwsl_notice("OOM trying to get user_space\n"); goto bail; } +#if defined(LWS_ROLE_WS) if (type & LWS_ADOPT_WS_PARENTIO) { new_wsi->desc.sockfd = LWS_SOCK_INVALID; lwsl_debug("binding to %s\n", new_wsi->protocol->name); lws_bind_protocol(new_wsi, new_wsi->protocol); - lws_union_transition(new_wsi, LWSCM_WS_SERVING); + lws_role_transition(new_wsi, LWSIFR_SERVER, + LRS_ESTABLISHED, &role_ops_ws); + /* allocate the ws struct for the wsi */ + new_wsi->ws = lws_zalloc(sizeof(*new_wsi->ws), "ws struct"); + if (!new_wsi->ws) { + lwsl_notice("OOM\n"); + goto bail; + } lws_server_init_wsi_for_ws(new_wsi); return new_wsi; } +#endif } else - if (type & LWS_ADOPT_HTTP) /* he will transition later */ +#if defined(LWS_ROLE_H1) + if (type & LWS_ADOPT_HTTP) {/* he will transition later */ new_wsi->protocol = &vh->protocols[vh->default_protocol_index]; - else { /* this is the only time he will transition */ + new_wsi->role_ops = &role_ops_h1; + } + else +#endif + { /* this is the only time he will transition */ lws_bind_protocol(new_wsi, &vh->protocols[vh->raw_protocol_index]); - lws_union_transition(new_wsi, LWSCM_RAW); + lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, + &role_ops_raw_skt); } if (type & LWS_ADOPT_SOCKET) { /* socket desc */ lwsl_debug("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, (int)(lws_intptr_t)fd.sockfd); +#if !defined(LWS_WITH_ESP32) + if (type & LWS_ADOPT_FLAG_UDP) + /* + * these can be >128 bytes, so just alloc for UDP + */ + new_wsi->udp = lws_malloc(sizeof(*new_wsi->udp), + "udp struct"); +#endif if (type & LWS_ADOPT_HTTP) /* the transport is accepted... @@ -2030,11 +1942,6 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, context->timeout_secs); -#if LWS_POSIX == 0 -#if defined(LWS_WITH_ESP8266) - esp8266_tcp_stream_accept(accept_fd, new_wsi); -#endif -#endif } else /* file desc */ lwsl_debug("%s: new wsi %p, filefd %d\n", __func__, new_wsi, (int)(lws_intptr_t)fd.filefd); @@ -2058,29 +1965,43 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, /* non-SSL */ if (!(type & LWS_ADOPT_HTTP)) { if (!(type & LWS_ADOPT_SOCKET)) - new_wsi->mode = LWSCM_RAW_FILEDESC; + lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, + &role_ops_raw_file); else - new_wsi->mode = LWSCM_RAW; + lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, + &role_ops_raw_skt); } +#if defined(LWS_ROLE_H1) + else + lws_role_transition(new_wsi, LWSIFR_SERVER, + LRS_HEADERS, &role_ops_h1); +#endif } else { /* SSL */ if (!(type & LWS_ADOPT_HTTP)) - new_wsi->mode = LWSCM_SSL_INIT_RAW; + lws_role_transition(new_wsi, 0, LRS_SSL_INIT, + &role_ops_raw_skt); +#if defined(LWS_ROLE_H1) else - new_wsi->mode = LWSCM_SSL_INIT; - + lws_role_transition(new_wsi, LWSIFR_SERVER, + LRS_SSL_INIT, &role_ops_h1); +#endif ssl = 1; } - lws_libev_accept(new_wsi, new_wsi->desc); - lws_libuv_accept(new_wsi, new_wsi->desc); - lws_libevent_accept(new_wsi, new_wsi->desc); + lwsl_debug("new wsi wsistate 0x%x\n", new_wsi->wsistate); + + if (context->event_loop_ops->accept) + context->event_loop_ops->accept(new_wsi); if (!ssl) { - if (insert_wsi_socket_into_fds(context, new_wsi)) { + lws_pt_lock(pt, __func__); + if (__insert_wsi_socket_into_fds(context, new_wsi)) { + lws_pt_unlock(pt); lwsl_err("%s: fail inserting socket\n", __func__); goto fail; } + lws_pt_unlock(pt); } else if (lws_server_socket_service_ssl(new_wsi, fd.sockfd)) { lwsl_info("%s: fail ssl negotiation\n", __func__); @@ -2102,11 +2023,13 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, lwsl_info("%s: waiting for ah\n", __func__); } + lws_cancel_service_pt(new_wsi); + return new_wsi; fail: if (type & LWS_ADOPT_SOCKET) - lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS); + lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt skt fail"); return NULL; @@ -2143,8 +2066,8 @@ static struct lws* adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) { struct lws_context_per_thread *pt; - struct allocated_headers *ah; struct lws_pollfd *pfd; + int n; if (!wsi) return NULL; @@ -2152,10 +2075,16 @@ adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) if (!readbuf || len == 0) return wsi; - if (len > sizeof(ah->rx)) { - lwsl_err("%s: rx in too big\n", __func__); + if (wsi->position_in_fds_table == LWS_NO_FDS_POS) + return wsi; + + pt = &wsi->context->pt[(int)wsi->tsi]; + + n = lws_buflist_append_segment(&wsi->buflist, (const uint8_t *)readbuf, len); + if (n < 0) goto bail; - } + if (n) + lws_dll_lws_add_front(&wsi->dll_buflist, &pt->dll_head_buflist); /* * we can't process the initial read data until we can attach an ah. @@ -2167,14 +2096,9 @@ adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) * readbuf data to wsi or ah yet, and we will do it next if we get * the ah. */ - if (wsi->u.hdr.ah || !lws_header_table_attach(wsi, 0)) { - ah = wsi->u.hdr.ah; - memcpy(ah->rx, readbuf, len); - ah->rxpos = 0; - ah->rxlen = (int16_t)len; + if (wsi->http.ah || !lws_header_table_attach(wsi, 0)) { lwsl_notice("%s: calling service on readbuf ah\n", __func__); - pt = &wsi->context->pt[(int)wsi->tsi]; /* unlike a normal connect, we have the headers already * (or the first part of them anyway). @@ -2191,25 +2115,11 @@ adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) return wsi; } lwsl_err("%s: deferring handling ah\n", __func__); - /* - * hum if no ah came, we are on the wait list and must defer - * dealing with this until the ah arrives. - * - * later successful lws_header_table_attach() will apply the - * below to the rx buffer (via lws_header_table_reset()). - */ - wsi->u.hdr.preamble_rx = lws_malloc(len, "preamble_rx"); - if (!wsi->u.hdr.preamble_rx) { - lwsl_err("OOM\n"); - goto bail; - } - memcpy(wsi->u.hdr.preamble_rx, readbuf, len); - wsi->u.hdr.preamble_rx_len = len; return wsi; bail: - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt skt readbuf fail"); return NULL; } @@ -2232,397 +2142,6 @@ lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, } LWS_VISIBLE int -lws_server_socket_service(struct lws_context *context, struct lws *wsi, - struct lws_pollfd *pollfd) -{ - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - lws_sockfd_type accept_fd = LWS_SOCK_INVALID; - struct allocated_headers *ah; - lws_sock_file_fd_type fd; - int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL; -#if LWS_POSIX - struct sockaddr_storage cli_addr; - socklen_t clilen; -#endif - int n, len; - - switch (wsi->mode) { - - case LWSCM_HTTP_SERVING: - case LWSCM_HTTP_SERVING_ACCEPTED: - case LWSCM_HTTP2_SERVING: - case LWSCM_RAW: - - /* handle http headers coming in */ - - /* pending truncated sends have uber priority */ - - if (wsi->trunc_len) { - if (!(pollfd->revents & LWS_POLLOUT)) - break; - - if (lws_issue_raw(wsi, wsi->trunc_alloc + - wsi->trunc_offset, - wsi->trunc_len) < 0) - goto fail; - /* - * we can't afford to allow input processing to send - * something new, so spin around he event loop until - * he doesn't have any partials - */ - break; - } - - /* any incoming data ready? */ - - if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) - goto try_pollout; - - /* - * If we previously just did POLLIN when IN and OUT were - * signalled (because POLLIN processing may have used up - * the POLLOUT), don't let that happen twice in a row... - * next time we see the situation favour POLLOUT - */ -#if !defined(LWS_WITH_ESP8266) - if (wsi->favoured_pollin && - (pollfd->revents & pollfd->events & LWS_POLLOUT)) { - lwsl_notice("favouring pollout\n"); - wsi->favoured_pollin = 0; - goto try_pollout; - } -#endif - - /* these states imply we MUST have an ah attached */ - - if (wsi->mode != LWSCM_RAW && (wsi->state == LWSS_HTTP || - wsi->state == LWSS_HTTP_ISSUING_FILE || - wsi->state == LWSS_HTTP_HEADERS)) { - if (!wsi->u.hdr.ah) { - /* no autoservice beacuse we will do it next */ - if (lws_header_table_attach(wsi, 0)) { - lwsl_info("wsi %p: ah get fail\n", wsi); - goto try_pollout; - } - } - ah = wsi->u.hdr.ah; - - /* if nothing in ah rx buffer, get some fresh rx */ - if (ah->rxpos == ah->rxlen) { - ah->rxlen = lws_ssl_capable_read(wsi, ah->rx, - sizeof(ah->rx)); - ah->rxpos = 0; - switch (ah->rxlen) { - case 0: - lwsl_info("%s: read 0 len a\n", __func__); - wsi->seen_zero_length_recv = 1; - lws_change_pollfd(wsi, LWS_POLLIN, 0); - goto try_pollout; - /* fallthru */ - case LWS_SSL_CAPABLE_ERROR: - goto fail; - case LWS_SSL_CAPABLE_MORE_SERVICE: - ah->rxlen = ah->rxpos = 0; - goto try_pollout; - } - - /* - * make sure ah does not get detached if we - * have live data in the rx - */ - if (ah->rxlen) - wsi->more_rx_waiting = 1; - } - - if (!(ah->rxpos != ah->rxlen && ah->rxlen)) { - lwsl_err("%s: assert: rxpos %d, rxlen %d\n", - __func__, ah->rxpos, ah->rxlen); - - assert(0); - } - - /* just ignore incoming if waiting for close */ - if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && - wsi->state != LWSS_HTTP_ISSUING_FILE) { - /* - * otherwise give it to whoever wants it - * according to the connection state - */ - - n = lws_read(wsi, ah->rx + ah->rxpos, - ah->rxlen - ah->rxpos); - if (n < 0) /* we closed wsi */ - return 1; - - if (!wsi->u.hdr.ah) - break; - if ( wsi->u.hdr.ah->rxlen) - wsi->u.hdr.ah->rxpos += n; - - lwsl_debug("%s: wsi %p: ah read rxpos %d, rxlen %d\n", - __func__, wsi, wsi->u.hdr.ah->rxpos, - wsi->u.hdr.ah->rxlen); - - if (lws_header_table_is_in_detachable_state(wsi) && - (wsi->mode != LWSCM_HTTP_SERVING && - wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED && - wsi->mode != LWSCM_HTTP2_SERVING)) - lws_header_table_detach(wsi, 1); - - break; - } - - goto try_pollout; - } - - len = lws_ssl_capable_read(wsi, pt->serv_buf, - context->pt_serv_buf_size); - lwsl_debug("%s: wsi %p read %d\r\n", __func__, wsi, len); - switch (len) { - case 0: - lwsl_info("%s: read 0 len b\n", __func__); - - /* fallthru */ - case LWS_SSL_CAPABLE_ERROR: - goto fail; - case LWS_SSL_CAPABLE_MORE_SERVICE: - goto try_pollout; - } - - if (len < 0) /* coverity */ - goto fail; - - if (wsi->mode == LWSCM_RAW) { - n = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_RAW_RX, - wsi->user_space, pt->serv_buf, len); - if (n < 0) { - lwsl_info("LWS_CALLBACK_RAW_RX_fail\n"); - goto fail; - } - goto try_pollout; - } - - /* just ignore incoming if waiting for close */ - if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && - wsi->state != LWSS_HTTP_ISSUING_FILE) { - /* - * this may want to send - * (via HTTP callback for example) - */ - n = lws_read(wsi, pt->serv_buf, len); - if (n < 0) /* we closed wsi */ - return 1; - /* - * he may have used up the - * writability above, if we will defer POLLOUT - * processing in favour of POLLIN, note it - */ - if (pollfd->revents & LWS_POLLOUT) - wsi->favoured_pollin = 1; - break; - } - /* - * he may have used up the - * writability above, if we will defer POLLOUT - * processing in favour of POLLIN, note it - */ - if (pollfd->revents & LWS_POLLOUT) - wsi->favoured_pollin = 1; - -try_pollout: - - /* this handles POLLOUT for http serving fragments */ - - if (!(pollfd->revents & LWS_POLLOUT)) - break; - - /* one shot */ - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_notice("%s a\n", __func__); - goto fail; - } - - if (wsi->mode == LWSCM_RAW) { - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_WRITEABLE_CB, 1); -#if defined(LWS_WITH_STATS) - if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - - wsi->active_writable_req_us; - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, - LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); - wsi->active_writable_req_us = 0; - } -#endif - n = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_RAW_WRITEABLE, - wsi->user_space, NULL, 0); - if (n < 0) { - lwsl_info("writeable_fail\n"); - goto fail; - } - break; - } - - if (!wsi->hdr_parsing_completed) - break; - - if (wsi->state != LWSS_HTTP_ISSUING_FILE) { - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_WRITEABLE_CB, 1); -#if defined(LWS_WITH_STATS) - if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - - wsi->active_writable_req_us; - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, - LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); - wsi->active_writable_req_us = 0; - } -#endif - - n = user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_HTTP_WRITEABLE, - wsi->user_space, NULL, 0); - if (n < 0) { - lwsl_info("writeable_fail\n"); - goto fail; - } - break; - } - - /* >0 == completion, <0 == error - * - * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when - * it's done. That's the case even if we just completed the - * send, so wait for that. - */ - n = lws_serve_http_file_fragment(wsi); - if (n < 0) - goto fail; - - break; - - case LWSCM_SERVER_LISTENER: - -#if LWS_POSIX - /* pollin means a client has connected to us then */ - - do { - if (!(pollfd->revents & LWS_POLLIN) || - !(pollfd->events & LWS_POLLIN)) - break; - -#ifdef LWS_OPENSSL_SUPPORT - /* - * can we really accept it, with regards to SSL limit? - * another vhost may also have had POLLIN on his listener this - * round and used it up already - */ - - if (wsi->vhost->use_ssl && - context->simultaneous_ssl_restriction && - context->simultaneous_ssl == - context->simultaneous_ssl_restriction) - /* no... ignore it, he won't come again until we are - * below the simultaneous_ssl_restriction limit and - * POLLIN is enabled on him again - */ - break; -#endif - /* listen socket got an unencrypted connection... */ - - clilen = sizeof(cli_addr); - lws_latency_pre(context, wsi); - - /* - * We cannot identify the peer who is in the listen - * socket connect queue before we accept it; even if - * we could, not accepting it due to PEER_LIMITS would - * block the connect queue for other legit peers. - */ - accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, - &clilen); - lws_latency(context, wsi, "listener accept", accept_fd, - accept_fd >= 0); - if (accept_fd < 0) { - if (LWS_ERRNO == LWS_EAGAIN || - LWS_ERRNO == LWS_EWOULDBLOCK) { - break; - } - lwsl_err("ERROR on accept: %s\n", strerror(LWS_ERRNO)); - break; - } - - lws_plat_set_socket_options(wsi->vhost, accept_fd); - -#if defined(LWS_WITH_IPV6) - lwsl_debug("accepted new conn port %u on fd=%d\n", - ((cli_addr.ss_family == AF_INET6) ? - ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : - ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), - accept_fd); -#else - lwsl_debug("accepted new conn port %u on fd=%d\n", - ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), - accept_fd); -#endif - -#else - /* not very beautiful... */ - accept_fd = (lws_sockfd_type)pollfd; -#endif - /* - * look at who we connected to and give user code a chance - * to reject based on client IP. There's no protocol selected - * yet so we issue this to protocols[0] - */ - if ((wsi->vhost->protocols[0].callback)(wsi, - LWS_CALLBACK_FILTER_NETWORK_CONNECTION, - NULL, (void *)(lws_intptr_t)accept_fd, 0)) { - lwsl_debug("Callback denied network connection\n"); - compatible_close(accept_fd); - break; - } - - if (!(wsi->vhost->options & LWS_SERVER_OPTION_ONLY_RAW)) - opts |= LWS_ADOPT_HTTP; - else - opts = LWS_ADOPT_SOCKET; - - fd.sockfd = accept_fd; - if (!lws_adopt_descriptor_vhost(wsi->vhost, opts, fd, - NULL, NULL)) - /* already closed cleanly as necessary */ - return 1; - -#if LWS_POSIX - } while (pt->fds_count < context->fd_limit_per_thread - 1 && - lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0); -#endif - return 0; - - default: - break; - } - - if (!lws_server_socket_service_ssl(wsi, accept_fd)) - return 0; - -fail: - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - - return 1; -} - -LWS_VISIBLE int lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, const char *other_headers, int other_headers_len) { @@ -2630,13 +2149,13 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; #if defined(LWS_WITH_RANGES) - struct lws_range_parsing *rp = &wsi->u.http.range; + struct lws_range_parsing *rp = &wsi->http.range; #endif char cache_control[50], *cc = "no-store"; unsigned char *response = pt->serv_buf + LWS_PRE; unsigned char *p = response; unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; - lws_filepos_t computed_total_content_length; + lws_filepos_t total_content_length; int ret = 0, cclen = 8, n = HTTP_STATUS_OK; lws_fop_flags_t fflags = LWS_O_RDONLY; #if defined(LWS_WITH_RANGES) @@ -2645,29 +2164,34 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, const struct lws_plat_file_ops *fops; const char *vpath; + if (wsi->handling_404) + n = HTTP_STATUS_NOT_FOUND; + /* * We either call the platform fops .open with first arg platform fops, * or we call fops_zip .open with first arg platform fops, and fops_zip * open will decide whether to switch to fops_zip or stay with fops_def. * - * If wsi->u.http.fop_fd is already set, the caller already opened it + * If wsi->http.fop_fd is already set, the caller already opened it */ - if (!wsi->u.http.fop_fd) { + if (!wsi->http.fop_fd) { fops = lws_vfs_select_fops(wsi->context->fops, file, &vpath); fflags |= lws_vfs_prepare_flags(wsi); - wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, + wsi->http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, file, vpath, &fflags); - if (!wsi->u.http.fop_fd) { - lwsl_err("Unable to open '%s'\n", file); - - return -1; + if (!wsi->http.fop_fd) { + lwsl_info("%s: Unable to open: '%s': errno %d\n", + __func__, file, errno); + if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) + return -1; + return !wsi->http2_substream; } } - wsi->u.http.filelen = lws_vfs_get_length(wsi->u.http.fop_fd); - computed_total_content_length = wsi->u.http.filelen; + wsi->http.filelen = lws_vfs_get_length(wsi->http.fop_fd); + total_content_length = wsi->http.filelen; #if defined(LWS_WITH_RANGES) - ranges = lws_ranges_init(wsi, rp, wsi->u.http.filelen); + ranges = lws_ranges_init(wsi, rp, wsi->http.filelen); lwsl_debug("Range count %d\n", ranges); /* @@ -2684,7 +2208,7 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, if (lws_http_transaction_completed(wsi)) return -1; /* <0 means just hang up */ - lws_vfs_file_close(&wsi->u.http.fop_fd); + lws_vfs_file_close(&wsi->http.fop_fd); return 0; /* == 0 means we dealt with the transaction complete */ } @@ -2695,7 +2219,7 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, if (lws_add_http_header_status(wsi, n, &p, end)) return -1; - if ((wsi->u.http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | + if ((wsi->http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) == (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) { if (lws_add_http_header_by_token(wsi, @@ -2710,20 +2234,24 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, ranges < 2 && #endif content_type && content_type[0]) - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, - strlen(content_type), &p, end)) + (int)strlen(content_type), + &p, end)) return -1; #if defined(LWS_WITH_RANGES) if (ranges >= 2) { /* multipart byteranges */ - strncpy(wsi->u.http.multipart_content_type, content_type, - sizeof(wsi->u.http.multipart_content_type) - 1); - wsi->u.http.multipart_content_type[ - sizeof(wsi->u.http.multipart_content_type) - 1] = '\0'; - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, - (unsigned char *)"multipart/byteranges; boundary=_lws", - 20, &p, end)) + lws_strncpy(wsi->http.multipart_content_type, content_type, + sizeof(wsi->http.multipart_content_type)); + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *) + "multipart/byteranges; " + "boundary=_lws", + 20, &p, end)) return -1; /* @@ -2738,8 +2266,8 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, * Precompute it for the main response header */ - computed_total_content_length = (lws_filepos_t)rp->agg + - 6 /* final _lws\r\n */; + total_content_length = (lws_filepos_t)rp->agg + + 6 /* final _lws\r\n */; lws_ranges_reset(rp); while (lws_ranges_next(rp)) { @@ -2747,7 +2275,7 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, "bytes %llu-%llu/%llu", rp->start, rp->end, rp->extent); - computed_total_content_length += + total_content_length += 6 /* header _lws\r\n */ + /* Content-Type: xxx/xxx\r\n */ 14 + strlen(content_type) + 2 + @@ -2761,35 +2289,38 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, } if (ranges == 1) { - computed_total_content_length = (lws_filepos_t)rp->agg; + total_content_length = (lws_filepos_t)rp->agg; n = lws_snprintf(cache_control, sizeof(cache_control), "bytes %llu-%llu/%llu", rp->start, rp->end, rp->extent); - if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_RANGE, + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_RANGE, (unsigned char *)cache_control, n, &p, end)) return -1; } - wsi->u.http.range.inside = 0; + wsi->http.range.inside = 0; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT_RANGES, (unsigned char *)"bytes", 5, &p, end)) return -1; #endif - if (!wsi->sending_chunked) { - if (lws_add_http_header_content_length(wsi, - computed_total_content_length, - &p, end)) - return -1; - } else { - if (lws_add_http_header_by_token(wsi, + if (!wsi->http2_substream) { + if (!wsi->sending_chunked) { + if (lws_add_http_header_content_length(wsi, + total_content_length, + &p, end)) + return -1; + } else { + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) - return -1; + return -1; + } } if (wsi->cache_secs && wsi->cache_reuse) { @@ -2808,7 +2339,7 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, (unsigned char *)cc, cclen, &p, end)) return -1; - if (wsi->u.http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) + if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, (unsigned char *)"keep-alive", 10, &p, end)) return -1; @@ -2830,91 +2361,268 @@ lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, return -1; } - wsi->u.http.filepos = 0; - wsi->state = LWSS_HTTP_ISSUING_FILE; + wsi->http.filepos = 0; + lwsi_set_state(wsi, LRS_ISSUING_FILE); lws_callback_on_writable(wsi); return 0; } -int -lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len) +LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) { - int m; + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_process_html_args args; + lws_filepos_t amount, poss; + unsigned char *p, *pstart; +#if defined(LWS_WITH_RANGES) + unsigned char finished = 0; +#endif + int n, m; + + lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream); + + do { + + if (wsi->trunc_len) { + if (lws_issue_raw(wsi, wsi->trunc_alloc + + wsi->trunc_offset, + wsi->trunc_len) < 0) { + lwsl_info("%s: closing\n", __func__); + goto file_had_it; + } + break; + } + + if (wsi->http.filepos == wsi->http.filelen) + goto all_sent; + + n = 0; + + pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH; + + p = pstart; + +#if defined(LWS_WITH_RANGES) + if (wsi->http.range.count_ranges && !wsi->http.range.inside) { + + lwsl_notice("%s: doing range start %llu\n", __func__, + wsi->http.range.start); + + if ((long long)lws_vfs_file_seek_cur(wsi->http.fop_fd, + wsi->http.range.start - + wsi->http.filepos) < 0) + goto file_had_it; + + wsi->http.filepos = wsi->http.range.start; + + if (wsi->http.range.count_ranges > 1) { + n = lws_snprintf((char *)p, + context->pt_serv_buf_size - + LWS_H2_FRAME_HEADER_LENGTH, + "_lws\x0d\x0a" + "Content-Type: %s\x0d\x0a" + "Content-Range: bytes %llu-%llu/%llu\x0d\x0a" + "\x0d\x0a", + wsi->http.multipart_content_type, + wsi->http.range.start, + wsi->http.range.end, + wsi->http.range.extent); + p += n; + } - lwsl_parser("%s: received %d byte packet\n", __func__, (int)len); -#if 0 - lwsl_hexdump(*buf, len); + wsi->http.range.budget = wsi->http.range.end - + wsi->http.range.start + 1; + wsi->http.range.inside = 1; + } #endif - /* let the rx protocol state machine have as much as it needs */ + poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH; + + if (wsi->http.tx_content_length) + if (poss > wsi->http.tx_content_remain) + poss = wsi->http.tx_content_remain; - while (len) { /* - * we were accepting input but now we stopped doing so + * if there is a hint about how much we will do well to send at + * one time, restrict ourselves to only trying to send that. */ - if (wsi->rxflow_bitmap) { - lws_rxflow_cache(wsi, *buf, 0, len); - lwsl_parser("%s: cached %ld\n", __func__, (long)len); - return 1; - } + if (wsi->protocol->tx_packet_size && + poss > wsi->protocol->tx_packet_size) + poss = wsi->protocol->tx_packet_size; - if (wsi->u.ws.rx_draining_ext) { - m = lws_rx_sm(wsi, 0); - if (m < 0) - return -1; - continue; - } + if (wsi->role_ops->tx_credit) { + lws_filepos_t txc = wsi->role_ops->tx_credit(wsi); - /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) { - wsi->rxflow_pos++; - if (wsi->rxflow_pos > wsi->rxflow_len) { - lwsl_err("bumped rxflow buffer too far (%d / %d)", wsi->rxflow_pos, wsi->rxflow_len); - assert(0); + if (!txc) { + lwsl_info("%s: came here with no tx credit\n", + __func__); + return 0; } + if (txc < poss) + poss = txc; + + /* + * consumption of the actual payload amount sent will be + * handled when the role data frame is sent + */ } - /* consume payload bytes efficiently */ - if (wsi->lws_rx_parse_state == - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED) { - m = lws_payload_until_length_exhausted(wsi, buf, &len); - if (wsi->rxflow_buffer) - wsi->rxflow_pos += m; +#if defined(LWS_WITH_RANGES) + if (wsi->http.range.count_ranges) { + if (wsi->http.range.count_ranges > 1) + poss -= 7; /* allow for final boundary */ + if (poss > wsi->http.range.budget) + poss = wsi->http.range.budget; + } +#endif + if (wsi->sending_chunked) { + /* we need to drop the chunk size in here */ + p += 10; + /* allow for the chunk to grow by 128 in translation */ + poss -= 10 + 128; } - if (wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { - lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi); - lws_free_set_NULL(wsi->rxflow_buffer); - /* having drained the rxflow buffer, can rearm POLLIN */ -#ifdef LWS_NO_SERVER - m = + if (lws_vfs_file_read(wsi->http.fop_fd, &amount, p, poss) < 0) + goto file_had_it; /* caller will close */ + + if (wsi->sending_chunked) + n = (int)amount; + else + n = lws_ptr_diff(p, pstart) + (int)amount; + + lwsl_debug("%s: sending %d\n", __func__, n); + + if (n) { + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + context->timeout_secs); + + if (wsi->interpreting) { + args.p = (char *)p; + args.len = n; + args.max_len = (unsigned int)poss + 128; + args.final = wsi->http.filepos + n == + wsi->http.filelen; + args.chunked = wsi->sending_chunked; + if (user_callback_handle_rxflow( + wsi->vhost->protocols[ + (int)wsi->protocol_interpret_idx].callback, + wsi, LWS_CALLBACK_PROCESS_HTML, + wsi->user_space, &args, 0) < 0) + goto file_had_it; + n = args.len; + p = (unsigned char *)args.p; + } else + p = pstart; + +#if defined(LWS_WITH_RANGES) + if (wsi->http.range.send_ctr + 1 == + wsi->http.range.count_ranges && // last range + wsi->http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart) + wsi->http.range.budget - amount == 0) {// final part + n += lws_snprintf((char *)pstart + n, 6, + "_lws\x0d\x0a"); // append trailing boundary + lwsl_debug("added trailing boundary\n"); + } +#endif + m = lws_write(wsi, p, n, + wsi->http.filepos + amount == wsi->http.filelen ? + LWS_WRITE_HTTP_FINAL : + LWS_WRITE_HTTP + ); + if (m < 0) + goto file_had_it; + + wsi->http.filepos += amount; + +#if defined(LWS_WITH_RANGES) + if (wsi->http.range.count_ranges >= 1) { + wsi->http.range.budget -= amount; + if (wsi->http.range.budget == 0) { + lwsl_notice("range budget exhausted\n"); + wsi->http.range.inside = 0; + wsi->http.range.send_ctr++; + + if (lws_ranges_next(&wsi->http.range) < 1) { + finished = 1; + goto all_sent; + } + } + } #endif - _lws_rx_flow_control(wsi); - /* m ignored, needed for NO_SERVER case */ + + if (m != n) { + /* adjust for what was not sent */ + if (lws_vfs_file_seek_cur(wsi->http.fop_fd, + m - n) == + (lws_fileofs_t)-1) + goto file_had_it; + } } - /* process the byte */ - m = lws_rx_sm(wsi, *(*buf)++); - if (m < 0) - return -1; - len--; - } +all_sent: + if ((!wsi->trunc_len && wsi->http.filepos >= wsi->http.filelen) +#if defined(LWS_WITH_RANGES) + || finished) +#else + ) +#endif + { + lwsi_set_state(wsi, LRS_ESTABLISHED); + /* we might be in keepalive, so close it off here */ + lws_vfs_file_close(&wsi->http.fop_fd); + + lwsl_debug("file completed\n"); + + if (wsi->protocol->callback && + user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, + wsi->user_space, NULL, + 0) < 0) { + /* + * For http/1.x, the choices from + * transaction_completed are either + * 0 to use the connection for pipelined + * or nonzero to hang it up. + * + * However for http/2. while we are + * still interested in hanging up the + * nwsi if there was a network-level + * fatal error, simply completing the + * transaction is a matter of the stream + * state, not the root connection at the + * network level + */ + if (wsi->http2_substream) + return 1; + else + return -1; + } + + return 1; /* >0 indicates completed */ + } + } while (0); // while (!lws_send_pipe_choked(wsi)) + + lws_callback_on_writable(wsi); - lwsl_parser("%s: exit with %d unused\n", __func__, (int)len); + return 0; /* indicates further processing must be done */ - return 0; +file_had_it: + lws_vfs_file_close(&wsi->http.fop_fd); + + return -1; } + LWS_VISIBLE void lws_server_get_canonical_hostname(struct lws_context *context, - struct lws_context_creation_info *info) + const struct lws_context_creation_info *info) { if (lws_check_opt(info->options, LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME)) return; -#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#if !defined(LWS_WITH_ESP32) /* find canonical hostname */ gethostname((char *)context->canonical_hostname, sizeof(context->canonical_hostname) - 1); @@ -2968,11 +2676,11 @@ skip: sp = s->start + 1; continue; } - if (hits == 1 && s->pos == strlen(s->vars[hit])) { + if (hits == 1 && s->pos == (int)strlen(s->vars[hit])) { pc = s->replace(s->data, hit); if (!pc) pc = "NULL"; - n = strlen(pc); + n = (int)strlen(pc); s->swallow[s->pos] = '\0'; if (n != s->pos) { memmove(s->start + n, @@ -2994,31 +2702,33 @@ skip: sp++; } - /* no space left for final chunk trailer */ - if (args->final && args->len + 7 >= args->max_len) - return -1; + if (args->chunked) { + /* no space left for final chunk trailer */ + if (args->final && args->len + 7 >= args->max_len) + return -1; - n = sprintf(buffer, "%X\x0d\x0a", args->len); - - args->p -= n; - memcpy(args->p, buffer, n); - args->len += n; - - if (args->final) { - sp = args->p + args->len; - *sp++ = '\x0d'; - *sp++ = '\x0a'; - *sp++ = '0'; - *sp++ = '\x0d'; - *sp++ = '\x0a'; - *sp++ = '\x0d'; - *sp++ = '\x0a'; - args->len += 7; - } else { - sp = args->p + args->len; - *sp++ = '\x0d'; - *sp++ = '\x0a'; - args->len += 2; + n = sprintf(buffer, "%X\x0d\x0a", args->len); + + args->p -= n; + memcpy(args->p, buffer, n); + args->len += n; + + if (args->final) { + sp = args->p + args->len; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + *sp++ = '0'; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + args->len += 7; + } else { + sp = args->p + args->len; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + args->len += 2; + } } return 0; diff --git a/thirdparty/libwebsockets/roles/listen/ops-listen.c b/thirdparty/libwebsockets/roles/listen/ops-listen.c new file mode 100644 index 0000000000..dbeb9753a2 --- /dev/null +++ b/thirdparty/libwebsockets/roles/listen/ops-listen.c @@ -0,0 +1,177 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +static int +rops_handle_POLLIN_listen(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_context *context = wsi->context; + lws_sockfd_type accept_fd = LWS_SOCK_INVALID; + lws_sock_file_fd_type fd; + int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL; + struct sockaddr_storage cli_addr; + socklen_t clilen; + + /* pollin means a client has connected to us then + * + * pollout is a hack on esp32 for background accepts signalling + * they completed + */ + + do { + struct lws *cwsi; + + if (!(pollfd->revents & (LWS_POLLIN | LWS_POLLOUT)) || + !(pollfd->events & LWS_POLLIN)) + break; + +#if defined(LWS_WITH_TLS) + /* + * can we really accept it, with regards to SSL limit? + * another vhost may also have had POLLIN on his + * listener this round and used it up already + */ + if (wsi->vhost->tls.use_ssl && + context->simultaneous_ssl_restriction && + context->simultaneous_ssl == + context->simultaneous_ssl_restriction) + /* + * no... ignore it, he won't come again until + * we are below the simultaneous_ssl_restriction + * limit and POLLIN is enabled on him again + */ + break; +#endif + /* listen socket got an unencrypted connection... */ + + clilen = sizeof(cli_addr); + lws_latency_pre(context, wsi); + + /* + * We cannot identify the peer who is in the listen + * socket connect queue before we accept it; even if + * we could, not accepting it due to PEER_LIMITS would + * block the connect queue for other legit peers. + */ + + accept_fd = accept((int)pollfd->fd, + (struct sockaddr *)&cli_addr, &clilen); + lws_latency(context, wsi, "listener accept", + (int)accept_fd, accept_fd != LWS_SOCK_INVALID); + if (accept_fd == LWS_SOCK_INVALID) { + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK) { + break; + } + lwsl_err("ERROR on accept: %s\n", + strerror(LWS_ERRNO)); + break; + } + + lws_plat_set_socket_options(wsi->vhost, accept_fd); + +#if defined(LWS_WITH_IPV6) + lwsl_debug("accepted new conn port %u on fd=%d\n", + ((cli_addr.ss_family == AF_INET6) ? + ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), + accept_fd); +#else + lwsl_debug("accepted new conn port %u on fd=%d\n", + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), + accept_fd); +#endif + + /* + * look at who we connected to and give user code a + * chance to reject based on client IP. There's no + * protocol selected yet so we issue this to + * protocols[0] + */ + if ((wsi->vhost->protocols[0].callback)(wsi, + LWS_CALLBACK_FILTER_NETWORK_CONNECTION, + NULL, + (void *)(lws_intptr_t)accept_fd, 0)) { + lwsl_debug("Callback denied net connection\n"); + compatible_close(accept_fd); + break; + } + + if (!(wsi->vhost->options & LWS_SERVER_OPTION_ONLY_RAW)) + opts |= LWS_ADOPT_HTTP; + else + opts = LWS_ADOPT_SOCKET; + + fd.sockfd = accept_fd; + cwsi = lws_adopt_descriptor_vhost(wsi->vhost, opts, fd, + NULL, NULL); + if (!cwsi) + /* already closed cleanly as necessary */ + return LWS_HPI_RET_WSI_ALREADY_DIED; + + if (lws_server_socket_service_ssl(cwsi, accept_fd)) { + lws_close_free_wsi(cwsi, LWS_CLOSE_STATUS_NOSTATUS, + "listen svc fail"); + return LWS_HPI_RET_WSI_ALREADY_DIED; + } + + lwsl_info("%s: new wsi %p: wsistate 0x%x, role_ops %s\n", + __func__, cwsi, cwsi->wsistate, cwsi->role_ops->name); + + } while (pt->fds_count < context->fd_limit_per_thread - 1 && + wsi->position_in_fds_table != LWS_NO_FDS_POS && + lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0); + + return LWS_HPI_RET_HANDLED; +} + +int rops_handle_POLLOUT_listen(struct lws *wsi) +{ + return LWS_HP_RET_USER_SERVICE; +} + +struct lws_role_ops role_ops_listen = { + /* role name */ "listen", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_listen, + /* handle_POLLOUT */ rops_handle_POLLOUT_listen, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, + /* file_handle */ 0, +}; diff --git a/thirdparty/libwebsockets/roles/pipe/ops-pipe.c b/thirdparty/libwebsockets/roles/pipe/ops-pipe.c new file mode 100644 index 0000000000..b9348d58d7 --- /dev/null +++ b/thirdparty/libwebsockets/roles/pipe/ops-pipe.c @@ -0,0 +1,81 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +static int +rops_handle_POLLIN_pipe(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ +#if !defined(WIN32) && !defined(_WIN32) + char s[100]; + int n; + + /* + * discard the byte(s) that signaled us + * We really don't care about the number of bytes, but coverity + * thinks we should. + */ + n = read(wsi->desc.sockfd, s, sizeof(s)); + (void)n; + if (n < 0) + return LWS_HPI_RET_PLEASE_CLOSE_ME; +#endif + /* + * the poll() wait, or the event loop for libuv etc is a + * process-wide resource that we interrupted. So let every + * protocol that may be interested in the pipe event know that + * it happened. + */ + if (lws_broadcast(wsi->context, LWS_CALLBACK_EVENT_WAIT_CANCELLED, + NULL, 0)) { + lwsl_info("closed in event cancel\n"); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + return LWS_HPI_RET_HANDLED; +} + +struct lws_role_ops role_ops_pipe = { + /* role name */ "pipe", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_pipe, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { 0, 0 }, + /* close cb clnt, srv */ { 0, 0 }, + /* file_handle */ 1, +}; diff --git a/thirdparty/libwebsockets/roles/private.h b/thirdparty/libwebsockets/roles/private.h new file mode 100644 index 0000000000..ae4278b5d3 --- /dev/null +++ b/thirdparty/libwebsockets/roles/private.h @@ -0,0 +1,282 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h + */ + +typedef uint32_t lws_wsi_state_t; + +/* + * The wsi->role_ops pointer decides almost everything about what role the wsi + * will play, h2, raw, ws, etc. + * + * However there are a few additional flags needed that vary, such as if the + * role is a client or server side, if it has that concept. And the connection + * fulfilling the role, has a separate dynamic state. + * + * 31 16 15 0 + * [ role flags ] [ state ] + * + * The role flags part is generally invariant for the lifetime of the wsi, + * although it can change if the connection role itself does, eg, if the + * connection upgrades from H1 -> WS1 the role flags may be changed at that + * point. + * + * The state part reflects the dynamic connection state, and the states are + * reused between roles. + * + * None of the internal role or state representations are made available outside + * of lws internals. Even for lws internals, if you add stuff here, please keep + * the constants inside this header only by adding necessary helpers here and + * use the helpers in the actual code. This is to ease any future refactors. + * + * Notice LWSIFR_ENCAP means we have a parent wsi that actually carries our + * data as a stream inside a different protocol. + */ + +#define _RS 16 + +#define LWSIFR_CLIENT (0x1000 << _RS) /* client side */ +#define LWSIFR_SERVER (0x2000 << _RS) /* server side */ + +#define LWSIFR_P_ENCAP_H2 (0x0100 << _RS) /* we are encapsulated by h2 */ + +enum lwsi_role { + LWSI_ROLE_MASK = (0xffff << _RS), + LWSI_ROLE_ENCAP_MASK = (0x0f00 << _RS), +}; + +#define lwsi_role(wsi) (wsi->wsistate & LWSI_ROLE_MASK) +#if !defined (_DEBUG) +#define lwsi_set_role(wsi, role) wsi->wsistate = \ + (wsi->wsistate & (~LWSI_ROLE_MASK)) | role +#else +void lwsi_set_role(struct lws *wsi, lws_wsi_state_t role); +#endif + +#define lwsi_role_client(wsi) (!!(wsi->wsistate & LWSIFR_CLIENT)) +#define lwsi_role_server(wsi) (!!(wsi->wsistate & LWSIFR_SERVER)) +#define lwsi_role_h2_ENCAPSULATION(wsi) \ + ((wsi->wsistate & LWSI_ROLE_ENCAP_MASK) == LWSIFR_P_ENCAP_H2) + +/* Pollout wants a callback in this state */ +#define LWSIFS_POCB (0x100) +/* Before any protocol connection was established */ +#define LWSIFS_NOT_EST (0x200) + +enum lwsi_state { + + /* Phase 1: pre-transport */ + + LRS_UNCONNECTED = LWSIFS_NOT_EST | 0, + LRS_WAITING_CONNECT = LWSIFS_NOT_EST | 1, + + /* Phase 2: establishing intermediaries on top of transport */ + + LRS_WAITING_PROXY_REPLY = LWSIFS_NOT_EST | 2, + LRS_WAITING_SSL = LWSIFS_NOT_EST | 3, + LRS_WAITING_SOCKS_GREETING_REPLY = LWSIFS_NOT_EST | 4, + LRS_WAITING_SOCKS_CONNECT_REPLY = LWSIFS_NOT_EST | 5, + LRS_WAITING_SOCKS_AUTH_REPLY = LWSIFS_NOT_EST | 6, + + /* Phase 3: establishing tls tunnel */ + + LRS_SSL_INIT = LWSIFS_NOT_EST | 7, + LRS_SSL_ACK_PENDING = LWSIFS_NOT_EST | 8, + LRS_PRE_WS_SERVING_ACCEPT = LWSIFS_NOT_EST | 9, + + /* Phase 4: connected */ + + LRS_WAITING_SERVER_REPLY = LWSIFS_NOT_EST | 10, + LRS_H2_AWAIT_PREFACE = LWSIFS_NOT_EST | 11, + LRS_H2_AWAIT_SETTINGS = LWSIFS_NOT_EST | + LWSIFS_POCB | 12, + + /* Phase 5: protocol logically established */ + + LRS_H2_CLIENT_SEND_SETTINGS = LWSIFS_POCB | 13, + LRS_H2_WAITING_TO_SEND_HEADERS = LWSIFS_POCB | 14, + LRS_DEFERRING_ACTION = LWSIFS_POCB | 15, + LRS_IDLING = 16, + LRS_H1C_ISSUE_HANDSHAKE = 17, + LRS_H1C_ISSUE_HANDSHAKE2 = 18, + LRS_ISSUE_HTTP_BODY = 19, + LRS_ISSUING_FILE = 20, + LRS_HEADERS = 21, + LRS_BODY = 22, + LRS_ESTABLISHED = LWSIFS_POCB | 23, + /* we are established, but we have embarked on serving a single + * transaction. Other transaction input may be pending, but we will + * not service it while we are busy dealing with the current + * transaction. + * + * When we complete the current transaction, we would reset our state + * back to ESTABLISHED and start to process the next transaction. + */ + LRS_DOING_TRANSACTION = LWSIFS_POCB | 24, + + /* Phase 6: finishing */ + + LRS_WAITING_TO_SEND_CLOSE = LWSIFS_POCB | 25, + LRS_RETURNED_CLOSE = LWSIFS_POCB | 26, + LRS_AWAITING_CLOSE_ACK = LWSIFS_POCB | 27, + LRS_FLUSHING_BEFORE_CLOSE = LWSIFS_POCB | 28, + LRS_SHUTDOWN = 29, + + /* Phase 7: dead */ + + LRS_DEAD_SOCKET = 30, + + LRS_MASK = 0xffff +}; + +#define lwsi_state(wsi) ((enum lwsi_state)(wsi->wsistate & LRS_MASK)) +#define lwsi_state_PRE_CLOSE(wsi) ((enum lwsi_state)(wsi->wsistate_pre_close & LRS_MASK)) +#define lwsi_state_est(wsi) (!(wsi->wsistate & LWSIFS_NOT_EST)) +#define lwsi_state_est_PRE_CLOSE(wsi) (!(wsi->wsistate_pre_close & LWSIFS_NOT_EST)) +#define lwsi_state_can_handle_POLLOUT(wsi) (wsi->wsistate & LWSIFS_POCB) +#if !defined (_DEBUG) +#define lwsi_set_state(wsi, lrs) wsi->wsistate = \ + (wsi->wsistate & (~LRS_MASK)) | lrs +#else +void lwsi_set_state(struct lws *wsi, lws_wsi_state_t lrs); +#endif + +/* + * internal role-specific ops + */ +struct lws_context_per_thread; +struct lws_role_ops { + const char *name; + const char *alpn; + /* + * After http headers have parsed, this is the last chance for a role + * to upgrade the connection to something else using the headers. + * ws-over-h2 is upgraded from h2 like this. + */ + int (*check_upgrades)(struct lws *wsi); + /* role-specific context init during context creation */ + int (*init_context)(struct lws_context *context, + const struct lws_context_creation_info *info); + /* role-specific per-vhost init during vhost creation */ + int (*init_vhost)(struct lws_vhost *vh, + const struct lws_context_creation_info *info); + /* role-specific per-vhost destructor during vhost destroy */ + int (*destroy_vhost)(struct lws_vhost *vh); + /* generic 1Hz callback for the role itself */ + int (*periodic_checks)(struct lws_context *context, int tsi, + time_t now); + /* chance for the role to force POLLIN without network activity */ + int (*service_flag_pending)(struct lws_context *context, int tsi); + /* an fd using this role has POLLIN signalled */ + int (*handle_POLLIN)(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd); + /* an fd using the role wanted a POLLOUT callback and now has it */ + int (*handle_POLLOUT)(struct lws *wsi); + /* perform user pollout */ + int (*perform_user_POLLOUT)(struct lws *wsi); + /* do effective callback on writeable */ + int (*callback_on_writable)(struct lws *wsi); + /* connection-specific tx credit in bytes */ + lws_fileofs_t (*tx_credit)(struct lws *wsi); + /* role-specific write formatting */ + int (*write_role_protocol)(struct lws *wsi, unsigned char *buf, + size_t len, enum lws_write_protocol *wp); + + /* get encapsulation parent */ + struct lws * (*encapsulation_parent)(struct lws *wsi); + + /* role-specific destructor */ + int (*alpn_negotiated)(struct lws *wsi, const char *alpn); + + /* chance for the role to handle close in the protocol */ + int (*close_via_role_protocol)(struct lws *wsi, + enum lws_close_status reason); + /* role-specific close processing */ + int (*close_role)(struct lws_context_per_thread *pt, struct lws *wsi); + /* role-specific connection close processing */ + int (*close_kill_connection)(struct lws *wsi, + enum lws_close_status reason); + /* role-specific destructor */ + int (*destroy_role)(struct lws *wsi); + + /* + * the callback reasons for WRITEABLE for client, server + * (just client applies if no concept of client or server) + */ + uint16_t writeable_cb[2]; + /* + * the callback reasons for CLOSE for client, server + * (just client applies if no concept of client or server) + */ + uint16_t close_cb[2]; + + unsigned int file_handle:1; /* role operates on files not sockets */ +}; + +/* core roles */ +extern struct lws_role_ops role_ops_raw_skt, role_ops_raw_file, role_ops_listen, + role_ops_pipe; + +/* bring in role private declarations */ + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + #include "roles/http/private.h" +#else + #define lwsi_role_http(wsi) (0) +#endif + +#if defined(LWS_ROLE_H1) + #include "roles/h1/private.h" +#else + #define lwsi_role_h1(wsi) (0) +#endif + +#if defined(LWS_ROLE_H2) + #include "roles/h2/private.h" +#else + #define lwsi_role_h2(wsi) (0) +#endif + +#if defined(LWS_ROLE_WS) + #include "roles/ws/private.h" +#else + #define lwsi_role_ws(wsi) (0) +#endif + +#if defined(LWS_ROLE_CGI) + #include "roles/cgi/private.h" +#else + #define lwsi_role_cgi(wsi) (0) +#endif + +enum { + LWS_HP_RET_BAIL_OK, + LWS_HP_RET_BAIL_DIE, + LWS_HP_RET_USER_SERVICE, + + LWS_HPI_RET_WSI_ALREADY_DIED, /* we closed it */ + LWS_HPI_RET_HANDLED, /* no probs */ + LWS_HPI_RET_PLEASE_CLOSE_ME, /* close it for us */ + + LWS_UPG_RET_DONE, + LWS_UPG_RET_CONTINUE, + LWS_UPG_RET_BAIL +}; diff --git a/thirdparty/libwebsockets/roles/raw/ops-raw.c b/thirdparty/libwebsockets/roles/raw/ops-raw.c new file mode 100644 index 0000000000..68b52bded6 --- /dev/null +++ b/thirdparty/libwebsockets/roles/raw/ops-raw.c @@ -0,0 +1,223 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +static int +rops_handle_POLLIN_raw_skt(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_tokens ebuf; + int n, buffered; + + /* pending truncated sends have uber priority */ + + if (wsi->trunc_len) { + if (!(pollfd->revents & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, + wsi->trunc_len) < 0) + goto fail; + /* + * we can't afford to allow input processing to send + * something new, so spin around he event loop until + * he doesn't have any partials + */ + return LWS_HPI_RET_HANDLED; + } + + if ((pollfd->revents & pollfd->events & LWS_POLLIN) && + /* any tunnel has to have been established... */ + lwsi_state(wsi) != LRS_SSL_ACK_PENDING && + !(wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT))) { + + buffered = lws_buflist_aware_read(pt, wsi, &ebuf); + switch (ebuf.len) { + case 0: + lwsl_info("%s: read 0 len\n", __func__); + wsi->seen_zero_length_recv = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + + /* + * we need to go to fail here, since it's the only + * chance we get to understand that the socket has + * closed + */ + // goto try_pollout; + goto fail; + + case LWS_SSL_CAPABLE_ERROR: + goto fail; + case LWS_SSL_CAPABLE_MORE_SERVICE: + goto try_pollout; + } + + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_RX, + wsi->user_space, ebuf.token, + ebuf.len); + if (n < 0) { + lwsl_info("LWS_CALLBACK_RAW_RX_fail\n"); + goto fail; + } + + if (lws_buflist_aware_consume(wsi, &ebuf, ebuf.len, buffered)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } else + if (wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT)) + /* we balanced the last favouring of pollin */ + wsi->favoured_pollin = 0; + +try_pollout: + + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & LWS_POLLOUT)) + return LWS_HPI_RET_HANDLED; + + /* one shot */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_notice("%s a\n", __func__); + goto fail; + } + + /* clear back-to-back write detection */ + wsi->could_have_pending = 0; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + + return LWS_HPI_RET_HANDLED; + +fail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "raw svc fail"); + + return LWS_HPI_RET_WSI_ALREADY_DIED; +} + + +static int +rops_handle_POLLIN_raw_file(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + int n; + + if (pollfd->revents & LWS_POLLOUT) { + n = lws_callback_as_writeable(wsi); + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return LWS_HPI_RET_WSI_ALREADY_DIED; + } + if (n) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + if (pollfd->revents & LWS_POLLIN) { + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_RX_FILE, + wsi->user_space, NULL, 0)) { + lwsl_debug("raw rx callback closed it\n"); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + } + + if (pollfd->revents & LWS_POLLHUP) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + + return LWS_HPI_RET_HANDLED; +} + + +struct lws_role_ops role_ops_raw_skt = { + /* role name */ "raw-skt", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_raw_skt, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { LWS_CALLBACK_RAW_WRITEABLE, 0 }, + /* close cb clnt, srv */ { LWS_CALLBACK_RAW_CLOSE, 0 }, + /* file_handle */ 0, +}; + + + +struct lws_role_ops role_ops_raw_file = { + /* role name */ "raw-file", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ NULL, + /* destroy_vhost */ NULL, + /* periodic_checks */ NULL, + /* service_flag_pending */ NULL, + /* handle_POLLIN */ rops_handle_POLLIN_raw_file, + /* handle_POLLOUT */ NULL, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ NULL, + /* tx_credit */ NULL, + /* write_role_protocol */ NULL, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ NULL, + /* close_role */ NULL, + /* close_kill_connection */ NULL, + /* destroy_role */ NULL, + /* writeable cb clnt, srv */ { LWS_CALLBACK_RAW_WRITEABLE_FILE, 0 }, + /* close cb clnt, srv */ { LWS_CALLBACK_RAW_CLOSE_FILE, 0 }, + /* file_handle */ 1, +}; diff --git a/thirdparty/lws/client/client-parser.c b/thirdparty/libwebsockets/roles/ws/client-parser-ws.c index 0e42dac362..aa561ce034 100644 --- a/thirdparty/lws/client/client-parser.c +++ b/thirdparty/libwebsockets/roles/ws/client-parser-ws.c @@ -19,31 +19,38 @@ * MA 02110-1301 USA */ -#include "private-libwebsockets.h" +#include "core/private.h" /* - * parsers.c: lws_rx_sm() needs to be roughly kept in + * parsers.c: lws_ws_rx_sm() needs to be roughly kept in * sync with changes here, esp related to ext draining */ -int lws_client_rx_sm(struct lws *wsi, unsigned char c) +int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c) { int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; - int handled, n, m, rx_draining_ext = 0; + int handled, m; unsigned short close_code; - struct lws_tokens eff_buf; + struct lws_tokens ebuf; unsigned char *pp; +#if !defined(LWS_WITHOUT_EXTENSIONS) + int rx_draining_ext = 0, n; +#endif + + ebuf.token = NULL; + ebuf.len = 0; - if (wsi->u.ws.rx_draining_ext) { +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { assert(!c); - eff_buf.token = NULL; - eff_buf.token_len = 0; + lws_remove_wsi_from_draining_ext_list(wsi); rx_draining_ext = 1; lwsl_debug("%s: doing draining flow\n", __func__); goto drain_extension; } +#endif if (wsi->socket_is_permanently_unusable) return -1; @@ -51,35 +58,38 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) switch (wsi->lws_rx_parse_state) { case LWS_RXPS_NEW: /* control frames (PING) may interrupt checkable sequences */ - wsi->u.ws.defeat_check_utf8 = 0; + wsi->ws->defeat_check_utf8 = 0; - switch (wsi->ietf_spec_revision) { + switch (wsi->ws->ietf_spec_revision) { case 13: - wsi->u.ws.opcode = c & 0xf; + wsi->ws->opcode = c & 0xf; /* revisit if an extension wants them... */ - switch (wsi->u.ws.opcode) { + switch (wsi->ws->opcode) { case LWSWSOPC_TEXT_FRAME: - wsi->u.ws.rsv_first_msg = (c & 0x70); - wsi->u.ws.continuation_possible = 1; - wsi->u.ws.check_utf8 = lws_check_opt( + wsi->ws->rsv_first_msg = (c & 0x70); + wsi->ws->continuation_possible = 1; + wsi->ws->check_utf8 = lws_check_opt( wsi->context->options, LWS_SERVER_OPTION_VALIDATE_UTF8); - wsi->u.ws.utf8 = 0; + wsi->ws->utf8 = 0; + wsi->ws->first_fragment = 1; break; case LWSWSOPC_BINARY_FRAME: - wsi->u.ws.rsv_first_msg = (c & 0x70); - wsi->u.ws.check_utf8 = 0; - wsi->u.ws.continuation_possible = 1; + wsi->ws->rsv_first_msg = (c & 0x70); + wsi->ws->check_utf8 = 0; + wsi->ws->continuation_possible = 1; + wsi->ws->first_fragment = 1; break; case LWSWSOPC_CONTINUATION: - if (!wsi->u.ws.continuation_possible) { + if (!wsi->ws->continuation_possible) { lwsl_info("disordered continuation\n"); return -1; } + wsi->ws->first_fragment = 0; break; case LWSWSOPC_CLOSE: - wsi->u.ws.check_utf8 = 0; - wsi->u.ws.utf8 = 0; + wsi->ws->check_utf8 = 0; + wsi->ws->utf8 = 0; break; case 3: case 4: @@ -94,45 +104,45 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) lwsl_info("illegal opcode\n"); return -1; default: - wsi->u.ws.defeat_check_utf8 = 1; + wsi->ws->defeat_check_utf8 = 1; break; } - wsi->u.ws.rsv = (c & 0x70); + wsi->ws->rsv = (c & 0x70); /* revisit if an extension wants them... */ if ( -#ifndef LWS_NO_EXTENSIONS - !wsi->count_act_ext && +#if !defined(LWS_WITHOUT_EXTENSIONS) + !wsi->ws->count_act_ext && #endif - wsi->u.ws.rsv) { + wsi->ws->rsv) { lwsl_info("illegal rsv bits set\n"); return -1; } - wsi->u.ws.final = !!((c >> 7) & 1); + wsi->ws->final = !!((c >> 7) & 1); lwsl_ext("%s: This RX frame Final %d\n", __func__, - wsi->u.ws.final); + wsi->ws->final); - if (wsi->u.ws.owed_a_fin && - (wsi->u.ws.opcode == LWSWSOPC_TEXT_FRAME || - wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME)) { + if (wsi->ws->owed_a_fin && + (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME || + wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) { lwsl_info("hey you owed us a FIN\n"); return -1; } - if ((!(wsi->u.ws.opcode & 8)) && wsi->u.ws.final) { - wsi->u.ws.continuation_possible = 0; - wsi->u.ws.owed_a_fin = 0; + if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) { + wsi->ws->continuation_possible = 0; + wsi->ws->owed_a_fin = 0; } - if ((wsi->u.ws.opcode & 8) && !wsi->u.ws.final) { + if ((wsi->ws->opcode & 8) && !wsi->ws->final) { lwsl_info("control msg can't be fragmented\n"); return -1; } - if (!wsi->u.ws.final) - wsi->u.ws.owed_a_fin = 1; + if (!wsi->ws->final) + wsi->ws->owed_a_fin = 1; - switch (wsi->u.ws.opcode) { + switch (wsi->ws->opcode) { case LWSWSOPC_TEXT_FRAME: case LWSWSOPC_BINARY_FRAME: - wsi->u.ws.frame_is_binary = wsi->u.ws.opcode == + wsi->ws->frame_is_binary = wsi->ws->opcode == LWSWSOPC_BINARY_FRAME; break; } @@ -141,38 +151,38 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) default: lwsl_err("unknown spec version %02d\n", - wsi->ietf_spec_revision); + wsi->ws->ietf_spec_revision); break; } break; case LWS_RXPS_04_FRAME_HDR_LEN: - wsi->u.ws.this_frame_masked = !!(c & 0x80); + wsi->ws->this_frame_masked = !!(c & 0x80); switch (c & 0x7f) { case 126: /* control frames are not allowed to have big lengths */ - if (wsi->u.ws.opcode & 8) + if (wsi->ws->opcode & 8) goto illegal_ctl_length; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; break; case 127: /* control frames are not allowed to have big lengths */ - if (wsi->u.ws.opcode & 8) + if (wsi->ws->opcode & 8) goto illegal_ctl_length; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; break; default: - wsi->u.ws.rx_packet_length = c; - if (wsi->u.ws.this_frame_masked) + wsi->ws->rx_packet_length = c & 0x7f; + if (wsi->ws->this_frame_masked) wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1; else { - if (c) + if (wsi->ws->rx_packet_length) { wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - else { + LWS_RXPS_WS_FRAME_PAYLOAD; + } else { wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; } @@ -182,18 +192,18 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) break; case LWS_RXPS_04_FRAME_HDR_LEN16_2: - wsi->u.ws.rx_packet_length = c << 8; + wsi->ws->rx_packet_length = c << 8; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; break; case LWS_RXPS_04_FRAME_HDR_LEN16_1: - wsi->u.ws.rx_packet_length |= c; - if (wsi->u.ws.this_frame_masked) + wsi->ws->rx_packet_length |= c; + if (wsi->ws->this_frame_masked) wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1; else { - if (wsi->u.ws.rx_packet_length) + if (wsi->ws->rx_packet_length) wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + LWS_RXPS_WS_FRAME_PAYLOAD; else { wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; @@ -208,58 +218,58 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) return -1; } #if defined __LP64__ - wsi->u.ws.rx_packet_length = ((size_t)c) << 56; + wsi->ws->rx_packet_length = ((size_t)c) << 56; #else - wsi->u.ws.rx_packet_length = 0; + wsi->ws->rx_packet_length = 0; #endif wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; break; case LWS_RXPS_04_FRAME_HDR_LEN64_7: #if defined __LP64__ - wsi->u.ws.rx_packet_length |= ((size_t)c) << 48; + wsi->ws->rx_packet_length |= ((size_t)c) << 48; #endif wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; break; case LWS_RXPS_04_FRAME_HDR_LEN64_6: #if defined __LP64__ - wsi->u.ws.rx_packet_length |= ((size_t)c) << 40; + wsi->ws->rx_packet_length |= ((size_t)c) << 40; #endif wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; break; case LWS_RXPS_04_FRAME_HDR_LEN64_5: #if defined __LP64__ - wsi->u.ws.rx_packet_length |= ((size_t)c) << 32; + wsi->ws->rx_packet_length |= ((size_t)c) << 32; #endif wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; break; case LWS_RXPS_04_FRAME_HDR_LEN64_4: - wsi->u.ws.rx_packet_length |= ((size_t)c) << 24; + wsi->ws->rx_packet_length |= ((size_t)c) << 24; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; break; case LWS_RXPS_04_FRAME_HDR_LEN64_3: - wsi->u.ws.rx_packet_length |= ((size_t)c) << 16; + wsi->ws->rx_packet_length |= ((size_t)c) << 16; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; break; case LWS_RXPS_04_FRAME_HDR_LEN64_2: - wsi->u.ws.rx_packet_length |= ((size_t)c) << 8; + wsi->ws->rx_packet_length |= ((size_t)c) << 8; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; break; case LWS_RXPS_04_FRAME_HDR_LEN64_1: - wsi->u.ws.rx_packet_length |= (size_t)c; - if (wsi->u.ws.this_frame_masked) + wsi->ws->rx_packet_length |= (size_t)c; + if (wsi->ws->this_frame_masked) wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1; else { - if (wsi->u.ws.rx_packet_length) + if (wsi->ws->rx_packet_length) wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + LWS_RXPS_WS_FRAME_PAYLOAD; else { wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; @@ -268,53 +278,53 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) break; case LWS_RXPS_07_COLLECT_FRAME_KEY_1: - wsi->u.ws.mask[0] = c; + wsi->ws->mask[0] = c; if (c) - wsi->u.ws.all_zero_nonce = 0; + wsi->ws->all_zero_nonce = 0; wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; break; case LWS_RXPS_07_COLLECT_FRAME_KEY_2: - wsi->u.ws.mask[1] = c; + wsi->ws->mask[1] = c; if (c) - wsi->u.ws.all_zero_nonce = 0; + wsi->ws->all_zero_nonce = 0; wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; break; case LWS_RXPS_07_COLLECT_FRAME_KEY_3: - wsi->u.ws.mask[2] = c; + wsi->ws->mask[2] = c; if (c) - wsi->u.ws.all_zero_nonce = 0; + wsi->ws->all_zero_nonce = 0; wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; break; case LWS_RXPS_07_COLLECT_FRAME_KEY_4: - wsi->u.ws.mask[3] = c; + wsi->ws->mask[3] = c; if (c) - wsi->u.ws.all_zero_nonce = 0; + wsi->ws->all_zero_nonce = 0; - if (wsi->u.ws.rx_packet_length) + if (wsi->ws->rx_packet_length) wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + LWS_RXPS_WS_FRAME_PAYLOAD; else { wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; } break; - case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: - - assert(wsi->u.ws.rx_ubuf); + case LWS_RXPS_WS_FRAME_PAYLOAD: - if (wsi->u.ws.rx_draining_ext) + assert(wsi->ws->rx_ubuf); +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) goto drain_extension; +#endif + if (wsi->ws->this_frame_masked && !wsi->ws->all_zero_nonce) + c ^= wsi->ws->mask[(wsi->ws->mask_idx++) & 3]; - if (wsi->u.ws.this_frame_masked && !wsi->u.ws.all_zero_nonce) - c ^= wsi->u.ws.mask[(wsi->u.ws.mask_idx++) & 3]; - - wsi->u.ws.rx_ubuf[LWS_PRE + (wsi->u.ws.rx_ubuf_head++)] = c; + wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] = c; - if (--wsi->u.ws.rx_packet_length == 0) { + if (--wsi->ws->rx_packet_length == 0) { /* spill because we have the whole frame */ wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; @@ -325,11 +335,11 @@ int lws_client_rx_sm(struct lws *wsi, unsigned char c) * supposed to default to context->pt_serv_buf_size */ if (!wsi->protocol->rx_buffer_size && - wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size) + wsi->ws->rx_ubuf_head != wsi->context->pt_serv_buf_size) break; if (wsi->protocol->rx_buffer_size && - wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size) + wsi->ws->rx_ubuf_head != wsi->protocol->rx_buffer_size) break; /* spill because we filled our rx buffer */ @@ -342,18 +352,18 @@ spill: * layer? If so service it and hide it from the user callback */ - switch (wsi->u.ws.opcode) { + switch (wsi->ws->opcode) { case LWSWSOPC_CLOSE: - pp = (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE]; + pp = (unsigned char *)&wsi->ws->rx_ubuf[LWS_PRE]; if (lws_check_opt(wsi->context->options, LWS_SERVER_OPTION_VALIDATE_UTF8) && - wsi->u.ws.rx_ubuf_head > 2 && - lws_check_utf8(&wsi->u.ws.utf8, pp + 2, - wsi->u.ws.rx_ubuf_head - 2)) + wsi->ws->rx_ubuf_head > 2 && + lws_check_utf8(&wsi->ws->utf8, pp + 2, + wsi->ws->rx_ubuf_head - 2)) goto utf8_fail; - /* is this an acknowledgement of our close? */ - if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { + /* is this an acknowledgment of our close? */ + if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) { /* * fine he has told us he is closing too, let's * finish our close @@ -363,8 +373,8 @@ spill: } lwsl_parser("client sees server close len = %d\n", - wsi->u.ws.rx_ubuf_head); - if (wsi->u.ws.rx_ubuf_head >= 2) { + wsi->ws->rx_ubuf_head); + if (wsi->ws->rx_ubuf_head >= 2) { close_code = (pp[0] << 8) | pp[1]; if (close_code < 1000 || close_code == 1004 || @@ -384,39 +394,32 @@ spill: wsi->protocol->callback, wsi, LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, wsi->user_space, pp, - wsi->u.ws.rx_ubuf_head)) + wsi->ws->rx_ubuf_head)) return -1; - if (lws_partial_buffered(wsi)) - /* - * if we're in the middle of something, - * we can't do a normal close response and - * have to just close our end. - */ - wsi->socket_is_permanently_unusable = 1; - else - /* - * parrot the close packet payload back - * we do not care about how it went, we are closing - * immediately afterwards - */ - lws_write(wsi, (unsigned char *) - &wsi->u.ws.rx_ubuf[LWS_PRE], - wsi->u.ws.rx_ubuf_head, - LWS_WRITE_CLOSE); - wsi->state = LWSS_RETURNED_CLOSE_ALREADY; - /* close the connection */ - return -1; + memcpy(wsi->ws->ping_payload_buf + LWS_PRE, pp, + wsi->ws->rx_ubuf_head); + wsi->ws->close_in_ping_buffer_len = wsi->ws->rx_ubuf_head; + + lwsl_info("%s: scheduling return close as ack\n", __func__); + __lws_change_pollfd(wsi, LWS_POLLIN, 0); + lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 3); + wsi->waiting_to_send_close_frame = 1; + wsi->close_needs_ack = 0; + lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE); + lws_callback_on_writable(wsi); + handled = 1; + break; case LWSWSOPC_PING: lwsl_info("received %d byte ping, sending pong\n", - wsi->u.ws.rx_ubuf_head); + wsi->ws->rx_ubuf_head); /* he set a close reason on this guy, ignore PING */ - if (wsi->u.ws.close_in_ping_buffer_len) + if (wsi->ws->close_in_ping_buffer_len) goto ping_drop; - if (wsi->u.ws.ping_pending_flag) { + if (wsi->ws->ping_pending_flag) { /* * there is already a pending ping payload * we should just log and drop @@ -426,30 +429,30 @@ spill: } /* control packets can only be < 128 bytes long */ - if (wsi->u.ws.rx_ubuf_head > 128 - 3) { + if (wsi->ws->rx_ubuf_head > 128 - 3) { lwsl_parser("DROP PING payload too large\n"); goto ping_drop; } /* stash the pong payload */ - memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE, - &wsi->u.ws.rx_ubuf[LWS_PRE], - wsi->u.ws.rx_ubuf_head); + memcpy(wsi->ws->ping_payload_buf + LWS_PRE, + &wsi->ws->rx_ubuf[LWS_PRE], + wsi->ws->rx_ubuf_head); - wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head; - wsi->u.ws.ping_pending_flag = 1; + wsi->ws->ping_payload_len = wsi->ws->rx_ubuf_head; + wsi->ws->ping_pending_flag = 1; /* get it sent as soon as possible */ lws_callback_on_writable(wsi); ping_drop: - wsi->u.ws.rx_ubuf_head = 0; + wsi->ws->rx_ubuf_head = 0; handled = 1; break; case LWSWSOPC_PONG: lwsl_info("client receied pong\n"); - lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE], - wsi->u.ws.rx_ubuf_head); + lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE], + wsi->ws->rx_ubuf_head); if (wsi->pending_timeout == PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { @@ -467,30 +470,11 @@ ping_drop: break; default: + /* not handled or failed */ + lwsl_ext("Unhandled ext opc 0x%x\n", wsi->ws->opcode); + wsi->ws->rx_ubuf_head = 0; - lwsl_parser("Reserved opc 0x%2X\n", wsi->u.ws.opcode); - - /* - * It's something special we can't understand here. - * Pass the payload up to the extension's parsing - * state machine. - */ - - eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; - eff_buf.token_len = wsi->u.ws.rx_ubuf_head; - - if (lws_ext_cb_active(wsi, - LWS_EXT_CB_EXTENDED_PAYLOAD_RX, - &eff_buf, 0) <= 0) { - /* not handled or failed */ - lwsl_ext("Unhandled ext opc 0x%x\n", - wsi->u.ws.opcode); - wsi->u.ws.rx_ubuf_head = 0; - - return 0; - } - handled = 1; - break; + return -1; } /* @@ -501,53 +485,71 @@ ping_drop: if (handled) goto already_done; - eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; - eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + ebuf.token = &wsi->ws->rx_ubuf[LWS_PRE]; + ebuf.len = wsi->ws->rx_ubuf_head; - if (wsi->u.ws.opcode == LWSWSOPC_PONG && !eff_buf.token_len) + if (wsi->ws->opcode == LWSWSOPC_PONG && !ebuf.len) goto already_done; +#if !defined(LWS_WITHOUT_EXTENSIONS) drain_extension: - lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); + lwsl_ext("%s: passing %d to ext\n", __func__, ebuf.len); - n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0); lwsl_ext("Ext RX returned %d\n", n); if (n < 0) { wsi->socket_is_permanently_unusable = 1; return -1; } +#endif + lwsl_debug("post inflate ebuf len %d\n", ebuf.len); - lwsl_ext("post inflate eff_buf len %d\n", eff_buf.token_len); - - if (rx_draining_ext && !eff_buf.token_len) { + if ( +#if !defined(LWS_WITHOUT_EXTENSIONS) + rx_draining_ext && +#endif + !ebuf.len) { lwsl_debug(" --- ending drain on 0 read result\n"); goto already_done; } - if (wsi->u.ws.check_utf8 && !wsi->u.ws.defeat_check_utf8) { - if (lws_check_utf8(&wsi->u.ws.utf8, - (unsigned char *)eff_buf.token, - eff_buf.token_len)) + if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) { + if (lws_check_utf8(&wsi->ws->utf8, + (unsigned char *)ebuf.token, + ebuf.len)) { + lws_close_reason(wsi, + LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"bad utf8", 8); goto utf8_fail; + } /* we are ending partway through utf-8 character? */ - if (!wsi->u.ws.rx_packet_length && wsi->u.ws.final && - wsi->u.ws.utf8 && !n) { + if (!wsi->ws->rx_packet_length && wsi->ws->final && + wsi->ws->utf8 +#if !defined(LWS_WITHOUT_EXTENSIONS) + && !n +#endif + ) { lwsl_info("FINAL utf8 error\n"); + lws_close_reason(wsi, + LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"partial utf8", 12); utf8_fail: lwsl_info("utf8 error\n"); + lwsl_hexdump_info(ebuf.token, ebuf.len); + return -1; } } - if (eff_buf.token_len < 0 && + if (ebuf.len < 0 && callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG) goto already_done; - if (!eff_buf.token) + if (!ebuf.token) goto already_done; - eff_buf.token[eff_buf.token_len] = '\0'; + ebuf.token[ebuf.len] = '\0'; if (!wsi->protocol->callback) goto already_done; @@ -555,7 +557,12 @@ utf8_fail: if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG) lwsl_info("Client doing pong callback\n"); - if (n && eff_buf.token_len) + if ( + /* coverity says dead code otherwise */ +#if !defined(LWS_WITHOUT_EXTENSIONS) + n && +#endif + ebuf.len) /* extension had more... main loop will come back * we want callback to be done with this set, if so, * because lws_is_final() hides it was final until the @@ -565,21 +572,26 @@ utf8_fail: else lws_remove_wsi_from_draining_ext_list(wsi); - if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || - wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || - wsi->state == LWSS_AWAITING_CLOSE_ACK) + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE || + lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) goto already_done; m = wsi->protocol->callback(wsi, (enum lws_callback_reasons)callback_action, - wsi->user_space, eff_buf.token, eff_buf.token_len); + wsi->user_space, ebuf.token, ebuf.len); + + wsi->ws->first_fragment = 0; + + // lwsl_notice("%s: bulk ws rx: input used %d, output %d\n", + // __func__, wsi->ws->rx_ubuf_head, ebuf.len); /* if user code wants to close, let caller know */ if (m) return 1; already_done: - wsi->u.ws.rx_ubuf_head = 0; + wsi->ws->rx_ubuf_head = 0; break; default: lwsl_err("client rx illegal state\n"); diff --git a/thirdparty/libwebsockets/roles/ws/client-ws.c b/thirdparty/libwebsockets/roles/ws/client-ws.c new file mode 100644 index 0000000000..fd6cf42551 --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/client-ws.c @@ -0,0 +1,629 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +/* + * In-place str to lower case + */ + +static void +strtolower(char *s) +{ + while (*s) { +#ifdef LWS_PLAT_OPTEE + int tolower_optee(int c); + *s = tolower_optee((int)*s); +#else + *s = tolower((int)*s); +#endif + s++; + } +} + +int +lws_create_client_ws_object(struct lws_client_connect_info *i, struct lws *wsi) +{ + int v = SPEC_LATEST_SUPPORTED; + + /* allocate the ws struct for the wsi */ + wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct"); + if (!wsi->ws) { + lwsl_notice("OOM\n"); + return 1; + } + + /* -1 means just use latest supported */ + if (i->ietf_version_or_minus_one != -1 && + i->ietf_version_or_minus_one) + v = i->ietf_version_or_minus_one; + + wsi->ws->ietf_spec_revision = v; + + return 0; +} + +#if !defined(LWS_NO_CLIENT) +int +lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) +{ + if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) && + (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) && + (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) && + !lwsi_role_client(wsi)) + return 0; + + // lwsl_notice("%s: hs client gets %d in\n", __func__, (int)len); + + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (lws_is_flowcontrolled(wsi)) { + //lwsl_notice("%s: caching %ld\n", __func__, (long)len); + lws_rxflow_cache(wsi, *buf, 0, (int)len); + *buf += len; + return 0; + } +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { + int m; + + //lwsl_notice("%s: draining ext\n", __func__); + if (lwsi_role_client(wsi)) + m = lws_ws_client_rx_sm(wsi, 0); + else + m = lws_ws_rx_sm(wsi, 0, 0); + if (m < 0) + return -1; + continue; + } +#endif + /* caller will account for buflist usage */ + + if (lws_ws_client_rx_sm(wsi, *(*buf)++)) { + lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n", + __func__, (int)len); + return -1; + } + len--; + } + // lwsl_notice("%s: finished with %ld\n", __func__, (long)len); + + return 0; +} +#endif + +char * +lws_generate_client_ws_handshake(struct lws *wsi, char *p) +{ + char buf[128], hash[20], key_b64[40]; + int n; +#if !defined(LWS_WITHOUT_EXTENSIONS) + const struct lws_extension *ext; + int ext_count = 0; +#endif + + /* + * create the random key + */ + n = lws_get_random(wsi->context, hash, 16); + if (n != 16) { + lwsl_err("Unable to read from random dev %s\n", + SYSTEM_RANDOM_FILEPATH); + return NULL; + } + + lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); + + p += sprintf(p, "Upgrade: websocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + strcpy(p, key_b64); + p += strlen(key_b64); + p += sprintf(p, "\x0d\x0a"); + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); + + /* tell the server what extensions we could support */ + +#if !defined(LWS_WITHOUT_EXTENSIONS) + ext = wsi->vhost->ws.extensions; + while (ext && ext->callback) { + + n = wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, + wsi->user_space, (char *)ext->name, 0); + + /* + * zero return from callback means go ahead and allow + * the extension, it's what we get if the callback is + * unhandled + */ + + if (n) { + ext++; + continue; + } + + /* apply it */ + + if (ext_count) + *p++ = ','; + else + p += sprintf(p, "Sec-WebSocket-Extensions: "); + p += sprintf(p, "%s", ext->client_offer); + ext_count++; + + ext++; + } + if (ext_count) + p += sprintf(p, "\x0d\x0a"); +#endif + + if (wsi->ws->ietf_spec_revision) + p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", + wsi->ws->ietf_spec_revision); + + /* prepare the expected server accept response */ + + key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ + n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + key_b64); + + lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); + + lws_b64_encode_string(hash, 20, + wsi->http.ah->initial_handshake_hash_base64, + sizeof(wsi->http.ah->initial_handshake_hash_base64)); + + return p; +} + +int +lws_client_ws_upgrade(struct lws *wsi, const char **cce) +{ + int n, len, okay = 0; + struct lws_context *context = wsi->context; + const char *pc; + char *p; +#if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *sb = (char *)&pt->serv_buf[0]; + const struct lws_ext_options *opts; + const struct lws_extension *ext; + char ext_name[128]; + const char *c, *a; + char ignore; + int more = 1; +#endif + + if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */ + lwsl_warn("%s: client ws-over-h2 upgrade not supported yet\n", + __func__); + *cce = "HS: h2 / ws upgrade unsupported"; + goto bail3; + } + + if (wsi->http.ah->http_response == 401) { + lwsl_warn( + "lws_client_handshake: got bad HTTP response '%d'\n", + wsi->http.ah->http_response); + *cce = "HS: ws upgrade unauthorized"; + goto bail3; + } + + if (wsi->http.ah->http_response != 101) { + lwsl_warn( + "lws_client_handshake: got bad HTTP response '%d'\n", + wsi->http.ah->http_response); + *cce = "HS: ws upgrade response not 101"; + goto bail3; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { + lwsl_info("no ACCEPT\n"); + *cce = "HS: ACCEPT missing"; + goto bail3; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); + if (!p) { + lwsl_info("no UPGRADE\n"); + *cce = "HS: UPGRADE missing"; + goto bail3; + } + strtolower(p); + if (strcmp(p, "websocket")) { + lwsl_warn( + "lws_client_handshake: got bad Upgrade header '%s'\n", p); + *cce = "HS: Upgrade to something other than websocket"; + goto bail3; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); + if (!p) { + lwsl_info("no Connection hdr\n"); + *cce = "HS: CONNECTION missing"; + goto bail3; + } + strtolower(p); + if (strcmp(p, "upgrade")) { + lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); + *cce = "HS: UPGRADE malformed"; + goto bail3; + } + + pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + if (!pc) { + lwsl_parser("lws_client_int_s_hs: no protocol list\n"); + } else + lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); + + /* + * confirm the protocol the server wants to talk was in the list + * of protocols we offered + */ + + len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + if (!len) { + lwsl_info("%s: WSI_TOKEN_PROTOCOL is null\n", __func__); + /* + * no protocol name to work from, + * default to first protocol + */ + n = 0; + wsi->protocol = &wsi->vhost->protocols[0]; + goto check_extensions; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); + len = (int)strlen(p); + + while (pc && *pc && !okay) { + if (!strncmp(pc, p, len) && + (pc[len] == ',' || pc[len] == '\0')) { + okay = 1; + continue; + } + while (*pc && *pc++ != ',') + ; + while (*pc && *pc == ' ') + pc++; + } + + if (!okay) { + lwsl_info("%s: got bad protocol %s\n", __func__, p); + *cce = "HS: PROTOCOL malformed"; + goto bail2; + } + + /* + * identify the selected protocol struct and set it + */ + n = 0; + /* keep client connection pre-bound protocol */ + if (!lwsi_role_client(wsi)) + wsi->protocol = NULL; + + while (wsi->vhost->protocols[n].callback) { + if (!wsi->protocol && + strcmp(p, wsi->vhost->protocols[n].name) == 0) { + wsi->protocol = &wsi->vhost->protocols[n]; + break; + } + n++; + } + + if (!wsi->vhost->protocols[n].callback) { /* no match */ + /* if server, that's already fatal */ + if (!lwsi_role_client(wsi)) { + lwsl_info("%s: fail protocol %s\n", __func__, p); + *cce = "HS: Cannot match protocol"; + goto bail2; + } + + /* for client, find the index of our pre-bound protocol */ + + n = 0; + while (wsi->vhost->protocols[n].callback) { + if (wsi->protocol && strcmp(wsi->protocol->name, + wsi->vhost->protocols[n].name) == 0) { + wsi->protocol = &wsi->vhost->protocols[n]; + break; + } + n++; + } + + if (!wsi->vhost->protocols[n].callback) { + if (wsi->protocol) + lwsl_err("Failed to match protocol %s\n", + wsi->protocol->name); + else + lwsl_err("No protocol on client\n"); + goto bail2; + } + } + + lwsl_debug("Selected protocol %s\n", wsi->protocol->name); + +check_extensions: + /* + * stitch protocol choice into the vh protocol linked list + * We always insert ourselves at the start of the list + * + * X <-> B + * X <-> pAn <-> pB + */ + + lws_vhost_lock(wsi->vhost); + + wsi->same_vh_protocol_prev = /* guy who points to us */ + &wsi->vhost->same_vh_protocol_list[n]; + wsi->same_vh_protocol_next = /* old first guy is our next */ + wsi->vhost->same_vh_protocol_list[n]; + /* we become the new first guy */ + wsi->vhost->same_vh_protocol_list[n] = wsi; + + if (wsi->same_vh_protocol_next) + /* old first guy points back to us now */ + wsi->same_vh_protocol_next->same_vh_protocol_prev = + &wsi->same_vh_protocol_next; + wsi->on_same_vh_list = 1; + + lws_vhost_unlock(wsi->vhost); + +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* instantiate the accepted extensions */ + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { + lwsl_ext("no client extensions allowed by server\n"); + goto check_accept; + } + + /* + * break down the list of server accepted extensions + * and go through matching them or identifying bogons + */ + + if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, + WSI_TOKEN_EXTENSIONS) < 0) { + lwsl_warn("ext list from server failed to copy\n"); + *cce = "HS: EXT: list too big"; + goto bail2; + } + + c = sb; + n = 0; + ignore = 0; + a = NULL; + while (more) { + + if (*c && (*c != ',' && *c != '\t')) { + if (*c == ';') { + ignore = 1; + if (!a) + a = c + 1; + } + if (ignore || *c == ' ') { + c++; + continue; + } + + ext_name[n] = *c++; + if (n < (int)sizeof(ext_name) - 1) + n++; + continue; + } + ext_name[n] = '\0'; + ignore = 0; + if (!*c) + more = 0; + else { + c++; + if (!n) + continue; + } + + /* check we actually support it */ + + lwsl_notice("checking client ext %s\n", ext_name); + + n = 0; + ext = wsi->vhost->ws.extensions; + while (ext && ext->callback) { + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + n = 1; + lwsl_notice("instantiating client ext %s\n", ext_name); + + /* instantiate the extension on this conn */ + + wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext; + + /* allow him to construct his ext instance */ + + if (ext->callback(lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_CLIENT_CONSTRUCT, + (void *)&wsi->ws->act_ext_user[wsi->ws->count_act_ext], + (void *)&opts, 0)) { + lwsl_info(" ext %s failed construction\n", + ext_name); + ext++; + continue; + } + + /* + * allow the user code to override ext defaults if it + * wants to + */ + ext_name[0] = '\0'; + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_WS_EXT_DEFAULTS, + (char *)ext->name, ext_name, + sizeof(ext_name))) { + *cce = "HS: EXT: failed setting defaults"; + goto bail2; + } + + if (ext_name[0] && + lws_ext_parse_options(ext, wsi, wsi->ws->act_ext_user[ + wsi->ws->count_act_ext], opts, ext_name, + (int)strlen(ext_name))) { + lwsl_err("%s: unable to parse user defaults '%s'", + __func__, ext_name); + *cce = "HS: EXT: failed parsing defaults"; + goto bail2; + } + + /* + * give the extension the server options + */ + if (a && lws_ext_parse_options(ext, wsi, + wsi->ws->act_ext_user[wsi->ws->count_act_ext], + opts, a, lws_ptr_diff(c, a))) { + lwsl_err("%s: unable to parse remote def '%s'", + __func__, a); + *cce = "HS: EXT: failed parsing options"; + goto bail2; + } + + if (ext->callback(lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_OPTION_CONFIRM, + wsi->ws->act_ext_user[wsi->ws->count_act_ext], + NULL, 0)) { + lwsl_err("%s: ext %s rejects server options %s", + __func__, ext->name, a); + *cce = "HS: EXT: Rejects server options"; + goto bail2; + } + + wsi->ws->count_act_ext++; + + ext++; + } + + if (n == 0) { + lwsl_warn("Unknown ext '%s'!\n", ext_name); + *cce = "HS: EXT: unknown ext"; + goto bail2; + } + + a = NULL; + n = 0; + } + +check_accept: +#endif + + /* + * Confirm his accept token is the one we precomputed + */ + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); + if (strcmp(p, wsi->http.ah->initial_handshake_hash_base64)) { + lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, + wsi->http.ah->initial_handshake_hash_base64); + *cce = "HS: Accept hash wrong"; + goto bail2; + } + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) { + lwsl_err("Problem allocating wsi user mem\n"); + *cce = "HS: OOM"; + goto bail2; + } + + /* + * we seem to be good to go, give client last chance to check + * headers and OK it + */ + if (wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, + wsi->user_space, NULL, 0)) { + *cce = "HS: Rejected by filter cb"; + goto bail2; + } + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* free up his parsing allocations */ + lws_header_table_detach(wsi, 0); + + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, + &role_ops_ws); + lws_restart_ws_ping_pong_timer(wsi); + + wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* + * create the frame buffer for this connection according to the + * size mentioned in the protocol definition. If 0 there, then + * use a big default for compatibility + */ + n = (int)wsi->protocol->rx_buffer_size; + if (!n) + n = context->pt_serv_buf_size; + n += LWS_PRE; + wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, + "client frame buffer"); + if (!wsi->ws->rx_ubuf) { + lwsl_err("Out of Mem allocating rx buffer %d\n", n); + *cce = "HS: OOM"; + goto bail2; + } + wsi->ws->rx_ubuf_alloc = n; + lwsl_info("Allocating client RX buffer %d\n", n); + +#if !defined(LWS_WITH_ESP32) + if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, + (const char *)&n, sizeof n)) { + lwsl_warn("Failed to set SNDBUF to %d", n); + *cce = "HS: SO_SNDBUF failed"; + goto bail3; + } +#endif + + lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); + + /* call him back to inform him he is up */ + + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi->user_space, NULL, 0)) { + *cce = "HS: Rejected at CLIENT_ESTABLISHED"; + goto bail3; + } + + return 0; + +bail3: + return 3; + +bail2: + return 2; +} diff --git a/thirdparty/libwebsockets/roles/ws/ops-ws.c b/thirdparty/libwebsockets/roles/ws/ops-ws.c new file mode 100644 index 0000000000..5ddaba9e18 --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/ops-ws.c @@ -0,0 +1,1992 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } + +/* + * client-parser.c: lws_ws_client_rx_sm() needs to be roughly kept in + * sync with changes here, esp related to ext draining + */ + +int +lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c) +{ + int callback_action = LWS_CALLBACK_RECEIVE; + int ret = 0; + unsigned short close_code; + struct lws_tokens ebuf; + unsigned char *pp; + int n = 0; +#if !defined(LWS_WITHOUT_EXTENSIONS) + int rx_draining_ext = 0; + int lin; +#endif + + ebuf.token = NULL; + ebuf.len = 0; + if (wsi->socket_is_permanently_unusable) + return -1; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { + ebuf.token = NULL; + ebuf.len = 0; + lws_remove_wsi_from_draining_ext_list(wsi); + rx_draining_ext = 1; + lwsl_debug("%s: doing draining flow\n", __func__); + + goto drain_extension; + } +#endif + switch (wsi->ws->ietf_spec_revision) { + case 13: + /* + * no prepended frame key any more + */ + wsi->ws->all_zero_nonce = 1; + goto handle_first; + + default: + lwsl_warn("lws_ws_rx_sm: unknown spec version %d\n", + wsi->ws->ietf_spec_revision); + break; + } + break; + case LWS_RXPS_04_mask_1: + wsi->ws->mask[1] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_04_mask_2; + break; + case LWS_RXPS_04_mask_2: + wsi->ws->mask[2] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_04_mask_3; + break; + case LWS_RXPS_04_mask_3: + wsi->ws->mask[3] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + + /* + * start from the zero'th byte in the XOR key buffer since + * this is the start of a frame with a new key + */ + + wsi->ws->mask_idx = 0; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1; + break; + + /* + * 04 logical framing from the spec (all this is masked when incoming + * and has to be unmasked) + * + * We ignore the possibility of extension data because we don't + * negotiate any extensions at the moment. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|R| Payload len | Extended payload length | + * |I|S|S|S| (4) |S| (7) | (16/63) | + * |N|V|V|V| |V| | (if payload len==126/127) | + * | |1|2|3| |4| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | | Extension data | + * +-------------------------------+ - - - - - - - - - - - - - - - + + * : : + * +---------------------------------------------------------------+ + * : Application data : + * +---------------------------------------------------------------+ + * + * We pass payload through to userland as soon as we get it, ignoring + * FIN. It's up to userland to buffer it up if it wants to see a + * whole unfragmented block of the original size (which may be up to + * 2^63 long!) + */ + + case LWS_RXPS_04_FRAME_HDR_1: +handle_first: + + wsi->ws->opcode = c & 0xf; + wsi->ws->rsv = c & 0x70; + wsi->ws->final = !!((c >> 7) & 1); + wsi->ws->defeat_check_utf8 = 0; + + if (((wsi->ws->opcode) & 8) && !wsi->ws->final) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, + (uint8_t *)"frag ctl", 8); + return -1; + } + + switch (wsi->ws->opcode) { + case LWSWSOPC_TEXT_FRAME: + wsi->ws->check_utf8 = lws_check_opt( + wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8); + /* fallthru */ + case LWSWSOPC_BINARY_FRAME: + if (wsi->ws->opcode == LWSWSOPC_BINARY_FRAME) + wsi->ws->check_utf8 = 0; + if (wsi->ws->continuation_possible) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad cont", 8); + return -1; + } + wsi->ws->rsv_first_msg = (c & 0x70); + wsi->ws->frame_is_binary = + wsi->ws->opcode == LWSWSOPC_BINARY_FRAME; + wsi->ws->first_fragment = 1; + wsi->ws->continuation_possible = !wsi->ws->final; + break; + case LWSWSOPC_CONTINUATION: + if (!wsi->ws->continuation_possible) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad cont", 8); + return -1; + } + break; + case LWSWSOPC_CLOSE: + wsi->ws->check_utf8 = 0; + wsi->ws->utf8 = 0; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 0xb: + case 0xc: + case 0xd: + case 0xe: + case 0xf: + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad opc", 7); + lwsl_info("illegal opcode\n"); + return -1; + } + + if (wsi->ws->owed_a_fin && + (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME || + wsi->ws->opcode == LWSWSOPC_BINARY_FRAME)) { + lwsl_info("hey you owed us a FIN\n"); + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (uint8_t *)"bad fin", 7); + return -1; + } + if ((!(wsi->ws->opcode & 8)) && wsi->ws->final) { + wsi->ws->continuation_possible = 0; + wsi->ws->owed_a_fin = 0; + } + + if (!wsi->ws->final) + wsi->ws->owed_a_fin = 1; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + if (wsi->ws->rsv && + ( +#if !defined(LWS_WITHOUT_EXTENSIONS) + !wsi->ws->count_act_ext || +#endif + (wsi->ws->rsv & ~0x40))) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, + (uint8_t *)"rsv bits", 8); + return -1; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN: + + wsi->ws->this_frame_masked = !!(c & 0x80); + + switch (c & 0x7f) { + case 126: + /* control frames are not allowed to have big lengths */ + if (wsi->ws->opcode & 8) + goto illegal_ctl_length; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; + break; + case 127: + /* control frames are not allowed to have big lengths */ + if (wsi->ws->opcode & 8) + goto illegal_ctl_length; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->ws->rx_packet_length = c & 0x7f; + + + if (wsi->ws->this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else + if (wsi->ws->rx_packet_length) { + wsi->lws_rx_parse_state = + LWS_RXPS_WS_FRAME_PAYLOAD; + } else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_2: + wsi->ws->rx_packet_length = c << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_1: + wsi->ws->rx_packet_length |= c; + if (wsi->ws->this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + wsi->lws_rx_parse_state = + LWS_RXPS_WS_FRAME_PAYLOAD; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_8: + if (c & 0x80) { + lwsl_warn("b63 of length must be zero\n"); + /* kill the connection */ + return -1; + } +#if defined __LP64__ + wsi->ws->rx_packet_length = ((size_t)c) << 56; +#else + wsi->ws->rx_packet_length = 0; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_7: +#if defined __LP64__ + wsi->ws->rx_packet_length |= ((size_t)c) << 48; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_6: +#if defined __LP64__ + wsi->ws->rx_packet_length |= ((size_t)c) << 40; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_5: +#if defined __LP64__ + wsi->ws->rx_packet_length |= ((size_t)c) << 32; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_4: + wsi->ws->rx_packet_length |= ((size_t)c) << 24; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_3: + wsi->ws->rx_packet_length |= ((size_t)c) << 16; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_2: + wsi->ws->rx_packet_length |= ((size_t)c) << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_1: + wsi->ws->rx_packet_length |= ((size_t)c); + if (wsi->ws->this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else + wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_1: + wsi->ws->mask[0] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_2: + wsi->ws->mask[1] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_3: + wsi->ws->mask[2] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_4: + wsi->ws->mask[3] = c; + if (c) + wsi->ws->all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_WS_FRAME_PAYLOAD; + wsi->ws->mask_idx = 0; + if (wsi->ws->rx_packet_length == 0) { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + + + case LWS_RXPS_WS_FRAME_PAYLOAD: + assert(wsi->ws->rx_ubuf); + + if (wsi->ws->rx_ubuf_head + LWS_PRE >= wsi->ws->rx_ubuf_alloc) { + lwsl_err("Attempted overflow \n"); + return -1; + } + if (!(already_processed & ALREADY_PROCESSED_IGNORE_CHAR)) { + if (wsi->ws->all_zero_nonce) + wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] = + c; + else + wsi->ws->rx_ubuf[LWS_PRE + (wsi->ws->rx_ubuf_head++)] = + c ^ wsi->ws->mask[(wsi->ws->mask_idx++) & 3]; + + --wsi->ws->rx_packet_length; + } + + if (!wsi->ws->rx_packet_length) { + lwsl_debug("%s: ws fragment length exhausted\n", __func__); + /* spill because we have the whole frame */ + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { + lwsl_debug("%s: UNTIL_EXHAUSTED draining\n", __func__); + goto drain_extension; + } +#endif + /* + * if there's no protocol max frame size given, we are + * supposed to default to context->pt_serv_buf_size + */ + if (!wsi->protocol->rx_buffer_size && + wsi->ws->rx_ubuf_head != wsi->context->pt_serv_buf_size) + break; + + if (wsi->protocol->rx_buffer_size && + wsi->ws->rx_ubuf_head != wsi->protocol->rx_buffer_size) + break; + + /* spill because we filled our rx buffer */ +spill: + /* + * is this frame a control packet we should take care of at this + * layer? If so service it and hide it from the user callback + */ + + lwsl_parser("spill on %s\n", wsi->protocol->name); + + switch (wsi->ws->opcode) { + case LWSWSOPC_CLOSE: + + if (wsi->ws->peer_has_sent_close) + break; + + wsi->ws->peer_has_sent_close = 1; + + pp = (unsigned char *)&wsi->ws->rx_ubuf[LWS_PRE]; + if (lws_check_opt(wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8) && + wsi->ws->rx_ubuf_head > 2 && + lws_check_utf8(&wsi->ws->utf8, pp + 2, + wsi->ws->rx_ubuf_head - 2)) + goto utf8_fail; + + /* is this an acknowledgment of our close? */ + if (lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + lwsl_parser("seen client close ack\n"); + return -1; + } + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE) + /* if he sends us 2 CLOSE, kill him */ + return -1; + + if (lws_partial_buffered(wsi)) { + /* + * if we're in the middle of something, + * we can't do a normal close response and + * have to just close our end. + */ + wsi->socket_is_permanently_unusable = 1; + lwsl_parser("Closing on peer close due to Pending tx\n"); + return -1; + } + + if (wsi->ws->rx_ubuf_head >= 2) { + close_code = (pp[0] << 8) | pp[1]; + if (close_code < 1000 || + close_code == 1004 || + close_code == 1005 || + close_code == 1006 || + close_code == 1012 || + close_code == 1013 || + close_code == 1014 || + close_code == 1015 || + (close_code >= 1016 && close_code < 3000) + ) { + pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff; + pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff; + } + } + + if (user_callback_handle_rxflow( + wsi->protocol->callback, wsi, + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, + wsi->user_space, + &wsi->ws->rx_ubuf[LWS_PRE], + wsi->ws->rx_ubuf_head)) + return -1; + + lwsl_parser("server sees client close packet\n"); + lwsi_set_state(wsi, LRS_RETURNED_CLOSE); + /* deal with the close packet contents as a PONG */ + wsi->ws->payload_is_close = 1; + goto process_as_ping; + + case LWSWSOPC_PING: + lwsl_info("received %d byte ping, sending pong\n", + wsi->ws->rx_ubuf_head); + + if (wsi->ws->ping_pending_flag) { + /* + * there is already a pending ping payload + * we should just log and drop + */ + lwsl_parser("DROP PING since one pending\n"); + goto ping_drop; + } +process_as_ping: + /* control packets can only be < 128 bytes long */ + if (wsi->ws->rx_ubuf_head > 128 - 3) { + lwsl_parser("DROP PING payload too large\n"); + goto ping_drop; + } + + /* stash the pong payload */ + memcpy(wsi->ws->ping_payload_buf + LWS_PRE, + &wsi->ws->rx_ubuf[LWS_PRE], + wsi->ws->rx_ubuf_head); + + wsi->ws->ping_payload_len = wsi->ws->rx_ubuf_head; + wsi->ws->ping_pending_flag = 1; + + /* get it sent as soon as possible */ + lws_callback_on_writable(wsi); +ping_drop: + wsi->ws->rx_ubuf_head = 0; + return 0; + + case LWSWSOPC_PONG: + lwsl_info("received pong\n"); + lwsl_hexdump(&wsi->ws->rx_ubuf[LWS_PRE], + wsi->ws->rx_ubuf_head); + + if (wsi->pending_timeout == + PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { + lwsl_info("received expected PONG on wsi %p\n", + wsi); + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + } + + /* issue it */ + callback_action = LWS_CALLBACK_RECEIVE_PONG; + break; + + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + case LWSWSOPC_CONTINUATION: + break; + + default: + lwsl_parser("unknown opc %x\n", wsi->ws->opcode); + + return -1; + } + + /* + * No it's real payload, pass it up to the user callback. + * It's nicely buffered with the pre-padding taken care of + * so it can be sent straight out again using lws_write + */ + + ebuf.token = &wsi->ws->rx_ubuf[LWS_PRE]; + ebuf.len = wsi->ws->rx_ubuf_head; + + if (wsi->ws->opcode == LWSWSOPC_PONG && !ebuf.len) + goto already_done; +#if !defined(LWS_WITHOUT_EXTENSIONS) +drain_extension: +#endif + // lwsl_notice("%s: passing %d to ext\n", __func__, ebuf.len); + + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) + goto already_done; +#if !defined(LWS_WITHOUT_EXTENSIONS) + lin = ebuf.len; + //if (lin) + // lwsl_hexdump_notice(ebuf.token, ebuf.len); + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0); + lwsl_debug("%s: ext says %d / ebuf.len %d\n", __func__, n, ebuf.len); + if (wsi->ws->rx_draining_ext) + already_processed &= ~ALREADY_PROCESSED_NO_CB; +#endif + /* + * ebuf may be pointing somewhere completely different now, + * it's the output + */ +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (n < 0) { + /* + * we may rely on this to get RX, just drop connection + */ + wsi->socket_is_permanently_unusable = 1; + return -1; + } +#endif + if ( +#if !defined(LWS_WITHOUT_EXTENSIONS) + rx_draining_ext && +#endif + ebuf.len == 0) + goto already_done; + + if ( +#if !defined(LWS_WITHOUT_EXTENSIONS) + n && +#endif + ebuf.len) + /* extension had more... main loop will come back */ + lws_add_wsi_to_draining_ext_list(wsi); + else + lws_remove_wsi_from_draining_ext_list(wsi); + + if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) { + if (lws_check_utf8(&wsi->ws->utf8, + (unsigned char *)ebuf.token, + ebuf.len)) { + lws_close_reason(wsi, + LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"bad utf8", 8); + goto utf8_fail; + } + + /* we are ending partway through utf-8 character? */ + if (!wsi->ws->rx_packet_length && wsi->ws->final && + wsi->ws->utf8 && !n) { + lwsl_info("FINAL utf8 error\n"); + lws_close_reason(wsi, + LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"partial utf8", 12); +utf8_fail: + lwsl_notice("utf8 error\n"); + lwsl_hexdump_notice(ebuf.token, ebuf.len); + + return -1; + } + } + + if (!wsi->wsistate_pre_close && (ebuf.len >= 0 || + callback_action == LWS_CALLBACK_RECEIVE_PONG)) { + if (ebuf.len) + ebuf.token[ebuf.len] = '\0'; + + if (wsi->protocol->callback && + !(already_processed & ALREADY_PROCESSED_NO_CB)) { + if (callback_action == LWS_CALLBACK_RECEIVE_PONG) + lwsl_info("Doing pong callback\n"); + + ret = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, (enum lws_callback_reasons) + callback_action, + wsi->user_space, + ebuf.token, + ebuf.len); + } + wsi->ws->first_fragment = 0; + } + +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (!lin) + break; +#endif + +already_done: + wsi->ws->rx_ubuf_head = 0; + break; + } + + return ret; + +illegal_ctl_length: + + lwsl_warn("Control frame with xtended length is illegal\n"); + /* kill the connection */ + return -1; +} + + +LWS_VISIBLE size_t +lws_remaining_packet_payload(struct lws *wsi) +{ + return wsi->ws->rx_packet_length; +} + +LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi) +{ + return wsi->ws->frame_is_binary; +} + +void +lws_add_wsi_to_draining_ext_list(struct lws *wsi) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (wsi->ws->rx_draining_ext) + return; + + lwsl_debug("%s: RX EXT DRAINING: Adding to list\n", __func__); + + wsi->ws->rx_draining_ext = 1; + wsi->ws->rx_draining_ext_list = pt->ws.rx_draining_ext_list; + pt->ws.rx_draining_ext_list = wsi; +#endif +} + +void +lws_remove_wsi_from_draining_ext_list(struct lws *wsi) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws **w = &pt->ws.rx_draining_ext_list; + + if (!wsi->ws->rx_draining_ext) + return; + + lwsl_debug("%s: RX EXT DRAINING: Removing from list\n", __func__); + + wsi->ws->rx_draining_ext = 0; + + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + /* if us, point it instead to who we were pointing to */ + *w = wsi->ws->rx_draining_ext_list; + break; + } + w = &((*w)->ws->rx_draining_ext_list); + } + wsi->ws->rx_draining_ext_list = NULL; +#endif +} + +LWS_EXTERN void +lws_restart_ws_ping_pong_timer(struct lws *wsi) +{ + if (!wsi->context->ws_ping_pong_interval || + !lwsi_role_ws(wsi)) + return; + + wsi->ws->time_next_ping_check = (time_t)lws_now_secs(); +} + +static int +lws_0405_frame_mask_generate(struct lws *wsi) +{ + int n; + /* fetch the per-frame nonce */ + + n = lws_get_random(lws_get_context(wsi), wsi->ws->mask, 4); + if (n != 4) { + lwsl_parser("Unable to read from random device %s %d\n", + SYSTEM_RANDOM_FILEPATH, n); + return 1; + } + + /* start masking from first byte of masking key buffer */ + wsi->ws->mask_idx = 0; + + return 0; +} + +int +lws_server_init_wsi_for_ws(struct lws *wsi) +{ + int n; + + lwsi_set_state(wsi, LRS_ESTABLISHED); + lws_restart_ws_ping_pong_timer(wsi); + + /* + * create the frame buffer for this connection according to the + * size mentioned in the protocol definition. If 0 there, use + * a big default for compatibility + */ + + n = (int)wsi->protocol->rx_buffer_size; + if (!n) + n = wsi->context->pt_serv_buf_size; + n += LWS_PRE; + wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf"); + if (!wsi->ws->rx_ubuf) { + lwsl_err("Out of Mem allocating rx buffer %d\n", n); + return 1; + } + wsi->ws->rx_ubuf_alloc = n; + lwsl_debug("Allocating RX buffer %d\n", n); + +#if !defined(LWS_WITH_ESP32) + if (!wsi->parent_carries_io && + !wsi->h2_stream_carries_ws) + if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, + (const char *)&n, sizeof n)) { + lwsl_warn("Failed to set SNDBUF to %d", n); + return 1; + } +#endif + + /* notify user code that we're ready to roll */ + + if (wsi->protocol->callback) + if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, +#ifdef LWS_WITH_TLS + wsi->tls.ssl, +#else + NULL, +#endif + wsi->h2_stream_carries_ws)) + return 1; + + lwsl_debug("ws established\n"); + + return 0; +} + + + +LWS_VISIBLE int +lws_is_final_fragment(struct lws *wsi) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) + lwsl_debug("%s: final %d, rx pk length %ld, draining %ld\n", __func__, + wsi->ws->final, (long)wsi->ws->rx_packet_length, + (long)wsi->ws->rx_draining_ext); + return wsi->ws->final && !wsi->ws->rx_packet_length && + !wsi->ws->rx_draining_ext; +#else + return wsi->ws->final && !wsi->ws->rx_packet_length; +#endif +} + +LWS_VISIBLE int +lws_is_first_fragment(struct lws *wsi) +{ + return wsi->ws->first_fragment; +} + +LWS_VISIBLE unsigned char +lws_get_reserved_bits(struct lws *wsi) +{ + return wsi->ws->rsv; +} + +LWS_VISIBLE LWS_EXTERN int +lws_get_close_length(struct lws *wsi) +{ + return wsi->ws->close_in_ping_buffer_len; +} + +LWS_VISIBLE LWS_EXTERN unsigned char * +lws_get_close_payload(struct lws *wsi) +{ + return &wsi->ws->ping_payload_buf[LWS_PRE]; +} + +LWS_VISIBLE LWS_EXTERN void +lws_close_reason(struct lws *wsi, enum lws_close_status status, + unsigned char *buf, size_t len) +{ + unsigned char *p, *start; + int budget = sizeof(wsi->ws->ping_payload_buf) - LWS_PRE; + + assert(lwsi_role_ws(wsi)); + + start = p = &wsi->ws->ping_payload_buf[LWS_PRE]; + + *p++ = (((int)status) >> 8) & 0xff; + *p++ = ((int)status) & 0xff; + + if (buf) + while (len-- && p < start + budget) + *p++ = *buf++; + + wsi->ws->close_in_ping_buffer_len = lws_ptr_diff(p, start); +} + +static int +lws_is_ws_with_ext(struct lws *wsi) +{ +#if defined(LWS_WITHOUT_EXTENSIONS) + return 0; +#else + return lwsi_role_ws(wsi) && !!wsi->ws->count_act_ext; +#endif +} + +static int +rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_tokens ebuf; + unsigned int pending = 0; + char buffered = 0; + int n = 0, m; +#if defined(LWS_WITH_HTTP2) + struct lws *wsi1; +#endif + + if (!wsi->ws) { + lwsl_err("ws role wsi with no ws\n"); + return 1; + } + + // lwsl_notice("%s: %s\n", __func__, wsi->protocol->name); + + //lwsl_info("%s: wsistate 0x%x, pollout %d\n", __func__, + // wsi->wsistate, pollfd->revents & LWS_POLLOUT); + + /* + * something went wrong with parsing the handshake, and + * we ended up back in the event loop without completing it + */ + if (lwsi_state(wsi) == LRS_PRE_WS_SERVING_ACCEPT) { + wsi->socket_is_permanently_unusable = 1; + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + ebuf.token = NULL; + ebuf.len = 0; + + if (lwsi_state(wsi) == LRS_WAITING_CONNECT) { +#if !defined(LWS_NO_CLIENT) + if ((pollfd->revents & LWS_POLLOUT) && + lws_handle_POLLOUT_event(wsi, pollfd)) { + lwsl_debug("POLLOUT event closed it\n"); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + n = lws_client_socket_service(wsi, pollfd, NULL); + if (n) + return LWS_HPI_RET_WSI_ALREADY_DIED; +#endif + return LWS_HPI_RET_HANDLED; + } + + //lwsl_notice("%s: wsi->ws->tx_draining_ext %d revents 0x%x 0x%x %d\n", __func__, wsi->ws->tx_draining_ext, pollfd->revents, wsi->wsistate, lwsi_state_can_handle_POLLOUT(wsi)); + + /* 1: something requested a callback when it was OK to write */ + + if ((pollfd->revents & LWS_POLLOUT) && + lwsi_state_can_handle_POLLOUT(wsi) && + lws_handle_POLLOUT_event(wsi, pollfd)) { + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE) + lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE); + + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE || + lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) { + /* + * we stopped caring about anything except control + * packets. Force flow control off, defeat tx + * draining. + */ + lws_rx_flow_control(wsi, 1); +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws) + wsi->ws->tx_draining_ext = 0; +#endif + } +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->tx_draining_ext) + /* + * We cannot deal with new RX until the TX ext path has + * been drained. It's because new rx will, eg, crap on + * the wsi rx buf that may be needed to retain state. + * + * TX ext drain path MUST go through event loop to avoid + * blocking. + */ + return LWS_HPI_RET_HANDLED; +#endif + if (lws_is_flowcontrolled(wsi)) { + /* We cannot deal with any kind of new RX because we are + * RX-flowcontrolled. + */ + lwsl_info("flowcontrolled\n"); + return LWS_HPI_RET_HANDLED; + } + +#if defined(LWS_WITH_HTTP2) + if (wsi->http2_substream || wsi->upgraded_to_http2) { + wsi1 = lws_get_network_wsi(wsi); + if (wsi1 && wsi1->trunc_len) + /* We cannot deal with any kind of new RX + * because we are dealing with a partial send + * (new RX may trigger new http_action() that + * expect to be able to send) + */ + return LWS_HPI_RET_HANDLED; + } +#endif + +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* 2: RX Extension needs to be drained + */ + + if (wsi->ws->rx_draining_ext) { + + lwsl_debug("%s: RX EXT DRAINING: Service\n", __func__); +#ifndef LWS_NO_CLIENT + if (lwsi_role_client(wsi)) { + n = lws_ws_client_rx_sm(wsi, 0); + if (n < 0) + /* we closed wsi */ + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } else +#endif + n = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0); + + return LWS_HPI_RET_HANDLED; + } + + if (wsi->ws->rx_draining_ext) + /* + * We have RX EXT content to drain, but can't do it + * right now. That means we cannot do anything lower + * priority either. + */ + return LWS_HPI_RET_HANDLED; +#endif + + /* 3: buflist needs to be drained + */ +read: + //lws_buflist_describe(&wsi->buflist, wsi); + ebuf.len = (int)lws_buflist_next_segment_len(&wsi->buflist, + (uint8_t **)&ebuf.token); + if (ebuf.len) { + lwsl_info("draining buflist (len %d)\n", ebuf.len); + buffered = 1; + goto drain; + } + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN) && !wsi->http.ah) + return LWS_HPI_RET_HANDLED; + + if (lws_is_flowcontrolled(wsi)) { + lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n", + __func__, wsi, wsi->rxflow_bitmap); + return LWS_HPI_RET_HANDLED; + } + + if (!(lwsi_role_client(wsi) && + (lwsi_state(wsi) != LRS_ESTABLISHED && + lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK && + lwsi_state(wsi) != LRS_H2_WAITING_TO_SEND_HEADERS))) { + /* + * In case we are going to react to this rx by scheduling + * writes, we need to restrict the amount of rx to the size + * the protocol reported for rx buffer. + * + * Otherwise we get a situation we have to absorb possibly a + * lot of reads before we get a chance to drain them by writing + * them, eg, with echo type tests in autobahn. + */ + + buffered = 0; + ebuf.token = (char *)pt->serv_buf; + if (lwsi_role_ws(wsi)) + ebuf.len = wsi->ws->rx_ubuf_alloc; + else + ebuf.len = wsi->context->pt_serv_buf_size; + + if ((unsigned int)ebuf.len > wsi->context->pt_serv_buf_size) + ebuf.len = wsi->context->pt_serv_buf_size; + + if ((int)pending > ebuf.len) + pending = ebuf.len; + + ebuf.len = lws_ssl_capable_read(wsi, (uint8_t *)ebuf.token, + pending ? (int)pending : + ebuf.len); + switch (ebuf.len) { + case 0: + lwsl_info("%s: zero length read\n", + __func__); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + case LWS_SSL_CAPABLE_MORE_SERVICE: + lwsl_info("SSL Capable more service\n"); + return LWS_HPI_RET_HANDLED; + case LWS_SSL_CAPABLE_ERROR: + lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n", + __func__); + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + // lwsl_notice("Actual RX %d\n", ebuf.len); + + lws_restart_ws_ping_pong_timer(wsi); + + /* + * coverity thinks ssl_capable_read() may read over + * 2GB. Dissuade it... + */ + ebuf.len &= 0x7fffffff; + } + +drain: + + /* + * give any active extensions a chance to munge the buffer + * before parse. We pass in a pointer to an lws_tokens struct + * prepared with the default buffer and content length that's in + * there. Rather than rewrite the default buffer, extensions + * that expect to grow the buffer can adapt .token to + * point to their own per-connection buffer in the extension + * user allocation. By default with no extensions or no + * extension callback handling, just the normal input buffer is + * used then so it is efficient. + */ + m = 0; + do { + + /* service incoming data */ + //lws_buflist_describe(&wsi->buflist, wsi); + if (ebuf.len) { +#if defined(LWS_ROLE_H2) + if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) + n = lws_read_h2(wsi, (unsigned char *)ebuf.token, + ebuf.len); + else +#endif + n = lws_read_h1(wsi, (unsigned char *)ebuf.token, + ebuf.len); + + if (n < 0) { + /* we closed wsi */ + n = 0; + return LWS_HPI_RET_WSI_ALREADY_DIED; + } + //lws_buflist_describe(&wsi->buflist, wsi); + //lwsl_notice("%s: consuming %d / %d\n", __func__, n, ebuf.len); + if (lws_buflist_aware_consume(wsi, &ebuf, n, buffered)) + return LWS_HPI_RET_PLEASE_CLOSE_ME; + } + + ebuf.token = NULL; + ebuf.len = 0; + } while (m); + + if (wsi->http.ah +#if !defined(LWS_NO_CLIENT) + && !wsi->client_h2_alpn +#endif + ) { + lwsl_info("%s: %p: detaching ah\n", __func__, wsi); + lws_header_table_detach(wsi, 0); + } + + pending = lws_ssl_pending(wsi); + if (pending) { + if (lws_is_ws_with_ext(wsi)) + pending = pending > wsi->ws->rx_ubuf_alloc ? + wsi->ws->rx_ubuf_alloc : pending; + else + pending = pending > wsi->context->pt_serv_buf_size ? + wsi->context->pt_serv_buf_size : pending; + goto read; + } + + if (buffered && /* were draining, now nothing left */ + !lws_buflist_next_segment_len(&wsi->buflist, NULL)) { + lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); + /* having drained the rxflow buffer, can rearm POLLIN */ +#ifdef LWS_NO_SERVER + n = +#endif + __lws_rx_flow_control(wsi); + /* n ignored, needed for NO_SERVER case */ + } + + /* n = 0 */ + return LWS_HPI_RET_HANDLED; +} + + +int rops_handle_POLLOUT_ws(struct lws *wsi) +{ + int write_type = LWS_WRITE_PONG; +#if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_tokens ebuf; + int ret, m; +#endif + int n; + +#if !defined(LWS_WITHOUT_EXTENSIONS) + lwsl_debug("%s: %s: wsi->ws->tx_draining_ext %d\n", __func__, + wsi->protocol->name, wsi->ws->tx_draining_ext); +#endif + + /* Priority 3: pending control packets (pong or close) + * + * 3a: close notification packet requested from close api + */ + + if (lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE) { + lwsl_debug("sending close packet\n"); + lwsl_hexdump_debug(&wsi->ws->ping_payload_buf[LWS_PRE], + wsi->ws->close_in_ping_buffer_len); + wsi->waiting_to_send_close_frame = 0; + n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE], + wsi->ws->close_in_ping_buffer_len, + LWS_WRITE_CLOSE); + if (n >= 0) { + if (wsi->close_needs_ack) { + lwsi_set_state(wsi, LRS_AWAITING_CLOSE_ACK); + lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 5); + lwsl_debug("sent close indication, awaiting ack\n"); + + return LWS_HP_RET_BAIL_OK; + } + wsi->close_needs_ack = 0; + lwsi_set_state(wsi, LRS_RETURNED_CLOSE); + } + + return LWS_HP_RET_BAIL_DIE; + } + + /* else, the send failed and we should just hang up */ + + if ((lwsi_role_ws(wsi) && wsi->ws->ping_pending_flag) || + (lwsi_state(wsi) == LRS_RETURNED_CLOSE && + wsi->ws->payload_is_close)) { + + if (wsi->ws->payload_is_close) + write_type = LWS_WRITE_CLOSE; + else { + if (wsi->wsistate_pre_close) { + /* we started close flow, forget pong */ + wsi->ws->ping_pending_flag = 0; + return LWS_HP_RET_BAIL_OK; + } + lwsl_info("issuing pong %d on wsi %p\n", wsi->ws->ping_payload_len, wsi); + } + + n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE], + wsi->ws->ping_payload_len, write_type); + if (n < 0) + return LWS_HP_RET_BAIL_DIE; + + /* well he is sent, mark him done */ + wsi->ws->ping_pending_flag = 0; + if (wsi->ws->payload_is_close) { + // assert(0); + /* oh... a close frame was it... then we are done */ + return LWS_HP_RET_BAIL_DIE; + } + + /* otherwise for PING, leave POLLOUT active either way */ + return LWS_HP_RET_BAIL_OK; + } + + if (lwsi_role_client(wsi) && !wsi->socket_is_permanently_unusable && + wsi->ws->send_check_ping) { + + lwsl_info("issuing ping on wsi %p\n", wsi); + wsi->ws->send_check_ping = 0; + n = lws_write(wsi, &wsi->ws->ping_payload_buf[LWS_PRE], + 0, LWS_WRITE_PING); + if (n < 0) + return LWS_HP_RET_BAIL_DIE; + + /* + * we apparently were able to send the PING in a reasonable time + * now reset the clock on our peer to be able to send the + * PONG in a reasonable time. + */ + + lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG, + wsi->context->timeout_secs); + + return LWS_HP_RET_BAIL_OK; + } + + /* Priority 4: if we are closing, not allowed to send more data frags + * which means user callback or tx ext flush banned now + */ + if (lwsi_state(wsi) == LRS_RETURNED_CLOSE) + return LWS_HP_RET_USER_SERVICE; + +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* Priority 5: Tx path extension with more to send + * + * These are handled as new fragments each time around + * So while we must block new writeable callback to enforce + * payload ordering, but since they are always complete + * fragments control packets can interleave OK. + */ + if (lwsi_role_client(wsi) && wsi->ws->tx_draining_ext) { + lwsl_ext("SERVICING TX EXT DRAINING\n"); + if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0) + return LWS_HP_RET_BAIL_DIE; + /* leave POLLOUT active */ + return LWS_HP_RET_BAIL_OK; + } + + /* Priority 6: extensions + */ + if (!wsi->ws->extension_data_pending) + return LWS_HP_RET_USER_SERVICE; + + /* + * check in on the active extensions, see if they + * had pending stuff to spill... they need to get the + * first look-in otherwise sequence will be disordered + * + * NULL, zero-length ebuf means just spill pending + */ + + ret = 1; + if (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file) + ret = 0; + + while (ret == 1) { + + /* default to nobody has more to spill */ + + ret = 0; + ebuf.token = NULL; + ebuf.len = 0; + + /* give every extension a chance to spill */ + + m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND, + &ebuf, 0); + if (m < 0) { + lwsl_err("ext reports fatal error\n"); + return LWS_HP_RET_BAIL_DIE; + } + if (m) + /* + * at least one extension told us he has more + * to spill, so we will go around again after + */ + ret = 1; + + /* assuming they gave us something to send, send it */ + + if (ebuf.len) { + n = lws_issue_raw(wsi, (unsigned char *)ebuf.token, + ebuf.len); + if (n < 0) { + lwsl_info("closing from POLLOUT spill\n"); + return LWS_HP_RET_BAIL_DIE; + } + /* + * Keep amount spilled small to minimize chance of this + */ + if (n != ebuf.len) { + lwsl_err("Unable to spill ext %d vs %d\n", + ebuf.len, n); + return LWS_HP_RET_BAIL_DIE; + } + } else + continue; + + /* no extension has more to spill */ + + if (!ret) + continue; + + /* + * There's more to spill from an extension, but we just sent + * something... did that leave the pipe choked? + */ + + if (!lws_send_pipe_choked(wsi)) + /* no we could add more */ + continue; + + lwsl_info("choked in POLLOUT service\n"); + + /* + * Yes, he's choked. Leave the POLLOUT masked on so we will + * come back here when he is unchoked. Don't call the user + * callback to enforce ordering of spilling, he'll get called + * when we come back here and there's nothing more to spill. + */ + + return LWS_HP_RET_BAIL_OK; + } + + wsi->ws->extension_data_pending = 0; +#endif + + return LWS_HP_RET_USER_SERVICE; +} + +static int +rops_periodic_checks_ws(struct lws_context *context, int tsi, time_t now) +{ + struct lws_vhost *vh; + + if (!context->ws_ping_pong_interval || + context->last_ws_ping_pong_check_s >= now + 10) + return 0; + + vh = context->vhost_list; + context->last_ws_ping_pong_check_s = now; + + while (vh) { + int n; + + lws_vhost_lock(vh); + + for (n = 0; n < vh->count_protocols; n++) { + struct lws *wsi = vh->same_vh_protocol_list[n]; + + while (wsi) { + if (lwsi_role_ws(wsi) && + !wsi->socket_is_permanently_unusable && + !wsi->ws->send_check_ping && + wsi->ws->time_next_ping_check && + lws_compare_time_t(context, now, + wsi->ws->time_next_ping_check) > + context->ws_ping_pong_interval) { + + lwsl_info("req pp on wsi %p\n", + wsi); + wsi->ws->send_check_ping = 1; + lws_set_timeout(wsi, + PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING, + context->timeout_secs); + lws_callback_on_writable(wsi); + wsi->ws->time_next_ping_check = + now; + } + wsi = wsi->same_vh_protocol_next; + } + } + + lws_vhost_unlock(vh); + vh = vh->vhost_next; + } + + return 0; +} + +static int +rops_service_flag_pending_ws(struct lws_context *context, int tsi) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct lws *wsi; + int forced = 0; + + /* POLLIN faking (the pt lock is taken by the parent) */ + + /* + * 1) For all guys with already-available ext data to drain, if they are + * not flowcontrolled, fake their POLLIN status + */ + wsi = pt->ws.rx_draining_ext_list; + while (wsi && wsi->position_in_fds_table != LWS_NO_FDS_POS) { + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) + forced = 1; + + wsi = wsi->ws->rx_draining_ext_list; + } + + return forced; +#else + return 0; +#endif +} + +static int +rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason) +{ + if (!wsi->ws->close_in_ping_buffer_len && /* already a reason */ + (reason == LWS_CLOSE_STATUS_NOSTATUS || + reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)) + return 0; + + lwsl_debug("%s: sending close indication...\n", __func__); + + /* if no prepared close reason, use 1000 and no aux data */ + + if (!wsi->ws->close_in_ping_buffer_len) { + wsi->ws->close_in_ping_buffer_len = 2; + wsi->ws->ping_payload_buf[LWS_PRE] = (reason >> 8) & 0xff; + wsi->ws->ping_payload_buf[LWS_PRE + 1] = reason & 0xff; + } + + wsi->waiting_to_send_close_frame = 1; + wsi->close_needs_ack = 1; + lwsi_set_state(wsi, LRS_WAITING_TO_SEND_CLOSE); + __lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 5); + + lws_callback_on_writable(wsi); + + return 1; +} + +static int +rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { + struct lws **w = &pt->ws.rx_draining_ext_list; + + wsi->ws->rx_draining_ext = 0; + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + *w = wsi->ws->rx_draining_ext_list; + break; + } + w = &((*w)->ws->rx_draining_ext_list); + } + wsi->ws->rx_draining_ext_list = NULL; + } + + if (wsi->ws->tx_draining_ext) { + struct lws **w = &pt->ws.tx_draining_ext_list; + lwsl_notice("%s: CLEARING tx_draining_ext\n", __func__); + wsi->ws->tx_draining_ext = 0; + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + *w = wsi->ws->tx_draining_ext_list; + break; + } + w = &((*w)->ws->tx_draining_ext_list); + } + wsi->ws->tx_draining_ext_list = NULL; + } +#endif + lws_free_set_NULL(wsi->ws->rx_ubuf); + + if (wsi->trunc_alloc) + /* not going to be completed... nuke it */ + lws_free_set_NULL(wsi->trunc_alloc); + + wsi->ws->ping_payload_len = 0; + wsi->ws->ping_pending_flag = 0; + + /* deallocate any active extension contexts */ + + if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0) + lwsl_warn("extension destruction failed\n"); + + return 0; +} + +static int +rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol *wp) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + enum lws_write_protocol wpt; +#endif + int masked7 = lwsi_role_client(wsi); + unsigned char is_masked_bit = 0; + unsigned char *dropmask = NULL; + struct lws_tokens ebuf; + size_t orig_len = len; + int pre = 0, n = 0; + + // lwsl_err("%s: wp 0x%x len %d\n", __func__, *wp, (int)len); +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->tx_draining_ext) { + /* remove us from the list */ + struct lws **w = &pt->ws.tx_draining_ext_list; + + lwsl_notice("%s: CLEARING tx_draining_ext\n", __func__); + wsi->ws->tx_draining_ext = 0; + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + *w = wsi->ws->tx_draining_ext_list; + break; + } + w = &((*w)->ws->tx_draining_ext_list); + } + wsi->ws->tx_draining_ext_list = NULL; + + wpt = *wp; + *wp = (wsi->ws->tx_draining_stashed_wp & 0xc0)| + LWS_WRITE_CONTINUATION; + + /* + * When we are just flushing (len == 0), we can trust the + * stashed wp info completely. Otherwise adjust it to the + * FIN status of the incoming packet. + */ + + if (!(wpt & LWS_WRITE_NO_FIN) && len) + *wp &= ~LWS_WRITE_NO_FIN; + + lwsl_notice("FORCED draining wp to 0x%02X (stashed 0x%02X, incoming 0x%02X)\n", *wp, + wsi->ws->tx_draining_stashed_wp, wpt); + // assert(0); + } +#endif + lws_restart_ws_ping_pong_timer(wsi); + + if (((*wp) & 0x1f) == LWS_WRITE_HTTP || + ((*wp) & 0x1f) == LWS_WRITE_HTTP_FINAL || + ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION || + ((*wp) & 0x1f) == LWS_WRITE_HTTP_HEADERS) + goto send_raw; + + + + /* if we are continuing a frame that already had its header done */ + + if (wsi->ws->inside_frame) { + lwsl_debug("INSIDE FRAME\n"); + goto do_more_inside_frame; + } + + wsi->ws->clean_buffer = 1; + + /* + * give a chance to the extensions to modify payload + * the extension may decide to produce unlimited payload erratically + * (eg, compression extension), so we require only that if he produces + * something, it will be a complete fragment of the length known at + * the time (just the fragment length known), and if he has + * more we will come back next time he is writeable and allow him to + * produce more fragments until he's drained. + * + * This allows what is sent each time it is writeable to be limited to + * a size that can be sent without partial sends or blocking, allows + * interleaving of control frames and other connection service. + */ + ebuf.token = (char *)buf; + ebuf.len = (int)len; + + switch ((int)*wp) { + case LWS_WRITE_PING: + case LWS_WRITE_PONG: + case LWS_WRITE_CLOSE: + break; + default: +#if !defined(LWS_WITHOUT_EXTENSIONS) + // lwsl_notice("LWS_EXT_CB_PAYLOAD_TX\n"); + // m = (int)ebuf.len; + /* returns 0 if no more tx pending, 1 if more pending */ + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &ebuf, *wp); + if (n < 0) + return -1; + // lwsl_notice("ext processed %d plaintext into %d compressed (wp 0x%x)\n", m, (int)ebuf.len, *wp); + + if (n && ebuf.len) { + lwsl_notice("write drain len %d (wp 0x%x) SETTING tx_draining_ext\n", (int)ebuf.len, *wp); + /* extension requires further draining */ + wsi->ws->tx_draining_ext = 1; + wsi->ws->tx_draining_ext_list = pt->ws.tx_draining_ext_list; + pt->ws.tx_draining_ext_list = wsi; + /* we must come back to do more */ + lws_callback_on_writable(wsi); + /* + * keep a copy of the write type for the overall + * action that has provoked generation of these + * fragments, so the last guy can use its FIN state. + */ + wsi->ws->tx_draining_stashed_wp = *wp; + /* this is definitely not actually the last fragment + * because the extension asserted he has more coming + * So make sure this intermediate one doesn't go out + * with a FIN. + */ + *wp |= LWS_WRITE_NO_FIN; + } +#endif + if (ebuf.len && wsi->ws->stashed_write_pending) { + wsi->ws->stashed_write_pending = 0; + *wp = ((*wp) & 0xc0) | (int)wsi->ws->stashed_write_type; + } + } + + /* + * an extension did something we need to keep... for example, if + * compression extension, it has already updated its state according + * to this being issued + */ + if ((char *)buf != ebuf.token) { + /* + * ext might eat it, but not have anything to issue yet. + * In that case we have to follow his lead, but stash and + * replace the write type that was lost here the first time. + */ + if (len && !ebuf.len) { + if (!wsi->ws->stashed_write_pending) + wsi->ws->stashed_write_type = (char)(*wp) & 0x3f; + wsi->ws->stashed_write_pending = 1; + return (int)len; + } + /* + * extension recreated it: + * need to buffer this if not all sent + */ + wsi->ws->clean_buffer = 0; + } + + buf = (unsigned char *)ebuf.token; + len = ebuf.len; + + if (!buf) { + lwsl_err("null buf (%d)\n", (int)len); + return -1; + } + + switch (wsi->ws->ietf_spec_revision) { + case 13: + if (masked7) { + pre += 4; + dropmask = &buf[0 - pre]; + is_masked_bit = 0x80; + } + + switch ((*wp) & 0xf) { + case LWS_WRITE_TEXT: + n = LWSWSOPC_TEXT_FRAME; + break; + case LWS_WRITE_BINARY: + n = LWSWSOPC_BINARY_FRAME; + break; + case LWS_WRITE_CONTINUATION: + n = LWSWSOPC_CONTINUATION; + break; + + case LWS_WRITE_CLOSE: + n = LWSWSOPC_CLOSE; + break; + case LWS_WRITE_PING: + n = LWSWSOPC_PING; + break; + case LWS_WRITE_PONG: + n = LWSWSOPC_PONG; + break; + default: + lwsl_warn("lws_write: unknown write opc / wp\n"); + return -1; + } + + if (!((*wp) & LWS_WRITE_NO_FIN)) + n |= 1 << 7; + + if (len < 126) { + pre += 2; + buf[-pre] = n; + buf[-pre + 1] = (unsigned char)(len | is_masked_bit); + } else { + if (len < 65536) { + pre += 4; + buf[-pre] = n; + buf[-pre + 1] = 126 | is_masked_bit; + buf[-pre + 2] = (unsigned char)(len >> 8); + buf[-pre + 3] = (unsigned char)len; + } else { + pre += 10; + buf[-pre] = n; + buf[-pre + 1] = 127 | is_masked_bit; +#if defined __LP64__ + buf[-pre + 2] = (len >> 56) & 0x7f; + buf[-pre + 3] = len >> 48; + buf[-pre + 4] = len >> 40; + buf[-pre + 5] = len >> 32; +#else + buf[-pre + 2] = 0; + buf[-pre + 3] = 0; + buf[-pre + 4] = 0; + buf[-pre + 5] = 0; +#endif + buf[-pre + 6] = (unsigned char)(len >> 24); + buf[-pre + 7] = (unsigned char)(len >> 16); + buf[-pre + 8] = (unsigned char)(len >> 8); + buf[-pre + 9] = (unsigned char)len; + } + } + break; + } + +do_more_inside_frame: + + /* + * Deal with masking if we are in client -> server direction and + * the wp demands it + */ + + if (masked7) { + if (!wsi->ws->inside_frame) + if (lws_0405_frame_mask_generate(wsi)) { + lwsl_err("frame mask generation failed\n"); + return -1; + } + + /* + * in v7, just mask the payload + */ + if (dropmask) { /* never set if already inside frame */ + for (n = 4; n < (int)len + 4; n++) + dropmask[n] = dropmask[n] ^ wsi->ws->mask[ + (wsi->ws->mask_idx++) & 3]; + + /* copy the frame nonce into place */ + memcpy(dropmask, wsi->ws->mask, 4); + } + } + + if (lwsi_role_h2_ENCAPSULATION(wsi)) { + struct lws *encap = lws_get_network_wsi(wsi); + + assert(encap != wsi); + return encap->role_ops->write_role_protocol(wsi, buf - pre, + len + pre, wp); + } + + switch ((*wp) & 0x1f) { + case LWS_WRITE_TEXT: + case LWS_WRITE_BINARY: + case LWS_WRITE_CONTINUATION: + if (!wsi->h2_stream_carries_ws) { + + /* + * give any active extensions a chance to munge the + * buffer before send. We pass in a pointer to an + * lws_tokens struct prepared with the default buffer + * and content length that's in there. Rather than + * rewrite the default buffer, extensions that expect + * to grow the buffer can adapt .token to point to their + * own per-connection buffer in the extension user + * allocation. By default with no extensions or no + * extension callback handling, just the normal input + * buffer is used then so it is efficient. + * + * callback returns 1 in case it wants to spill more + * buffers + * + * This takes care of holding the buffer if send is + * incomplete, ie, if wsi->ws->clean_buffer is 0 + * (meaning an extension meddled with the buffer). If + * wsi->ws->clean_buffer is 1, it will instead return + * to the user code how much OF THE USER BUFFER was + * consumed. + */ + + n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre); + wsi->ws->inside_frame = 1; + if (n <= 0) + return n; + + if (n == (int)len + pre) { + /* everything in the buffer was handled + * (or rebuffered...) */ + wsi->ws->inside_frame = 0; + return (int)orig_len; + } + + /* + * it is how many bytes of user buffer got sent... may + * be < orig_len in which case callback when writable + * has already been arranged and user code can call + * lws_write() again with the rest later. + */ + + return n - pre; + } + break; + default: + break; + } + +send_raw: + return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre); +} + +static int +rops_close_kill_connection_ws(struct lws *wsi, enum lws_close_status reason) +{ + /* deal with ws encapsulation in h2 */ +#if defined(LWS_WITH_HTTP2) + if (wsi->http2_substream && wsi->h2_stream_carries_ws) + return role_ops_h2.close_kill_connection(wsi, reason); + + return 0; +#else + return 0; +#endif +} + +static int +rops_callback_on_writable_ws(struct lws *wsi) +{ +#if defined(LWS_WITH_HTTP2) + if (lwsi_role_h2_ENCAPSULATION(wsi)) { + /* we know then that it has an h2 parent */ + struct lws *enc = role_ops_h2.encapsulation_parent(wsi); + + assert(enc); + if (enc->role_ops->callback_on_writable(wsi)) + return 1; + } +#endif + return 0; +} + +static int +rops_init_vhost_ws(struct lws_vhost *vh, + const struct lws_context_creation_info *info) +{ +#if !defined(LWS_WITHOUT_EXTENSIONS) +#ifdef LWS_WITH_PLUGINS + struct lws_plugin *plugin = vh->context->plugin_list; + int m; + + if (vh->context->plugin_extension_count) { + + m = 0; + while (info->extensions && info->extensions[m].callback) + m++; + + /* + * give the vhost a unified list of extensions including the + * ones that came from plugins + */ + vh->ws.extensions = lws_zalloc(sizeof(struct lws_extension) * + (m + vh->context->plugin_extension_count + 1), + "extensions"); + if (!vh->ws.extensions) + return 1; + + memcpy((struct lws_extension *)vh->ws.extensions, info->extensions, + sizeof(struct lws_extension) * m); + plugin = vh->context->plugin_list; + while (plugin) { + memcpy((struct lws_extension *)&vh->ws.extensions[m], + plugin->caps.extensions, + sizeof(struct lws_extension) * + plugin->caps.count_extensions); + m += plugin->caps.count_extensions; + plugin = plugin->list; + } + } else +#endif + vh->ws.extensions = info->extensions; +#endif + + return 0; +} + +static int +rops_destroy_vhost_ws(struct lws_vhost *vh) +{ +#ifdef LWS_WITH_PLUGINS +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (vh->context->plugin_extension_count) + lws_free((void *)vh->ws.extensions); +#endif +#endif + + return 0; +} + +static int +rops_destroy_role_ws(struct lws *wsi) +{ + lws_free_set_NULL(wsi->ws); + + return 0; +} + +struct lws_role_ops role_ops_ws = { + /* role name */ "ws", + /* alpn id */ NULL, + /* check_upgrades */ NULL, + /* init_context */ NULL, + /* init_vhost */ rops_init_vhost_ws, + /* destroy_vhost */ rops_destroy_vhost_ws, + /* periodic_checks */ rops_periodic_checks_ws, + /* service_flag_pending */ rops_service_flag_pending_ws, + /* handle_POLLIN */ rops_handle_POLLIN_ws, + /* handle_POLLOUT */ rops_handle_POLLOUT_ws, + /* perform_user_POLLOUT */ NULL, + /* callback_on_writable */ rops_callback_on_writable_ws, + /* tx_credit */ NULL, + /* write_role_protocol */ rops_write_role_protocol_ws, + /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, + /* close_via_role_protocol */ rops_close_via_role_protocol_ws, + /* close_role */ rops_close_role_ws, + /* close_kill_connection */ rops_close_kill_connection_ws, + /* destroy_role */ rops_destroy_role_ws, + /* writeable cb clnt, srv */ { LWS_CALLBACK_CLIENT_WRITEABLE, + LWS_CALLBACK_SERVER_WRITEABLE }, + /* close cb clnt, srv */ { LWS_CALLBACK_CLIENT_CLOSED, + LWS_CALLBACK_CLOSED }, + /* file handles */ 0 +}; diff --git a/thirdparty/libwebsockets/roles/ws/private.h b/thirdparty/libwebsockets/roles/ws/private.h new file mode 100644 index 0000000000..71ffcaea96 --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/private.h @@ -0,0 +1,164 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h if LWS_ROLE_WS + */ + +extern struct lws_role_ops role_ops_ws; + +#define lwsi_role_ws(wsi) (wsi->role_ops == &role_ops_ws) + +enum lws_rx_parse_state { + LWS_RXPS_NEW, + + LWS_RXPS_04_mask_1, + LWS_RXPS_04_mask_2, + LWS_RXPS_04_mask_3, + + LWS_RXPS_04_FRAME_HDR_1, + LWS_RXPS_04_FRAME_HDR_LEN, + LWS_RXPS_04_FRAME_HDR_LEN16_2, + LWS_RXPS_04_FRAME_HDR_LEN16_1, + LWS_RXPS_04_FRAME_HDR_LEN64_8, + LWS_RXPS_04_FRAME_HDR_LEN64_7, + LWS_RXPS_04_FRAME_HDR_LEN64_6, + LWS_RXPS_04_FRAME_HDR_LEN64_5, + LWS_RXPS_04_FRAME_HDR_LEN64_4, + LWS_RXPS_04_FRAME_HDR_LEN64_3, + LWS_RXPS_04_FRAME_HDR_LEN64_2, + LWS_RXPS_04_FRAME_HDR_LEN64_1, + + LWS_RXPS_07_COLLECT_FRAME_KEY_1, + LWS_RXPS_07_COLLECT_FRAME_KEY_2, + LWS_RXPS_07_COLLECT_FRAME_KEY_3, + LWS_RXPS_07_COLLECT_FRAME_KEY_4, + + LWS_RXPS_WS_FRAME_PAYLOAD +}; + +enum lws_websocket_opcodes_07 { + LWSWSOPC_CONTINUATION = 0, + LWSWSOPC_TEXT_FRAME = 1, + LWSWSOPC_BINARY_FRAME = 2, + + LWSWSOPC_NOSPEC__MUX = 7, + + /* control extensions 8+ */ + + LWSWSOPC_CLOSE = 8, + LWSWSOPC_PING = 9, + LWSWSOPC_PONG = 0xa, +}; + +/* this is not usable directly by user code any more, lws_close_reason() */ +#define LWS_WRITE_CLOSE 4 + +#define ALREADY_PROCESSED_IGNORE_CHAR 1 +#define ALREADY_PROCESSED_NO_CB 2 + +#if !defined(LWS_WITHOUT_EXTENSIONS) +struct lws_vhost_role_ws { + const struct lws_extension *extensions; +}; + +struct lws_pt_role_ws { + struct lws *rx_draining_ext_list; + struct lws *tx_draining_ext_list; +}; +#endif + +struct _lws_websocket_related { + char *rx_ubuf; +#if !defined(LWS_WITHOUT_EXTENSIONS) + const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE]; + void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE]; + struct lws *rx_draining_ext_list; + struct lws *tx_draining_ext_list; +#endif + /* Also used for close content... control opcode == < 128 */ + uint8_t ping_payload_buf[128 - 3 + LWS_PRE]; + uint8_t mask[4]; + + time_t time_next_ping_check; + size_t rx_packet_length; + uint32_t rx_ubuf_head; + uint32_t rx_ubuf_alloc; + + uint8_t ping_payload_len; + uint8_t mask_idx; + uint8_t opcode; + uint8_t rsv; + uint8_t rsv_first_msg; + /* zero if no info, or length including 2-byte close code */ + uint8_t close_in_ping_buffer_len; + uint8_t utf8; + uint8_t stashed_write_type; + uint8_t tx_draining_stashed_wp; + uint8_t ietf_spec_revision; + + unsigned int final:1; + unsigned int frame_is_binary:1; + unsigned int all_zero_nonce:1; + unsigned int this_frame_masked:1; + unsigned int inside_frame:1; /* next write will be more of frame */ + unsigned int clean_buffer:1; /* buffer not rewritten by extension */ + unsigned int payload_is_close:1; /* process as PONG, but it is close */ + unsigned int ping_pending_flag:1; + unsigned int continuation_possible:1; + unsigned int owed_a_fin:1; + unsigned int check_utf8:1; + unsigned int defeat_check_utf8:1; + unsigned int stashed_write_pending:1; + unsigned int send_check_ping:1; + unsigned int first_fragment:1; + unsigned int peer_has_sent_close:1; +#if !defined(LWS_WITHOUT_EXTENSIONS) + unsigned int extension_data_pending:1; + unsigned int rx_draining_ext:1; + unsigned int tx_draining_ext:1; + + uint8_t count_act_ext; +#endif +}; + +int +lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len); + +#if !defined(LWS_WITHOUT_EXTENSIONS) +LWS_VISIBLE void +lws_context_init_extensions(const struct lws_context_creation_info *info, + struct lws_context *context); +LWS_EXTERN int +lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r, + void *v, size_t len); + +LWS_EXTERN int +lws_ext_cb_active(struct lws *wsi, int reason, void *buf, int len); +LWS_EXTERN int +lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, int reason, + void *arg, int len); +#endif + +int +handshake_0405(struct lws_context *context, struct lws *wsi); +int +lws_process_ws_upgrade(struct lws *wsi); +int +lws_server_init_wsi_for_ws(struct lws *wsi); diff --git a/thirdparty/libwebsockets/roles/ws/server-ws.c b/thirdparty/libwebsockets/roles/ws/server-ws.c new file mode 100644 index 0000000000..62bcd8524f --- /dev/null +++ b/thirdparty/libwebsockets/roles/ws/server-ws.c @@ -0,0 +1,836 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include <core/private.h> + +#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } + +#if !defined(LWS_WITHOUT_EXTENSIONS) +static int +lws_extension_server_handshake(struct lws *wsi, char **p, int budget) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char ext_name[64], *args, *end = (*p) + budget - 1; + const struct lws_ext_options *opts, *po; + const struct lws_extension *ext; + struct lws_ext_option_arg oa; + int n, m, more = 1; + int ext_count = 0; + char ignore; + char *c; + + /* + * Figure out which extensions the client has that we want to + * enable on this connection, and give him back the list + */ + if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) + return 0; + + /* + * break down the list of client extensions + * and go through them + */ + + if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size, + WSI_TOKEN_EXTENSIONS) < 0) + return 1; + + c = (char *)pt->serv_buf; + lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c); + wsi->ws->count_act_ext = 0; + ignore = 0; + n = 0; + args = NULL; + + /* + * We may get a simple request + * + * Sec-WebSocket-Extensions: permessage-deflate + * + * or an elaborated one with requested options + * + * Sec-WebSocket-Extensions: permessage-deflate; \ + * server_no_context_takeover; \ + * client_no_context_takeover + */ + + while (more) { + + if (c >= (char *)pt->serv_buf + 255) + return -1; + + if (*c && (*c != ',' && *c != '\t')) { + if (*c == ';') { + ignore = 1; + if (!args) + args = c + 1; + } + if (ignore || *c == ' ') { + c++; + continue; + } + ext_name[n] = *c++; + if (n < (int)sizeof(ext_name) - 1) + n++; + continue; + } + ext_name[n] = '\0'; + + ignore = 0; + if (!*c) + more = 0; + else { + c++; + if (!n) + continue; + } + + while (args && *args && *args == ' ') + args++; + + /* check a client's extension against our support */ + + ext = wsi->vhost->ws.extensions; + + while (ext && ext->callback) { + + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + /* + * oh, we do support this one he asked for... but let's + * confirm he only gave it once + */ + for (m = 0; m < wsi->ws->count_act_ext; m++) + if (wsi->ws->active_extensions[m] == ext) { + lwsl_info("extension mentioned twice\n"); + return 1; /* shenanigans */ + } + + /* + * ask user code if it's OK to apply it on this + * particular connection + protocol + */ + m = (wsi->protocol->callback)(wsi, + LWS_CALLBACK_CONFIRM_EXTENSION_OKAY, + wsi->user_space, ext_name, 0); + + /* + * zero return from callback means go ahead and allow + * the extension, it's what we get if the callback is + * unhandled + */ + if (m) { + ext++; + continue; + } + + /* apply it */ + + ext_count++; + + /* instantiate the extension on this conn */ + + wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext; + + /* allow him to construct his context */ + + if (ext->callback(lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_CONSTRUCT, + (void *)&wsi->ws->act_ext_user[ + wsi->ws->count_act_ext], + (void *)&opts, 0)) { + lwsl_info("ext %s failed construction\n", + ext_name); + ext_count--; + ext++; + + continue; + } + + if (ext_count > 1) + *(*p)++ = ','; + else + LWS_CPYAPP(*p, + "\x0d\x0aSec-WebSocket-Extensions: "); + *p += lws_snprintf(*p, (end - *p), "%s", ext_name); + + /* + * The client may send a bunch of different option + * sets for the same extension, we are supposed to + * pick one we like the look of. The option sets are + * separated by comma. + * + * Actually we just either accept the first one or + * nothing. + * + * Go through the options trying to apply the + * recognized ones + */ + + lwsl_info("ext args %s\n", args); + + while (args && *args && *args != ',') { + while (*args == ' ') + args++; + po = opts; + while (po->name) { + /* only support arg-less options... */ + if (po->type != EXTARG_NONE || + strncmp(args, po->name, + strlen(po->name))) { + po++; + continue; + } + oa.option_name = NULL; + oa.option_index = (int)(po - opts); + oa.start = NULL; + oa.len = 0; + lwsl_info("setting '%s'\n", po->name); + if (!ext->callback(lws_get_context(wsi), + ext, wsi, + LWS_EXT_CB_OPTION_SET, + wsi->ws->act_ext_user[ + wsi->ws->count_act_ext], + &oa, (end - *p))) { + + *p += lws_snprintf(*p, (end - *p), + "; %s", po->name); + lwsl_debug("adding option %s\n", + po->name); + } + po++; + } + while (*args && *args != ',' && *args != ';') + args++; + + if (*args == ';') + args++; + } + + wsi->ws->count_act_ext++; + lwsl_parser("cnt_act_ext <- %d\n", wsi->ws->count_act_ext); + + if (args && *args == ',') + more = 0; + + ext++; + } + + n = 0; + args = NULL; + } + + return 0; +} +#endif + + + +int +lws_process_ws_upgrade(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char protocol_list[128], protocol_name[64], *p; + int protocol_len, hit, n = 0, non_space_char_found = 0; + + if (!wsi->protocol) + lwsl_err("NULL protocol at lws_read\n"); + + /* + * It's either websocket or h2->websocket + * + * Select the first protocol we support from the list + * the client sent us. + * + * Copy it to remove header fragmentation + */ + + if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, + WSI_TOKEN_PROTOCOL) < 0) { + lwsl_err("protocol list too long"); + return 1; + } + + protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + protocol_list[protocol_len] = '\0'; + p = protocol_list; + hit = 0; + + while (*p && !hit) { + n = 0; + non_space_char_found = 0; + while (n < (int)sizeof(protocol_name) - 1 && + *p && *p != ',') { + /* ignore leading spaces */ + if (!non_space_char_found && *p == ' ') { + n++; + continue; + } + non_space_char_found = 1; + protocol_name[n++] = *p++; + } + protocol_name[n] = '\0'; + if (*p) + p++; + + lwsl_debug("checking %s\n", protocol_name); + + n = 0; + while (wsi->vhost->protocols[n].callback) { + lwsl_debug("try %s\n", + wsi->vhost->protocols[n].name); + + if (wsi->vhost->protocols[n].name && + !strcmp(wsi->vhost->protocols[n].name, + protocol_name)) { + wsi->protocol = &wsi->vhost->protocols[n]; + hit = 1; + break; + } + + n++; + } + } + + /* we didn't find a protocol he wanted? */ + + if (!hit) { + if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { + lwsl_notice("No protocol from \"%s\" supported\n", + protocol_list); + return 1; + } + /* + * some clients only have one protocol and + * do not send the protocol list header... + * allow it and match to the vhost's default + * protocol (which itself defaults to zero) + */ + lwsl_info("defaulting to prot handler %d\n", + wsi->vhost->default_protocol_index); + n = wsi->vhost->default_protocol_index; + wsi->protocol = &wsi->vhost->protocols[ + (int)wsi->vhost->default_protocol_index]; + } + + /* allocate the ws struct for the wsi */ + wsi->ws = lws_zalloc(sizeof(*wsi->ws), "ws struct"); + if (!wsi->ws) { + lwsl_notice("OOM\n"); + return 1; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION)) + wsi->ws->ietf_spec_revision = + atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION)); + + /* allocate wsi->user storage */ + if (lws_ensure_user_space(wsi)) { + lwsl_notice("problem with user space\n"); + return 1; + } + + /* + * Give the user code a chance to study the request and + * have the opportunity to deny it + */ + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, + wsi->user_space, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { + lwsl_warn("User code denied connection\n"); + return 1; + } + + /* + * Perform the handshake according to the protocol version the + * client announced + */ + + switch (wsi->ws->ietf_spec_revision) { + default: + lwsl_notice("Unknown client spec version %d\n", + wsi->ws->ietf_spec_revision); + wsi->ws->ietf_spec_revision = 13; + //return 1; + /* fallthru */ + case 13: +#if defined(LWS_WITH_HTTP2) + if (wsi->h2_stream_carries_ws) { + if (lws_h2_ws_handshake(wsi)) { + lwsl_notice("h2 ws handshake failed\n"); + return 1; + } + } else +#endif + { + lwsl_parser("lws_parse calling handshake_04\n"); + if (handshake_0405(wsi->context, wsi)) { + lwsl_notice("hs0405 has failed the connection\n"); + return 1; + } + } + break; + } + + lws_same_vh_protocol_insert(wsi, n); + + /* + * We are upgrading to ws, so http/1.1 + h2 and keepalive + pipelined + * header considerations about keeping the ah around no longer apply. + * + * However it's common for the first ws protocol data to have been + * coalesced with the browser upgrade request and to already be in the + * ah rx buffer. + */ + + lws_pt_lock(pt, __func__); + + if (wsi->h2_stream_carries_ws) + lws_role_transition(wsi, LWSIFR_SERVER | LWSIFR_P_ENCAP_H2, + LRS_ESTABLISHED, &role_ops_ws); + else + lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED, + &role_ops_ws); + + lws_pt_unlock(pt); + + lws_server_init_wsi_for_ws(wsi); + lwsl_parser("accepted v%02d connection\n", wsi->ws->ietf_spec_revision); + + lwsl_info("%s: %p: dropping ah on ws upgrade\n", __func__, wsi); + lws_header_table_detach(wsi, 1); + + return 0; +} + +int +handshake_0405(struct lws_context *context, struct lws *wsi) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_process_html_args args; + unsigned char hash[20]; + int n, accept_len; + char *response; + char *p; + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) || + !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) { + lwsl_info("handshake_04 missing pieces\n"); + /* completed header processing, but missing some bits */ + goto bail; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >= MAX_WEBSOCKET_04_KEY_LEN) { + lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN); + goto bail; + } + + /* + * since key length is restricted above (currently 128), cannot + * overflow + */ + n = sprintf((char *)pt->serv_buf, + "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY)); + + lws_SHA1(pt->serv_buf, n, hash); + + accept_len = lws_b64_encode_string((char *)hash, 20, + (char *)pt->serv_buf, context->pt_serv_buf_size); + if (accept_len < 0) { + lwsl_warn("Base64 encoded hash too long\n"); + goto bail; + } + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) + goto bail; + + /* create the response packet */ + + /* make a buffer big enough for everything */ + + response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + 256 + LWS_PRE; + p = response; + LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Accept: "); + strcpy(p, (char *)pt->serv_buf); + p += accept_len; + + /* we can only return the protocol header if: + * - one came in, and ... */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && + /* - it is not an empty string */ + wsi->protocol->name && + wsi->protocol->name[0]) { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); + p += lws_snprintf(p, 128, "%s", wsi->protocol->name); + } + +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* + * Figure out which extensions the client has that we want to + * enable on this connection, and give him back the list. + * + * Give him a limited write bugdet + */ + if (lws_extension_server_handshake(wsi, &p, 192)) + goto bail; +#endif + LWS_CPYAPP(p, "\x0d\x0a"); + + args.p = p; + args.max_len = lws_ptr_diff((char *)pt->serv_buf + + context->pt_serv_buf_size, p); + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_ADD_HEADERS, + wsi->user_space, &args, 0)) + goto bail; + + p = args.p; + + /* end of response packet */ + + LWS_CPYAPP(p, "\x0d\x0a"); + + /* okay send the handshake response accepting the connection */ + + lwsl_parser("issuing resp pkt %d len\n", + lws_ptr_diff(p, response)); +#if defined(DEBUG) + fwrite(response, 1, p - response, stderr); +#endif + n = lws_write(wsi, (unsigned char *)response, p - response, + LWS_WRITE_HTTP_HEADERS); + if (n != (p - response)) { + lwsl_info("%s: ERROR writing to socket %d\n", __func__, n); + goto bail; + } + + /* alright clean up and set ourselves into established state */ + + lwsi_set_state(wsi, LRS_ESTABLISHED); + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + + { + const char * uri_ptr = + lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); + int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); + const struct lws_http_mount *hit = + lws_find_mount(wsi, uri_ptr, uri_len); + if (hit && hit->cgienv && + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, + wsi->user_space, (void *)hit->cgienv, 0)) + return 1; + } + + return 0; + +bail: + /* caller will free up his parsing allocations */ + return -1; +} + + + +/* + * Once we reach LWS_RXPS_WS_FRAME_PAYLOAD, we know how much + * to expect in that state and can deal with it in bulk more efficiently. + */ + +static int +lws_ws_frame_rest_is_payload(struct lws *wsi, uint8_t **buf, size_t len) +{ + uint8_t *buffer = *buf, mask[4]; + struct lws_tokens ebuf; + unsigned int avail = (unsigned int)len; +#if !defined(LWS_WITHOUT_EXTENSIONS) + unsigned int old_packet_length = (int)wsi->ws->rx_packet_length; +#endif + int n = 0; + + /* + * With zlib, we can give it as much input as we like. The pmd + * extension will draw it down in chunks (default 1024). + * + * If we try to restrict how much we give it, because we must go + * back to the event loop each time, we will drop the remainder... + */ + +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (!wsi->ws->count_act_ext) +#endif + { + if (wsi->protocol->rx_buffer_size) + avail = (int)wsi->protocol->rx_buffer_size; + else + avail = wsi->context->pt_serv_buf_size; + } + + /* do not consume more than we should */ + if (avail > wsi->ws->rx_packet_length) + avail = (unsigned int)wsi->ws->rx_packet_length; + + /* do not consume more than what is in the buffer */ + if (avail > len) + avail = (unsigned int)len; + + if (avail <= 0) + return 0; + + ebuf.token = (char *)buffer; + ebuf.len = avail; + + //lwsl_hexdump_notice(ebuf.token, ebuf.len); + + if (!wsi->ws->all_zero_nonce) { + + for (n = 0; n < 4; n++) + mask[n] = wsi->ws->mask[(wsi->ws->mask_idx + n) & 3]; + + /* deal with 4-byte chunks using unwrapped loop */ + n = avail >> 2; + while (n--) { + *(buffer) = *(buffer) ^ mask[0]; + buffer++; + *(buffer) = *(buffer) ^ mask[1]; + buffer++; + *(buffer) = *(buffer) ^ mask[2]; + buffer++; + *(buffer) = *(buffer) ^ mask[3]; + buffer++; + } + /* and the remaining bytes bytewise */ + for (n = 0; n < (int)(avail & 3); n++) { + *(buffer) = *(buffer) ^ mask[n]; + buffer++; + } + + wsi->ws->mask_idx = (wsi->ws->mask_idx + avail) & 3; + } + + lwsl_info("%s: using %d of raw input (total %d on offer)\n", __func__, + avail, (int)len); + + (*buf) += avail; + len -= avail; + +#if !defined(LWS_WITHOUT_EXTENSIONS) + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0); + lwsl_info("%s: ext says %d / ebuf.len %d\n", __func__, n, ebuf.len); +#endif + /* + * ebuf may be pointing somewhere completely different now, + * it's the output + */ + +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (n < 0) { + /* + * we may rely on this to get RX, just drop connection + */ + lwsl_notice("%s: LWS_EXT_CB_PAYLOAD_RX blew out\n", __func__); + wsi->socket_is_permanently_unusable = 1; + return -1; + } +#endif + + wsi->ws->rx_packet_length -= avail; + +#if !defined(LWS_WITHOUT_EXTENSIONS) + /* + * if we had an rx fragment right at the last compressed byte of the + * message, we can get a zero length inflated output, where no prior + * rx inflated output marked themselves with FIN, since there was + * raw ws payload still to drain at that time. + * + * Then we need to generate a zero length ws rx that can be understood + * as the message completion. + */ + + if (!ebuf.len && /* zero-length inflation output */ + !n && /* nothing left to drain from the inflator */ + wsi->ws->count_act_ext && /* we are using pmd */ + old_packet_length && /* we gave the inflator new input */ + !wsi->ws->rx_packet_length && /* raw ws packet payload all gone */ + wsi->ws->final && /* the raw ws packet is a FIN guy */ + wsi->protocol->callback && + !wsi->wsistate_pre_close) { + + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_RECEIVE, + wsi->user_space, NULL, 0)) + return -1; + + return avail; + } +#endif + + if (!ebuf.len) + return avail; + + if ( +#if !defined(LWS_WITHOUT_EXTENSIONS) + n && +#endif + ebuf.len) + /* extension had more... main loop will come back */ + lws_add_wsi_to_draining_ext_list(wsi); + else + lws_remove_wsi_from_draining_ext_list(wsi); + + if (wsi->ws->check_utf8 && !wsi->ws->defeat_check_utf8) { + if (lws_check_utf8(&wsi->ws->utf8, + (unsigned char *)ebuf.token, ebuf.len)) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"bad utf8", 8); + goto utf8_fail; + } + + /* we are ending partway through utf-8 character? */ + if (!wsi->ws->rx_packet_length && wsi->ws->final && + wsi->ws->utf8 && !n) { + lwsl_info("FINAL utf8 error\n"); + lws_close_reason(wsi, LWS_CLOSE_STATUS_INVALID_PAYLOAD, + (uint8_t *)"partial utf8", 12); + +utf8_fail: + lwsl_info("utf8 error\n"); + lwsl_hexdump_info(ebuf.token, ebuf.len); + + return -1; + } + } + + if (wsi->protocol->callback && !wsi->wsistate_pre_close) + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_RECEIVE, + wsi->user_space, + ebuf.token, ebuf.len)) + return -1; + + wsi->ws->first_fragment = 0; + +#if !defined(LWS_WITHOUT_EXTENSIONS) + lwsl_info("%s: input used %d, output %d, rem len %d, rx_draining_ext %d\n", + __func__, avail, ebuf.len, (int)len, wsi->ws->rx_draining_ext); +#endif + + return avail; /* how much we used from the input */ +} + + +int +lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m, bulk = 0; + + lwsl_debug("%s: received %d byte packet\n", __func__, (int)len); + + //lwsl_hexdump_notice(*buf, len); + + /* let the rx protocol state machine have as much as it needs */ + + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (wsi->rxflow_bitmap) { + lwsl_info("%s: doing rxflow\n", __func__); + lws_rxflow_cache(wsi, *buf, 0, (int)len); + lwsl_parser("%s: cached %ld\n", __func__, (long)len); + *buf += len; /* stashing it is taking care of it */ + return 1; + } +#if !defined(LWS_WITHOUT_EXTENSIONS) + if (wsi->ws->rx_draining_ext) { + lwsl_debug("%s: draining rx ext\n", __func__); + m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0); + if (m < 0) + return -1; + continue; + } +#endif + + /* consume payload bytes efficiently */ + while (wsi->lws_rx_parse_state == LWS_RXPS_WS_FRAME_PAYLOAD && + (wsi->ws->opcode == LWSWSOPC_TEXT_FRAME || + wsi->ws->opcode == LWSWSOPC_BINARY_FRAME || + wsi->ws->opcode == LWSWSOPC_CONTINUATION) && + len) { + uint8_t *bin = *buf; + + bulk = 1; + m = lws_ws_frame_rest_is_payload(wsi, buf, len); + assert((int)lws_ptr_diff(*buf, bin) <= (int)len); + len -= lws_ptr_diff(*buf, bin); + + if (!m) { + + break; + } + if (m < 0) { + lwsl_info("%s: rest_is_payload bailed\n", + __func__); + return -1; + } + } + + if (!bulk) { + /* process the byte */ + m = lws_ws_rx_sm(wsi, 0, *(*buf)++); + len--; + } else { + /* + * We already handled this byte in bulk, just deal + * with the ramifications + */ +#if !defined(LWS_WITHOUT_EXTENSIONS) + lwsl_debug("%s: coming out of bulk with len %d, " + "wsi->ws->rx_draining_ext %d\n", + __func__, (int)len, + wsi->ws->rx_draining_ext); +#endif + m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR | + ALREADY_PROCESSED_NO_CB, 0); + } + + if (m < 0) { + lwsl_info("%s: lws_ws_rx_sm bailed %d\n", __func__, + bulk); + + return -1; + } + + bulk = 0; + } + + lwsl_debug("%s: exit with %d unused\n", __func__, (int)len); + + return 0; +} diff --git a/thirdparty/libwebsockets/tls/mbedtls/lws-genhash.c b/thirdparty/libwebsockets/tls/mbedtls/lws-genhash.c new file mode 100644 index 0000000000..ce4ee6e382 --- /dev/null +++ b/thirdparty/libwebsockets/tls/mbedtls/lws-genhash.c @@ -0,0 +1,202 @@ +/* + * libwebsockets - generic hash and HMAC api hiding the backend + * + * Copyright (C) 2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * lws_genhash provides a hash / hmac abstraction api in lws that works the + * same whether you are using openssl or mbedtls hash functions underneath. + */ +#include "libwebsockets.h" +#include <mbedtls/version.h> + +#if (MBEDTLS_VERSION_NUMBER >= 0x02070000) +#define MBA(fn) fn##_ret +#else +#define MBA(fn) fn +#endif + +size_t +lws_genhash_size(enum lws_genhash_types type) +{ + switch(type) { + case LWS_GENHASH_TYPE_SHA1: + return 20; + case LWS_GENHASH_TYPE_SHA256: + return 32; + case LWS_GENHASH_TYPE_SHA384: + return 48; + case LWS_GENHASH_TYPE_SHA512: + return 64; + } + + return 0; +} + +int +lws_genhash_init(struct lws_genhash_ctx *ctx, enum lws_genhash_types type) +{ + ctx->type = type; + + switch (ctx->type) { + case LWS_GENHASH_TYPE_SHA1: + mbedtls_sha1_init(&ctx->u.sha1); + MBA(mbedtls_sha1_starts)(&ctx->u.sha1); + break; + case LWS_GENHASH_TYPE_SHA256: + mbedtls_sha256_init(&ctx->u.sha256); + MBA(mbedtls_sha256_starts)(&ctx->u.sha256, 0); + break; + case LWS_GENHASH_TYPE_SHA384: + mbedtls_sha512_init(&ctx->u.sha512); + MBA(mbedtls_sha512_starts)(&ctx->u.sha512, 1 /* is384 */); + break; + case LWS_GENHASH_TYPE_SHA512: + mbedtls_sha512_init(&ctx->u.sha512); + MBA(mbedtls_sha512_starts)(&ctx->u.sha512, 0); + break; + default: + return 1; + } + + return 0; +} + +int +lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len) +{ + switch (ctx->type) { + case LWS_GENHASH_TYPE_SHA1: + MBA(mbedtls_sha1_update)(&ctx->u.sha1, in, len); + break; + case LWS_GENHASH_TYPE_SHA256: + MBA(mbedtls_sha256_update)(&ctx->u.sha256, in, len); + break; + case LWS_GENHASH_TYPE_SHA384: + MBA(mbedtls_sha512_update)(&ctx->u.sha512, in, len); + break; + case LWS_GENHASH_TYPE_SHA512: + MBA(mbedtls_sha512_update)(&ctx->u.sha512, in, len); + break; + } + + return 0; +} + +int +lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result) +{ + switch (ctx->type) { + case LWS_GENHASH_TYPE_SHA1: + MBA(mbedtls_sha1_finish)(&ctx->u.sha1, result); + mbedtls_sha1_free(&ctx->u.sha1); + break; + case LWS_GENHASH_TYPE_SHA256: + MBA(mbedtls_sha256_finish)(&ctx->u.sha256, result); + mbedtls_sha256_free(&ctx->u.sha256); + break; + case LWS_GENHASH_TYPE_SHA384: + MBA(mbedtls_sha512_finish)(&ctx->u.sha512, result); + mbedtls_sha512_free(&ctx->u.sha512); + break; + case LWS_GENHASH_TYPE_SHA512: + MBA(mbedtls_sha512_finish)(&ctx->u.sha512, result); + mbedtls_sha512_free(&ctx->u.sha512); + break; + } + + return 0; +} + +size_t +lws_genhmac_size(enum lws_genhmac_types type) +{ + switch(type) { + case LWS_GENHMAC_TYPE_SHA256: + return 32; + case LWS_GENHMAC_TYPE_SHA384: + return 48; + case LWS_GENHMAC_TYPE_SHA512: + return 64; + } + + return 0; +} + +int +lws_genhmac_init(struct lws_genhmac_ctx *ctx, enum lws_genhmac_types type, + const uint8_t *key, size_t key_len) +{ + int t; + + ctx->type = type; + + switch (type) { + case LWS_GENHMAC_TYPE_SHA256: + t = MBEDTLS_MD_SHA256; + break; + case LWS_GENHMAC_TYPE_SHA384: + t = MBEDTLS_MD_SHA384; + break; + case LWS_GENHMAC_TYPE_SHA512: + t = MBEDTLS_MD_SHA512; + break; + default: + return -1; + } + + ctx->hmac = mbedtls_md_info_from_type(t); + if (!ctx->hmac) + return -1; + + if (mbedtls_md_init_ctx(&ctx->ctx, ctx->hmac)) + return -1; + + if (mbedtls_md_hmac_starts(&ctx->ctx, key, key_len)) { + mbedtls_md_free(&ctx->ctx); + ctx->hmac = NULL; + + return -1; + } + + return 0; +} + +int +lws_genhmac_update(struct lws_genhmac_ctx *ctx, const void *in, size_t len) +{ + if (mbedtls_md_hmac_update(&ctx->ctx, in, len)) + return -1; + + return 0; +} + +int +lws_genhmac_destroy(struct lws_genhmac_ctx *ctx, void *result) +{ + int n = 0; + + if (result) + n = mbedtls_md_hmac_finish(&ctx->ctx, result); + + mbedtls_md_free(&ctx->ctx); + ctx->hmac = NULL; + if (n) + return -1; + + return 0; +} diff --git a/thirdparty/libwebsockets/tls/mbedtls/lws-genrsa.c b/thirdparty/libwebsockets/tls/mbedtls/lws-genrsa.c new file mode 100644 index 0000000000..70a9fcf42c --- /dev/null +++ b/thirdparty/libwebsockets/tls/mbedtls/lws-genrsa.c @@ -0,0 +1,329 @@ +/* + * libwebsockets - generic RSA api hiding the backend + * + * Copyright (C) 2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * lws_genhash provides a hash / hmac abstraction api in lws that works the + * same whether you are using openssl or mbedtls hash functions underneath. + */ +#include "core/private.h" + +LWS_VISIBLE void +lws_jwk_destroy_genrsa_elements(struct lws_genrsa_elements *el) +{ + int n; + + for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) + if (el->e[n].buf) + lws_free_set_NULL(el->e[n].buf); +} + +LWS_VISIBLE int +lws_genrsa_create(struct lws_genrsa_ctx *ctx, struct lws_genrsa_elements *el) +{ + int n; + + memset(ctx, 0, sizeof(*ctx)); + ctx->ctx = lws_zalloc(sizeof(*ctx->ctx), "genrsa"); + if (!ctx->ctx) + return 1; + + mbedtls_rsa_init(ctx->ctx, MBEDTLS_RSA_PKCS_V15, 0); + + { + mbedtls_mpi *mpi[LWS_COUNT_RSA_ELEMENTS] = { + &ctx->ctx->E, &ctx->ctx->N, &ctx->ctx->D, &ctx->ctx->P, + &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, + &ctx->ctx->QP, + }; + + for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) + if (el->e[n].buf && + mbedtls_mpi_read_binary(mpi[n], el->e[n].buf, + el->e[n].len)) { + lwsl_notice("mpi load failed\n"); + lws_free_set_NULL(ctx->ctx); + + return -1; + } + } + + ctx->ctx->len = el->e[JWK_KEY_N].len; + + return 0; +} + +static int +_rngf(void *context, unsigned char *buf, size_t len) +{ + if ((size_t)lws_get_random(context, buf, len) == len) + return 0; + + return -1; +} + +LWS_VISIBLE int +lws_genrsa_new_keypair(struct lws_context *context, struct lws_genrsa_ctx *ctx, + struct lws_genrsa_elements *el, int bits) +{ + int n; + + memset(ctx, 0, sizeof(*ctx)); + ctx->ctx = lws_zalloc(sizeof(*ctx->ctx), "genrsa"); + if (!ctx->ctx) + return -1; + + mbedtls_rsa_init(ctx->ctx, MBEDTLS_RSA_PKCS_V15, 0); + + n = mbedtls_rsa_gen_key(ctx->ctx, _rngf, context, bits, 65537); + if (n) { + lwsl_err("mbedtls_rsa_gen_key failed 0x%x\n", -n); + goto cleanup_1; + } + + { + mbedtls_mpi *mpi[LWS_COUNT_RSA_ELEMENTS] = { + &ctx->ctx->E, &ctx->ctx->N, &ctx->ctx->D, &ctx->ctx->P, + &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, + &ctx->ctx->QP, + }; + + for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) + if (mbedtls_mpi_size(mpi[n])) { + el->e[n].buf = lws_malloc( + mbedtls_mpi_size(mpi[n]), "genrsakey"); + if (!el->e[n].buf) + goto cleanup; + el->e[n].len = mbedtls_mpi_size(mpi[n]); + mbedtls_mpi_write_binary(mpi[n], el->e[n].buf, + el->e[n].len); + } + } + + return 0; + +cleanup: + for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) + if (el->e[n].buf) + lws_free_set_NULL(el->e[n].buf); +cleanup_1: + lws_free(ctx->ctx); + + return -1; +} + +LWS_VISIBLE int +lws_genrsa_public_decrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out, size_t out_max) +{ + size_t olen = 0; + int n; + + ctx->ctx->len = in_len; + n = mbedtls_rsa_rsaes_pkcs1_v15_decrypt(ctx->ctx, NULL, NULL, + MBEDTLS_RSA_PUBLIC, + &olen, in, out, out_max); + if (n) { + lwsl_notice("%s: -0x%x\n", __func__, -n); + + return -1; + } + + return olen; +} + +LWS_VISIBLE int +lws_genrsa_public_encrypt(struct lws_genrsa_ctx *ctx, const uint8_t *in, + size_t in_len, uint8_t *out) +{ + int n; + + ctx->ctx->len = in_len; + n = mbedtls_rsa_rsaes_pkcs1_v15_encrypt(ctx->ctx, NULL, NULL, + MBEDTLS_RSA_PRIVATE, + in_len, in, out); + if (n) { + lwsl_notice("%s: -0x%x\n", __func__, -n); + + return -1; + } + + return 0; +} + +static int +lws_genrsa_genrsa_hash_to_mbed_hash(enum lws_genhash_types hash_type) +{ + int h = -1; + + switch (hash_type) { + case LWS_GENHASH_TYPE_SHA1: + h = MBEDTLS_MD_SHA1; + break; + case LWS_GENHASH_TYPE_SHA256: + h = MBEDTLS_MD_SHA256; + break; + case LWS_GENHASH_TYPE_SHA384: + h = MBEDTLS_MD_SHA384; + break; + case LWS_GENHASH_TYPE_SHA512: + h = MBEDTLS_MD_SHA512; + break; + } + + return h; +} + +LWS_VISIBLE int +lws_genrsa_public_verify(struct lws_genrsa_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, const uint8_t *sig, + size_t sig_len) +{ + int n, h = lws_genrsa_genrsa_hash_to_mbed_hash(hash_type); + + if (h < 0) + return -1; + + n = mbedtls_rsa_rsassa_pkcs1_v15_verify(ctx->ctx, NULL, NULL, + MBEDTLS_RSA_PUBLIC, + h, 0, in, sig); + if (n < 0) { + lwsl_notice("%s: -0x%x\n", __func__, -n); + + return -1; + } + + return n; +} + +LWS_VISIBLE int +lws_genrsa_public_sign(struct lws_genrsa_ctx *ctx, const uint8_t *in, + enum lws_genhash_types hash_type, uint8_t *sig, + size_t sig_len) +{ + int n, h = lws_genrsa_genrsa_hash_to_mbed_hash(hash_type); + + if (h < 0) + return -1; + + /* + * The "sig" buffer must be as large as the size of ctx->N + * (eg. 128 bytes if RSA-1024 is used). + */ + if (sig_len < ctx->ctx->len) + return -1; + + n = mbedtls_rsa_rsassa_pkcs1_v15_sign(ctx->ctx, NULL, NULL, + MBEDTLS_RSA_PRIVATE, h, 0, in, + sig); + if (n < 0) { + lwsl_notice("%s: -0x%x\n", __func__, -n); + + return -1; + } + + return ctx->ctx->len; +} + +LWS_VISIBLE int +lws_genrsa_render_pkey_asn1(struct lws_genrsa_ctx *ctx, int _private, + uint8_t *pkey_asn1, size_t pkey_asn1_len) +{ + uint8_t *p = pkey_asn1, *totlen, *end = pkey_asn1 + pkey_asn1_len - 1; + mbedtls_mpi *mpi[LWS_COUNT_RSA_ELEMENTS] = { + &ctx->ctx->N, &ctx->ctx->E, &ctx->ctx->D, &ctx->ctx->P, + &ctx->ctx->Q, &ctx->ctx->DP, &ctx->ctx->DQ, + &ctx->ctx->QP, + }; + int n; + + /* 30 82 - sequence + * 09 29 <-- length(0x0929) less 4 bytes + * 02 01 <- length (1) + * 00 + * 02 82 + * 02 01 <- length (513) N + * ... + * + * 02 03 <- length (3) E + * 01 00 01 + * + * 02 82 + * 02 00 <- length (512) D P Q EXP1 EXP2 COEFF + * + * */ + + *p++ = 0x30; + *p++ = 0x82; + totlen = p; + p += 2; + + *p++ = 0x02; + *p++ = 0x01; + *p++ = 0x00; + + for (n = 0; n < LWS_COUNT_RSA_ELEMENTS; n++) { + int m = mbedtls_mpi_size(mpi[n]); + uint8_t *elen; + + *p++ = 0x02; + elen = p; + if (m < 0x7f) + *p++ = m; + else { + *p++ = 0x82; + *p++ = m >> 8; + *p++ = m & 0xff; + } + + if (p + m > end) + return -1; + + mbedtls_mpi_write_binary(mpi[n], p, m); + if (p[0] & 0x80) { + p[0] = 0x00; + mbedtls_mpi_write_binary(mpi[n], &p[1], m); + m++; + } + if (m < 0x7f) + *elen = m; + else { + *elen++ = 0x82; + *elen++ = m >> 8; + *elen = m & 0xff; + } + p += m; + } + + n = lws_ptr_diff(p, pkey_asn1); + + *totlen++ = (n - 4) >> 8; + *totlen = (n - 4) & 0xff; + + return n; +} + +LWS_VISIBLE void +lws_genrsa_destroy(struct lws_genrsa_ctx *ctx) +{ + if (!ctx->ctx) + return; + mbedtls_rsa_free(ctx->ctx); + lws_free(ctx->ctx); + ctx->ctx = NULL; +} diff --git a/thirdparty/libwebsockets/tls/mbedtls/mbedtls-client.c b/thirdparty/libwebsockets/tls/mbedtls/mbedtls-client.c new file mode 100644 index 0000000000..a7864ab790 --- /dev/null +++ b/thirdparty/libwebsockets/tls/mbedtls/mbedtls-client.c @@ -0,0 +1,240 @@ +/* + * libwebsockets - mbedtls-specific client TLS code + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +static int +OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + return 0; +} + +int +lws_ssl_client_bio_create(struct lws *wsi) +{ + X509_VERIFY_PARAM *param; + char hostname[128], *p; + const char *alpn_comma = wsi->context->tls.alpn_default; + struct alpn_ctx protos; + + if (lws_hdr_copy(wsi, hostname, sizeof(hostname), + _WSI_TOKEN_CLIENT_HOST) <= 0) { + lwsl_err("%s: Unable to get hostname\n", __func__); + + return -1; + } + + /* + * remove any :port part on the hostname... necessary for network + * connection but typical certificates do not contain it + */ + p = hostname; + while (*p) { + if (*p == ':') { + *p = '\0'; + break; + } + p++; + } + + wsi->tls.ssl = SSL_new(wsi->vhost->tls.ssl_client_ctx); + if (!wsi->tls.ssl) + return -1; + + if (wsi->vhost->tls.ssl_info_event_mask) + SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback); + + if (!(wsi->tls.use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) { + param = SSL_get0_param(wsi->tls.ssl); + /* Enable automatic hostname checks */ + // X509_VERIFY_PARAM_set_hostflags(param, + // X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + X509_VERIFY_PARAM_set1_host(param, hostname, 0); + } + + if (wsi->vhost->tls.alpn) + alpn_comma = wsi->vhost->tls.alpn; + + if (lws_hdr_copy(wsi, hostname, sizeof(hostname), + _WSI_TOKEN_CLIENT_ALPN) > 0) + alpn_comma = hostname; + + lwsl_info("%s: %p: client conn sending ALPN list '%s'\n", + __func__, wsi, alpn_comma); + + protos.len = lws_alpn_comma_to_openssl(alpn_comma, protos.data, + sizeof(protos.data) - 1); + + /* with mbedtls, protos is not pointed to after exit from this call */ + SSL_set_alpn_select_cb(wsi->tls.ssl, &protos); + + /* + * use server name indication (SNI), if supported, + * when establishing connection + */ + SSL_set_verify(wsi->tls.ssl, SSL_VERIFY_PEER, + OpenSSL_client_verify_callback); + + SSL_set_fd(wsi->tls.ssl, wsi->desc.sockfd); + + return 0; +} + +int ERR_get_error(void) +{ + return 0; +} + +enum lws_ssl_capable_status +lws_tls_client_connect(struct lws *wsi) +{ + int m, n = SSL_connect(wsi->tls.ssl); + const unsigned char *prot; + unsigned int len; + + if (n == 1) { + SSL_get0_alpn_selected(wsi->tls.ssl, &prot, &len); + lws_role_call_alpn_negotiated(wsi, (const char *)prot); + lwsl_info("client connect OK\n"); + return LWS_SSL_CAPABLE_DONE; + } + + m = SSL_get_error(wsi->tls.ssl, n); + + if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) + return LWS_SSL_CAPABLE_MORE_SERVICE_READ; + + if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) + return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; + + if (!n) /* we don't know what he wants, but he says to retry */ + return LWS_SSL_CAPABLE_MORE_SERVICE; + + return LWS_SSL_CAPABLE_ERROR; +} + +int +lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, int ebuf_len) +{ + int n; + X509 *peer = SSL_get_peer_certificate(wsi->tls.ssl); + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char *sb = (char *)&pt->serv_buf[0]; + + if (!peer) { + lwsl_info("peer did not provide cert\n"); + + return -1; + } + lwsl_info("peer provided cert\n"); + + n = SSL_get_verify_result(wsi->tls.ssl); + lws_latency(wsi->context, wsi, + "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); + + lwsl_debug("get_verify says %d\n", n); + + if (n == X509_V_OK) + return 0; + + if (n == X509_V_ERR_HOSTNAME_MISMATCH && + (wsi->tls.use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) { + lwsl_info("accepting certificate for invalid hostname\n"); + return 0; + } + + if (n == X509_V_ERR_INVALID_CA && + (wsi->tls.use_ssl & LCCSCF_ALLOW_SELFSIGNED)) { + lwsl_info("accepting certificate from untrusted CA\n"); + return 0; + } + + if ((n == X509_V_ERR_CERT_NOT_YET_VALID || + n == X509_V_ERR_CERT_HAS_EXPIRED) && + (wsi->tls.use_ssl & LCCSCF_ALLOW_EXPIRED)) { + lwsl_info("accepting expired or not yet valid certificate\n"); + + return 0; + } + lws_snprintf(ebuf, ebuf_len, + "server's cert didn't look good, X509_V_ERR = %d: %s\n", + n, ERR_error_string(n, sb)); + lwsl_info("%s\n", ebuf); + lws_ssl_elaborate_error(); + + return -1; +} + +int +lws_tls_client_create_vhost_context(struct lws_vhost *vh, + const struct lws_context_creation_info *info, + const char *cipher_list, + const char *ca_filepath, + const char *cert_filepath, + const char *private_key_filepath) +{ + X509 *d2i_X509(X509 **cert, const unsigned char *buffer, long len); + SSL_METHOD *method = (SSL_METHOD *)TLS_client_method(); + unsigned long error; + lws_filepos_t len; + uint8_t *buf; + + if (!method) { + error = ERR_get_error(); + lwsl_err("problem creating ssl method %lu: %s\n", + error, ERR_error_string(error, + (char *)vh->context->pt[0].serv_buf)); + return 1; + } + /* create context */ + vh->tls.ssl_client_ctx = SSL_CTX_new(method); + if (!vh->tls.ssl_client_ctx) { + error = ERR_get_error(); + lwsl_err("problem creating ssl context %lu: %s\n", + error, ERR_error_string(error, + (char *)vh->context->pt[0].serv_buf)); + return 1; + } + + if (!ca_filepath) + return 0; + + if (alloc_file(vh->context, ca_filepath, &buf, &len)) { + lwsl_err("Load CA cert file %s failed\n", ca_filepath); + return 1; + } + + vh->tls.x509_client_CA = d2i_X509(NULL, buf, len); + free(buf); + if (!vh->tls.x509_client_CA) { + lwsl_err("client CA: x509 parse failed\n"); + return 1; + } + + if (!vh->tls.ssl_ctx) + SSL_CTX_add_client_CA(vh->tls.ssl_client_ctx, vh->tls.x509_client_CA); + else + SSL_CTX_add_client_CA(vh->tls.ssl_ctx, vh->tls.x509_client_CA); + + lwsl_notice("client loaded CA for verification %s\n", ca_filepath); + + return 0; +} diff --git a/thirdparty/libwebsockets/tls/mbedtls/mbedtls-server.c b/thirdparty/libwebsockets/tls/mbedtls/mbedtls-server.c new file mode 100644 index 0000000000..2de6d422e3 --- /dev/null +++ b/thirdparty/libwebsockets/tls/mbedtls/mbedtls-server.c @@ -0,0 +1,694 @@ +/* + * libwebsockets - mbedTLS-specific server functions + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" +#include <mbedtls/x509_csr.h> + +int +lws_tls_server_client_cert_verify_config(struct lws_vhost *vh) +{ + int verify_options = SSL_VERIFY_PEER; + + /* as a server, are we requiring clients to identify themselves? */ + if (!lws_check_opt(vh->options, + LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) { + lwsl_notice("no client cert required\n"); + return 0; + } + + /* + * The wrapper has this messed-up mapping: + * + * else if (ctx->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT) + * mode = MBEDTLS_SSL_VERIFY_OPTIONAL; + * + * ie the meaning is inverted. So where we should test for ! we don't + */ + if (lws_check_opt(vh->options, LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED)) + verify_options = SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + lwsl_notice("%s: vh %s requires client cert %d\n", __func__, vh->name, + verify_options); + + SSL_CTX_set_verify(vh->tls.ssl_ctx, verify_options, NULL); + + return 0; +} + +static int +lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx, + const unsigned char *servername, size_t len) +{ + SSL *ssl = SSL_SSL_from_mbedtls_ssl_context(mbedtls_ctx); + struct lws_context *context = (struct lws_context *)arg; + struct lws_vhost *vhost, *vh; + + lwsl_notice("%s: %s\n", __func__, servername); + + /* + * We can only get ssl accepted connections by using a vhost's ssl_ctx + * find out which listening one took us and only match vhosts on the + * same port. + */ + vh = context->vhost_list; + while (vh) { + if (!vh->being_destroyed && + vh->tls.ssl_ctx == SSL_get_SSL_CTX(ssl)) + break; + vh = vh->vhost_next; + } + + if (!vh) { + assert(vh); /* can't match the incoming vh? */ + return 0; + } + + vhost = lws_select_vhost(context, vh->listen_port, + (const char *)servername); + if (!vhost) { + lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port); + + return 0; + } + + lwsl_info("SNI: Found: %s:%d at vhost '%s'\n", servername, + vh->listen_port, vhost->name); + + /* select the ssl ctx from the selected vhost for this conn */ + SSL_set_SSL_CTX(ssl, vhost->tls.ssl_ctx); + + return 0; +} + +int +lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi, + const char *cert, const char *private_key, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t mem_privkey_len) +{ + int n, f = 0; + const char *filepath = private_key; + uint8_t *mem = NULL, *p = NULL; + size_t mem_len = 0; + lws_filepos_t flen; + long err; + + if ((!cert || !private_key) && (!mem_cert || !mem_privkey)) { + lwsl_notice("%s: no usable input\n", __func__); + return 0; + } + + n = lws_tls_generic_cert_checks(vhost, cert, private_key); + + if (n == LWS_TLS_EXTANT_NO && (!mem_cert || !mem_privkey)) + return 0; + + /* + * we can't read the root-privs files. But if mem_cert is provided, + * we should use that. + */ + if (n == LWS_TLS_EXTANT_NO) + n = LWS_TLS_EXTANT_ALTERNATIVE; + + if (n == LWS_TLS_EXTANT_ALTERNATIVE && (!mem_cert || !mem_privkey)) + return 1; /* no alternative */ + + if (n == LWS_TLS_EXTANT_ALTERNATIVE) { + /* + * Although we have prepared update certs, we no longer have + * the rights to read our own cert + key we saved. + * + * If we were passed copies in memory buffers, use those + * instead. + * + * The passed memory-buffer cert image is in DER, and the + * memory-buffer private key image is PEM. + */ + /* mem cert is already DER */ + p = (uint8_t *)mem_cert; + flen = len_mem_cert; + /* mem private key is PEM, so go through the motions */ + mem = (uint8_t *)mem_privkey; + mem_len = mem_privkey_len; + filepath = NULL; + } else { + if (lws_tls_alloc_pem_to_der_file(vhost->context, cert, NULL, + 0, &p, &flen)) { + lwsl_err("couldn't find cert file %s\n", cert); + + return 1; + } + f = 1; + } + err = SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx, flen, p); + if (!err) { + free(p); + lwsl_err("Problem loading cert\n"); + return 1; + } + + if (f) + free(p); + p = NULL; + + if (private_key || n == LWS_TLS_EXTANT_ALTERNATIVE) { + if (lws_tls_alloc_pem_to_der_file(vhost->context, filepath, + (char *)mem, mem_len, &p, + &flen)) { + lwsl_err("couldn't find private key file %s\n", + private_key); + + return 1; + } + err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->tls.ssl_ctx, p, flen); + if (!err) { + free(p); + lwsl_err("Problem loading key\n"); + + return 1; + } + } + + if (p && !mem_privkey) { + free(p); + p = NULL; + } + + if (!private_key && !mem_privkey && + vhost->protocols[0].callback(wsi, + LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY, + vhost->tls.ssl_ctx, NULL, 0)) { + lwsl_err("ssl private key not set\n"); + + return 1; + } + + vhost->tls.skipped_certs = 0; + + return 0; +} + +int +lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info, + struct lws_vhost *vhost, struct lws *wsi) +{ + const SSL_METHOD *method = TLS_server_method(); + uint8_t *p; + lws_filepos_t flen; + int n; + + vhost->tls.ssl_ctx = SSL_CTX_new(method); /* create context */ + if (!vhost->tls.ssl_ctx) { + lwsl_err("problem creating ssl context\n"); + return 1; + } + + if (!vhost->tls.use_ssl || !info->ssl_cert_filepath) + return 0; + + if (info->ssl_ca_filepath) { + lwsl_notice("%s: vh %s: loading CA filepath %s\n", __func__, + vhost->name, info->ssl_ca_filepath); + if (lws_tls_alloc_pem_to_der_file(vhost->context, + info->ssl_ca_filepath, NULL, 0, &p, &flen)) { + lwsl_err("couldn't find client CA file %s\n", + info->ssl_ca_filepath); + + return 1; + } + + if (SSL_CTX_add_client_CA_ASN1(vhost->tls.ssl_ctx, (int)flen, p) != 1) { + lwsl_err("%s: SSL_CTX_add_client_CA_ASN1 unhappy\n", + __func__); + free(p); + return 1; + } + free(p); + } + + n = lws_tls_server_certs_load(vhost, wsi, info->ssl_cert_filepath, + info->ssl_private_key_filepath, NULL, + 0, NULL, 0); + if (n) + return n; + + return 0; +} + +int +lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd) +{ + errno = 0; + wsi->tls.ssl = SSL_new(wsi->vhost->tls.ssl_ctx); + if (wsi->tls.ssl == NULL) { + lwsl_err("SSL_new failed: errno %d\n", errno); + + lws_ssl_elaborate_error(); + return 1; + } + + SSL_set_fd(wsi->tls.ssl, accept_fd); + + if (wsi->vhost->tls.ssl_info_event_mask) + SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback); + + SSL_set_sni_callback(wsi->tls.ssl, lws_mbedtls_sni_cb, wsi->context); + + return 0; +} + +int +lws_tls_server_abort_connection(struct lws *wsi) +{ + __lws_tls_shutdown(wsi); + SSL_free(wsi->tls.ssl); + + return 0; +} + +enum lws_ssl_capable_status +lws_tls_server_accept(struct lws *wsi) +{ + union lws_tls_cert_info_results ir; + int m, n; + + n = SSL_accept(wsi->tls.ssl); + if (n == 1) { + + if (strstr(wsi->vhost->name, ".invalid")) { + lwsl_notice("%s: vhost has .invalid, rejecting accept\n", __func__); + + return LWS_SSL_CAPABLE_ERROR; + } + + n = lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME, &ir, + sizeof(ir.ns.name)); + if (!n) + lwsl_notice("%s: client cert CN '%s'\n", + __func__, ir.ns.name); + else + lwsl_info("%s: couldn't get client cert CN\n", __func__); + return LWS_SSL_CAPABLE_DONE; + } + + m = SSL_get_error(wsi->tls.ssl, n); + lwsl_debug("%s: %p: accept SSL_get_error %d errno %d\n", __func__, + wsi, m, errno); + + // mbedtls wrapper only + if (m == SSL_ERROR_SYSCALL && errno == 11) + return LWS_SSL_CAPABLE_MORE_SERVICE_READ; + + if (m == SSL_ERROR_SYSCALL || m == SSL_ERROR_SSL) + return LWS_SSL_CAPABLE_ERROR; + + if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) { + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + lwsl_info("%s: WANT_READ change_pollfd failed\n", __func__); + return LWS_SSL_CAPABLE_ERROR; + } + + lwsl_info("SSL_ERROR_WANT_READ\n"); + return LWS_SSL_CAPABLE_MORE_SERVICE_READ; + } + if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) { + lwsl_debug("%s: WANT_WRITE\n", __func__); + + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { + lwsl_info("%s: WANT_WRITE change_pollfd failed\n", __func__); + return LWS_SSL_CAPABLE_ERROR; + } + return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; + } + + return LWS_SSL_CAPABLE_ERROR; +} + +#if defined(LWS_WITH_ACME) +/* + * mbedtls doesn't support SAN for cert creation. So we use a known-good + * tls-sni-01 cert from OpenSSL that worked on Let's Encrypt, and just replace + * the pubkey n part and the signature part. + * + * This will need redoing for tls-sni-02... + */ + +static uint8_t ss_cert_leadin[] = { + 0x30, 0x82, + 0x05, 0x56, /* total length: LEN1 (+2 / +3) (correct for 513 + 512)*/ + + 0x30, 0x82, /* length: LEN2 (+6 / +7) (correct for 513) */ + 0x03, 0x3e, + + /* addition: v3 cert (+5 bytes)*/ + 0xa0, 0x03, + 0x02, 0x01, 0x02, + + 0x02, 0x01, 0x01, + 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x3f, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, + 0x42, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0b, + 0x73, 0x6f, 0x6d, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x31, + 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x11, 0x74, 0x65, + 0x6d, 0x70, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, 0x6e, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x30, 0x1e, 0x17, 0x0d, + + /* from 2017-10-29 ... */ + 0x31, 0x37, 0x31, 0x30, 0x32, 0x39, 0x31, 0x31, 0x34, 0x39, 0x34, 0x35, + 0x5a, 0x17, 0x0d, + + /* thru 2049-10-29 we immediately discard the private key, no worries */ + 0x34, 0x39, 0x31, 0x30, 0x32, 0x39, 0x31, 0x32, 0x34, 0x39, 0x34, 0x35, + 0x5a, + + 0x30, 0x3f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x47, 0x42, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x0b, 0x73, 0x6f, 0x6d, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x79, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x11, + 0x74, 0x65, 0x6d, 0x70, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, 0x6e, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x30, + + 0x82, + 0x02, 0x22, /* LEN3 (+C3 / C4) */ + 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, + 0x03, + + 0x82, + 0x02, 0x0f, /* LEN4 (+D6 / D7) */ + + 0x00, 0x30, 0x82, + + 0x02, 0x0a, /* LEN5 (+ DB / DC) */ + + 0x02, 0x82, + + //0x02, 0x01, /* length of n in bytes (including leading 00 if any) */ + }, + + /* 1 + (keybits / 8) bytes N */ + + ss_cert_san_leadin[] = { + /* e - fixed */ + 0x02, 0x03, 0x01, 0x00, 0x01, + + 0xa3, 0x5d, 0x30, 0x5b, 0x30, 0x59, 0x06, 0x03, 0x55, 0x1d, + 0x11, 0x04, 0x52, 0x30, 0x50, /* <-- SAN length + 2 */ + + 0x82, 0x4e, /* <-- SAN length */ + }, + + /* 78 bytes of SAN (tls-sni-01) + 0x61, 0x64, 0x34, 0x31, 0x61, 0x66, 0x62, 0x65, 0x30, 0x63, 0x61, 0x34, + 0x36, 0x34, 0x32, 0x66, 0x30, 0x61, 0x34, 0x34, 0x39, 0x64, 0x39, 0x63, + 0x61, 0x37, 0x36, 0x65, 0x62, 0x61, 0x61, 0x62, 0x2e, 0x32, 0x38, 0x39, + 0x34, 0x64, 0x34, 0x31, 0x36, 0x63, 0x39, 0x38, 0x33, 0x66, 0x31, 0x32, + 0x65, 0x64, 0x37, 0x33, 0x31, 0x61, 0x33, 0x30, 0x66, 0x35, 0x63, 0x34, + 0x34, 0x37, 0x37, 0x66, 0x65, 0x2e, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x69, + 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, */ + + /* end of LEN2 area */ + + ss_cert_sig_leadin[] = { + /* it's saying that the signature is SHA256 + RSA */ + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, + + 0x82, + 0x02, 0x01, + 0x00, + }; + + /* (keybits / 8) bytes signature to end of LEN1 area */ + +#define SAN_A_LENGTH 78 + +LWS_VISIBLE int +lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, + const char *san_b) +{ + int buflen = 0x560; + uint8_t *buf = lws_malloc(buflen, "tmp cert buf"), *p = buf, *pkey_asn1; + struct lws_genrsa_ctx ctx; + struct lws_genrsa_elements el; + uint8_t digest[32]; + struct lws_genhash_ctx hash_ctx; + int pkey_asn1_len = 3 * 1024; + int n, m, keybits = lws_plat_recommended_rsa_bits(), adj; + + if (!buf) + return 1; + + n = lws_genrsa_new_keypair(vhost->context, &ctx, &el, keybits); + if (n < 0) { + lws_jwk_destroy_genrsa_elements(&el); + goto bail1; + } + + n = sizeof(ss_cert_leadin); + memcpy(p, ss_cert_leadin, n); + p += n; + + adj = (0x0556 - 0x401) + (keybits / 4) + 1; + buf[2] = adj >> 8; + buf[3] = adj & 0xff; + + adj = (0x033e - 0x201) + (keybits / 8) + 1; + buf[6] = adj >> 8; + buf[7] = adj & 0xff; + + adj = (0x0222 - 0x201) + (keybits / 8) + 1; + buf[0xc3] = adj >> 8; + buf[0xc4] = adj & 0xff; + + adj = (0x020f - 0x201) + (keybits / 8) + 1; + buf[0xd6] = adj >> 8; + buf[0xd7] = adj & 0xff; + + adj = (0x020a - 0x201) + (keybits / 8) + 1; + buf[0xdb] = adj >> 8; + buf[0xdc] = adj & 0xff; + + *p++ = ((keybits / 8) + 1) >> 8; + *p++ = ((keybits / 8) + 1) & 0xff; + + /* we need to drop 1 + (keybits / 8) bytes of n in here, 00 + key */ + + *p++ = 0x00; + memcpy(p, el.e[JWK_KEY_N].buf, el.e[JWK_KEY_N].len); + p += el.e[JWK_KEY_N].len; + + memcpy(p, ss_cert_san_leadin, sizeof(ss_cert_san_leadin)); + p += sizeof(ss_cert_san_leadin); + + /* drop in 78 bytes of san_a */ + + memcpy(p, san_a, SAN_A_LENGTH); + p += SAN_A_LENGTH; + memcpy(p, ss_cert_sig_leadin, sizeof(ss_cert_sig_leadin)); + + p[17] = ((keybits / 8) + 1) >> 8; + p[18] = ((keybits / 8) + 1) & 0xff; + + p += sizeof(ss_cert_sig_leadin); + + /* hash the cert plaintext */ + + if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256)) + goto bail2; + + if (lws_genhash_update(&hash_ctx, buf, lws_ptr_diff(p, buf))) { + lws_genhash_destroy(&hash_ctx, NULL); + + goto bail2; + } + if (lws_genhash_destroy(&hash_ctx, digest)) + goto bail2; + + /* sign the hash */ + + n = lws_genrsa_public_sign(&ctx, digest, LWS_GENHASH_TYPE_SHA256, p, + buflen - lws_ptr_diff(p, buf)); + if (n < 0) + goto bail2; + p += n; + + pkey_asn1 = lws_malloc(pkey_asn1_len, "mbed crt tmp"); + if (!pkey_asn1) + goto bail2; + + m = lws_genrsa_render_pkey_asn1(&ctx, 1, pkey_asn1, pkey_asn1_len); + if (m < 0) { + lws_free(pkey_asn1); + goto bail2; + } + +// lwsl_hexdump_level(LLL_DEBUG, buf, lws_ptr_diff(p, buf)); + n = SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx, + lws_ptr_diff(p, buf), buf); + if (n != 1) { + lws_free(pkey_asn1); + lwsl_err("%s: generated cert failed to load 0x%x\n", + __func__, -n); + } else { + //lwsl_debug("private key\n"); + //lwsl_hexdump_level(LLL_DEBUG, pkey_asn1, n); + + /* and to use our generated private key */ + n = SSL_CTX_use_PrivateKey_ASN1(0, vhost->tls.ssl_ctx, pkey_asn1, m); + lws_free(pkey_asn1); + if (n != 1) { + lwsl_err("%s: SSL_CTX_use_PrivateKey_ASN1 failed\n", + __func__); + } + } + + lws_genrsa_destroy(&ctx); + lws_jwk_destroy_genrsa_elements(&el); + + lws_free(buf); + + return n != 1; + +bail2: + lws_genrsa_destroy(&ctx); + lws_jwk_destroy_genrsa_elements(&el); +bail1: + lws_free(buf); + + return -1; +} + +void +lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost) +{ +} + +#if defined(LWS_WITH_JWS) +static int +_rngf(void *context, unsigned char *buf, size_t len) +{ + if ((size_t)lws_get_random(context, buf, len) == len) + return 0; + + return -1; +} + +static const char *x5[] = { "C", "ST", "L", "O", "CN" }; + +/* + * CSR is output formatted as b64url(DER) + * Private key is output as a PEM in memory + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[], + uint8_t *dcsr, size_t csr_len, char **privkey_pem, + size_t *privkey_len) +{ + mbedtls_x509write_csr csr; + mbedtls_pk_context mpk; + int buf_size = 4096, n; + char subject[200], *p = subject, *end = p + sizeof(subject) - 1; + uint8_t *buf = malloc(buf_size); /* malloc because given to user code */ + + if (!buf) + return -1; + + mbedtls_x509write_csr_init(&csr); + + mbedtls_pk_init(&mpk); + if (mbedtls_pk_setup(&mpk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA))) { + lwsl_notice("%s: pk_setup failed\n", __func__); + goto fail; + } + + n = mbedtls_rsa_gen_key(mbedtls_pk_rsa(mpk), _rngf, context, + lws_plat_recommended_rsa_bits(), 65537); + if (n) { + lwsl_notice("%s: failed to generate keys\n", __func__); + + goto fail1; + } + + /* subject must be formatted like "C=TW,O=warmcat,CN=myserver" */ + + for (n = 0; n < (int)ARRAY_SIZE(x5); n++) { + if (p != subject) + *p++ = ','; + if (elements[n]) + p += lws_snprintf(p, end - p, "%s=%s", x5[n], + elements[n]); + } + + if (mbedtls_x509write_csr_set_subject_name(&csr, subject)) + goto fail1; + + mbedtls_x509write_csr_set_key(&csr, &mpk); + mbedtls_x509write_csr_set_md_alg(&csr, MBEDTLS_MD_SHA256); + + /* + * data is written at the end of the buffer! Use the + * return value to determine where you should start + * using the buffer + */ + n = mbedtls_x509write_csr_der(&csr, buf, buf_size, _rngf, context); + if (n < 0) { + lwsl_notice("%s: write csr der failed\n", __func__); + goto fail1; + } + + /* we have it in DER, we need it in b64URL */ + + n = lws_jws_base64_enc((char *)(buf + buf_size) - n, n, + (char *)dcsr, csr_len); + if (n < 0) + goto fail1; + + /* + * okay, the CSR is done, last we need the private key in PEM + * re-use the DER CSR buf as the result buffer since we cn do it in + * one step + */ + + if (mbedtls_pk_write_key_pem(&mpk, buf, buf_size)) { + lwsl_notice("write key pem failed\n"); + goto fail1; + } + + *privkey_pem = (char *)buf; + *privkey_len = strlen((const char *)buf); + + mbedtls_pk_free(&mpk); + mbedtls_x509write_csr_free(&csr); + + return n; + +fail1: + mbedtls_pk_free(&mpk); +fail: + mbedtls_x509write_csr_free(&csr); + free(buf); + + return -1; +} +#endif +#endif diff --git a/thirdparty/libwebsockets/tls/mbedtls/ssl.c b/thirdparty/libwebsockets/tls/mbedtls/ssl.c new file mode 100644 index 0000000000..6ae9d2556b --- /dev/null +++ b/thirdparty/libwebsockets/tls/mbedtls/ssl.c @@ -0,0 +1,520 @@ +/* + * libwebsockets - mbedTLS-specific lws apis + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" +#include <mbedtls/oid.h> + +void +lws_ssl_elaborate_error(void) +{ +} + +int +lws_context_init_ssl_library(const struct lws_context_creation_info *info) +{ + lwsl_info(" Compiled with MbedTLS support\n"); + + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) + lwsl_info(" SSL disabled: no LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT\n"); + + return 0; +} + +LWS_VISIBLE void +lws_ssl_destroy(struct lws_vhost *vhost) +{ + if (!lws_check_opt(vhost->context->options, + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) + return; + + if (vhost->tls.ssl_ctx) + SSL_CTX_free(vhost->tls.ssl_ctx); + if (!vhost->tls.user_supplied_ssl_ctx && vhost->tls.ssl_client_ctx) + SSL_CTX_free(vhost->tls.ssl_client_ctx); + + if (vhost->tls.x509_client_CA) + X509_free(vhost->tls.x509_client_CA); +} + +LWS_VISIBLE int +lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n = 0, m; + + if (!wsi->tls.ssl) + return lws_ssl_capable_read_no_ssl(wsi, buf, len); + + lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); + + errno = 0; + n = SSL_read(wsi->tls.ssl, buf, len); +#if defined(LWS_WITH_ESP32) + if (!n && errno == ENOTCONN) { + lwsl_debug("%p: SSL_read ENOTCONN\n", wsi); + return LWS_SSL_CAPABLE_ERROR; + } +#endif +#if defined(LWS_WITH_STATS) + if (!wsi->seen_rx) { + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_SSL_RX_DELAY, + time_in_microseconds() - wsi->accept_start_us); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNS_HAD_RX, 1); + wsi->seen_rx = 1; + } +#endif + + + lwsl_debug("%p: SSL_read says %d\n", wsi, n); + /* manpage: returning 0 means connection shut down */ + if (!n) { + wsi->socket_is_permanently_unusable = 1; + + return LWS_SSL_CAPABLE_ERROR; + } + + if (n < 0) { + m = SSL_get_error(wsi->tls.ssl, n); + lwsl_debug("%p: ssl err %d errno %d\n", wsi, m, errno); + if (m == SSL_ERROR_ZERO_RETURN || + m == SSL_ERROR_SYSCALL) + return LWS_SSL_CAPABLE_ERROR; + + if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) { + lwsl_debug("%s: WANT_READ\n", __func__); + lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) { + lwsl_debug("%s: WANT_WRITE\n", __func__); + lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + wsi->socket_is_permanently_unusable = 1; + + return LWS_SSL_CAPABLE_ERROR; + } + + lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); + + if (wsi->vhost) + wsi->vhost->conn_stats.rx += n; + + lws_restart_ws_ping_pong_timer(wsi); + + /* + * if it was our buffer that limited what we read, + * check if SSL has additional data pending inside SSL buffers. + * + * Because these won't signal at the network layer with POLLIN + * and if we don't realize, this data will sit there forever + */ + if (n != len) + goto bail; + if (!wsi->tls.ssl) + goto bail; + + if (!SSL_pending(wsi->tls.ssl)) + goto bail; + + if (wsi->tls.pending_read_list_next) + return n; + if (wsi->tls.pending_read_list_prev) + return n; + if (pt->tls.pending_read_list == wsi) + return n; + + /* add us to the linked list of guys with pending ssl */ + if (pt->tls.pending_read_list) + pt->tls.pending_read_list->tls.pending_read_list_prev = wsi; + + wsi->tls.pending_read_list_next = pt->tls.pending_read_list; + wsi->tls.pending_read_list_prev = NULL; + pt->tls.pending_read_list = wsi; + + return n; +bail: + lws_ssl_remove_wsi_from_buffered_list(wsi); + + return n; +} + +LWS_VISIBLE int +lws_ssl_pending(struct lws *wsi) +{ + if (!wsi->tls.ssl) + return 0; + + return SSL_pending(wsi->tls.ssl); +} + +LWS_VISIBLE int +lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len) +{ + int n, m; + + if (!wsi->tls.ssl) + return lws_ssl_capable_write_no_ssl(wsi, buf, len); + + n = SSL_write(wsi->tls.ssl, buf, len); + if (n > 0) + return n; + + m = SSL_get_error(wsi->tls.ssl, n); + if (m != SSL_ERROR_SYSCALL) { + if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) { + lwsl_notice("%s: want read\n", __func__); + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + + if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->tls.ssl)) { + lws_set_blocking_send(wsi); + lwsl_notice("%s: want write\n", __func__); + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + } + + lwsl_debug("%s failed: %d\n",__func__, m); + wsi->socket_is_permanently_unusable = 1; + + return LWS_SSL_CAPABLE_ERROR; +} + +int openssl_SSL_CTX_private_data_index; + +void +lws_ssl_info_callback(const SSL *ssl, int where, int ret) +{ + struct lws *wsi; + struct lws_context *context; + struct lws_ssl_info si; + + context = (struct lws_context *)SSL_CTX_get_ex_data( + SSL_get_SSL_CTX(ssl), + openssl_SSL_CTX_private_data_index); + if (!context) + return; + wsi = wsi_from_fd(context, SSL_get_fd(ssl)); + if (!wsi) + return; + + if (!(where & wsi->vhost->tls.ssl_info_event_mask)) + return; + + si.where = where; + si.ret = ret; + + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_SSL_INFO, + wsi->user_space, &si, 0)) + lws_set_timeout(wsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, -1); +} + + +LWS_VISIBLE int +lws_ssl_close(struct lws *wsi) +{ + lws_sockfd_type n; + + if (!wsi->tls.ssl) + return 0; /* not handled */ + +#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) + /* kill ssl callbacks, becausse we will remove the fd from the + * table linking it to the wsi + */ + if (wsi->vhost->tls.ssl_info_event_mask) + SSL_set_info_callback(wsi->tls.ssl, NULL); +#endif + + n = SSL_get_fd(wsi->tls.ssl); + if (!wsi->socket_is_permanently_unusable) + SSL_shutdown(wsi->tls.ssl); + compatible_close(n); + SSL_free(wsi->tls.ssl); + wsi->tls.ssl = NULL; + + if (!lwsi_role_client(wsi) && + wsi->context->simultaneous_ssl_restriction && + wsi->context->simultaneous_ssl-- == + wsi->context->simultaneous_ssl_restriction) + /* we made space and can do an accept */ + lws_gate_accepts(wsi->context, 1); + +#if defined(LWS_WITH_STATS) + wsi->context->updated = 1; +#endif + + return 1; /* handled */ +} + +void +lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost) +{ + if (vhost->tls.ssl_ctx) + SSL_CTX_free(vhost->tls.ssl_ctx); + + if (!vhost->tls.user_supplied_ssl_ctx && vhost->tls.ssl_client_ctx) + SSL_CTX_free(vhost->tls.ssl_client_ctx); +#if defined(LWS_WITH_ACME) + lws_tls_acme_sni_cert_destroy(vhost); +#endif +} + +void +lws_ssl_context_destroy(struct lws_context *context) +{ +} + +lws_tls_ctx * +lws_tls_ctx_from_wsi(struct lws *wsi) +{ + if (!wsi->tls.ssl) + return NULL; + + return SSL_get_SSL_CTX(wsi->tls.ssl); +} + +enum lws_ssl_capable_status +__lws_tls_shutdown(struct lws *wsi) +{ + int n = SSL_shutdown(wsi->tls.ssl); + + lwsl_debug("SSL_shutdown=%d for fd %d\n", n, wsi->desc.sockfd); + + switch (n) { + case 1: /* successful completion */ + n = shutdown(wsi->desc.sockfd, SHUT_WR); + return LWS_SSL_CAPABLE_DONE; + + case 0: /* needs a retry */ + __lws_change_pollfd(wsi, 0, LWS_POLLIN); + return LWS_SSL_CAPABLE_MORE_SERVICE; + + default: /* fatal error, or WANT */ + n = SSL_get_error(wsi->tls.ssl, n); + if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) { + if (SSL_want_read(wsi->tls.ssl)) { + lwsl_debug("(wants read)\n"); + __lws_change_pollfd(wsi, 0, LWS_POLLIN); + return LWS_SSL_CAPABLE_MORE_SERVICE_READ; + } + if (SSL_want_write(wsi->tls.ssl)) { + lwsl_debug("(wants write)\n"); + __lws_change_pollfd(wsi, 0, LWS_POLLOUT); + return LWS_SSL_CAPABLE_MORE_SERVICE_WRITE; + } + } + return LWS_SSL_CAPABLE_ERROR; + } +} + +static time_t +lws_tls_mbedtls_time_to_unix(mbedtls_x509_time *xtime) +{ + struct tm t; + + if (!xtime || !xtime->year || xtime->year < 0) + return (time_t)(long long)-1; + + memset(&t, 0, sizeof(t)); + + t.tm_year = xtime->year - 1900; + t.tm_mon = xtime->mon - 1; /* mbedtls months are 1+, tm are 0+ */ + t.tm_mday = xtime->day - 1; /* mbedtls days are 1+, tm are 0+ */ + t.tm_hour = xtime->hour; + t.tm_min = xtime->min; + t.tm_sec = xtime->sec; + t.tm_isdst = -1; + + return mktime(&t); +} + +static int +lws_tls_mbedtls_get_x509_name(mbedtls_x509_name *name, + union lws_tls_cert_info_results *buf, size_t len) +{ + while (name) { + if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid)) { + name = name->next; + continue; + } + + if (len - 1 < name->val.len) + return -1; + + memcpy(&buf->ns.name[0], name->val.p, name->val.len); + buf->ns.name[name->val.len] = '\0'; + buf->ns.len = name->val.len; + + return 0; + } + + return -1; +} + +static int +lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + if (!x509) + return -1; + + switch (type) { + case LWS_TLS_CERT_INFO_VALIDITY_FROM: + buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_from); + if (buf->time == (time_t)(long long)-1) + return -1; + break; + + case LWS_TLS_CERT_INFO_VALIDITY_TO: + buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_to); + if (buf->time == (time_t)(long long)-1) + return -1; + break; + + case LWS_TLS_CERT_INFO_COMMON_NAME: + return lws_tls_mbedtls_get_x509_name(&x509->subject, buf, len); + + case LWS_TLS_CERT_INFO_ISSUER_NAME: + return lws_tls_mbedtls_get_x509_name(&x509->issuer, buf, len); + + case LWS_TLS_CERT_INFO_USAGE: + buf->usage = x509->key_usage; + break; + + case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: + { + char *p = buf->ns.name; + size_t r = len, u; + + switch (mbedtls_pk_get_type(&x509->pk)) { + case MBEDTLS_PK_RSA: + { + mbedtls_rsa_context *rsa = mbedtls_pk_rsa(x509->pk); + + if (mbedtls_mpi_write_string(&rsa->N, 16, p, r, &u)) + return -1; + r -= u; + p += u; + if (mbedtls_mpi_write_string(&rsa->E, 16, p, r, &u)) + return -1; + + p += u; + buf->ns.len = lws_ptr_diff(p, buf->ns.name); + break; + } + case MBEDTLS_PK_ECKEY: + { + mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(x509->pk); + + if (mbedtls_mpi_write_string(&ecp->Q.X, 16, p, r, &u)) + return -1; + r -= u; + p += u; + if (mbedtls_mpi_write_string(&ecp->Q.Y, 16, p, r, &u)) + return -1; + r -= u; + p += u; + if (mbedtls_mpi_write_string(&ecp->Q.Z, 16, p, r, &u)) + return -1; + p += u; + buf->ns.len = lws_ptr_diff(p, buf->ns.name); + break; + } + default: + lwsl_notice("%s: x509 has unsupported pubkey type %d\n", + __func__, + mbedtls_pk_get_type(&x509->pk)); + + return -1; + } + break; + } + + default: + return -1; + } + + return 0; +} + +LWS_VISIBLE LWS_EXTERN int +lws_tls_vhost_cert_info(struct lws_vhost *vhost, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + mbedtls_x509_crt *x509 = ssl_ctx_get_mbedtls_x509_crt(vhost->tls.ssl_ctx); + + return lws_tls_mbedtls_cert_info(x509, type, buf, len); +} + +LWS_VISIBLE int +lws_tls_peer_cert_info(struct lws *wsi, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len) +{ + mbedtls_x509_crt *x509; + + wsi = lws_get_network_wsi(wsi); + + x509 = ssl_get_peer_mbedtls_x509_crt(wsi->tls.ssl); + + if (!x509) + return -1; + + switch (type) { + case LWS_TLS_CERT_INFO_VERIFIED: + buf->verified = SSL_get_verify_result(wsi->tls.ssl) == X509_V_OK; + return 0; + default: + return lws_tls_mbedtls_cert_info(x509, type, buf, len); + } + + return -1; +} + +static int +tops_fake_POLLIN_for_buffered_mbedtls(struct lws_context_per_thread *pt) +{ + return lws_tls_fake_POLLIN_for_buffered(pt); +} + +static int +tops_periodic_housekeeping_mbedtls(struct lws_context *context, time_t now) +{ + int n; + + n = lws_compare_time_t(context, now, context->tls.last_cert_check_s); + if ((!context->tls.last_cert_check_s || n > (24 * 60 * 60)) && + !lws_tls_check_all_cert_lifetimes(context)) + context->tls.last_cert_check_s = now; + + return 0; +} + +const struct lws_tls_ops tls_ops_mbedtls = { + /* fake_POLLIN_for_buffered */ tops_fake_POLLIN_for_buffered_mbedtls, + /* periodic_housekeeping */ tops_periodic_housekeeping_mbedtls, +}; diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl3.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl3.h index 007b392f3e..007b392f3e 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl3.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl3.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_cert.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_cert.h index 86cf31ad51..86cf31ad51 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_cert.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_cert.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_code.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_code.h index 80fdbb20f3..80fdbb20f3 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_code.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_code.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_dbg.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_dbg.h index ad32cb92ff..ad32cb92ff 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_dbg.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_dbg.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_lib.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_lib.h index 42b2de7501..42b2de7501 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_lib.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_lib.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_methods.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_methods.h index cd2f8c0533..cd2f8c0533 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_methods.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_methods.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_pkey.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_pkey.h index e790fcc995..e790fcc995 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_pkey.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_pkey.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_stack.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_stack.h index 7a7051a026..7a7051a026 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_stack.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_stack.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_types.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_types.h index 2ca438c422..ba19663d9e 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_types.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_types.h @@ -203,6 +203,8 @@ struct ssl_st const SSL_METHOD *method; + const char **alpn_protos; + RECORD_LAYER rlayer; /* where we are */ diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_x509.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_x509.h index 7594d064b4..7594d064b4 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/ssl_x509.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/ssl_x509.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/tls1.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/tls1.h index 7af1b0157d..7af1b0157d 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/tls1.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/tls1.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/internal/x509_vfy.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/x509_vfy.h index 26bf6c88a8..26bf6c88a8 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/internal/x509_vfy.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/internal/x509_vfy.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/openssl/ssl.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/openssl/ssl.h index 5a84b4552e..e2b74fc6af 100644..100755 --- a/thirdparty/lws/mbedtls_wrapper/include/openssl/ssl.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/openssl/ssl.h @@ -35,6 +35,22 @@ #define X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS (1 << 3) #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS (1 << 4) + mbedtls_x509_crt * + ssl_ctx_get_mbedtls_x509_crt(SSL_CTX *ssl_ctx); + + mbedtls_x509_crt * + ssl_get_peer_mbedtls_x509_crt(SSL *ssl); + + int SSL_set_sni_callback(SSL *ssl, int(*cb)(void *, mbedtls_ssl_context *, + const unsigned char *, size_t), void *param); + + void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx); + + int SSL_CTX_add_client_CA_ASN1(SSL_CTX *ssl, int len, + const unsigned char *d); + + SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc); + /** * @brief create a SSL context * @@ -305,6 +321,7 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, void *arg), void *arg); +void SSL_set_alpn_select_cb(SSL *ssl, void *arg); /** * @brief set the SSL context ALPN select protocol diff --git a/thirdparty/lws/mbedtls_wrapper/include/platform/ssl_pm.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/platform/ssl_pm.h index cbbe3aa3a2..cbbe3aa3a2 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/platform/ssl_pm.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/platform/ssl_pm.h diff --git a/thirdparty/lws/mbedtls_wrapper/include/platform/ssl_port.h b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/platform/ssl_port.h index eca68f20d1..74c7634355 100644 --- a/thirdparty/lws/mbedtls_wrapper/include/platform/ssl_port.h +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/include/platform/ssl_port.h @@ -19,19 +19,11 @@ extern "C" { #endif -/* -#include "esp_types.h" -#include "esp_log.h" -*/ #include "string.h" - -/* GODOT ADDITION */ -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) -#include <stdlib.h> -#else +#include "stdlib.h" +#if defined(LWS_HAVE_MALLOC_H) #include "malloc.h" #endif -/* END GODOT ADDITION */ void *ssl_mem_zalloc(size_t size); diff --git a/thirdparty/lws/mbedtls_wrapper/library/ssl_cert.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_cert.c index 5c608125ac..5c608125ac 100644 --- a/thirdparty/lws/mbedtls_wrapper/library/ssl_cert.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_cert.c diff --git a/thirdparty/lws/mbedtls_wrapper/library/ssl_lib.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_lib.c index d8fdd06fad..2f688ca9ef 100644 --- a/thirdparty/lws/mbedtls_wrapper/library/ssl_lib.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_lib.c @@ -19,6 +19,9 @@ #include "ssl_dbg.h" #include "ssl_port.h" +char * +lws_strncpy(char *dest, const char *src, size_t size); + #define SSL_SEND_DATA_MAX_LENGTH 1460 /** @@ -348,6 +351,9 @@ void SSL_free(SSL *ssl) SSL_SESSION_free(ssl->session); + if (ssl->alpn_protos) + ssl_mem_free(ssl->alpn_protos); + ssl_mem_free(ssl); } @@ -1582,7 +1588,7 @@ void SSL_set_verify(SSL *ssl, int mode, int (*verify_callback)(int, X509_STORE_C void ERR_error_string_n(unsigned long e, char *buf, size_t len) { - strncpy(buf, "unknown", len); + lws_strncpy(buf, "unknown", len); } void ERR_free_strings(void) @@ -1591,11 +1597,34 @@ void ERR_free_strings(void) char *ERR_error_string(unsigned long e, char *buf) { - if (buf) { - strcpy(buf, "unknown"); + if (!buf) + return "unknown"; + + switch(e) { + case X509_V_ERR_INVALID_CA: + strcpy(buf, "CA is not trusted"); + break; + case X509_V_ERR_HOSTNAME_MISMATCH: + strcpy(buf, "Hostname mismatch"); + break; + case X509_V_ERR_CA_KEY_TOO_SMALL: + strcpy(buf, "CA key too small"); + break; + case X509_V_ERR_CA_MD_TOO_WEAK: + strcpy(buf, "MD key too weak"); + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + strcpy(buf, "Cert from the future"); + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + strcpy(buf, "Cert expired"); + break; + default: + strcpy(buf, "unknown"); + break; } - return "unknown"; + return buf; } void *SSL_CTX_get_ex_data(const SSL_CTX *ctx, int idx) @@ -1619,15 +1648,16 @@ void *SSL_CTX_get_ex_data(const SSL_CTX *ctx, int idx) */ struct alpn_ctx { - unsigned char *data; - unsigned short len; + unsigned char data[23]; + unsigned char len; }; -void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, next_proto_cb cb, void *arg) +static void +_openssl_alpn_to_mbedtls(struct alpn_ctx *ac, char ***palpn_protos) { - struct alpn_ctx *ac = arg; unsigned char *p = ac->data, *q; unsigned char len; + char **alpn_protos; int count = 0; /* find out how many entries he gave us */ @@ -1644,23 +1674,28 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, next_proto_cb cb, void *arg) break; } + if (!len) + count++; + if (!count) return; /* allocate space for count + 1 pointers and the data afterwards */ - ctx->alpn_protos = ssl_mem_zalloc((count + 1) * sizeof(char *) + ac->len + 1); - if (!ctx->alpn_protos) + alpn_protos = ssl_mem_zalloc((count + 1) * sizeof(char *) + ac->len + 1); + if (!alpn_protos) return; + *palpn_protos = alpn_protos; + /* convert to mbedtls format */ - q = (unsigned char *)ctx->alpn_protos + (count + 1) * sizeof(char *); + q = (unsigned char *)alpn_protos + (count + 1) * sizeof(char *); p = ac->data; count = 0; len = *p++; - ctx->alpn_protos[count] = (char *)q; + alpn_protos[count] = (char *)q; while (p - ac->data < ac->len) { if (len--) { *q++ = *p++; @@ -1669,11 +1704,33 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, next_proto_cb cb, void *arg) *q++ = '\0'; count++; len = *p++; - ctx->alpn_protos[count] = (char *)q; + alpn_protos[count] = (char *)q; if (!len) break; } - ctx->alpn_protos[count] = NULL; /* last pointer ends list with NULL */ + if (!len) { + *q++ = '\0'; + count++; + len = *p++; + alpn_protos[count] = (char *)q; + } + alpn_protos[count] = NULL; /* last pointer ends list with NULL */ +} + +void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, next_proto_cb cb, void *arg) +{ + struct alpn_ctx *ac = arg; ctx->alpn_cb = cb; + + _openssl_alpn_to_mbedtls(ac, (char ***)&ctx->alpn_protos); +} + +void SSL_set_alpn_select_cb(SSL *ssl, void *arg) +{ + struct alpn_ctx *ac = arg; + + _openssl_alpn_to_mbedtls(ac, (char ***)&ssl->alpn_protos); + + _ssl_set_alpn_list(ssl); } diff --git a/thirdparty/lws/mbedtls_wrapper/library/ssl_methods.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_methods.c index 0002360846..0002360846 100644 --- a/thirdparty/lws/mbedtls_wrapper/library/ssl_methods.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_methods.c diff --git a/thirdparty/lws/mbedtls_wrapper/library/ssl_pkey.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_pkey.c index 567a33e2c2..567a33e2c2 100644 --- a/thirdparty/lws/mbedtls_wrapper/library/ssl_pkey.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_pkey.c diff --git a/thirdparty/lws/mbedtls_wrapper/library/ssl_stack.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_stack.c index da836daf9c..da836daf9c 100644 --- a/thirdparty/lws/mbedtls_wrapper/library/ssl_stack.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_stack.c diff --git a/thirdparty/lws/mbedtls_wrapper/library/ssl_x509.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_x509.c index 4441490a03..ed79150831 100644 --- a/thirdparty/lws/mbedtls_wrapper/library/ssl_x509.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/library/ssl_x509.c @@ -17,6 +17,8 @@ #include "ssl_dbg.h" #include "ssl_port.h" +#include <assert.h> + /** * @brief show X509 certification information */ @@ -155,7 +157,7 @@ int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x) { SSL_ASSERT1(ctx); SSL_ASSERT1(x); - + assert(ctx); if (ctx->client_CA == x) return 1; @@ -169,6 +171,28 @@ int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *x) /** * @brief add CA client certification into the SSL */ +int SSL_CTX_add_client_CA_ASN1(SSL_CTX *ctx, int len, + const unsigned char *d) +{ + X509 *x; + + x = d2i_X509(NULL, d, len); + if (!x) { + SSL_DEBUG(SSL_PKEY_ERROR_LEVEL, "d2i_X509() return NULL"); + return 0; + } + SSL_ASSERT1(ctx); + + X509_free(ctx->client_CA); + + ctx->client_CA = x; + + return 1; +} + +/** + * @brief add CA client certification into the SSL + */ int SSL_add_client_CA(SSL *ssl, X509 *x) { SSL_ASSERT1(ssl); diff --git a/thirdparty/lws/mbedtls_wrapper/platform/ssl_pm.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/platform/ssl_pm.c index 4e3d611095..4716c1ff56 100644..100755 --- a/thirdparty/lws/mbedtls_wrapper/platform/ssl_pm.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -25,6 +25,8 @@ #include "mbedtls/error.h" #include "mbedtls/certs.h" +#include <libwebsockets.h> + #define X509_INFO_STRING_LENGTH 8192 struct ssl_pm @@ -41,6 +43,8 @@ struct ssl_pm mbedtls_ssl_context ssl; mbedtls_entropy_context entropy; + + SSL *owner; }; struct x509_pm @@ -62,7 +66,7 @@ unsigned int max_content_len; /*********************************************************************************************/ /************************************ SSL arch interface *************************************/ -#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG +//#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG /* mbedtls debug level */ #define MBEDTLS_DEBUG_LEVEL 4 @@ -79,13 +83,13 @@ static void ssl_platform_debug(void *ctx, int level, This is a bit wasteful because the macros are compiled in with the full _FILE_ path in each case. */ - char *file_sep = rindex(file, '/'); - if(file_sep) - file = file_sep + 1; +// char *file_sep = rindex(file, '/'); + // if(file_sep) + // file = file_sep + 1; - SSL_DEBUG(SSL_DEBUG_ON, "%s:%d %s", file, line, str); + printf("%s:%d %s", file, line, str); } -#endif +//#endif /** * @brief create SSL low-level object @@ -109,6 +113,8 @@ int ssl_pm_new(SSL *ssl) goto no_mem; } + ssl_pm->owner = ssl; + if (!ssl->ctx->read_buffer_len) ssl->ctx->read_buffer_len = 2048; @@ -159,12 +165,12 @@ int ssl_pm_new(SSL *ssl) mbedtls_ssl_conf_rng(&ssl_pm->conf, mbedtls_ctr_drbg_random, &ssl_pm->ctr_drbg); -#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG - mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL); +//#ifdef CONFIG_OPENSSL_LOWLEVEL_DEBUG + // mbedtls_debug_set_threshold(MBEDTLS_DEBUG_LEVEL); +// mbedtls_ssl_conf_dbg(&ssl_pm->conf, ssl_platform_debug, NULL); +//#else mbedtls_ssl_conf_dbg(&ssl_pm->conf, ssl_platform_debug, NULL); -#else - mbedtls_ssl_conf_dbg(&ssl_pm->conf, NULL, NULL); -#endif +//#endif ret = mbedtls_ssl_setup(&ssl_pm->ssl, &ssl_pm->conf); if (ret) { @@ -261,7 +267,7 @@ static int mbedtls_handshake( mbedtls_ssl_context *ssl ) while (ssl->state != MBEDTLS_SSL_HANDSHAKE_OVER) { ret = mbedtls_ssl_handshake_step(ssl); - SSL_DEBUG(SSL_PLATFORM_DEBUG_LEVEL, "ssl ret %d state %d", ret, ssl->state); + lwsl_info("%s: ssl ret -%x state %d\n", __func__, -ret, ssl->state); if (ret != 0) break; @@ -270,14 +276,21 @@ static int mbedtls_handshake( mbedtls_ssl_context *ssl ) return ret; } +#include <errno.h> + int ssl_pm_handshake(SSL *ssl) { int ret; struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; + ssl->err = 0; + errno = 0; + ret = ssl_pm_reload_crt(ssl); - if (ret) + if (ret) { + printf("%s: cert reload failed\n", __func__); return 0; + } if (ssl_pm->ssl.state != MBEDTLS_SSL_HANDSHAKE_OVER) { ssl_speed_up_enter(); @@ -298,6 +311,7 @@ int ssl_pm_handshake(SSL *ssl) * <0 = death */ if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + ssl->err = ret; SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_handshake() return -0x%x", -ret); return 0; /* OpenSSL: did not complete but may be retried */ } @@ -309,6 +323,14 @@ int ssl_pm_handshake(SSL *ssl) return 1; /* openssl successful */ } + if (errno == 11) { + ssl->err = ret == MBEDTLS_ERR_SSL_WANT_READ; + + return 0; + } + + printf("%s: mbedtls_ssl_handshake() returned -0x%x\n", __func__, -ret); + /* it's had it */ ssl->err = SSL_ERROR_SYSCALL; @@ -316,6 +338,28 @@ int ssl_pm_handshake(SSL *ssl) return -1; /* openssl death */ } +mbedtls_x509_crt * +ssl_ctx_get_mbedtls_x509_crt(SSL_CTX *ssl_ctx) +{ + struct x509_pm *x509_pm = (struct x509_pm *)ssl_ctx->cert->x509->x509_pm; + + if (!x509_pm) + return NULL; + + return x509_pm->x509_crt; +} + +mbedtls_x509_crt * +ssl_get_peer_mbedtls_x509_crt(SSL *ssl) +{ + struct x509_pm *x509_pm = (struct x509_pm *)ssl->session->peer->x509_pm; + + if (!x509_pm) + return NULL; + + return x509_pm->ex_crt; +} + int ssl_pm_shutdown(SSL *ssl) { int ret; @@ -351,8 +395,10 @@ int ssl_pm_read(SSL *ssl, void *buffer, int len) ret = mbedtls_ssl_read(&ssl_pm->ssl, buffer, len); if (ret < 0) { + // lwsl_notice("%s: mbedtls_ssl_read says -0x%x\n", __func__, -ret); SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_read() return -0x%x", -ret); - if (ret == MBEDTLS_ERR_NET_CONN_RESET) + if (ret == MBEDTLS_ERR_NET_CONN_RESET || + ret <= MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE) /* fatal errors */ ssl->err = SSL_ERROR_SYSCALL; ret = -1; } @@ -392,6 +438,7 @@ int ssl_pm_send(SSL *ssl, const void *buffer, int len) if (ret < 0) { SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_write() return -0x%x", -ret); switch (ret) { + case MBEDTLS_ERR_NET_SEND_FAILED: case MBEDTLS_ERR_NET_CONN_RESET: ssl->err = SSL_ERROR_SYSCALL; break; @@ -589,22 +636,27 @@ int x509_pm_load(X509 *x, const unsigned char *buffer, int len) } } - load_buf = ssl_mem_malloc(len + 1); - if (!load_buf) { - SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (load_buf)"); - goto failed; - } - - ssl_memcpy(load_buf, buffer, len); - load_buf[len] = '\0'; - mbedtls_x509_crt_init(x509_pm->x509_crt); + if (buffer[0] != 0x30) { + load_buf = ssl_mem_malloc(len + 1); + if (!load_buf) { + SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "no enough memory > (load_buf)"); + goto failed; + } + + ssl_memcpy(load_buf, buffer, len); + load_buf[len] = '\0'; + + ret = mbedtls_x509_crt_parse(x509_pm->x509_crt, load_buf, len + 1); + ssl_mem_free(load_buf); + } else { + printf("parsing as der\n"); - ret = mbedtls_x509_crt_parse(x509_pm->x509_crt, load_buf, len + 1); - ssl_mem_free(load_buf); + ret = mbedtls_x509_crt_parse_der(x509_pm->x509_crt, buffer, len); + } if (ret) { - SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_x509_crt_parse return -0x%x", -ret); + printf("mbedtls_x509_crt_parse return -0x%x", -ret); goto failed; } @@ -707,46 +759,44 @@ void ssl_pm_set_bufflen(SSL *ssl, int len) long ssl_pm_get_verify_result(const SSL *ssl) { - uint32_t ret; - long verify_result; - struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; - - ret = mbedtls_ssl_get_verify_result(&ssl_pm->ssl); + uint32_t ret; + long verify_result; + struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; - if (!ret) - return X509_V_OK; + ret = mbedtls_ssl_get_verify_result(&ssl_pm->ssl); + if (!ret) + return X509_V_OK; - if (ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED || - (ret & MBEDTLS_X509_BADCRL_NOT_TRUSTED)) - // Allows us to use LCCSCF_ALLOW_SELFSIGNED to skip verification - verify_result = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN; + if (ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED || + (ret & MBEDTLS_X509_BADCRL_NOT_TRUSTED)) + verify_result = X509_V_ERR_INVALID_CA; - else if (ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) - verify_result = X509_V_ERR_HOSTNAME_MISMATCH; + else if (ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) + verify_result = X509_V_ERR_HOSTNAME_MISMATCH; - else if ((ret & MBEDTLS_X509_BADCERT_BAD_KEY) || - (ret & MBEDTLS_X509_BADCRL_BAD_KEY)) - verify_result = X509_V_ERR_CA_KEY_TOO_SMALL; + else if ((ret & MBEDTLS_X509_BADCERT_BAD_KEY) || + (ret & MBEDTLS_X509_BADCRL_BAD_KEY)) + verify_result = X509_V_ERR_CA_KEY_TOO_SMALL; - else if ((ret & MBEDTLS_X509_BADCERT_BAD_MD) || - (ret & MBEDTLS_X509_BADCRL_BAD_MD)) - verify_result = X509_V_ERR_CA_MD_TOO_WEAK; + else if ((ret & MBEDTLS_X509_BADCERT_BAD_MD) || + (ret & MBEDTLS_X509_BADCRL_BAD_MD)) + verify_result = X509_V_ERR_CA_MD_TOO_WEAK; - else if ((ret & MBEDTLS_X509_BADCERT_FUTURE) || - (ret & MBEDTLS_X509_BADCRL_FUTURE)) - verify_result = X509_V_ERR_CERT_NOT_YET_VALID; + else if ((ret & MBEDTLS_X509_BADCERT_FUTURE) || + (ret & MBEDTLS_X509_BADCRL_FUTURE)) + verify_result = X509_V_ERR_CERT_NOT_YET_VALID; - else if ((ret & MBEDTLS_X509_BADCERT_EXPIRED) || - (ret & MBEDTLS_X509_BADCRL_EXPIRED)) - verify_result = X509_V_ERR_CERT_HAS_EXPIRED; + else if ((ret & MBEDTLS_X509_BADCERT_EXPIRED) || + (ret & MBEDTLS_X509_BADCRL_EXPIRED)) + verify_result = X509_V_ERR_CERT_HAS_EXPIRED; - else - verify_result = X509_V_ERR_UNSPECIFIED; + else + verify_result = X509_V_ERR_UNSPECIFIED; - SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, - "mbedtls_ssl_get_verify_result() return 0x%x", ret); + SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, + "mbedtls_ssl_get_verify_result() return 0x%x", ret); - return verify_result; + return verify_result; } /** @@ -779,6 +829,12 @@ int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, void _ssl_set_alpn_list(const SSL *ssl) { + if (ssl->alpn_protos) { + if (mbedtls_ssl_conf_alpn_protocols(&((struct ssl_pm *)(ssl->ssl_pm))->conf, ssl->alpn_protos)) + fprintf(stderr, "mbedtls_ssl_conf_alpn_protocols failed\n"); + + return; + } if (!ssl->ctx->alpn_protos) return; if (mbedtls_ssl_conf_alpn_protocols(&((struct ssl_pm *)(ssl->ssl_pm))->conf, ssl->ctx->alpn_protos)) @@ -797,3 +853,55 @@ void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, *len = 0; } +int SSL_set_sni_callback(SSL *ssl, int(*cb)(void *, mbedtls_ssl_context *, + const unsigned char *, size_t), void *param) +{ + struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; + + mbedtls_ssl_conf_sni(&ssl_pm->conf, cb, param); + + return 0; +} + +SSL *SSL_SSL_from_mbedtls_ssl_context(mbedtls_ssl_context *msc) +{ + struct ssl_pm *ssl_pm = (struct ssl_pm *)((char *)msc - offsetof(struct ssl_pm, ssl)); + + return ssl_pm->owner; +} + +#include "ssl_cert.h" + +void SSL_set_SSL_CTX(SSL *ssl, SSL_CTX *ctx) +{ + struct ssl_pm *ssl_pm = ssl->ssl_pm; + struct x509_pm *x509_pm = (struct x509_pm *)ctx->cert->x509->x509_pm; + struct x509_pm *x509_pm_ca = (struct x509_pm *)ctx->client_CA->x509_pm; + + struct pkey_pm *pkey_pm = (struct pkey_pm *)ctx->cert->pkey->pkey_pm; + int mode; + + if (ssl->cert) + ssl_cert_free(ssl->cert); + ssl->ctx = ctx; + ssl->cert = __ssl_cert_new(ctx->cert); + + if (ctx->verify_mode == SSL_VERIFY_PEER) + mode = MBEDTLS_SSL_VERIFY_OPTIONAL; + else if (ctx->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT) + mode = MBEDTLS_SSL_VERIFY_OPTIONAL; + else if (ctx->verify_mode == SSL_VERIFY_CLIENT_ONCE) + mode = MBEDTLS_SSL_VERIFY_UNSET; + else + mode = MBEDTLS_SSL_VERIFY_NONE; + + // printf("ssl: %p, client ca x509_crt %p, mbedtls mode %d\n", ssl, x509_pm_ca->x509_crt, mode); + + /* apply new ctx cert to ssl */ + + ssl->verify_mode = ctx->verify_mode; + + mbedtls_ssl_set_hs_ca_chain(&ssl_pm->ssl, x509_pm_ca->x509_crt, NULL); + mbedtls_ssl_set_hs_own_cert(&ssl_pm->ssl, x509_pm->x509_crt, pkey_pm->pkey); + mbedtls_ssl_set_hs_authmode(&ssl_pm->ssl, mode); +} diff --git a/thirdparty/lws/mbedtls_wrapper/platform/ssl_port.c b/thirdparty/libwebsockets/tls/mbedtls/wrapper/platform/ssl_port.c index 8c7a31338b..8c7a31338b 100644 --- a/thirdparty/lws/mbedtls_wrapper/platform/ssl_port.c +++ b/thirdparty/libwebsockets/tls/mbedtls/wrapper/platform/ssl_port.c diff --git a/thirdparty/libwebsockets/tls/private.h b/thirdparty/libwebsockets/tls/private.h new file mode 100644 index 0000000000..606e2574dc --- /dev/null +++ b/thirdparty/libwebsockets/tls/private.h @@ -0,0 +1,281 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + * + * This is included from core/private.h if LWS_WITH_TLS + */ + +#if defined(LWS_WITH_TLS) + +#if defined(USE_WOLFSSL) + #if defined(USE_OLD_CYASSL) + #if defined(_WIN32) + #include <IDE/WIN/user_settings.h> + #include <cyassl/ctaocrypt/settings.h> + #else + #include <cyassl/options.h> + #endif + #include <cyassl/openssl/ssl.h> + #include <cyassl/error-ssl.h> + #else + #if defined(_WIN32) + #include <IDE/WIN/user_settings.h> + #include <wolfssl/wolfcrypt/settings.h> + #else + #include <wolfssl/options.h> + #endif + #include <wolfssl/openssl/ssl.h> + #include <wolfssl/error-ssl.h> + #define OPENSSL_NO_TLSEXT + #endif /* not USE_OLD_CYASSL */ +#else /* WOLFSSL */ + #if defined(LWS_WITH_ESP32) + #define OPENSSL_NO_TLSEXT + #undef MBEDTLS_CONFIG_FILE + #define MBEDTLS_CONFIG_FILE <mbedtls/esp_config.h> + #include <mbedtls/ssl.h> + #include <mbedtls/x509_crt.h> + #include "tls/mbedtls/wrapper/include/openssl/ssl.h" /* wrapper !!!! */ + #else /* not esp32 */ + #if defined(LWS_WITH_MBEDTLS) + #include <mbedtls/ssl.h> + #include <mbedtls/x509_crt.h> + #include <mbedtls/x509_csr.h> + #include "tls/mbedtls/wrapper/include/openssl/ssl.h" /* wrapper !!!! */ + #else + #include <openssl/ssl.h> + #include <openssl/evp.h> + #include <openssl/err.h> + #include <openssl/md5.h> + #include <openssl/sha.h> + #ifdef LWS_HAVE_OPENSSL_ECDH_H + #include <openssl/ecdh.h> + #endif + #include <openssl/x509v3.h> + #endif /* not mbedtls */ + #if defined(OPENSSL_VERSION_NUMBER) + #if (OPENSSL_VERSION_NUMBER < 0x0009080afL) +/* later openssl defines this to negate the presence of tlsext... but it was only + * introduced at 0.9.8j. Earlier versions don't know it exists so don't + * define it... making it look like the feature exists... + */ + #define OPENSSL_NO_TLSEXT + #endif + #endif + #endif /* not ESP32 */ +#endif /* not USE_WOLFSSL */ + +#endif /* LWS_WITH_TLS */ + +enum lws_tls_extant { + LWS_TLS_EXTANT_NO, + LWS_TLS_EXTANT_YES, + LWS_TLS_EXTANT_ALTERNATIVE +}; + +struct lws_context_per_thread; + +struct lws_tls_ops { + int (*fake_POLLIN_for_buffered)(struct lws_context_per_thread *pt); + int (*periodic_housekeeping)(struct lws_context *context, time_t now); +}; + +#if defined(LWS_WITH_TLS) + +typedef SSL lws_tls_conn; +typedef SSL_CTX lws_tls_ctx; +typedef BIO lws_tls_bio; +typedef X509 lws_tls_x509; + + +#define LWS_SSL_ENABLED(context) (context->tls.use_ssl) + +extern const struct lws_tls_ops tls_ops_openssl, tls_ops_mbedtls; + +struct lws_context_tls { + char alpn_discovered[32]; + const char *alpn_default; + time_t last_cert_check_s; +}; + +struct lws_pt_tls { + struct lws *pending_read_list; /* linked list */ +}; + +struct lws_tls_ss_pieces; + +struct alpn_ctx { + uint8_t data[23]; + uint8_t len; +}; + +struct lws_vhost_tls { + lws_tls_ctx *ssl_ctx; + lws_tls_ctx *ssl_client_ctx; + const char *alpn; + struct lws_tls_ss_pieces *ss; /* for acme tls certs */ + char *alloc_cert_path; + char *key_path; +#if defined(LWS_WITH_MBEDTLS) + lws_tls_x509 *x509_client_CA; +#endif + char ecdh_curve[16]; + struct alpn_ctx alpn_ctx; + + int use_ssl; + int allow_non_ssl_on_ssl_port; + int ssl_info_event_mask; + + unsigned int user_supplied_ssl_ctx:1; + unsigned int skipped_certs:1; +}; + +struct lws_lws_tls { + lws_tls_conn *ssl; + lws_tls_bio *client_bio; + struct lws *pending_read_list_prev, *pending_read_list_next; + unsigned int use_ssl; + unsigned int redirect_to_https:1; +}; + +LWS_EXTERN void +lws_context_init_alpn(struct lws_vhost *vhost); +LWS_EXTERN enum lws_tls_extant +lws_tls_use_any_upgrade_check_extant(const char *name); +LWS_EXTERN int openssl_websocket_private_data_index; +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_pending(struct lws *wsi); +LWS_EXTERN int +lws_context_init_ssl_library(const struct lws_context_creation_info *info); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_server_socket_service_ssl(struct lws *new_wsi, lws_sockfd_type accept_fd); +LWS_EXTERN int +lws_ssl_close(struct lws *wsi); +LWS_EXTERN void +lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost); +LWS_EXTERN void +lws_ssl_context_destroy(struct lws_context *context); +void +__lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi); +LWS_VISIBLE void +lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi); +LWS_EXTERN int +lws_ssl_client_bio_create(struct lws *wsi); +LWS_EXTERN int +lws_ssl_client_connect1(struct lws *wsi); +LWS_EXTERN int +lws_ssl_client_connect2(struct lws *wsi, char *errbuf, int len); +LWS_EXTERN void +lws_ssl_elaborate_error(void); +LWS_EXTERN int +lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt); +LWS_EXTERN int +lws_gate_accepts(struct lws_context *context, int on); +LWS_EXTERN void +lws_ssl_bind_passphrase(lws_tls_ctx *ssl_ctx, + const struct lws_context_creation_info *info); +LWS_EXTERN void +lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret); +LWS_EXTERN int +lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, + union lws_tls_cert_info_results *buf, size_t len); +LWS_EXTERN int +lws_tls_check_all_cert_lifetimes(struct lws_context *context); +LWS_EXTERN int +lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi, + const char *cert, const char *private_key, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t mem_privkey_len); +LWS_EXTERN enum lws_tls_extant +lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert, + const char *private_key); +LWS_EXTERN int +lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename, + const char *inbuf, lws_filepos_t inlen, + uint8_t **buf, lws_filepos_t *amount); + +#if !defined(LWS_NO_SERVER) + LWS_EXTERN int + lws_context_init_server_ssl(const struct lws_context_creation_info *info, + struct lws_vhost *vhost); + void + lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost); +#else + #define lws_context_init_server_ssl(_a, _b) (0) + #define lws_tls_acme_sni_cert_destroy(_a) +#endif + +LWS_EXTERN void +lws_ssl_destroy(struct lws_vhost *vhost); +LWS_EXTERN char * +lws_ssl_get_error_string(int status, int ret, char *buf, size_t len); + +/* + * lws_tls_ abstract backend implementations + */ + +LWS_EXTERN int +lws_tls_server_client_cert_verify_config(struct lws_vhost *vh); +LWS_EXTERN int +lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info, + struct lws_vhost *vhost, struct lws *wsi); +LWS_EXTERN int +lws_tls_server_new_nonblocking(struct lws *wsi, lws_sockfd_type accept_fd); + +LWS_EXTERN enum lws_ssl_capable_status +lws_tls_server_accept(struct lws *wsi); + +LWS_EXTERN enum lws_ssl_capable_status +lws_tls_server_abort_connection(struct lws *wsi); + +LWS_EXTERN enum lws_ssl_capable_status +__lws_tls_shutdown(struct lws *wsi); + +LWS_EXTERN enum lws_ssl_capable_status +lws_tls_client_connect(struct lws *wsi); +LWS_EXTERN int +lws_tls_client_confirm_peer_cert(struct lws *wsi, char *ebuf, int ebuf_len); +LWS_EXTERN int +lws_tls_client_create_vhost_context(struct lws_vhost *vh, + const struct lws_context_creation_info *info, + const char *cipher_list, + const char *ca_filepath, + const char *cert_filepath, + const char *private_key_filepath); + +LWS_EXTERN lws_tls_ctx * +lws_tls_ctx_from_wsi(struct lws *wsi); +LWS_EXTERN int +lws_ssl_get_error(struct lws *wsi, int n); + +LWS_EXTERN int +lws_context_init_client_ssl(const struct lws_context_creation_info *info, + struct lws_vhost *vhost); + +LWS_EXTERN void +lws_ssl_info_callback(const lws_tls_conn *ssl, int where, int ret); + +int +lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt); + +#endif
\ No newline at end of file diff --git a/thirdparty/libwebsockets/tls/tls-client.c b/thirdparty/libwebsockets/tls/tls-client.c new file mode 100644 index 0000000000..70eb6f6078 --- /dev/null +++ b/thirdparty/libwebsockets/tls/tls-client.c @@ -0,0 +1,150 @@ +/* + * libwebsockets - client-related ssl code independent of backend + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +int +lws_ssl_client_connect1(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + int n = 0; + + lws_latency_pre(context, wsi); + n = lws_tls_client_connect(wsi); + lws_latency(context, wsi, "SSL_connect hs", n, n > 0); + + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + return -1; + case LWS_SSL_CAPABLE_DONE: + return 1; /* connected */ + case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: + lws_callback_on_writable(wsi); + /* fallthru */ + case LWS_SSL_CAPABLE_MORE_SERVICE_READ: + lwsi_set_state(wsi, LRS_WAITING_SSL); + break; + case LWS_SSL_CAPABLE_MORE_SERVICE: + break; + } + + return 0; /* retry */ +} + +int +lws_ssl_client_connect2(struct lws *wsi, char *errbuf, int len) +{ + int n = 0; + + if (lwsi_state(wsi) == LRS_WAITING_SSL) { + lws_latency_pre(wsi->context, wsi); + + n = lws_tls_client_connect(wsi); + lwsl_debug("%s: SSL_connect says %d\n", __func__, n); + lws_latency(wsi->context, wsi, + "SSL_connect LRS_WAITING_SSL", n, n > 0); + + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + lws_snprintf(errbuf, len, "client connect failed"); + return -1; + case LWS_SSL_CAPABLE_DONE: + break; /* connected */ + case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: + lws_callback_on_writable(wsi); + /* fallthru */ + case LWS_SSL_CAPABLE_MORE_SERVICE_READ: + lwsi_set_state(wsi, LRS_WAITING_SSL); + /* fallthru */ + case LWS_SSL_CAPABLE_MORE_SERVICE: + return 0; + } + } + + if (lws_tls_client_confirm_peer_cert(wsi, errbuf, len)) + return -1; + + return 1; +} + + +int lws_context_init_client_ssl(const struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ + const char *ca_filepath = info->ssl_ca_filepath; + const char *cipher_list = info->ssl_cipher_list; + const char *private_key_filepath = info->ssl_private_key_filepath; + const char *cert_filepath = info->ssl_cert_filepath; + struct lws wsi; + + if (vhost->options & LWS_SERVER_OPTION_ONLY_RAW) + return 0; + + /* + * for backwards-compatibility default to using ssl_... members, but + * if the newer client-specific ones are given, use those + */ + if (info->client_ssl_cipher_list) + cipher_list = info->client_ssl_cipher_list; + if (info->client_ssl_cert_filepath) + cert_filepath = info->client_ssl_cert_filepath; + if (info->client_ssl_private_key_filepath) + private_key_filepath = info->client_ssl_private_key_filepath; + + if (info->client_ssl_ca_filepath) + ca_filepath = info->client_ssl_ca_filepath; + + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) + return 0; + + if (vhost->tls.ssl_client_ctx) + return 0; + + if (info->provided_client_ssl_ctx) { + /* use the provided OpenSSL context if given one */ + vhost->tls.ssl_client_ctx = info->provided_client_ssl_ctx; + /* nothing for lib to delete */ + vhost->tls.user_supplied_ssl_ctx = 1; + + return 0; + } + + if (lws_tls_client_create_vhost_context(vhost, info, cipher_list, + ca_filepath, cert_filepath, + private_key_filepath)) + return 1; + + lwsl_notice("created client ssl context for %s\n", vhost->name); + + /* + * give him a fake wsi with context set, so he can use + * lws_get_context() in the callback + */ + memset(&wsi, 0, sizeof(wsi)); + wsi.vhost = vhost; + wsi.context = vhost->context; + + vhost->protocols[0].callback(&wsi, + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS, + vhost->tls.ssl_client_ctx, NULL, 0); + + return 0; +} diff --git a/thirdparty/libwebsockets/tls/tls-server.c b/thirdparty/libwebsockets/tls/tls-server.c new file mode 100644 index 0000000000..440e790660 --- /dev/null +++ b/thirdparty/libwebsockets/tls/tls-server.c @@ -0,0 +1,382 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2018 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) +static int +alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ +#if !defined(LWS_WITH_MBEDTLS) + struct alpn_ctx *alpn_ctx = (struct alpn_ctx *)arg; + + if (SSL_select_next_proto((unsigned char **)out, outlen, alpn_ctx->data, + alpn_ctx->len, in, inlen) != + OPENSSL_NPN_NEGOTIATED) + return SSL_TLSEXT_ERR_NOACK; +#endif + + return SSL_TLSEXT_ERR_OK; +} +#endif + +void +lws_context_init_alpn(struct lws_vhost *vhost) +{ +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) + const char *alpn_comma = vhost->context->tls.alpn_default; + + if (vhost->tls.alpn) + alpn_comma = vhost->tls.alpn; + + lwsl_info(" Server '%s' advertising ALPN: %s\n", + vhost->name, alpn_comma); + vhost->tls.alpn_ctx.len = lws_alpn_comma_to_openssl(alpn_comma, + vhost->tls.alpn_ctx.data, + sizeof(vhost->tls.alpn_ctx.data) - 1); + + SSL_CTX_set_alpn_select_cb(vhost->tls.ssl_ctx, alpn_cb, &vhost->tls.alpn_ctx); +#else + lwsl_err( + " HTTP2 / ALPN configured but not supported by OpenSSL 0x%lx\n", + OPENSSL_VERSION_NUMBER); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +int +lws_tls_server_conn_alpn(struct lws *wsi) +{ +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) + const unsigned char *name = NULL; + char cstr[10]; + unsigned len; + + SSL_get0_alpn_selected(wsi->tls.ssl, &name, &len); + if (!len) { + lwsl_info("no ALPN upgrade\n"); + return 0; + } + + if (len > sizeof(cstr) - 1) + len = sizeof(cstr) - 1; + + memcpy(cstr, name, len); + cstr[len] = '\0'; + + lwsl_info("negotiated '%s' using ALPN\n", cstr); + wsi->tls.use_ssl |= LCCSCF_USE_SSL; + + return lws_role_call_alpn_negotiated(wsi, (const char *)cstr); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + return 0; +} + +LWS_VISIBLE int +lws_context_init_server_ssl(const struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ + struct lws_context *context = vhost->context; + struct lws wsi; + + if (!lws_check_opt(info->options, + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) { + vhost->tls.use_ssl = 0; + + return 0; + } + + /* + * If he is giving a cert filepath, take it as a sign he wants to use + * it on this vhost. User code can leave the cert filepath NULL and + * set the LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX option itself, in + * which case he's expected to set up the cert himself at + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which + * provides the vhost SSL_CTX * in the user parameter. + */ + if (info->ssl_cert_filepath) + vhost->options |= LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; + + if (info->port != CONTEXT_PORT_NO_LISTEN) { + + vhost->tls.use_ssl = lws_check_opt(vhost->options, + LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX); + + if (vhost->tls.use_ssl && info->ssl_cipher_list) + lwsl_notice(" SSL ciphers: '%s'\n", + info->ssl_cipher_list); + + if (vhost->tls.use_ssl) + lwsl_notice(" Using SSL mode\n"); + else + lwsl_notice(" Using non-SSL mode\n"); + } + + /* + * give him a fake wsi with context + vhost set, so he can use + * lws_get_context() in the callback + */ + memset(&wsi, 0, sizeof(wsi)); + wsi.vhost = vhost; + wsi.context = context; + + /* + * as a server, if we are requiring clients to identify themselves + * then set the backend up for it + */ + if (lws_check_opt(info->options, + LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT)) + /* Normally SSL listener rejects non-ssl, optionally allow */ + vhost->tls.allow_non_ssl_on_ssl_port = 1; + + /* + * give user code a chance to load certs into the server + * allowing it to verify incoming client certs + */ + if (vhost->tls.use_ssl) { + if (lws_tls_server_vhost_backend_init(info, vhost, &wsi)) + return -1; + + lws_tls_server_client_cert_verify_config(vhost); + + if (vhost->protocols[0].callback(&wsi, + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, + vhost->tls.ssl_ctx, vhost, 0)) + return -1; + } + + if (vhost->tls.use_ssl) + lws_context_init_alpn(vhost); + + return 0; +} + +LWS_VISIBLE int +lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) +{ + struct lws_context *context = wsi->context; + struct lws_vhost *vh; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n; + char buf[256]; + + (void)buf; + + if (!LWS_SSL_ENABLED(wsi->vhost)) + return 0; + + switch (lwsi_state(wsi)) { + case LRS_SSL_INIT: + + if (wsi->tls.ssl) + lwsl_err("%s: leaking ssl\n", __func__); + if (accept_fd == LWS_SOCK_INVALID) + assert(0); + if (context->simultaneous_ssl_restriction && + context->simultaneous_ssl >= + context->simultaneous_ssl_restriction) { + lwsl_notice("unable to deal with SSL connection\n"); + return 1; + } + + if (lws_tls_server_new_nonblocking(wsi, accept_fd)) { + if (accept_fd != LWS_SOCK_INVALID) + compatible_close(accept_fd); + goto fail; + } + + if (context->simultaneous_ssl_restriction && + ++context->simultaneous_ssl == + context->simultaneous_ssl_restriction) + /* that was the last allowed SSL connection */ + lws_gate_accepts(context, 0); + +#if defined(LWS_WITH_STATS) + context->updated = 1; +#endif + /* + * we are not accepted yet, but we need to enter ourselves + * as a live connection. That way we can retry when more + * pieces come if we're not sorted yet + */ + lwsi_set_state(wsi, LRS_SSL_ACK_PENDING); + + lws_pt_lock(pt, __func__); + if (__insert_wsi_socket_into_fds(context, wsi)) { + lwsl_err("%s: failed to insert into fds\n", __func__); + goto fail; + } + lws_pt_unlock(pt); + + lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT, + context->timeout_secs); + + lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n"); + + /* fallthru */ + + case LRS_SSL_ACK_PENDING: + + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_err("%s: lws_change_pollfd failed\n", __func__); + goto fail; + } + + lws_latency_pre(context, wsi); + + if (wsi->vhost->tls.allow_non_ssl_on_ssl_port) { + + n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, + context->pt_serv_buf_size, MSG_PEEK); + + /* + * optionally allow non-SSL connect on SSL listening socket + * This is disabled by default, if enabled it goes around any + * SSL-level access control (eg, client-side certs) so leave + * it disabled unless you know it's not a problem for you + */ + if (n >= 1 && pt->serv_buf[0] >= ' ') { + /* + * TLS content-type for Handshake is 0x16, and + * for ChangeCipherSpec Record, it's 0x14 + * + * A non-ssl session will start with the HTTP + * method in ASCII. If we see it's not a legit + * SSL handshake kill the SSL for this + * connection and try to handle as a HTTP + * connection upgrade directly. + */ + wsi->tls.use_ssl = 0; + + lws_tls_server_abort_connection(wsi); + /* + * care... this creates wsi with no ssl + * when ssl is enabled and normally + * mandatory + */ + wsi->tls.ssl = NULL; + if (lws_check_opt(context->options, + LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS)) + wsi->tls.redirect_to_https = 1; + lwsl_debug("accepted as non-ssl\n"); + goto accepted; + } + if (!n) { + /* + * connection is gone, fail out + */ + lwsl_debug("PEEKed 0\n"); + goto fail; + } + if (n < 0 && (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK)) { + /* + * well, we get no way to know ssl or not + * so go around again waiting for something + * to come and give us a hint, or timeout the + * connection. + */ + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + lwsl_info("%s: change_pollfd failed\n", + __func__); + return -1; + } + + lwsl_info("SSL_ERROR_WANT_READ\n"); + return 0; + } + } + + /* normal SSL connection processing path */ + +#if defined(LWS_WITH_STATS) + if (!wsi->accept_start_us) + wsi->accept_start_us = time_in_microseconds(); +#endif + errno = 0; + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN, 1); + n = lws_tls_server_accept(wsi); + lws_latency(context, wsi, + "SSL_accept LRS_SSL_ACK_PENDING\n", n, n == 1); + lwsl_info("SSL_accept says %d\n", n); + switch (n) { + case LWS_SSL_CAPABLE_DONE: + break; + case LWS_SSL_CAPABLE_ERROR: + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1); + lwsl_info("SSL_accept failed socket %u: %d\n", + wsi->desc.sockfd, n); + wsi->socket_is_permanently_unusable = 1; + goto fail; + + default: /* MORE_SERVICE */ + return 0; + } + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1); +#if defined(LWS_WITH_STATS) + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, + time_in_microseconds() - wsi->accept_start_us); + wsi->accept_start_us = time_in_microseconds(); +#endif + +accepted: + + /* adapt our vhost to match the SNI SSL_CTX that was chosen */ + vh = context->vhost_list; + while (vh) { + if (!vh->being_destroyed && wsi->tls.ssl && + vh->tls.ssl_ctx == lws_tls_ctx_from_wsi(wsi)) { + lwsl_info("setting wsi to vh %s\n", vh->name); + wsi->vhost = vh; + break; + } + vh = vh->vhost_next; + } + + /* OK, we are accepted... give him some time to negotiate */ + lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, + context->timeout_secs); + + lwsi_set_state(wsi, LRS_ESTABLISHED); + if (lws_tls_server_conn_alpn(wsi)) + goto fail; + lwsl_debug("accepted new SSL conn\n"); + break; + + default: + break; + } + + return 0; + +fail: + return 1; +} + diff --git a/thirdparty/libwebsockets/tls/tls.c b/thirdparty/libwebsockets/tls/tls.c new file mode 100644 index 0000000000..92b7c5593c --- /dev/null +++ b/thirdparty/libwebsockets/tls/tls.c @@ -0,0 +1,522 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * 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: + * version 2.1 of the License. + * + * 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 + */ + +#include "core/private.h" + +/* + * fakes POLLIN on all tls guys with buffered rx + * + * returns nonzero if any tls guys had POLLIN faked + */ + +int +lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt) +{ + struct lws *wsi, *wsi_next; + int ret = 0; + + wsi = pt->tls.pending_read_list; + while (wsi && wsi->position_in_fds_table != LWS_NO_FDS_POS) { + wsi_next = wsi->tls.pending_read_list_next; + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + ret |= pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN; + + wsi = wsi_next; + } + + return !!ret; +} + +void +__lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + + if (!wsi->tls.pending_read_list_prev && + !wsi->tls.pending_read_list_next && + pt->tls.pending_read_list != wsi) + /* we are not on the list */ + return; + + /* point previous guy's next to our next */ + if (!wsi->tls.pending_read_list_prev) + pt->tls.pending_read_list = wsi->tls.pending_read_list_next; + else + wsi->tls.pending_read_list_prev->tls.pending_read_list_next = + wsi->tls.pending_read_list_next; + + /* point next guy's previous to our previous */ + if (wsi->tls.pending_read_list_next) + wsi->tls.pending_read_list_next->tls.pending_read_list_prev = + wsi->tls.pending_read_list_prev; + + wsi->tls.pending_read_list_prev = NULL; + wsi->tls.pending_read_list_next = NULL; +} + +void +lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt, __func__); + __lws_ssl_remove_wsi_from_buffered_list(wsi); + lws_pt_unlock(pt); +} + +#if defined(LWS_WITH_ESP32) +int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + nvs_handle nvh; + size_t s; + int n = 0; + + ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); + if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) { + n = 1; + goto bail; + } + *buf = lws_malloc(s + 1, "alloc_file"); + if (!*buf) { + n = 2; + goto bail; + } + if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) { + lws_free(*buf); + n = 1; + goto bail; + } + + *amount = s; + (*buf)[s] = '\0'; + + lwsl_notice("%s: nvs: read %s, %d bytes\n", __func__, filename, (int)s); + +bail: + nvs_close(nvh); + + return n; +} +#else +int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + FILE *f; + size_t s; + int n = 0; + + f = fopen(filename, "rb"); + if (f == NULL) { + n = 1; + goto bail; + } + + if (fseek(f, 0, SEEK_END) != 0) { + n = 1; + goto bail; + } + + s = ftell(f); + if (s == (size_t)-1) { + n = 1; + goto bail; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + n = 1; + goto bail; + } + + *buf = lws_malloc(s, "alloc_file"); + if (!*buf) { + n = 2; + goto bail; + } + + if (fread(*buf, s, 1, f) != 1) { + lws_free(*buf); + n = 1; + goto bail; + } + + *amount = s; + +bail: + if (f) + fclose(f); + + return n; + +} +#endif + +int +lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename, + const char *inbuf, lws_filepos_t inlen, + uint8_t **buf, lws_filepos_t *amount) +{ + const uint8_t *pem, *p, *end; + uint8_t *q; + lws_filepos_t len; + int n; + + if (filename) { + n = alloc_file(context, filename, (uint8_t **)&pem, &len); + if (n) + return n; + } else { + pem = (const uint8_t *)inbuf; + len = inlen; + } + + /* trim the first line */ + + p = pem; + end = p + len; + if (strncmp((char *)p, "-----", 5)) + goto bail; + p += 5; + while (p < end && *p != '\n' && *p != '-') + p++; + + if (*p != '-') + goto bail; + + while (p < end && *p != '\n') + p++; + + if (p >= end) + goto bail; + + p++; + + /* trim the last line */ + + q = (uint8_t *)end - 2; + + while (q > pem && *q != '\n') + q--; + + if (*q != '\n') + goto bail; + + *q = '\0'; + + *amount = lws_b64_decode_string((char *)p, (char *)pem, + (int)(long long)len); + *buf = (uint8_t *)pem; + + return 0; + +bail: + lws_free((uint8_t *)pem); + + return 4; +} + +int +lws_tls_check_cert_lifetime(struct lws_vhost *v) +{ + union lws_tls_cert_info_results ir; + time_t now = (time_t)lws_now_secs(), life = 0; + struct lws_acme_cert_aging_args caa; + int n; + + if (v->tls.ssl_ctx && !v->tls.skipped_certs) { + + if (now < 1464083026) /* May 2016 */ + /* our clock is wrong and we can't judge the certs */ + return -1; + + n = lws_tls_vhost_cert_info(v, LWS_TLS_CERT_INFO_VALIDITY_TO, &ir, 0); + if (n) + return 1; + + life = (ir.time - now) / (24 * 3600); + lwsl_notice(" vhost %s: cert expiry: %dd\n", v->name, (int)life); + } else + lwsl_notice(" vhost %s: no cert\n", v->name); + + memset(&caa, 0, sizeof(caa)); + caa.vh = v; + lws_broadcast(v->context, LWS_CALLBACK_VHOST_CERT_AGING, (void *)&caa, + (size_t)(ssize_t)life); + + return 0; +} + +int +lws_tls_check_all_cert_lifetimes(struct lws_context *context) +{ + struct lws_vhost *v = context->vhost_list; + + while (v) { + if (lws_tls_check_cert_lifetime(v) < 0) + return -1; + v = v->vhost_next; + } + + return 0; +} +#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE) +static int +lws_tls_extant(const char *name) +{ + /* it exists if we can open it... */ + int fd = open(name, O_RDONLY), n; + char buf[1]; + + if (fd < 0) + return 1; + + /* and we can read at least one byte out of it */ + n = read(fd, buf, 1); + close(fd); + + return n != 1; +} +#endif +/* + * Returns 0 if the filepath "name" exists and can be read from. + * + * In addition, if "name".upd exists, backup "name" to "name.old.1" + * and rename "name".upd to "name" before reporting its existence. + * + * There are four situations and three results possible: + * + * 1) LWS_TLS_EXTANT_NO: There are no certs at all (we are waiting for them to + * be provisioned). We also feel like this if we need privs we don't have + * any more to look in the directory. + * + * 2) There are provisioned certs written (xxx.upd) and we still have root + * privs... in this case we rename any existing cert to have a backup name + * and move the upd cert into place with the correct name. This then becomes + * situation 4 for the caller. + * + * 3) LWS_TLS_EXTANT_ALTERNATIVE: There are provisioned certs written (xxx.upd) + * but we no longer have the privs needed to read or rename them. In this + * case, indicate that the caller should use temp copies if any we do have + * rights to access. This is normal after we have updated the cert. + * + * But if we dropped privs, we can't detect the provisioned xxx.upd cert + + * key, because we can't see in the dir. So we have to upgrade NO to + * ALTERNATIVE when we actually have the in-memory alternative. + * + * 4) LWS_TLS_EXTANT_YES: The certs are present with the correct name and we + * have the rights to read them. + */ +enum lws_tls_extant +lws_tls_use_any_upgrade_check_extant(const char *name) +{ +#if !defined(LWS_PLAT_OPTEE) + + int n; + +#if !defined(LWS_WITH_ESP32) + char buf[256]; + + lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name); + if (!lws_tls_extant(buf)) { + /* ah there is an updated file... how about the desired file? */ + if (!lws_tls_extant(name)) { + /* rename the desired file */ + for (n = 0; n < 50; n++) { + lws_snprintf(buf, sizeof(buf) - 1, + "%s.old.%d", name, n); + if (!rename(name, buf)) + break; + } + if (n == 50) { + lwsl_notice("unable to rename %s\n", name); + + return LWS_TLS_EXTANT_ALTERNATIVE; + } + lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name); + } + /* desired file is out of the way, rename the updated file */ + if (rename(buf, name)) { + lwsl_notice("unable to rename %s to %s\n", buf, name); + + return LWS_TLS_EXTANT_ALTERNATIVE; + } + } + + if (lws_tls_extant(name)) + return LWS_TLS_EXTANT_NO; +#else + nvs_handle nvh; + size_t s = 8192; + + if (nvs_open("lws-station", NVS_READWRITE, &nvh)) { + lwsl_notice("%s: can't open nvs\n", __func__); + return LWS_TLS_EXTANT_NO; + } + + n = nvs_get_blob(nvh, name, NULL, &s); + nvs_close(nvh); + + if (n) + return LWS_TLS_EXTANT_NO; +#endif +#endif + return LWS_TLS_EXTANT_YES; +} + +/* + * LWS_TLS_EXTANT_NO : skip adding the cert + * LWS_TLS_EXTANT_YES : use the cert and private key paths normally + * LWS_TLS_EXTANT_ALTERNATIVE: normal paths not usable, try alternate if poss + */ +enum lws_tls_extant +lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert, + const char *private_key) +{ + int n, m; + + /* + * The user code can choose to either pass the cert and + * key filepaths using the info members like this, or it can + * leave them NULL; force the vhost SSL_CTX init using the info + * options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and + * set up the cert himself using the user callback + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which + * happened just above and has the vhost SSL_CTX * in the user + * parameter. + */ + + if (!cert || !private_key) + return LWS_TLS_EXTANT_NO; + + n = lws_tls_use_any_upgrade_check_extant(cert); + if (n == LWS_TLS_EXTANT_ALTERNATIVE) + return LWS_TLS_EXTANT_ALTERNATIVE; + m = lws_tls_use_any_upgrade_check_extant(private_key); + if (m == LWS_TLS_EXTANT_ALTERNATIVE) + return LWS_TLS_EXTANT_ALTERNATIVE; + + if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) && + (vhost->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) { + lwsl_notice("Ignoring missing %s or %s\n", cert, private_key); + vhost->tls.skipped_certs = 1; + + return LWS_TLS_EXTANT_NO; + } + + /* + * the cert + key exist + */ + + return LWS_TLS_EXTANT_YES; +} + +#if !defined(LWS_NO_SERVER) +/* + * update the cert for every vhost using the given path + */ + +LWS_VISIBLE int +lws_tls_cert_updated(struct lws_context *context, const char *certpath, + const char *keypath, + const char *mem_cert, size_t len_mem_cert, + const char *mem_privkey, size_t len_mem_privkey) +{ + struct lws wsi; + + wsi.context = context; + + lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { + wsi.vhost = v; + if (v->tls.alloc_cert_path && v->tls.key_path && + !strcmp(v->tls.alloc_cert_path, certpath) && + !strcmp(v->tls.key_path, keypath)) { + lws_tls_server_certs_load(v, &wsi, certpath, keypath, + mem_cert, len_mem_cert, + mem_privkey, len_mem_privkey); + + if (v->tls.skipped_certs) + lwsl_notice("%s: vhost %s: cert unset\n", + __func__, v->name); + } + } lws_end_foreach_ll(v, vhost_next); + + return 0; +} +#endif + +int +lws_gate_accepts(struct lws_context *context, int on) +{ + struct lws_vhost *v = context->vhost_list; + + lwsl_notice("%s: on = %d\n", __func__, on); + +#if defined(LWS_WITH_STATS) + context->updated = 1; +#endif + + while (v) { + if (v->tls.use_ssl && v->lserv_wsi && + lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on, + (LWS_POLLIN) * on)) + lwsl_notice("Unable to set accept POLLIN %d\n", on); + + v = v->vhost_next; + } + + return 0; +} + +/* comma-separated alpn list, like "h2,http/1.1" to openssl alpn format */ + +int +lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len) +{ + uint8_t *oos = os, *plen = NULL; + + while (*comma && len > 1) { + if (!plen && *comma == ' ') { + comma++; + continue; + } + if (!plen) { + plen = os++; + len--; + } + + if (*comma == ',') { + *plen = lws_ptr_diff(os, plen + 1); + plen = NULL; + comma++; + } else { + *os++ = *comma++; + len--; + } + } + + if (plen) + *plen = lws_ptr_diff(os, plen + 1); + + return lws_ptr_diff(os, oos); +} + diff --git a/thirdparty/lws/win32helpers/getopt.c b/thirdparty/libwebsockets/win32helpers/getopt.c index 3bb21f6f28..2181f1cb12 100644 --- a/thirdparty/lws/win32helpers/getopt.c +++ b/thirdparty/libwebsockets/win32helpers/getopt.c @@ -1,153 +1,153 @@ -/* $NetBSD: getopt.c,v 1.16 1999/12/02 13:15:56 kleink Exp $ */ - -/* - * Copyright (c) 1987, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * 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. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS 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. - */ - -#if 0 -static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; -#endif - -#include <assert.h> -#include <errno.h> -#include <stdio.h> -#include <string.h> - -#define __P(x) x -#define _DIAGASSERT(x) assert(x) - -#ifdef __weak_alias -__weak_alias(getopt,_getopt); -#endif - - -int opterr = 1, /* if error message should be printed */ - optind = 1, /* index into parent argv vector */ - optopt, /* character checked for validity */ - optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ - -static char * _progname __P((char *)); -int getopt_internal __P((int, char * const *, const char *)); - -static char * -_progname(nargv0) - char * nargv0; -{ - char * tmp; - - _DIAGASSERT(nargv0 != NULL); - - tmp = strrchr(nargv0, '/'); - if (tmp) - tmp++; - else - tmp = nargv0; - return(tmp); -} - -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt(nargc, nargv, ostr) - int nargc; - char * const nargv[]; - const char *ostr; -{ - static char *__progname = 0; - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ - __progname = __progname?__progname:_progname(*nargv); - - _DIAGASSERT(nargv != NULL); - _DIAGASSERT(ostr != NULL); - - if (optreset || !*place) { /* update scanning pointer */ - optreset = 0; - if (optind >= nargc || *(place = nargv[optind]) != '-') { - place = EMSG; - return (-1); - } - if (place[1] && *++place == '-' /* found "--" */ - && place[1] == '\0') { - ++optind; - place = EMSG; - return (-1); - } - } /* option letter okay? */ - if ((optopt = (int)*place++) == (int)':' || - !(oli = strchr(ostr, optopt))) { - /* - * if the user didn't specify '-' as an option, - * assume it means -1. - */ - if (optopt == (int)'-') - return (-1); - if (!*place) - ++optind; - if (opterr && *ostr != ':') - (void)fprintf(stderr, - "%s: illegal option -- %c\n", __progname, optopt); - return (BADCH); - } - if (*++oli != ':') { /* don't need argument */ - optarg = NULL; - if (!*place) - ++optind; - } - else { /* need an argument */ - if (*place) /* no white space */ - optarg = place; - else if (nargc <= ++optind) { /* no arg */ - place = EMSG; - if (*ostr == ':') - return (BADARG); - if (opterr) - (void)fprintf(stderr, - "%s: option requires an argument -- %c\n", - __progname, optopt); - return (BADCH); - } - else /* white space */ - optarg = nargv[optind]; - place = EMSG; - ++optind; - } - return (optopt); /* dump back option letter */ -} - +/* $NetBSD: getopt.c,v 1.16 1999/12/02 13:15:56 kleink Exp $ */
+
+/*
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS 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.
+ */
+
+#if 0
+static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95";
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#define __P(x) x
+#define _DIAGASSERT(x) assert(x)
+
+#ifdef __weak_alias
+__weak_alias(getopt,_getopt);
+#endif
+
+
+int opterr = 1, /* if error message should be printed */
+ optind = 1, /* index into parent argv vector */
+ optopt, /* character checked for validity */
+ optreset; /* reset getopt */
+char *optarg; /* argument associated with option */
+
+static char * _progname __P((char *));
+int getopt_internal __P((int, char * const *, const char *));
+
+static char *
+_progname(nargv0)
+ char * nargv0;
+{
+ char * tmp;
+
+ _DIAGASSERT(nargv0 != NULL);
+
+ tmp = strrchr(nargv0, '/');
+ if (tmp)
+ tmp++;
+ else
+ tmp = nargv0;
+ return(tmp);
+}
+
+#define BADCH (int)'?'
+#define BADARG (int)':'
+#define EMSG ""
+
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt(nargc, nargv, ostr)
+ int nargc;
+ char * const nargv[];
+ const char *ostr;
+{
+ static char *__progname = 0;
+ static char *place = EMSG; /* option letter processing */
+ char *oli; /* option letter list index */
+ __progname = __progname?__progname:_progname(*nargv);
+
+ _DIAGASSERT(nargv != NULL);
+ _DIAGASSERT(ostr != NULL);
+
+ if (optreset || !*place) { /* update scanning pointer */
+ optreset = 0;
+ if (optind >= nargc || *(place = nargv[optind]) != '-') {
+ place = EMSG;
+ return (-1);
+ }
+ if (place[1] && *++place == '-' /* found "--" */
+ && place[1] == '\0') {
+ ++optind;
+ place = EMSG;
+ return (-1);
+ }
+ } /* option letter okay? */
+ if ((optopt = (int)*place++) == (int)':' ||
+ !(oli = strchr(ostr, optopt))) {
+ /*
+ * if the user didn't specify '-' as an option,
+ * assume it means -1.
+ */
+ if (optopt == (int)'-')
+ return (-1);
+ if (!*place)
+ ++optind;
+ if (opterr && *ostr != ':')
+ (void)fprintf(stderr,
+ "%s: illegal option -- %c\n", __progname, optopt);
+ return (BADCH);
+ }
+ if (*++oli != ':') { /* don't need argument */
+ optarg = NULL;
+ if (!*place)
+ ++optind;
+ }
+ else { /* need an argument */
+ if (*place) /* no white space */
+ optarg = place;
+ else if (nargc <= ++optind) { /* no arg */
+ place = EMSG;
+ if (*ostr == ':')
+ return (BADARG);
+ if (opterr)
+ (void)fprintf(stderr,
+ "%s: option requires an argument -- %c\n",
+ __progname, optopt);
+ return (BADCH);
+ }
+ else /* white space */
+ optarg = nargv[optind];
+ place = EMSG;
+ ++optind;
+ }
+ return (optopt); /* dump back option letter */
+}
+
diff --git a/thirdparty/lws/win32helpers/getopt.h b/thirdparty/libwebsockets/win32helpers/getopt.h index 7137f0379c..7137f0379c 100644 --- a/thirdparty/lws/win32helpers/getopt.h +++ b/thirdparty/libwebsockets/win32helpers/getopt.h diff --git a/thirdparty/lws/win32helpers/getopt_long.c b/thirdparty/libwebsockets/win32helpers/getopt_long.c index 5bcf40060f..22e5fa8945 100644 --- a/thirdparty/lws/win32helpers/getopt_long.c +++ b/thirdparty/libwebsockets/win32helpers/getopt_long.c @@ -1,237 +1,240 @@ - -/* - * Copyright (c) 1987, 1993, 1994, 1996 - * The Regents of the University of California. All rights reserved. - * - * 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. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#include <assert.h> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include "getopt.h" - -extern int opterr; /* if error message should be printed */ -extern int optind; /* index into parent argv vector */ -extern int optopt; /* character checked for validity */ -extern int optreset; /* reset getopt */ -extern char *optarg; /* argument associated with option */ - -#define __P(x) x -#define _DIAGASSERT(x) assert(x) - -static char * __progname __P((char *)); -int getopt_internal __P((int, char * const *, const char *)); - -static char * -__progname(nargv0) - char * nargv0; -{ - char * tmp; - - _DIAGASSERT(nargv0 != NULL); - - tmp = strrchr(nargv0, '/'); - if (tmp) - tmp++; - else - tmp = nargv0; - return(tmp); -} - -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt_internal(nargc, nargv, ostr) - int nargc; - char * const *nargv; - const char *ostr; -{ - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ - - _DIAGASSERT(nargv != NULL); - _DIAGASSERT(ostr != NULL); - - if (optreset || !*place) { /* update scanning pointer */ - optreset = 0; - if (optind >= nargc || *(place = nargv[optind]) != '-') { - place = EMSG; - return (-1); - } - if (place[1] && *++place == '-') { /* found "--" */ - /* ++optind; */ - place = EMSG; - return (-2); - } - } /* option letter okay? */ - if ((optopt = (int)*place++) == (int)':' || - !(oli = strchr(ostr, optopt))) { - /* - * if the user didn't specify '-' as an option, - * assume it means -1. - */ - if (optopt == (int)'-') - return (-1); - if (!*place) - ++optind; - if (opterr && *ostr != ':') - (void)fprintf(stderr, - "%s: illegal option -- %c\n", __progname(nargv[0]), optopt); - return (BADCH); - } - if (*++oli != ':') { /* don't need argument */ - optarg = NULL; - if (!*place) - ++optind; - } else { /* need an argument */ - if (*place) /* no white space */ - optarg = place; - else if (nargc <= ++optind) { /* no arg */ - place = EMSG; - if ((opterr) && (*ostr != ':')) - (void)fprintf(stderr, - "%s: option requires an argument -- %c\n", - __progname(nargv[0]), optopt); - return (BADARG); - } else /* white space */ - optarg = nargv[optind]; - place = EMSG; - ++optind; - } - return (optopt); /* dump back option letter */ -} - -#if 0 -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt2(nargc, nargv, ostr) - int nargc; - char * const *nargv; - const char *ostr; -{ - int retval; - - if ((retval = getopt_internal(nargc, nargv, ostr)) == -2) { - retval = -1; - ++optind; - } - return(retval); -} -#endif - -/* - * getopt_long -- - * Parse argc/argv argument vector. - */ -int -getopt_long(nargc, nargv, options, long_options, index) - int nargc; - char ** nargv; - char * options; - struct option * long_options; - int * index; -{ - int retval; - - _DIAGASSERT(nargv != NULL); - _DIAGASSERT(options != NULL); - _DIAGASSERT(long_options != NULL); - /* index may be NULL */ - - if ((retval = getopt_internal(nargc, nargv, options)) == -2) { - char *current_argv = nargv[optind++] + 2, *has_equal; - int i, current_argv_len, match = -1; - - if (*current_argv == '\0') { - return(-1); - } - if ((has_equal = strchr(current_argv, '=')) != NULL) { - current_argv_len = has_equal - current_argv; - has_equal++; - } else - current_argv_len = strlen(current_argv); - - for (i = 0; long_options[i].name; i++) { - if (strncmp(current_argv, long_options[i].name, current_argv_len)) - continue; - - if (strlen(long_options[i].name) == (unsigned)current_argv_len) { - match = i; - break; - } - if (match == -1) - match = i; - } - if (match != -1) { - if (long_options[match].has_arg == required_argument || - long_options[match].has_arg == optional_argument) { - if (has_equal) - optarg = has_equal; - else - optarg = nargv[optind++]; - } - if ((long_options[match].has_arg == required_argument) - && (optarg == NULL)) { - /* - * Missing argument, leading : - * indicates no error should be generated - */ - if ((opterr) && (*options != ':')) - (void)fprintf(stderr, - "%s: option requires an argument -- %s\n", - __progname(nargv[0]), current_argv); - return (BADARG); - } - } else { /* No matching argument */ - if ((opterr) && (*options != ':')) - (void)fprintf(stderr, - "%s: illegal option -- %s\n", __progname(nargv[0]), current_argv); - return (BADCH); - } - if (long_options[match].flag) { - *long_options[match].flag = long_options[match].val; - retval = 0; - } else - retval = long_options[match].val; - if (index) - *index = match; - } - return(retval); -} +
+/*
+ * Copyright (c) 1987, 1993, 1994, 1996
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "getopt.h"
+
+#define lws_ptr_diff(head, tail) \
+ ((int)((char *)(head) - (char *)(tail)))
+
+extern int opterr; /* if error message should be printed */
+extern int optind; /* index into parent argv vector */
+extern int optopt; /* character checked for validity */
+extern int optreset; /* reset getopt */
+extern char *optarg; /* argument associated with option */
+
+#define __P(x) x
+#define _DIAGASSERT(x) assert(x)
+
+static char * __progname __P((char *));
+int getopt_internal __P((int, char * const *, const char *));
+
+static char *
+__progname(nargv0)
+ char * nargv0;
+{
+ char * tmp;
+
+ _DIAGASSERT(nargv0 != NULL);
+
+ tmp = strrchr(nargv0, '/');
+ if (tmp)
+ tmp++;
+ else
+ tmp = nargv0;
+ return(tmp);
+}
+
+#define BADCH (int)'?'
+#define BADARG (int)':'
+#define EMSG ""
+
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_internal(nargc, nargv, ostr)
+ int nargc;
+ char * const *nargv;
+ const char *ostr;
+{
+ static char *place = EMSG; /* option letter processing */
+ char *oli; /* option letter list index */
+
+ _DIAGASSERT(nargv != NULL);
+ _DIAGASSERT(ostr != NULL);
+
+ if (optreset || !*place) { /* update scanning pointer */
+ optreset = 0;
+ if (optind >= nargc || *(place = nargv[optind]) != '-') {
+ place = EMSG;
+ return (-1);
+ }
+ if (place[1] && *++place == '-') { /* found "--" */
+ /* ++optind; */
+ place = EMSG;
+ return (-2);
+ }
+ } /* option letter okay? */
+ if ((optopt = (int)*place++) == (int)':' ||
+ !(oli = strchr(ostr, optopt))) {
+ /*
+ * if the user didn't specify '-' as an option,
+ * assume it means -1.
+ */
+ if (optopt == (int)'-')
+ return (-1);
+ if (!*place)
+ ++optind;
+ if (opterr && *ostr != ':')
+ (void)fprintf(stderr,
+ "%s: illegal option -- %c\n", __progname(nargv[0]), optopt);
+ return (BADCH);
+ }
+ if (*++oli != ':') { /* don't need argument */
+ optarg = NULL;
+ if (!*place)
+ ++optind;
+ } else { /* need an argument */
+ if (*place) /* no white space */
+ optarg = place;
+ else if (nargc <= ++optind) { /* no arg */
+ place = EMSG;
+ if ((opterr) && (*ostr != ':'))
+ (void)fprintf(stderr,
+ "%s: option requires an argument -- %c\n",
+ __progname(nargv[0]), optopt);
+ return (BADARG);
+ } else /* white space */
+ optarg = nargv[optind];
+ place = EMSG;
+ ++optind;
+ }
+ return (optopt); /* dump back option letter */
+}
+
+#if 0
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt2(nargc, nargv, ostr)
+ int nargc;
+ char * const *nargv;
+ const char *ostr;
+{
+ int retval;
+
+ if ((retval = getopt_internal(nargc, nargv, ostr)) == -2) {
+ retval = -1;
+ ++optind;
+ }
+ return(retval);
+}
+#endif
+
+/*
+ * getopt_long --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long(nargc, nargv, options, long_options, index)
+ int nargc;
+ char ** nargv;
+ char * options;
+ struct option * long_options;
+ int * index;
+{
+ int retval;
+
+ _DIAGASSERT(nargv != NULL);
+ _DIAGASSERT(options != NULL);
+ _DIAGASSERT(long_options != NULL);
+ /* index may be NULL */
+
+ if ((retval = getopt_internal(nargc, nargv, options)) == -2) {
+ char *current_argv = nargv[optind++] + 2, *has_equal;
+ int i, current_argv_len, match = -1;
+
+ if (*current_argv == '\0') {
+ return(-1);
+ }
+ if ((has_equal = strchr(current_argv, '=')) != NULL) {
+ current_argv_len = lws_ptr_diff(has_equal, current_argv);
+ has_equal++;
+ } else
+ current_argv_len = (int)strlen(current_argv);
+
+ for (i = 0; long_options[i].name; i++) {
+ if (strncmp(current_argv, long_options[i].name, current_argv_len))
+ continue;
+
+ if (strlen(long_options[i].name) == (unsigned)current_argv_len) {
+ match = i;
+ break;
+ }
+ if (match == -1)
+ match = i;
+ }
+ if (match != -1) {
+ if (long_options[match].has_arg == required_argument ||
+ long_options[match].has_arg == optional_argument) {
+ if (has_equal)
+ optarg = has_equal;
+ else
+ optarg = nargv[optind++];
+ }
+ if ((long_options[match].has_arg == required_argument)
+ && (optarg == NULL)) {
+ /*
+ * Missing argument, leading :
+ * indicates no error should be generated
+ */
+ if ((opterr) && (*options != ':'))
+ (void)fprintf(stderr,
+ "%s: option requires an argument -- %s\n",
+ __progname(nargv[0]), current_argv);
+ return (BADARG);
+ }
+ } else { /* No matching argument */
+ if ((opterr) && (*options != ':'))
+ (void)fprintf(stderr,
+ "%s: illegal option -- %s\n", __progname(nargv[0]), current_argv);
+ return (BADCH);
+ }
+ if (long_options[match].flag) {
+ *long_options[match].flag = long_options[match].val;
+ retval = 0;
+ } else
+ retval = long_options[match].val;
+ if (index)
+ *index = match;
+ }
+ return(retval);
+}
diff --git a/thirdparty/lws/win32helpers/gettimeofday.c b/thirdparty/libwebsockets/win32helpers/gettimeofday.c index 35dd73531d..08385c2320 100644 --- a/thirdparty/lws/win32helpers/gettimeofday.c +++ b/thirdparty/libwebsockets/win32helpers/gettimeofday.c @@ -1,36 +1,36 @@ -#include <time.h> -#include <windows.h> //I've omitted context line - -#include "gettimeofday.h" - -int gettimeofday(struct timeval *tv, struct timezone *tz) -{ - FILETIME ft; - unsigned __int64 tmpres = 0; - static int tzflag; - - if (NULL != tv) { - GetSystemTimeAsFileTime(&ft); - - tmpres |= ft.dwHighDateTime; - tmpres <<= 32; - tmpres |= ft.dwLowDateTime; - - /*converting file time to unix epoch*/ - tmpres /= 10; /*convert into microseconds*/ +#include <time.h>
+#include <windows.h> //I've omitted context line
+
+#include "gettimeofday.h"
+
+int gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+ FILETIME ft;
+ unsigned __int64 tmpres = 0;
+ static int tzflag;
+
+ if (NULL != tv) {
+ GetSystemTimeAsFileTime(&ft);
+
+ tmpres |= ft.dwHighDateTime;
+ tmpres <<= 32;
+ tmpres |= ft.dwLowDateTime;
+
+ /*converting file time to unix epoch*/
+ tmpres /= 10; /*convert into microseconds*/
tmpres -= DELTA_EPOCH_IN_MICROSECS; - tv->tv_sec = (long)(tmpres / 1000000UL); - tv->tv_usec = (long)(tmpres % 1000000UL); - } - - if (NULL != tz) { - if (!tzflag) { - _tzset(); - tzflag++; - } - tz->tz_minuteswest = _timezone / 60; - tz->tz_dsttime = _daylight; - } - - return 0; -} + tv->tv_sec = (long)(tmpres / 1000000UL);
+ tv->tv_usec = (long)(tmpres % 1000000UL);
+ }
+
+ if (NULL != tz) {
+ if (!tzflag) {
+ _tzset();
+ tzflag++;
+ }
+ tz->tz_minuteswest = _timezone / 60;
+ tz->tz_dsttime = _daylight;
+ }
+
+ return 0;
+}
diff --git a/thirdparty/lws/win32helpers/gettimeofday.h b/thirdparty/libwebsockets/win32helpers/gettimeofday.h index 33e7a750fe..33e7a750fe 100644 --- a/thirdparty/lws/win32helpers/gettimeofday.h +++ b/thirdparty/libwebsockets/win32helpers/gettimeofday.h diff --git a/thirdparty/lws/client/client.c b/thirdparty/lws/client/client.c deleted file mode 100644 index ded4e4bf0b..0000000000 --- a/thirdparty/lws/client/client.c +++ /dev/null @@ -1,1304 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -int -lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) -{ - int m; - - switch (wsi->mode) { - case LWSCM_WSCL_WAITING_PROXY_REPLY: - case LWSCM_WSCL_ISSUE_HANDSHAKE: - case LWSCM_WSCL_WAITING_SERVER_REPLY: - case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: - case LWSCM_WS_CLIENT: - while (len) { - /* - * we were accepting input but now we stopped doing so - */ - if (lws_is_flowcontrolled(wsi)) { - lwsl_debug("%s: caching %ld\n", __func__, (long)len); - lws_rxflow_cache(wsi, *buf, 0, len); - return 0; - } - if (wsi->u.ws.rx_draining_ext) { -#if !defined(LWS_NO_CLIENT) - if (wsi->mode == LWSCM_WS_CLIENT) - m = lws_client_rx_sm(wsi, 0); - else -#endif - m = lws_rx_sm(wsi, 0); - if (m < 0) - return -1; - continue; - } - /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) - wsi->rxflow_pos++; - - if (lws_client_rx_sm(wsi, *(*buf)++)) { - lwsl_debug("client_rx_sm exited\n"); - return -1; - } - len--; - } - lwsl_debug("%s: finished with %ld\n", __func__, (long)len); - return 0; - default: - break; - } - - return 0; -} - -LWS_VISIBLE LWS_EXTERN void -lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) -{ - wsi->client_http_body_pending = !!something_left_to_send; -} - -int -lws_client_socket_service(struct lws_context *context, struct lws *wsi, - struct lws_pollfd *pollfd) -{ - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - char *p = (char *)&pt->serv_buf[0]; - const char *cce = NULL; - unsigned char c; - char *sb = p; - int n = 0; - ssize_t len = 0; -#if defined(LWS_WITH_SOCKS5) - char conn_mode = 0, pending_timeout = 0; -#endif - - switch (wsi->mode) { - - case LWSCM_WSCL_WAITING_CONNECT: - - /* - * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE - * timeout protection set in client-handshake.c - */ - - if (!lws_client_connect_2(wsi)) { - /* closed */ - lwsl_client("closed\n"); - return -1; - } - - /* either still pending connection, or changed mode */ - return 0; - -#if defined(LWS_WITH_SOCKS5) - /* SOCKS Greeting Reply */ - case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY: - case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY: - case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY: - - /* handle proxy hung up on us */ - - if (pollfd->revents & LWS_POLLHUP) { - lwsl_warn("SOCKS connection %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); - goto bail3; - } - - n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); - if (n < 0) { - if (LWS_ERRNO == LWS_EAGAIN) { - lwsl_debug("SOCKS read EAGAIN, retrying\n"); - return 0; - } - lwsl_err("ERROR reading from SOCKS socket\n"); - goto bail3; - } - - switch (wsi->mode) { - - case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY: - if (pt->serv_buf[0] != SOCKS_VERSION_5) - goto socks_reply_fail; - - if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { - lwsl_client("SOCKS greeting reply: No Auth Method\n"); - socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); - conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; - pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; - goto socks_send; - } - - if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { - lwsl_client("SOCKS greeting reply: User/Pw Method\n"); - socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD, &len); - conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY; - pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; - goto socks_send; - } - goto socks_reply_fail; - - case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY: - if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 || - pt->serv_buf[1] != SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) - goto socks_reply_fail; - - lwsl_client("SOCKS password OK, sending connect\n"); - socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); - conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; - pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; -socks_send: - n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, - MSG_NOSIGNAL); - if (n < 0) { - lwsl_debug("ERROR writing to socks proxy\n"); - goto bail3; - } - - lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); - wsi->mode = conn_mode; - break; - -socks_reply_fail: - lwsl_notice("socks reply: v%d, err %d\n", - pt->serv_buf[0], pt->serv_buf[1]); - goto bail3; - - case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY: - if (pt->serv_buf[0] != SOCKS_VERSION_5 || - pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS) - goto socks_reply_fail; - - lwsl_client("socks connect OK\n"); - - /* free stash since we are done with it */ - lws_free_set_NULL(wsi->u.hdr.stash); - if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, - wsi->vhost->socks_proxy_address)) - goto bail3; - - wsi->c_port = wsi->vhost->socks_proxy_port; - - /* clear his proxy connection timeout */ - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - goto start_ws_handshake; - } - break; -#endif - - case LWSCM_WSCL_WAITING_PROXY_REPLY: - - /* handle proxy hung up on us */ - - if (pollfd->revents & LWS_POLLHUP) { - - lwsl_warn("Proxy connection %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); - - goto bail3; - } - - n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); - if (n < 0) { - if (LWS_ERRNO == LWS_EAGAIN) { - lwsl_debug("Proxy read returned EAGAIN... retrying\n"); - return 0; - } - lwsl_err("ERROR reading from proxy socket\n"); - goto bail3; - } - - pt->serv_buf[13] = '\0'; - if (strcmp(sb, "HTTP/1.0 200 ") && - strcmp(sb, "HTTP/1.1 200 ")) { - lwsl_err("ERROR proxy: %s\n", sb); - goto bail3; - } - - /* clear his proxy connection timeout */ - - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - - /* fallthru */ - - case LWSCM_WSCL_ISSUE_HANDSHAKE: - - /* - * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE - * timeout protection set in client-handshake.c - * - * take care of our lws_callback_on_writable - * happening at a time when there's no real connection yet - */ -#if defined(LWS_WITH_SOCKS5) -start_ws_handshake: -#endif - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) - return -1; - -#ifdef LWS_OPENSSL_SUPPORT - /* we can retry this... just cook the SSL BIO the first time */ - - if (wsi->use_ssl && !wsi->ssl && - lws_ssl_client_bio_create(wsi) < 0) { - cce = "bio_create failed"; - goto bail3; - } - - if (wsi->use_ssl) { - n = lws_ssl_client_connect1(wsi); - if (!n) - return 0; - if (n < 0) { - cce = "lws_ssl_client_connect1 failed"; - goto bail3; - } - } else - wsi->ssl = NULL; - - /* fallthru */ - - case LWSCM_WSCL_WAITING_SSL: - - if (wsi->use_ssl) { - n = lws_ssl_client_connect2(wsi); - if (!n) - return 0; - if (n < 0) { - cce = "lws_ssl_client_connect2 failed"; - goto bail3; - } - } else - wsi->ssl = NULL; -#endif - - wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2; - lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, - context->timeout_secs); - - /* fallthru */ - - case LWSCM_WSCL_ISSUE_HANDSHAKE2: - p = lws_generate_client_handshake(wsi, p); - if (p == NULL) { - if (wsi->mode == LWSCM_RAW) - return 0; - - lwsl_err("Failed to generate handshake for client\n"); - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - return 0; - } - - /* send our request to the server */ - lws_latency_pre(context, wsi); - - n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb); - lws_latency(context, wsi, "send lws_issue_raw", n, - n == p - sb); - switch (n) { - case LWS_SSL_CAPABLE_ERROR: - lwsl_debug("ERROR writing to client socket\n"); - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - return 0; - case LWS_SSL_CAPABLE_MORE_SERVICE: - lws_callback_on_writable(wsi); - break; - } - - if (wsi->client_http_body_pending) { - wsi->mode = LWSCM_WSCL_ISSUE_HTTP_BODY; - lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, - context->timeout_secs); - /* user code must ask for writable callback */ - break; - } - - goto client_http_body_sent; - - case LWSCM_WSCL_ISSUE_HTTP_BODY: - if (wsi->client_http_body_pending) { - lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, - context->timeout_secs); - /* user code must ask for writable callback */ - break; - } -client_http_body_sent: - wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; - wsi->u.hdr.lextable_pos = 0; - wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY; - lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, - context->timeout_secs); - break; - - case LWSCM_WSCL_WAITING_SERVER_REPLY: - /* - * handle server hanging up on us... - * but if there is POLLIN waiting, handle that first - */ - if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == - LWS_POLLHUP) { - - lwsl_debug("Server connection %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); - cce = "Peer hung up"; - goto bail3; - } - - if (!(pollfd->revents & LWS_POLLIN)) - break; - - /* interpret the server response - * - * HTTP/1.1 101 Switching Protocols - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= - * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== - * Sec-WebSocket-Protocol: chat - * - * we have to take some care here to only take from the - * socket bytewise. The browser may (and has been seen to - * in the case that onopen() performs websocket traffic) - * coalesce both handshake response and websocket traffic - * in one packet, since at that point the connection is - * definitively ready from browser pov. - */ - len = 1; - while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE && - len > 0) { - n = lws_ssl_capable_read(wsi, &c, 1); - lws_latency(context, wsi, "send lws_issue_raw", n, - n == 1); - switch (n) { - case 0: - case LWS_SSL_CAPABLE_ERROR: - cce = "read failed"; - goto bail3; - case LWS_SSL_CAPABLE_MORE_SERVICE: - return 0; - } - - if (lws_parse(wsi, c)) { - lwsl_warn("problems parsing header\n"); - goto bail3; - } - } - - /* - * hs may also be coming in multiple packets, there is a 5-sec - * libwebsocket timeout still active here too, so if parsing did - * not complete just wait for next packet coming in this state - */ - if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) - break; - - /* - * otherwise deal with the handshake. If there's any - * packet traffic already arrived we'll trigger poll() again - * right away and deal with it that way - */ - return lws_client_interpret_server_handshake(wsi); - -bail3: - lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); - if (cce) - lwsl_info("reason: %s\n", cce); - wsi->protocol->callback(wsi, - LWS_CALLBACK_CLIENT_CONNECTION_ERROR, - wsi->user_space, (void *)cce, cce ? strlen(cce) : 0); - wsi->already_did_cce = 1; - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - return -1; - - case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: - lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n"); - break; - - case LWSCM_WSCL_PENDING_CANDIDATE_CHILD: - lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n"); - break; - default: - break; - } - - return 0; -} - -/* - * In-place str to lower case - */ - -static void -strtolower(char *s) -{ - while (*s) { -#ifdef LWS_PLAT_OPTEE - int tolower_optee(int c); - *s = tolower_optee((int)*s); -#else - *s = tolower((int)*s); -#endif - s++; - } -} - -int LWS_WARN_UNUSED_RESULT -lws_http_transaction_completed_client(struct lws *wsi) -{ - lwsl_debug("%s: wsi %p\n", __func__, wsi); - /* if we can't go back to accept new headers, drop the connection */ - if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { - lwsl_info("%s: %p: close connection\n", __func__, wsi); - return 1; - } - - /* we don't support chained client connections yet */ - return 1; -#if 0 - /* otherwise set ourselves up ready to go again */ - wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; - wsi->mode = LWSCM_HTTP_CLIENT_ACCEPTED; - wsi->u.http.rx_content_length = 0; - wsi->hdr_parsing_completed = 0; - - /* He asked for it to stay alive indefinitely */ - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - - /* - * As client, nothing new is going to come until we ask for it - * we can drop the ah, if any - */ - if (wsi->u.hdr.ah) { - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 0); - } - - /* If we're (re)starting on headers, need other implied init */ - wsi->u.hdr.ues = URIES_IDLE; - - lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); - - return 0; -#endif -} - -LWS_VISIBLE LWS_EXTERN unsigned int -lws_http_client_http_response(struct lws *wsi) -{ - if (!wsi->u.http.ah) - return 0; - - return wsi->u.http.ah->http_response; -} - -int -lws_client_interpret_server_handshake(struct lws *wsi) -{ - int n, len, okay = 0, port = 0, ssl = 0; - int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; - struct lws_context *context = wsi->context; - const char *pc, *prot, *ads = NULL, *path, *cce = NULL; - struct allocated_headers *ah = NULL; - char *p, *q; - char new_path[300]; -#ifndef LWS_NO_EXTENSIONS - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - char *sb = (char *)&pt->serv_buf[0]; - const struct lws_ext_options *opts; - const struct lws_extension *ext; - char ext_name[128]; - const char *c, *a; - char ignore; - int more = 1; - void *v; -#endif - if (wsi->u.hdr.stash) - lws_free_set_NULL(wsi->u.hdr.stash); - - ah = wsi->u.hdr.ah; - if (!wsi->do_ws) { - /* we are being an http client... - */ - lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED); - wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; - wsi->u.http.ah = ah; - ah->http_response = 0; - } - - /* - * well, what the server sent looked reasonable for syntax. - * Now let's confirm it sent all the necessary headers - * - * http (non-ws) client will expect something like this - * - * HTTP/1.0.200 - * server:.libwebsockets - * content-type:.text/html - * content-length:.17703 - * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 - * - * - * - */ - - wsi->u.http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); - if (wsi->do_ws && !p) { - lwsl_info("no URI\n"); - cce = "HS: URI missing"; - goto bail3; - } - if (!p) { - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); - wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; - } - if (!p) { - cce = "HS: URI missing"; - lwsl_info("no URI\n"); - goto bail3; - } - n = atoi(p); - if (ah) - ah->http_response = n; - - if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); - if (!p) { - cce = "HS: Redirect code but no Location"; - goto bail3; - } - - /* Relative reference absolute path */ - if (p[0] == '/') - { -#ifdef LWS_OPENSSL_SUPPORT - ssl = wsi->use_ssl; -#endif - ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); - port = wsi->c_port; - path = p + 1; /* +1 as lws_client_reset expects leading / to be omitted */ - } - /* Absolute (Full) URI */ - else if (strchr(p, ':')) - { - if (lws_parse_uri(p, &prot, &ads, &port, &path)) { - cce = "HS: URI did not parse"; - goto bail3; - } - - if (!strcmp(prot, "wss") || !strcmp(prot, "https")) - ssl = 1; - } - /* Relative reference relative path */ - else - { - /* This doesn't try to calculate an absolute path, that will be left to the server */ -#ifdef LWS_OPENSSL_SUPPORT - ssl = wsi->use_ssl; -#endif - ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); - port = wsi->c_port; - path = new_path + 1; /* +1 as lws_client_reset expects leading / to be omitted */ - strncpy(new_path, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), sizeof(new_path)); - new_path[sizeof(new_path) - 1] = '\0'; - q = strrchr(new_path, '/'); - if (q) - { - strncpy(q + 1, p, sizeof(new_path) - (q - new_path) - 1); - new_path[sizeof(new_path) - 1] = '\0'; - } - else - { - path = p; - } - } - -#ifdef LWS_OPENSSL_SUPPORT - if (wsi->use_ssl && !ssl) { - cce = "HS: Redirect attempted SSL downgrade"; - goto bail3; - } -#endif - - if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) { - /* there are two ways to fail out with NULL return... - * simple, early problem where the wsi is intact, or - * we went through with the reconnect attempt and the - * wsi is already closed. In the latter case, the wsi - * has beet set to NULL additionally. - */ - lwsl_err("Redirect failed\n"); - cce = "HS: Redirect failed"; - if (wsi) - goto bail3; - - return 1; - } - return 0; - } - - if (!wsi->do_ws) { - -#ifdef LWS_WITH_HTTP_PROXY - wsi->perform_rewrite = 0; - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { - if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), - "text/html", 9)) - wsi->perform_rewrite = 1; - } -#endif - - /* allocate the per-connection user memory (if any) */ - if (lws_ensure_user_space(wsi)) { - lwsl_err("Problem allocating wsi user mem\n"); - cce = "HS: OOM"; - goto bail2; - } - - /* he may choose to send us stuff in chunked transfer-coding */ - wsi->chunked = 0; - wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { - wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, - WSI_TOKEN_HTTP_TRANSFER_ENCODING), - "chunked"); - /* first thing is hex, after payload there is crlf */ - wsi->chunk_parser = ELCP_HEX; - } - - if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { - wsi->u.http.rx_content_length = - atoll(lws_hdr_simple_ptr(wsi, - WSI_TOKEN_HTTP_CONTENT_LENGTH)); - lwsl_notice("%s: incoming content length %llu\n", __func__, - (unsigned long long)wsi->u.http.rx_content_length); - wsi->u.http.rx_content_remain = wsi->u.http.rx_content_length; - } else /* can't do 1.1 without a content length or chunked */ - if (!wsi->chunked) - wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; - - /* - * we seem to be good to go, give client last chance to check - * headers and OK it - */ - if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, - wsi->user_space, NULL, 0)) { - - cce = "HS: disallowed by client filter"; - goto bail2; - } - - /* clear his proxy connection timeout */ - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - - wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; - - /* call him back to inform him he is up */ - if (wsi->protocol->callback(wsi, - LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, - wsi->user_space, NULL, 0)) { - cce = "HS: disallowed at ESTABLISHED"; - goto bail3; - } - - /* free up his parsing allocations */ - lws_header_table_detach(wsi, 0); - - lwsl_notice("%s: client connection up\n", __func__); - - return 0; - } - - if (p && !strncmp(p, "401", 3)) { - lwsl_warn( - "lws_client_handshake: got bad HTTP response '%s'\n", p); - cce = "HS: ws upgrade unauthorized"; - goto bail3; - } - - if (p && strncmp(p, "101", 3)) { - lwsl_warn( - "lws_client_handshake: got bad HTTP response '%s'\n", p); - cce = "HS: ws upgrade response not 101"; - goto bail3; - } - - if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { - lwsl_info("no ACCEPT\n"); - cce = "HS: ACCEPT missing"; - goto bail3; - } - - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); - if (!p) { - lwsl_info("no UPGRADE\n"); - cce = "HS: UPGRADE missing"; - goto bail3; - } - strtolower(p); - if (strcmp(p, "websocket")) { - lwsl_warn( - "lws_client_handshake: got bad Upgrade header '%s'\n", p); - cce = "HS: Upgrade to something other than websocket"; - goto bail3; - } - - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); - if (!p) { - lwsl_info("no Connection hdr\n"); - cce = "HS: CONNECTION missing"; - goto bail3; - } - strtolower(p); - if (strcmp(p, "upgrade")) { - lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); - cce = "HS: UPGRADE malformed"; - goto bail3; - } - - pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); - if (!pc) { - lwsl_parser("lws_client_int_s_hs: no protocol list\n"); - } else - lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); - - /* - * confirm the protocol the server wants to talk was in the list - * of protocols we offered - */ - - len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); - if (!len) { - lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n"); - /* - * no protocol name to work from, - * default to first protocol - */ - n = 0; - wsi->protocol = &wsi->vhost->protocols[0]; - goto check_extensions; - } - - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); - len = strlen(p); - - while (pc && *pc && !okay) { - if (!strncmp(pc, p, len) && - (pc[len] == ',' || pc[len] == '\0')) { - okay = 1; - continue; - } - while (*pc && *pc++ != ',') - ; - while (*pc && *pc == ' ') - pc++; - } - - if (!okay) { - lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p); - cce = "HS: PROTOCOL malformed"; - goto bail2; - } - - /* - * identify the selected protocol struct and set it - */ - n = 0; - wsi->protocol = NULL; - while (wsi->vhost->protocols[n].callback && !wsi->protocol) { - if (strcmp(p, wsi->vhost->protocols[n].name) == 0) { - wsi->protocol = &wsi->vhost->protocols[n]; - break; - } - n++; - } - - if (wsi->protocol == NULL) { - lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p); - cce = "HS: Cannot match protocol"; - goto bail2; - } - -check_extensions: - /* - * stitch protocol choice into the vh protocol linked list - * We always insert ourselves at the start of the list - * - * X <-> B - * X <-> pAn <-> pB - */ - //lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n", - // __func__, - // wsi->vhost->same_vh_protocol_list[n], - // wsi->same_vh_protocol_prev); - wsi->same_vh_protocol_prev = /* guy who points to us */ - &wsi->vhost->same_vh_protocol_list[n]; - wsi->same_vh_protocol_next = /* old first guy is our next */ - wsi->vhost->same_vh_protocol_list[n]; - /* we become the new first guy */ - wsi->vhost->same_vh_protocol_list[n] = wsi; - - if (wsi->same_vh_protocol_next) - /* old first guy points back to us now */ - wsi->same_vh_protocol_next->same_vh_protocol_prev = - &wsi->same_vh_protocol_next; - -#ifndef LWS_NO_EXTENSIONS - /* instantiate the accepted extensions */ - - if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { - lwsl_ext("no client extensions allowed by server\n"); - goto check_accept; - } - - /* - * break down the list of server accepted extensions - * and go through matching them or identifying bogons - */ - - if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) { - lwsl_warn("ext list from server failed to copy\n"); - cce = "HS: EXT: list too big"; - goto bail2; - } - - c = sb; - n = 0; - ignore = 0; - a = NULL; - while (more) { - - if (*c && (*c != ',' && *c != '\t')) { - if (*c == ';') { - ignore = 1; - if (!a) - a = c + 1; - } - if (ignore || *c == ' ') { - c++; - continue; - } - - ext_name[n] = *c++; - if (n < sizeof(ext_name) - 1) - n++; - continue; - } - ext_name[n] = '\0'; - ignore = 0; - if (!*c) - more = 0; - else { - c++; - if (!n) - continue; - } - - /* check we actually support it */ - - lwsl_notice("checking client ext %s\n", ext_name); - - n = 0; - ext = wsi->vhost->extensions; - while (ext && ext->callback) { - if (strcmp(ext_name, ext->name)) { - ext++; - continue; - } - - n = 1; - lwsl_notice("instantiating client ext %s\n", ext_name); - - /* instantiate the extension on this conn */ - - wsi->active_extensions[wsi->count_act_ext] = ext; - - /* allow him to construct his ext instance */ - - if (ext->callback(lws_get_context(wsi), ext, wsi, - LWS_EXT_CB_CLIENT_CONSTRUCT, - (void *)&wsi->act_ext_user[wsi->count_act_ext], - (void *)&opts, 0)) { - lwsl_info(" ext %s failed construction\n", ext_name); - ext++; - continue; - } - - /* - * allow the user code to override ext defaults if it - * wants to - */ - ext_name[0] = '\0'; - if (user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_WS_EXT_DEFAULTS, - (char *)ext->name, ext_name, - sizeof(ext_name))) { - cce = "HS: EXT: failed setting defaults"; - goto bail2; - } - - if (ext_name[0] && - lws_ext_parse_options(ext, wsi, wsi->act_ext_user[ - wsi->count_act_ext], opts, ext_name, - strlen(ext_name))) { - lwsl_err("%s: unable to parse user defaults '%s'", - __func__, ext_name); - cce = "HS: EXT: failed parsing defaults"; - goto bail2; - } - - /* - * give the extension the server options - */ - if (a && lws_ext_parse_options(ext, wsi, - wsi->act_ext_user[wsi->count_act_ext], - opts, a, c - a)) { - lwsl_err("%s: unable to parse remote def '%s'", - __func__, a); - cce = "HS: EXT: failed parsing options"; - goto bail2; - } - - if (ext->callback(lws_get_context(wsi), ext, wsi, - LWS_EXT_CB_OPTION_CONFIRM, - wsi->act_ext_user[wsi->count_act_ext], - NULL, 0)) { - lwsl_err("%s: ext %s rejects server options %s", - __func__, ext->name, a); - cce = "HS: EXT: Rejects server options"; - goto bail2; - } - - wsi->count_act_ext++; - - ext++; - } - - if (n == 0) { - lwsl_warn("Unknown ext '%s'!\n", ext_name); - cce = "HS: EXT: unknown ext"; - goto bail2; - } - - a = NULL; - n = 0; - } - -check_accept: -#endif - - /* - * Confirm his accept token is the one we precomputed - */ - - p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); - if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) { - lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, - wsi->u.hdr.ah->initial_handshake_hash_base64); - cce = "HS: Accept hash wrong"; - goto bail2; - } - - /* allocate the per-connection user memory (if any) */ - if (lws_ensure_user_space(wsi)) { - lwsl_err("Problem allocating wsi user mem\n"); - cce = "HS: OOM"; - goto bail2; - } - - /* - * we seem to be good to go, give client last chance to check - * headers and OK it - */ - if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, - wsi->user_space, NULL, 0)) { - cce = "HS: Rejected by filter cb"; - goto bail2; - } - - /* clear his proxy connection timeout */ - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - - /* free up his parsing allocations */ - lws_header_table_detach(wsi, 0); - - lws_union_transition(wsi, LWSCM_WS_CLIENT); - wsi->state = LWSS_ESTABLISHED; - lws_restart_ws_ping_pong_timer(wsi); - - wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; - - /* - * create the frame buffer for this connection according to the - * size mentioned in the protocol definition. If 0 there, then - * use a big default for compatibility - */ - n = wsi->protocol->rx_buffer_size; - if (!n) - n = context->pt_serv_buf_size; - n += LWS_PRE; - wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "client frame buffer"); - if (!wsi->u.ws.rx_ubuf) { - lwsl_err("Out of Mem allocating rx buffer %d\n", n); - cce = "HS: OOM"; - goto bail2; - } - wsi->u.ws.rx_ubuf_alloc = n; - lwsl_info("Allocating client RX buffer %d\n", n); - -#if !defined(LWS_WITH_ESP32) - if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n, - sizeof n)) { - lwsl_warn("Failed to set SNDBUF to %d", n); - cce = "HS: SO_SNDBUF failed"; - goto bail3; - } -#endif - - lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); - - /* call him back to inform him he is up */ - - if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, - wsi->user_space, NULL, 0)) { - cce = "HS: Rejected at CLIENT_ESTABLISHED"; - goto bail3; - } -#ifndef LWS_NO_EXTENSIONS - /* - * inform all extensions, not just active ones since they - * already know - */ - ext = wsi->vhost->extensions; - - while (ext && ext->callback) { - v = NULL; - for (n = 0; n < wsi->count_act_ext; n++) - if (wsi->active_extensions[n] == ext) - v = wsi->act_ext_user[n]; - - ext->callback(context, ext, wsi, - LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0); - ext++; - } -#endif - - return 0; - -bail3: - close_reason = LWS_CLOSE_STATUS_NOSTATUS; - -bail2: - if (wsi->protocol) - wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, - wsi->user_space, (void *)cce, - (unsigned int)strlen(cce)); - wsi->already_did_cce = 1; - - lwsl_info("closing connection due to bail2 connection error\n"); - - /* closing will free up his parsing allocations */ - lws_close_free_wsi(wsi, close_reason); - - return 1; -} - - -char * -lws_generate_client_handshake(struct lws *wsi, char *pkt) -{ - char buf[128], hash[20], key_b64[40], *p = pkt; - struct lws_context *context = wsi->context; - const char *meth; - int n; -#ifndef LWS_NO_EXTENSIONS - const struct lws_extension *ext; - int ext_count = 0; -#endif - const char *pp = lws_hdr_simple_ptr(wsi, - _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); - - meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); - if (!meth) { - meth = "GET"; - wsi->do_ws = 1; - } else { - wsi->do_ws = 0; - } - - if (!strcmp(meth, "RAW")) { - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - lwsl_notice("client transition to raw\n"); - - if (pp) { - const struct lws_protocols *pr; - - pr = lws_vhost_name_to_protocol(wsi->vhost, pp); - - if (!pr) { - lwsl_err("protocol %s not enabled on vhost\n", - pp); - return NULL; - } - - lws_bind_protocol(wsi, pr); - } - - if ((wsi->protocol->callback)(wsi, - LWS_CALLBACK_RAW_ADOPT, - wsi->user_space, NULL, 0)) - return NULL; - - lws_header_table_force_to_detachable_state(wsi); - lws_union_transition(wsi, LWSCM_RAW); - lws_header_table_detach(wsi, 1); - - return NULL; - } - - if (wsi->do_ws) { - /* - * create the random key - */ - n = lws_get_random(context, hash, 16); - if (n != 16) { - lwsl_err("Unable to read from random dev %s\n", - SYSTEM_RANDOM_FILEPATH); - return NULL; - } - - lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); - } - - /* - * 04 example client handshake - * - * GET /chat HTTP/1.1 - * Host: server.example.com - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - * Sec-WebSocket-Origin: http://example.com - * Sec-WebSocket-Protocol: chat, superchat - * Sec-WebSocket-Version: 4 - */ - - p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); - - p += sprintf(p, "Pragma: no-cache\x0d\x0a" - "Cache-Control: no-cache\x0d\x0a"); - - p += sprintf(p, "Host: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); - - if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { - if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) - p += sprintf(p, "Origin: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); - else - p += sprintf(p, "Origin: http://%s\x0d\x0a", - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); - } - - if (wsi->do_ws) { - p += sprintf(p, "Upgrade: websocket\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Key: "); - strcpy(p, key_b64); - p += strlen(key_b64); - p += sprintf(p, "\x0d\x0a"); - if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) - p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); - - /* tell the server what extensions we could support */ - -#ifndef LWS_NO_EXTENSIONS - ext = wsi->vhost->extensions; - while (ext && ext->callback) { - n = lws_ext_cb_all_exts(context, wsi, - LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, - (char *)ext->name, 0); - if (n) { /* an extension vetos us */ - lwsl_ext("ext %s vetoed\n", (char *)ext->name); - ext++; - continue; - } - n = wsi->vhost->protocols[0].callback(wsi, - LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, - wsi->user_space, (char *)ext->name, 0); - - /* - * zero return from callback means - * go ahead and allow the extension, - * it's what we get if the callback is - * unhandled - */ - - if (n) { - ext++; - continue; - } - - /* apply it */ - - if (ext_count) - *p++ = ','; - else - p += sprintf(p, "Sec-WebSocket-Extensions: "); - p += sprintf(p, "%s", ext->client_offer); - ext_count++; - - ext++; - } - if (ext_count) - p += sprintf(p, "\x0d\x0a"); -#endif - - if (wsi->ietf_spec_revision) - p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", - wsi->ietf_spec_revision); - - /* prepare the expected server accept response */ - - key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ - n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); - - lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); - - lws_b64_encode_string(hash, 20, - wsi->u.hdr.ah->initial_handshake_hash_base64, - sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); - } - - /* give userland a chance to append, eg, cookies */ - - if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, - wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12)) - return NULL; - - p += sprintf(p, "\x0d\x0a"); - - return p; -} - diff --git a/thirdparty/lws/client/ssl-client.c b/thirdparty/lws/client/ssl-client.c deleted file mode 100644 index 962c6e3cb5..0000000000 --- a/thirdparty/lws/client/ssl-client.c +++ /dev/null @@ -1,625 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -extern int openssl_websocket_private_data_index, - openssl_SSL_CTX_private_data_index; - -extern void -lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info); - -extern int lws_ssl_get_error(struct lws *wsi, int n); - -#if defined(USE_WOLFSSL) -#else - -static int -OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) -{ -#if defined(LWS_WITH_MBEDTLS) - lwsl_notice("%s\n", __func__); - - return 0; -#else - SSL *ssl; - int n; - struct lws *wsi; - - /* keep old behaviour accepting self-signed server certs */ - if (!preverify_ok) { - int err = X509_STORE_CTX_get_error(x509_ctx); - - if (err != X509_V_OK) { - ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); - - if ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || - err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && - wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED) { - lwsl_notice("accepting self-signed certificate (verify_callback)\n"); - X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); - return 1; // ok - } else if ((err == X509_V_ERR_CERT_NOT_YET_VALID || - err == X509_V_ERR_CERT_HAS_EXPIRED) && - wsi->use_ssl & LCCSCF_ALLOW_EXPIRED) { - if (err == X509_V_ERR_CERT_NOT_YET_VALID) - lwsl_notice("accepting not yet valid certificate (verify_callback)\n"); - else if (err == X509_V_ERR_CERT_HAS_EXPIRED) - lwsl_notice("accepting expired certificate (verify_callback)\n"); - X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); - return 1; // ok - } - } - } - - ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); - - n = lws_get_context_protocol(wsi->context, 0).callback(wsi, - LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION, - x509_ctx, ssl, preverify_ok); - - /* keep old behaviour if something wrong with server certs */ - /* if ssl error is overruled in callback and cert is ok, - * X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); must be set and - * return value is 0 from callback */ - if (!preverify_ok) { - int err = X509_STORE_CTX_get_error(x509_ctx); - - if (err != X509_V_OK) { /* cert validation error was not handled in callback */ - int depth = X509_STORE_CTX_get_error_depth(x509_ctx); - const char* msg = X509_verify_cert_error_string(err); - lwsl_err("SSL error: %s (preverify_ok=%d;err=%d;depth=%d)\n", msg, preverify_ok, err, depth); - return preverify_ok; // not ok - } - } - /* convert callback return code from 0 = OK to verify callback return value 1 = OK */ - return !n; -#endif -} -#endif - -int -lws_ssl_client_bio_create(struct lws *wsi) -{ - char hostname[128], *p; - - if (lws_hdr_copy(wsi, hostname, sizeof(hostname), - _WSI_TOKEN_CLIENT_HOST) <= 0) { - lwsl_err("%s: Unable to get hostname\n", __func__); - - return -1; - } - - /* - * remove any :port part on the hostname... necessary for network - * connection but typical certificates do not contain it - */ - p = hostname; - while (*p) { - if (*p == ':') { - *p = '\0'; - break; - } - p++; - } - - wsi->ssl = SSL_new(wsi->vhost->ssl_client_ctx); - if (!wsi->ssl) { - lwsl_err("SSL_new failed: %s\n", - ERR_error_string(lws_ssl_get_error(wsi, 0), NULL)); - lws_ssl_elaborate_error(); - return -1; - } - -#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) - if (wsi->vhost->ssl_info_event_mask) - SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); -#endif - -#if defined LWS_HAVE_X509_VERIFY_PARAM_set1_host - X509_VERIFY_PARAM *param; - (void)param; - - if (!(wsi->use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) { - param = SSL_get0_param(wsi->ssl); - /* Enable automatic hostname checks */ - X509_VERIFY_PARAM_set_hostflags(param, - X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - X509_VERIFY_PARAM_set1_host(param, hostname, 0); - } - -#endif - -#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) -#ifndef USE_OLD_CYASSL - /* OpenSSL_client_verify_callback will be called @ SSL_connect() */ - SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); -#endif -#endif - -#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) - SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); -#endif - /* - * use server name indication (SNI), if supported, - * when establishing connection - */ -#ifdef USE_WOLFSSL -#ifdef USE_OLD_CYASSL -#ifdef CYASSL_SNI_HOST_NAME - CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname)); -#endif -#else -#ifdef WOLFSSL_SNI_HOST_NAME - wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname)); -#endif -#endif -#else -#if defined(LWS_WITH_MBEDTLS) - SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); -#else -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - SSL_set_tlsext_host_name(wsi->ssl, hostname); -#endif -#endif -#endif - -#ifdef USE_WOLFSSL - /* - * wolfSSL/CyaSSL does certificate verification differently - * from OpenSSL. - * If we should ignore the certificate, we need to set - * this before SSL_new and SSL_connect is called. - * Otherwise the connect will simply fail with error code -155 - */ -#ifdef USE_OLD_CYASSL - if (wsi->use_ssl == 2) - CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); -#else - if (wsi->use_ssl == 2) - wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); -#endif -#endif /* USE_WOLFSSL */ - -#if !defined(LWS_WITH_MBEDTLS) - wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE); - SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); -#else - SSL_set_fd(wsi->ssl, wsi->desc.sockfd); -#endif - -#ifdef USE_WOLFSSL -#ifdef USE_OLD_CYASSL - CyaSSL_set_using_nonblock(wsi->ssl, 1); -#else - wolfSSL_set_using_nonblock(wsi->ssl, 1); -#endif -#else -#if !defined(LWS_WITH_MBEDTLS) - BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */ -#endif -#endif - -#if !defined(LWS_WITH_MBEDTLS) - SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index, - wsi); -#endif - - return 0; -} - -#if defined(LWS_WITH_MBEDTLS) -int ERR_get_error(void) -{ - return 0; -} -#endif - -int -lws_ssl_client_connect1(struct lws *wsi) -{ - struct lws_context *context = wsi->context; - int n = 0; - - lws_latency_pre(context, wsi); - - n = SSL_connect(wsi->ssl); - - lws_latency(context, wsi, - "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0); - - if (n < 0) { - n = lws_ssl_get_error(wsi, n); - - if (n == SSL_ERROR_WANT_READ) - goto some_wait; - - if (n == SSL_ERROR_WANT_WRITE) { - /* - * wants us to retry connect due to - * state of the underlying ssl layer... - * but since it may be stalled on - * blocked write, no incoming data may - * arrive to trigger the retry. - * Force (possibly many times if the SSL - * state persists in returning the - * condition code, but other sockets - * are getting serviced inbetweentimes) - * us to get called back when writable. - */ - lwsl_info("%s: WANT_WRITE... retrying\n", __func__); - lws_callback_on_writable(wsi); -some_wait: - wsi->mode = LWSCM_WSCL_WAITING_SSL; - - return 0; /* no error */ - } - - { - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - char *p = (char *)&pt->serv_buf[0]; - char *sb = p; - - lwsl_err("ssl hs1 error, X509_V_ERR = %d: errno %d: %s\n", - n, errno, ERR_error_string(n, sb)); - lws_ssl_elaborate_error(); -#if defined(LWS_WITH_MBEDTLS) - if (n == SSL_ERROR_SYSCALL) - return -1; -#endif - } - - n = -1; - } - - if (n <= 0) { - /* - * retry if new data comes until we - * run into the connection timeout or win - */ - - unsigned long error = ERR_get_error(); - - if (error != SSL_ERROR_NONE) { - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - char *p = (char *)&pt->serv_buf[0]; - char *sb = p; - lwsl_err("SSL connect error %lu: %s\n", - error, ERR_error_string(error, sb)); - return -1; - } - - return 0; - } - - return 1; -} - -int -lws_ssl_client_connect2(struct lws *wsi) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - char *p = (char *)&pt->serv_buf[0]; - char *sb = p; - int n = 0; - - if (wsi->mode == LWSCM_WSCL_WAITING_SSL) { - lws_latency_pre(context, wsi); - n = SSL_connect(wsi->ssl); - lwsl_debug("%s: SSL_connect says %d\n", __func__, n); - - lws_latency(context, wsi, - "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0); - - if (n < 0) { - n = lws_ssl_get_error(wsi, n); - - if (n == SSL_ERROR_WANT_READ) { - lwsl_info("SSL_connect WANT_READ... retrying\n"); - - wsi->mode = LWSCM_WSCL_WAITING_SSL; - - return 0; /* no error */ - } - - if (n == SSL_ERROR_WANT_WRITE) { - /* - * wants us to retry connect due to - * state of the underlying ssl layer... - * but since it may be stalled on - * blocked write, no incoming data may - * arrive to trigger the retry. - * Force (possibly many times if the SSL - * state persists in returning the - * condition code, but other sockets - * are getting serviced inbetweentimes) - * us to get called back when writable. - */ - lwsl_info("SSL_connect WANT_WRITE... retrying\n"); - lws_callback_on_writable(wsi); - - wsi->mode = LWSCM_WSCL_WAITING_SSL; - - return 0; /* no error */ - } - - n = -1; - } - - if (n <= 0) { - /* - * retry if new data comes until we - * run into the connection timeout or win - */ - unsigned long error = ERR_get_error(); - if (error != SSL_ERROR_NONE) { - lwsl_err("SSL connect error %lu: %s\n", - error, ERR_error_string(error, sb)); - return -1; - } - } - } - -#if defined(LWS_WITH_MBEDTLS) - { - X509 *peer = SSL_get_peer_certificate(wsi->ssl); - - if (!peer) { - lwsl_notice("peer did not provide cert\n"); - - return -1; - } - lwsl_notice("peer provided cert\n"); - } -#endif - -#ifndef USE_WOLFSSL - /* - * See comment above about wolfSSL certificate - * verification - */ - lws_latency_pre(context, wsi); - n = SSL_get_verify_result(wsi->ssl); - lws_latency(context, wsi, - "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); - - lwsl_debug("get_verify says %d\n", n); - - if (n != X509_V_OK) { - if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || - n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && - (wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED)) { - lwsl_notice("accepting self-signed certificate\n"); - } else if ((n == X509_V_ERR_CERT_NOT_YET_VALID || - n == X509_V_ERR_CERT_HAS_EXPIRED) && - (wsi->use_ssl & LCCSCF_ALLOW_EXPIRED)) { - lwsl_notice("accepting expired certificate\n"); - } else if (n == X509_V_ERR_CERT_NOT_YET_VALID) { - lwsl_notice("Cert is from the future... " - "probably our clock... accepting...\n"); - } else { - lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n", - n, ERR_error_string(n, sb)); - lws_ssl_elaborate_error(); - return -1; - } - } - -#endif /* USE_WOLFSSL */ - - return 1; -} - - -int lws_context_init_client_ssl(struct lws_context_creation_info *info, - struct lws_vhost *vhost) -{ - SSL_METHOD *method = NULL; - struct lws wsi; - unsigned long error; - const char *ca_filepath = info->ssl_ca_filepath; -#if !defined(LWS_WITH_MBEDTLS) - const char *cipher_list = info->ssl_cipher_list; - const char *private_key_filepath = info->ssl_private_key_filepath; - const char *cert_filepath = info->ssl_cert_filepath; - int n; - - if (vhost->options & LWS_SERVER_OPTION_ONLY_RAW) - return 0; - - /* - * for backwards-compatibility default to using ssl_... members, but - * if the newer client-specific ones are given, use those - */ - if (info->client_ssl_cipher_list) - cipher_list = info->client_ssl_cipher_list; - if (info->client_ssl_cert_filepath) - cert_filepath = info->client_ssl_cert_filepath; - if (info->client_ssl_private_key_filepath) - private_key_filepath = info->client_ssl_private_key_filepath; -#endif - if (info->client_ssl_ca_filepath) - ca_filepath = info->client_ssl_ca_filepath; - - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) - return 0; - - if (vhost->ssl_client_ctx) - return 0; - - if (info->provided_client_ssl_ctx) { - /* use the provided OpenSSL context if given one */ - vhost->ssl_client_ctx = info->provided_client_ssl_ctx; - /* nothing for lib to delete */ - vhost->user_supplied_ssl_ctx = 1; - - return 0; - } - - /* basic openssl init already happened in context init */ - - /* choose the most recent spin of the api */ -#if defined(LWS_HAVE_TLS_CLIENT_METHOD) - method = (SSL_METHOD *)TLS_client_method(); -#elif defined(LWS_HAVE_TLSV1_2_CLIENT_METHOD) - method = (SSL_METHOD *)TLSv1_2_client_method(); -#else - method = (SSL_METHOD *)SSLv23_client_method(); -#endif - if (!method) { - error = ERR_get_error(); - lwsl_err("problem creating ssl method %lu: %s\n", - error, ERR_error_string(error, - (char *)vhost->context->pt[0].serv_buf)); - return 1; - } - /* create context */ - vhost->ssl_client_ctx = SSL_CTX_new(method); - if (!vhost->ssl_client_ctx) { - error = ERR_get_error(); - lwsl_err("problem creating ssl context %lu: %s\n", - error, ERR_error_string(error, - (char *)vhost->context->pt[0].serv_buf)); - return 1; - } - - lwsl_notice("created client ssl context for %s\n", vhost->name); - -#ifdef SSL_OP_NO_COMPRESSION - SSL_CTX_set_options(vhost->ssl_client_ctx, SSL_OP_NO_COMPRESSION); -#endif - -#if defined(LWS_WITH_MBEDTLS) - if (ca_filepath) { - lws_filepos_t len; - uint8_t *buf; - /* - * prototype this here, the shim does not export it in the - * header, and we need to use the shim unchanged for ESP32 case - */ - X509 *d2i_X509(X509 **cert, const unsigned char *buffer, long len); - - if (alloc_file(vhost->context, ca_filepath, &buf, &len)) { - lwsl_err("Load CA cert file %s failed\n", ca_filepath); - return 1; - } - - vhost->x509_client_CA = d2i_X509(NULL, buf, len); - free(buf); - if (!vhost->x509_client_CA) { - lwsl_err("client CA: x509 parse failed\n"); - return 1; - } - - SSL_CTX_add_client_CA(vhost->ssl_client_ctx, - vhost->x509_client_CA); - - lwsl_notice("client loaded CA for verification %s\n", ca_filepath); - } -#else - SSL_CTX_set_options(vhost->ssl_client_ctx, - SSL_OP_CIPHER_SERVER_PREFERENCE); - - if (cipher_list) - SSL_CTX_set_cipher_list(vhost->ssl_client_ctx, cipher_list); - -#ifdef LWS_SSL_CLIENT_USE_OS_CA_CERTS - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS)) - /* loads OS default CA certs */ - SSL_CTX_set_default_verify_paths(vhost->ssl_client_ctx); -#endif - - /* openssl init for cert verification (for client sockets) */ - if (!ca_filepath) { - if (!SSL_CTX_load_verify_locations( - vhost->ssl_client_ctx, NULL, LWS_OPENSSL_CLIENT_CERTS)) - lwsl_err("Unable to load SSL Client certs from %s " - "(set by LWS_OPENSSL_CLIENT_CERTS) -- " - "client ssl isn't going to work\n", - LWS_OPENSSL_CLIENT_CERTS); - } else - if (!SSL_CTX_load_verify_locations( - vhost->ssl_client_ctx, ca_filepath, NULL)) { - lwsl_err( - "Unable to load SSL Client certs " - "file from %s -- client ssl isn't " - "going to work\n", info->client_ssl_ca_filepath); - lws_ssl_elaborate_error(); - } - else - lwsl_info("loaded ssl_ca_filepath\n"); - - /* - * callback allowing user code to load extra verification certs - * helping the client to verify server identity - */ - - /* support for client-side certificate authentication */ - if (cert_filepath) { - lwsl_notice("%s: doing cert filepath\n", __func__); - n = SSL_CTX_use_certificate_chain_file(vhost->ssl_client_ctx, - cert_filepath); - if (n < 1) { - lwsl_err("problem %d getting cert '%s'\n", n, - cert_filepath); - lws_ssl_elaborate_error(); - return 1; - } - lwsl_notice("Loaded client cert %s\n", cert_filepath); - } - if (private_key_filepath) { - lwsl_notice("%s: doing private key filepath\n", __func__); - lws_ssl_bind_passphrase(vhost->ssl_client_ctx, info); - /* set the private key from KeyFile */ - if (SSL_CTX_use_PrivateKey_file(vhost->ssl_client_ctx, - private_key_filepath, SSL_FILETYPE_PEM) != 1) { - lwsl_err("use_PrivateKey_file '%s'\n", - private_key_filepath); - lws_ssl_elaborate_error(); - return 1; - } - lwsl_notice("Loaded client cert private key %s\n", - private_key_filepath); - - /* verify private key */ - if (!SSL_CTX_check_private_key(vhost->ssl_client_ctx)) { - lwsl_err("Private SSL key doesn't match cert\n"); - return 1; - } - } -#endif - /* - * give him a fake wsi with context set, so he can use - * lws_get_context() in the callback - */ - memset(&wsi, 0, sizeof(wsi)); - wsi.vhost = vhost; - wsi.context = vhost->context; - - vhost->protocols[0].callback(&wsi, - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS, - vhost->ssl_client_ctx, NULL, 0); - - return 0; -} diff --git a/thirdparty/lws/ext/extension-permessage-deflate.c b/thirdparty/lws/ext/extension-permessage-deflate.c deleted file mode 100644 index e2be2ae615..0000000000 --- a/thirdparty/lws/ext/extension-permessage-deflate.c +++ /dev/null @@ -1,473 +0,0 @@ -/* - * ./lib/extension-permessage-deflate.c - * - * Copyright (C) 2016 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" -#include "extension-permessage-deflate.h" -#include <stdio.h> -#include <string.h> -#include <assert.h> - -#define LWS_ZLIB_MEMLEVEL 8 - -const struct lws_ext_options lws_ext_pm_deflate_options[] = { - /* public RFC7692 settings */ - { "server_no_context_takeover", EXTARG_NONE }, - { "client_no_context_takeover", EXTARG_NONE }, - { "server_max_window_bits", EXTARG_OPT_DEC }, - { "client_max_window_bits", EXTARG_OPT_DEC }, - /* ones only user code can set */ - { "rx_buf_size", EXTARG_DEC }, - { "tx_buf_size", EXTARG_DEC }, - { "compression_level", EXTARG_DEC }, - { "mem_level", EXTARG_DEC }, - { NULL, 0 }, /* sentinel */ -}; - -static void -lws_extension_pmdeflate_restrict_args(struct lws *wsi, - struct lws_ext_pm_deflate_priv *priv) -{ - int n, extra; - - /* cap the RX buf at the nearest power of 2 to protocol rx buf */ - - n = wsi->context->pt_serv_buf_size; - if (wsi->protocol->rx_buffer_size) - n = wsi->protocol->rx_buffer_size; - - extra = 7; - while (n >= 1 << (extra + 1)) - extra++; - - if (extra < priv->args[PMD_RX_BUF_PWR2]) { - priv->args[PMD_RX_BUF_PWR2] = extra; - lwsl_info(" Capping pmd rx to %d\n", 1 << extra); - } -} - -LWS_VISIBLE int -lws_extension_callback_pm_deflate(struct lws_context *context, - const struct lws_extension *ext, - struct lws *wsi, - enum lws_extension_callback_reasons reason, - void *user, void *in, size_t len) -{ - struct lws_ext_pm_deflate_priv *priv = - (struct lws_ext_pm_deflate_priv *)user; - struct lws_tokens *eff_buf = (struct lws_tokens *)in; - static unsigned char trail[] = { 0, 0, 0xff, 0xff }; - int n, ret = 0, was_fin = 0, extra; - struct lws_ext_option_arg *oa; - - switch (reason) { - case LWS_EXT_CB_NAMED_OPTION_SET: - oa = in; - if (!oa->option_name) - break; - for (n = 0; n < ARRAY_SIZE(lws_ext_pm_deflate_options); n++) - if (!strcmp(lws_ext_pm_deflate_options[n].name, oa->option_name)) - break; - - if (n == ARRAY_SIZE(lws_ext_pm_deflate_options)) - break; - oa->option_index = n; - - /* fallthru */ - - case LWS_EXT_CB_OPTION_SET: - oa = in; - lwsl_notice("%s: option set: idx %d, %s, len %d\n", __func__, - oa->option_index, oa->start, oa->len); - if (oa->start) - priv->args[oa->option_index] = atoi(oa->start); - else - priv->args[oa->option_index] = 1; - - if (priv->args[PMD_CLIENT_MAX_WINDOW_BITS] == 8) - priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 9; - - lws_extension_pmdeflate_restrict_args(wsi, priv); - break; - - case LWS_EXT_CB_OPTION_CONFIRM: - if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 || - priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 || - priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 || - priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15) - return -1; - break; - - case LWS_EXT_CB_CLIENT_CONSTRUCT: - case LWS_EXT_CB_CONSTRUCT: - - n = context->pt_serv_buf_size; - if (wsi->protocol->rx_buffer_size) - n = wsi->protocol->rx_buffer_size; - - if (n < 128) { - lwsl_info(" permessage-deflate requires the protocol (%s) to have an RX buffer >= 128\n", - wsi->protocol->name); - return -1; - } - - /* fill in **user */ - priv = lws_zalloc(sizeof(*priv), "pmd priv"); - *((void **)user) = priv; - lwsl_ext("%s: LWS_EXT_CB_*CONSTRUCT\n", __func__); - memset(priv, 0, sizeof(*priv)); - - /* fill in pointer to options list */ - if (in) - *((const struct lws_ext_options **)in) = - lws_ext_pm_deflate_options; - - /* fallthru */ - - case LWS_EXT_CB_OPTION_DEFAULT: - - /* set the public, RFC7692 defaults... */ - - priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0, - priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0; - priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15; - priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15; - - /* ...and the ones the user code can override */ - - priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */ - priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */ - priv->args[PMD_COMP_LEVEL] = 1; - priv->args[PMD_MEM_LEVEL] = 8; - - lws_extension_pmdeflate_restrict_args(wsi, priv); - break; - - case LWS_EXT_CB_DESTROY: - lwsl_ext("%s: LWS_EXT_CB_DESTROY\n", __func__); - lws_free(priv->buf_rx_inflated); - lws_free(priv->buf_tx_deflated); - if (priv->rx_init) - (void)inflateEnd(&priv->rx); - if (priv->tx_init) - (void)deflateEnd(&priv->tx); - lws_free(priv); - return ret; - - case LWS_EXT_CB_PAYLOAD_RX: - lwsl_ext(" %s: LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d\n", - __func__, eff_buf->token_len, priv->rx.avail_in); - if (!(wsi->u.ws.rsv_first_msg & 0x40)) - return 0; - -#if 0 - for (n = 0; n < eff_buf->token_len; n++) { - printf("%02X ", (unsigned char)eff_buf->token[n]); - if ((n & 15) == 15) - printf("\n"); - } - printf("\n"); -#endif - if (!priv->rx_init) - if (inflateInit2(&priv->rx, -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) { - lwsl_err("%s: iniflateInit failed\n", __func__); - return -1; - } - priv->rx_init = 1; - if (!priv->buf_rx_inflated) - priv->buf_rx_inflated = lws_malloc(LWS_PRE + 7 + 5 + - (1 << priv->args[PMD_RX_BUF_PWR2]), "pmd rx inflate buf"); - if (!priv->buf_rx_inflated) { - lwsl_err("%s: OOM\n", __func__); - return -1; - } - - /* - * We have to leave the input stream alone if we didn't - * finish with it yet. The input stream is held in the wsi - * rx buffer by the caller, so this assumption is safe while - * we block new rx while draining the existing rx - */ - if (!priv->rx.avail_in && eff_buf->token && eff_buf->token_len) { - priv->rx.next_in = (unsigned char *)eff_buf->token; - priv->rx.avail_in = eff_buf->token_len; - } - priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE; - eff_buf->token = (char *)priv->rx.next_out; - priv->rx.avail_out = 1 << priv->args[PMD_RX_BUF_PWR2]; - - if (priv->rx_held_valid) { - lwsl_ext("-- RX piling on held byte --\n"); - *(priv->rx.next_out++) = priv->rx_held; - priv->rx.avail_out--; - priv->rx_held_valid = 0; - } - - /* if... - * - * - he has no remaining input content for this message, and - * - and this is the final fragment, and - * - we used everything that could be drained on the input side - * - * ...then put back the 00 00 FF FF the sender stripped as our - * input to zlib - */ - if (!priv->rx.avail_in && wsi->u.ws.final && - !wsi->u.ws.rx_packet_length) { - lwsl_ext("RX APPEND_TRAILER-DO\n"); - was_fin = 1; - priv->rx.next_in = trail; - priv->rx.avail_in = sizeof(trail); - } - - n = inflate(&priv->rx, Z_NO_FLUSH); - lwsl_ext("inflate ret %d, avi %d, avo %d, wsifinal %d\n", n, - priv->rx.avail_in, priv->rx.avail_out, wsi->u.ws.final); - switch (n) { - case Z_NEED_DICT: - case Z_STREAM_ERROR: - case Z_DATA_ERROR: - case Z_MEM_ERROR: - lwsl_info("zlib error inflate %d: %s\n", - n, priv->rx.msg); - return -1; - } - /* - * If we did not already send in the 00 00 FF FF, and he's - * out of input, he did not EXACTLY fill the output buffer - * (which is ambiguous and we will force it to go around - * again by withholding a byte), and he's otherwise working on - * being a FIN fragment, then do the FIN message processing - * of faking up the 00 00 FF FF that the sender stripped. - */ - if (!priv->rx.avail_in && wsi->u.ws.final && - !wsi->u.ws.rx_packet_length && !was_fin && - priv->rx.avail_out /* ambiguous as to if it is the end */ - ) { - lwsl_ext("RX APPEND_TRAILER-DO\n"); - was_fin = 1; - priv->rx.next_in = trail; - priv->rx.avail_in = sizeof(trail); - n = inflate(&priv->rx, Z_SYNC_FLUSH); - lwsl_ext("RX trailer inf returned %d, avi %d, avo %d\n", n, - priv->rx.avail_in, priv->rx.avail_out); - switch (n) { - case Z_NEED_DICT: - case Z_STREAM_ERROR: - case Z_DATA_ERROR: - case Z_MEM_ERROR: - lwsl_info("zlib error inflate %d: %s\n", - n, priv->rx.msg); - return -1; - } - } - /* - * we must announce in our returncode now if there is more - * output to be expected from inflate, so we can decide to - * set the FIN bit on this bufferload or not. However zlib - * is ambiguous when we exactly filled the inflate buffer. It - * does not give us a clue as to whether we should understand - * that to mean he ended on a buffer boundary, or if there is - * more in the pipeline. - * - * So to work around that safely, if it used all output space - * exactly, we ALWAYS say there is more coming and we withhold - * the last byte of the buffer to guarantee that is true. - * - * That still leaves us at least one byte to finish with a FIN - * on, even if actually nothing more is coming from the next - * inflate action itself. - */ - if (!priv->rx.avail_out) { /* he used all available out buf */ - lwsl_ext("-- rx grabbing held --\n"); - /* snip the last byte and hold it for next time */ - priv->rx_held = *(--priv->rx.next_out); - priv->rx_held_valid = 1; - } - - eff_buf->token_len = (char *)priv->rx.next_out - eff_buf->token; - priv->count_rx_between_fin += eff_buf->token_len; - - lwsl_ext(" %s: RX leaving with new effbuff len %d, " - "ret %d, rx.avail_in=%d, TOTAL RX since FIN %lu\n", - __func__, eff_buf->token_len, priv->rx_held_valid, - priv->rx.avail_in, - (unsigned long)priv->count_rx_between_fin); - - if (was_fin) { - priv->count_rx_between_fin = 0; - if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) { - (void)inflateEnd(&priv->rx); - priv->rx_init = 0; - } - } -#if 0 - for (n = 0; n < eff_buf->token_len; n++) - putchar(eff_buf->token[n]); - puts("\n"); -#endif - - return priv->rx_held_valid; - - case LWS_EXT_CB_PAYLOAD_TX: - - if (!priv->tx_init) { - n = deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL], - Z_DEFLATED, - -priv->args[PMD_SERVER_MAX_WINDOW_BITS + - (wsi->vhost->listen_port <= 0)], - priv->args[PMD_MEM_LEVEL], - Z_DEFAULT_STRATEGY); - if (n != Z_OK) { - lwsl_ext("inflateInit2 failed %d\n", n); - return 1; - } - } - priv->tx_init = 1; - if (!priv->buf_tx_deflated) - priv->buf_tx_deflated = lws_malloc(LWS_PRE + 7 + 5 + - (1 << priv->args[PMD_TX_BUF_PWR2]), "pmd tx deflate buf"); - if (!priv->buf_tx_deflated) { - lwsl_err("%s: OOM\n", __func__); - return -1; - } - - if (eff_buf->token) { - lwsl_ext("%s: TX: eff_buf length %d\n", __func__, - eff_buf->token_len); - priv->tx.next_in = (unsigned char *)eff_buf->token; - priv->tx.avail_in = eff_buf->token_len; - } - -#if 0 - for (n = 0; n < eff_buf->token_len; n++) { - printf("%02X ", (unsigned char)eff_buf->token[n]); - if ((n & 15) == 15) - printf("\n"); - } - printf("\n"); -#endif - - priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5; - eff_buf->token = (char *)priv->tx.next_out; - priv->tx.avail_out = 1 << priv->args[PMD_TX_BUF_PWR2]; - - n = deflate(&priv->tx, Z_SYNC_FLUSH); - if (n == Z_STREAM_ERROR) { - lwsl_ext("%s: Z_STREAM_ERROR\n", __func__); - return -1; - } - - if (priv->tx_held_valid) { - priv->tx_held_valid = 0; - if (priv->tx.avail_out == 1 << priv->args[PMD_TX_BUF_PWR2]) - /* - * we can get a situation he took something in - * but did not generate anything out, at the end - * of a message (eg, next thing he sends is 80 - * 00, a zero length FIN, like Authobahn can - * send). - * If we have come back as a FIN, we must not - * place the pending trailer 00 00 FF FF, just - * the 1 byte of live data - */ - *(--eff_buf->token) = priv->tx_held[0]; - else { - /* he generated data, prepend whole pending */ - eff_buf->token -= 5; - for (n = 0; n < 5; n++) - eff_buf->token[n] = priv->tx_held[n]; - - } - } - priv->compressed_out = 1; - eff_buf->token_len = (int)(priv->tx.next_out - - (unsigned char *)eff_buf->token); - - /* - * we must announce in our returncode now if there is more - * output to be expected from inflate, so we can decide to - * set the FIN bit on this bufferload or not. However zlib - * is ambiguous when we exactly filled the inflate buffer. It - * does not give us a clue as to whether we should understand - * that to mean he ended on a buffer boundary, or if there is - * more in the pipeline. - * - * Worse, the guy providing the stuff we are sending may not - * know until after that this was, actually, the last chunk, - * that can happen even if we did not fill the output buf, ie - * he may send after this a zero-length FIN fragment. - * - * This is super difficult because we must snip the last 4 - * bytes in the case this is the last compressed output of the - * message. The only way to deal with it is defer sending the - * last 5 bytes of each frame until the next one, when we will - * be in a position to understand if that has a FIN or not. - */ - - extra = !!(len & LWS_WRITE_NO_FIN) || !priv->tx.avail_out; - - if (eff_buf->token_len >= 4 + extra) { - lwsl_ext("tx held %d\n", 4 + extra); - priv->tx_held_valid = extra; - for (n = 3 + extra; n >= 0; n--) - priv->tx_held[n] = *(--priv->tx.next_out); - eff_buf->token_len -= 4 + extra; - } - lwsl_ext(" TX rewritten with new effbuff len %d, ret %d\n", - eff_buf->token_len, !priv->tx.avail_out); - - return !priv->tx.avail_out; /* 1 == have more tx pending */ - - case LWS_EXT_CB_PACKET_TX_PRESEND: - if (!priv->compressed_out) - break; - priv->compressed_out = 0; - - if ((*(eff_buf->token) & 0x80) && - priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) { - lwsl_debug("PMD_CLIENT_NO_CONTEXT_TAKEOVER\n"); - (void)deflateEnd(&priv->tx); - priv->tx_init = 0; - } - - n = *(eff_buf->token) & 15; - /* set RSV1, but not on CONTINUATION */ - if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME) - *eff_buf->token |= 0x40; -#if 0 - for (n = 0; n < eff_buf->token_len; n++) { - printf("%02X ", (unsigned char)eff_buf->token[n]); - if ((n & 15) == 15) - puts("\n"); - } - puts("\n"); -#endif - lwsl_ext("%s: tx opcode 0x%02X\n", __func__, - (unsigned char)*eff_buf->token); - break; - - default: - break; - } - - return 0; -} - diff --git a/thirdparty/lws/ext/extension-permessage-deflate.h b/thirdparty/lws/ext/extension-permessage-deflate.h deleted file mode 100644 index 8737736897..0000000000 --- a/thirdparty/lws/ext/extension-permessage-deflate.h +++ /dev/null @@ -1,41 +0,0 @@ - -#include <zlib.h> - -#define DEFLATE_FRAME_COMPRESSION_LEVEL_SERVER 1 -#define DEFLATE_FRAME_COMPRESSION_LEVEL_CLIENT Z_DEFAULT_COMPRESSION - -enum arg_indexes { - PMD_SERVER_NO_CONTEXT_TAKEOVER, - PMD_CLIENT_NO_CONTEXT_TAKEOVER, - PMD_SERVER_MAX_WINDOW_BITS, - PMD_CLIENT_MAX_WINDOW_BITS, - PMD_RX_BUF_PWR2, - PMD_TX_BUF_PWR2, - PMD_COMP_LEVEL, - PMD_MEM_LEVEL, - - PMD_ARG_COUNT -}; - -struct lws_ext_pm_deflate_priv { - z_stream rx; - z_stream tx; - - unsigned char *buf_rx_inflated; /* RX inflated output buffer */ - unsigned char *buf_tx_deflated; /* TX deflated output buffer */ - - size_t count_rx_between_fin; - - unsigned char args[PMD_ARG_COUNT]; - unsigned char tx_held[5]; - unsigned char rx_held; - - unsigned char tx_init:1; - unsigned char rx_init:1; - unsigned char compressed_out:1; - unsigned char rx_held_valid:1; - unsigned char tx_held_valid:1; - unsigned char rx_append_trailer:1; - unsigned char pending_tx_trailer:1; -}; - diff --git a/thirdparty/lws/ext/extension.c b/thirdparty/lws/ext/extension.c deleted file mode 100644 index ac28204034..0000000000 --- a/thirdparty/lws/ext/extension.c +++ /dev/null @@ -1,344 +0,0 @@ -#include "private-libwebsockets.h" - -#include "extension-permessage-deflate.h" - -LWS_VISIBLE void -lws_context_init_extensions(struct lws_context_creation_info *info, - struct lws_context *context) -{ - lwsl_info(" LWS_MAX_EXTENSIONS_ACTIVE: %u\n", LWS_MAX_EXTENSIONS_ACTIVE); -} - -enum lws_ext_option_parser_states { - LEAPS_SEEK_NAME, - LEAPS_EAT_NAME, - LEAPS_SEEK_VAL, - LEAPS_EAT_DEC, - LEAPS_SEEK_ARG_TERM -}; - -LWS_VISIBLE int -lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi, - void *ext_user, const struct lws_ext_options *opts, - const char *in, int len) -{ - enum lws_ext_option_parser_states leap = LEAPS_SEEK_NAME; - unsigned int match_map = 0, n, m, w = 0, count_options = 0, - pending_close_quote = 0; - struct lws_ext_option_arg oa; - - oa.option_name = NULL; - - while (opts[count_options].name) - count_options++; - while (len) { - lwsl_ext("'%c' %d", *in, leap); - switch (leap) { - case LEAPS_SEEK_NAME: - if (*in == ' ') - break; - if (*in == ',') { - len = 1; - break; - } - match_map = (1 << count_options) - 1; - leap = LEAPS_EAT_NAME; - w = 0; - - /* fallthru */ - - case LEAPS_EAT_NAME: - oa.start = NULL; - oa.len = 0; - m = match_map; - n = 0; - pending_close_quote = 0; - while (m) { - if (m & 1) { - lwsl_ext(" m=%d, n=%d, w=%d\n", m, n, w); - - if (*in == opts[n].name[w]) { - if (!opts[n].name[w + 1]) { - oa.option_index = n; - lwsl_ext("hit %d\n", oa.option_index); - leap = LEAPS_SEEK_VAL; - if (len == 1) - goto set_arg; - break; - } - } else { - match_map &= ~(1 << n); - if (!match_map) { - lwsl_ext("empty match map\n"); - return -1; - } - } - } - m >>= 1; - n++; - } - w++; - break; - case LEAPS_SEEK_VAL: - if (*in == ' ') - break; - if (*in == ',') { - len = 1; - break; - } - if (*in == ';' || len == 1) { /* ie,nonoptional */ - if (opts[oa.option_index].type == EXTARG_DEC) - return -1; - leap = LEAPS_SEEK_NAME; - goto set_arg; - } - if (*in == '=') { - w = 0; - pending_close_quote = 0; - if (opts[oa.option_index].type == EXTARG_NONE) - return -1; - - leap = LEAPS_EAT_DEC; - break; - } - return -1; - - case LEAPS_EAT_DEC: - if (*in >= '0' && *in <= '9') { - if (!w) - oa.start = in; - w++; - if (len != 1) - break; - } - if (!w && *in =='"') { - pending_close_quote = 1; - break; - } - if (!w) - return -1; - if (pending_close_quote && *in != '"' && len != 1) - return -1; - leap = LEAPS_SEEK_ARG_TERM; - if (oa.start) - oa.len = in - oa.start; - if (len == 1) - oa.len++; - -set_arg: - ext->callback(lws_get_context(wsi), - ext, wsi, LWS_EXT_CB_OPTION_SET, - ext_user, (char *)&oa, 0); - if (len == 1) - break; - if (pending_close_quote && *in == '"') - break; - - /* fallthru */ - - case LEAPS_SEEK_ARG_TERM: - if (*in == ' ') - break; - if (*in == ';') { - leap = LEAPS_SEEK_NAME; - break; - } - if (*in == ',') { - len = 1; - break; - } - return -1; - } - len--; - in++; - } - - return 0; -} - - -/* 0 = nobody had nonzero return, 1 = somebody had positive return, -1 = fail */ - -int lws_ext_cb_active(struct lws *wsi, int reason, void *arg, int len) -{ - int n, m, handled = 0; - - for (n = 0; n < wsi->count_act_ext; n++) { - m = wsi->active_extensions[n]->callback(lws_get_context(wsi), - wsi->active_extensions[n], wsi, reason, - wsi->act_ext_user[n], arg, len); - if (m < 0) { - lwsl_ext("Ext '%s' failed to handle callback %d!\n", - wsi->active_extensions[n]->name, reason); - return -1; - } - /* valgrind... */ - if (reason == LWS_EXT_CB_DESTROY) - wsi->act_ext_user[n] = NULL; - if (m > handled) - handled = m; - } - - return handled; -} - -int lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, - int reason, void *arg, int len) -{ - int n = 0, m, handled = 0; - const struct lws_extension *ext; - - if (!wsi || !wsi->vhost) - return 0; - - ext = wsi->vhost->extensions; - - while (ext && ext->callback && !handled) { - m = ext->callback(context, ext, wsi, reason, - (void *)(lws_intptr_t)n, arg, len); - if (m < 0) { - lwsl_ext("Ext '%s' failed to handle callback %d!\n", - wsi->active_extensions[n]->name, reason); - return -1; - } - if (m) - handled = 1; - - ext++; - n++; - } - - return 0; -} - -int -lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len) -{ - struct lws_tokens eff_buf; - int ret, m, n = 0; - - eff_buf.token = (char *)buf; - eff_buf.token_len = len; - - /* - * while we have original buf to spill ourselves, or extensions report - * more in their pipeline - */ - - ret = 1; - while (ret == 1) { - - /* default to nobody has more to spill */ - - ret = 0; - - /* show every extension the new incoming data */ - m = lws_ext_cb_active(wsi, - LWS_EXT_CB_PACKET_TX_PRESEND, &eff_buf, 0); - if (m < 0) - return -1; - if (m) /* handled */ - ret = 1; - - if ((char *)buf != eff_buf.token) - /* - * extension recreated it: - * need to buffer this if not all sent - */ - wsi->u.ws.clean_buffer = 0; - - /* assuming they left us something to send, send it */ - - if (eff_buf.token_len) { - n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, - eff_buf.token_len); - if (n < 0) { - lwsl_info("closing from ext access\n"); - return -1; - } - - /* always either sent it all or privately buffered */ - if (wsi->u.ws.clean_buffer) - len = n; - } - - lwsl_parser("written %d bytes to client\n", n); - - /* no extension has more to spill? Then we can go */ - - if (!ret) - break; - - /* we used up what we had */ - - eff_buf.token = NULL; - eff_buf.token_len = 0; - - /* - * Did that leave the pipe choked? - * Or we had to hold on to some of it? - */ - - if (!lws_send_pipe_choked(wsi) && !wsi->trunc_len) - /* no we could add more, lets's do that */ - continue; - - lwsl_debug("choked\n"); - - /* - * Yes, he's choked. Don't spill the rest now get a callback - * when he is ready to send and take care of it there - */ - lws_callback_on_writable(wsi); - wsi->extension_data_pending = 1; - ret = 0; - } - - return len; -} - -int -lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r, - void *v, size_t len) -{ - struct lws_context *context = wsi->context; - int n, handled = 0; - - /* maybe an extension will take care of it for us */ - - for (n = 0; n < wsi->count_act_ext && !handled; n++) { - if (!wsi->active_extensions[n]->callback) - continue; - - handled |= wsi->active_extensions[n]->callback(context, - wsi->active_extensions[n], wsi, - r, wsi->act_ext_user[n], v, len); - } - - return handled; -} - -int -lws_set_extension_option(struct lws *wsi, const char *ext_name, - const char *opt_name, const char *opt_val) -{ - struct lws_ext_option_arg oa; - int idx = 0; - - /* first identify if the ext is active on this wsi */ - while (idx < wsi->count_act_ext && - strcmp(wsi->active_extensions[idx]->name, ext_name)) - idx++; - - if (idx == wsi->count_act_ext) - return -1; /* request ext not active on this wsi */ - - oa.option_name = opt_name; - oa.option_index = 0; - oa.start = opt_val; - oa.len = 0; - - return wsi->active_extensions[idx]->callback( - wsi->context, wsi->active_extensions[idx], wsi, - LWS_EXT_CB_NAMED_OPTION_SET, wsi->act_ext_user[idx], &oa, 0); -} diff --git a/thirdparty/lws/handshake.c b/thirdparty/lws/handshake.c deleted file mode 100644 index bc7609d920..0000000000 --- a/thirdparty/lws/handshake.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -/* - * -04 of the protocol (actually the 80th version) has a radically different - * handshake. The 04 spec gives the following idea - * - * The handshake from the client looks as follows: - * - * GET /chat HTTP/1.1 - * Host: server.example.com - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - * Sec-WebSocket-Origin: http://example.com - * Sec-WebSocket-Protocol: chat, superchat - * Sec-WebSocket-Version: 4 - * - * The handshake from the server looks as follows: - * - * HTTP/1.1 101 Switching Protocols - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= - * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== - * Sec-WebSocket-Protocol: chat - */ - -#ifndef min -#define min(a, b) ((a) < (b) ? (a) : (b)) -#endif - -/* - * We have to take care about parsing because the headers may be split - * into multiple fragments. They may contain unknown headers with arbitrary - * argument lengths. So, we parse using a single-character at a time state - * machine that is completely independent of packet size. - * - * Returns <0 for error or length of chars consumed from buf (up to len) - */ - -LWS_VISIBLE int -lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) -{ - unsigned char *last_char, *oldbuf = buf; - lws_filepos_t body_chunk_len; - size_t n; - - switch (wsi->state) { -#ifdef LWS_WITH_HTTP2 - case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: - case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: - case LWSS_HTTP2_ESTABLISHED: - n = 0; - //lwsl_debug("%s: starting new block of %d\n", __func__, (int)len); - /* - * wsi here is always the network connection wsi, not a stream - * wsi. - */ - while (n < len) { - /* - * we were accepting input but now we stopped doing so - */ - if (lws_is_flowcontrolled(wsi)) { - lws_rxflow_cache(wsi, buf, n, len); - - return 1; - } - - /* account for what we're using in rxflow buffer */ - if (wsi->rxflow_buffer) { - wsi->rxflow_pos++; - assert(wsi->rxflow_pos <= wsi->rxflow_len); - } - - if (lws_h2_parser(wsi, buf[n++])) { - lwsl_debug("%s: http2_parser bailed\n", __func__); - goto bail; - } - } - lwsl_debug("%s: used up block of %d\n", __func__, (int)len); - break; -#endif - - case LWSS_HTTP_ISSUING_FILE: - return 0; - - case LWSS_CLIENT_HTTP_ESTABLISHED: - break; - - case LWSS_HTTP: - wsi->hdr_parsing_completed = 0; - - /* fallthru */ - - case LWSS_HTTP_HEADERS: - if (!wsi->u.hdr.ah) { - lwsl_err("%s: LWSS_HTTP_HEADERS: NULL ah\n", __func__); - assert(0); - } - lwsl_parser("issuing %d bytes to parser\n", (int)len); - - lwsl_hexdump(buf, (size_t)len); - - if (lws_handshake_client(wsi, &buf, (size_t)len)) - goto bail; - - last_char = buf; - if (lws_handshake_server(wsi, &buf, (size_t)len)) - /* Handshake indicates this session is done. */ - goto bail; - - /* we might have transitioned to RAW */ - if (wsi->mode == LWSCM_RAW) - /* we gave the read buffer to RAW handler already */ - goto read_ok; - - /* - * It's possible that we've exhausted our data already, or - * rx flow control has stopped us dealing with this early, - * but lws_handshake_server doesn't update len for us. - * Figure out how much was read, so that we can proceed - * appropriately: - */ - len -= (buf - last_char); - lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len); - - if (!wsi->hdr_parsing_completed) - /* More header content on the way */ - goto read_ok; - - switch (wsi->state) { - case LWSS_HTTP: - case LWSS_HTTP_HEADERS: - goto read_ok; - case LWSS_HTTP_ISSUING_FILE: - goto read_ok; - case LWSS_HTTP_BODY: - wsi->u.http.rx_content_remain = - wsi->u.http.rx_content_length; - if (wsi->u.http.rx_content_remain) - goto http_postbody; - - /* there is no POST content */ - goto postbody_completion; - default: - break; - } - break; - - case LWSS_HTTP_BODY: -http_postbody: - //lwsl_notice("http post body\n"); - while (len && wsi->u.http.rx_content_remain) { - /* Copy as much as possible, up to the limit of: - * what we have in the read buffer (len) - * remaining portion of the POST body (content_remain) - */ - body_chunk_len = min(wsi->u.http.rx_content_remain, len); - wsi->u.http.rx_content_remain -= body_chunk_len; - len -= body_chunk_len; -#ifdef LWS_WITH_CGI - if (wsi->cgi) { - struct lws_cgi_args args; - - args.ch = LWS_STDIN; - args.stdwsi = &wsi->cgi->stdwsi[0]; - args.data = buf; - args.len = body_chunk_len; - - /* returns how much used */ - n = user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, LWS_CALLBACK_CGI_STDIN_DATA, - wsi->user_space, - (void *)&args, 0); - if ((int)n < 0) - goto bail; - } else { -#endif - n = wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_BODY, wsi->user_space, - buf, (size_t)body_chunk_len); - if (n) - goto bail; - n = (size_t)body_chunk_len; -#ifdef LWS_WITH_CGI - } -#endif - buf += n; - - if (wsi->u.http.rx_content_remain) { - lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, - wsi->context->timeout_secs); - break; - } - /* he sent all the content in time */ -postbody_completion: -#ifdef LWS_WITH_CGI - /* - * If we're running a cgi, we can't let him off the - * hook just because he sent his POST data - */ - if (wsi->cgi) - lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, - wsi->context->timeout_secs); - else -#endif - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); -#ifdef LWS_WITH_CGI - if (!wsi->cgi) -#endif - { - lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); - n = wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_BODY_COMPLETION, - wsi->user_space, NULL, 0); - if (n) - goto bail; - - if (wsi->http2_substream) - wsi->state = LWSS_HTTP2_ESTABLISHED; - } - - break; - } - break; - - case LWSS_ESTABLISHED: - case LWSS_AWAITING_CLOSE_ACK: - case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION: - case LWSS_SHUTDOWN: - if (lws_handshake_client(wsi, &buf, (size_t)len)) - goto bail; - switch (wsi->mode) { - case LWSCM_WS_SERVING: - - if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) { - lwsl_info("interpret_incoming_packet has bailed\n"); - goto bail; - } - break; - } - break; - default: - lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state); - break; - } - -read_ok: - /* Nothing more to do for now */ - lwsl_info("%s: read_ok, used %ld\n", __func__, (long)(buf - oldbuf)); - - return buf - oldbuf; - -bail: - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - - return -1; -} diff --git a/thirdparty/lws/lextable.h b/thirdparty/lws/lextable.h deleted file mode 100644 index f940afd25b..0000000000 --- a/thirdparty/lws/lextable.h +++ /dev/null @@ -1,805 +0,0 @@ -/* pos 0000: 0 */ 0x67 /* 'g' */, 0x40, 0x00 /* (to 0x0040 state 1) */, - 0x70 /* 'p' */, 0x42, 0x00 /* (to 0x0045 state 5) */, - 0x6F /* 'o' */, 0x51, 0x00 /* (to 0x0057 state 10) */, - 0x68 /* 'h' */, 0x5D, 0x00 /* (to 0x0066 state 18) */, - 0x63 /* 'c' */, 0x69, 0x00 /* (to 0x0075 state 23) */, - 0x75 /* 'u' */, 0x8A, 0x00 /* (to 0x0099 state 34) */, - 0x73 /* 's' */, 0xA0, 0x00 /* (to 0x00B2 state 48) */, - 0x0D /* '.' */, 0xD9, 0x00 /* (to 0x00EE state 68) */, - 0x61 /* 'a' */, 0x31, 0x01 /* (to 0x0149 state 129) */, - 0x69 /* 'i' */, 0x70, 0x01 /* (to 0x018B state 163) */, - 0x64 /* 'd' */, 0x19, 0x02 /* (to 0x0237 state 265) */, - 0x72 /* 'r' */, 0x22, 0x02 /* (to 0x0243 state 270) */, - 0x3A /* ':' */, 0x53, 0x02 /* (to 0x0277 state 299) */, - 0x65 /* 'e' */, 0xDF, 0x02 /* (to 0x0306 state 409) */, - 0x66 /* 'f' */, 0xFB, 0x02 /* (to 0x0325 state 425) */, - 0x6C /* 'l' */, 0x1D, 0x03 /* (to 0x034A state 458) */, - 0x6D /* 'm' */, 0x40, 0x03 /* (to 0x0370 state 484) */, - 0x74 /* 't' */, 0xAF, 0x03 /* (to 0x03E2 state 578) */, - 0x76 /* 'v' */, 0xD0, 0x03 /* (to 0x0406 state 606) */, - 0x77 /* 'w' */, 0xDD, 0x03 /* (to 0x0416 state 614) */, - 0x78 /* 'x' */, 0x04, 0x04 /* (to 0x0440 state 650) */, - 0x08, /* fail */ -/* pos 0040: 1 */ 0xE5 /* 'e' -> */, -/* pos 0041: 2 */ 0xF4 /* 't' -> */, -/* pos 0042: 3 */ 0xA0 /* ' ' -> */, -/* pos 0043: 4 */ 0x00, 0x00 /* - terminal marker 0 - */, -/* pos 0045: 5 */ 0x6F /* 'o' */, 0x0D, 0x00 /* (to 0x0052 state 6) */, - 0x72 /* 'r' */, 0x95, 0x01 /* (to 0x01DD state 211) */, - 0x61 /* 'a' */, 0xDD, 0x03 /* (to 0x0428 state 631) */, - 0x75 /* 'u' */, 0xDF, 0x03 /* (to 0x042D state 635) */, - 0x08, /* fail */ -/* pos 0052: 6 */ 0xF3 /* 's' -> */, -/* pos 0053: 7 */ 0xF4 /* 't' -> */, -/* pos 0054: 8 */ 0xA0 /* ' ' -> */, -/* pos 0055: 9 */ 0x00, 0x01 /* - terminal marker 1 - */, -/* pos 0057: 10 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x005E state 11) */, - 0x72 /* 'r' */, 0x51, 0x00 /* (to 0x00AB state 42) */, - 0x08, /* fail */ -/* pos 005e: 11 */ 0xF4 /* 't' -> */, -/* pos 005f: 12 */ 0xE9 /* 'i' -> */, -/* pos 0060: 13 */ 0xEF /* 'o' -> */, -/* pos 0061: 14 */ 0xEE /* 'n' -> */, -/* pos 0062: 15 */ 0xF3 /* 's' -> */, -/* pos 0063: 16 */ 0xA0 /* ' ' -> */, -/* pos 0064: 17 */ 0x00, 0x02 /* - terminal marker 2 - */, -/* pos 0066: 18 */ 0x6F /* 'o' */, 0x0A, 0x00 /* (to 0x0070 state 19) */, - 0x74 /* 't' */, 0xBF, 0x00 /* (to 0x0128 state 110) */, - 0x65 /* 'e' */, 0xF8, 0x03 /* (to 0x0464 state 676) */, - 0x08, /* fail */ -/* pos 0070: 19 */ 0xF3 /* 's' -> */, -/* pos 0071: 20 */ 0xF4 /* 't' -> */, -/* pos 0072: 21 */ 0xBA /* ':' -> */, -/* pos 0073: 22 */ 0x00, 0x03 /* - terminal marker 3 - */, -/* pos 0075: 23 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x007C state 24) */, - 0x61 /* 'a' */, 0x72, 0x01 /* (to 0x01EA state 217) */, - 0x08, /* fail */ -/* pos 007c: 24 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x0083 state 25) */, - 0x6F /* 'o' */, 0x87, 0x01 /* (to 0x0206 state 243) */, - 0x08, /* fail */ -/* pos 0083: 25 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x008A state 26) */, - 0x74 /* 't' */, 0x86, 0x01 /* (to 0x020C state 248) */, - 0x08, /* fail */ -/* pos 008a: 26 */ 0xE5 /* 'e' -> */, -/* pos 008b: 27 */ 0xE3 /* 'c' -> */, -/* pos 008c: 28 */ 0xF4 /* 't' -> */, -/* pos 008d: 29 */ 0x69 /* 'i' */, 0x07, 0x00 /* (to 0x0094 state 30) */, - 0x20 /* ' ' */, 0xD2, 0x03 /* (to 0x0462 state 675) */, - 0x08, /* fail */ -/* pos 0094: 30 */ 0xEF /* 'o' -> */, -/* pos 0095: 31 */ 0xEE /* 'n' -> */, -/* pos 0096: 32 */ 0xBA /* ':' -> */, -/* pos 0097: 33 */ 0x00, 0x04 /* - terminal marker 4 - */, -/* pos 0099: 34 */ 0x70 /* 'p' */, 0x0A, 0x00 /* (to 0x00A3 state 35) */, - 0x73 /* 's' */, 0x5F, 0x03 /* (to 0x03FB state 596) */, - 0x72 /* 'r' */, 0x97, 0x03 /* (to 0x0436 state 642) */, - 0x08, /* fail */ -/* pos 00a3: 35 */ 0xE7 /* 'g' -> */, -/* pos 00a4: 36 */ 0xF2 /* 'r' -> */, -/* pos 00a5: 37 */ 0xE1 /* 'a' -> */, -/* pos 00a6: 38 */ 0xE4 /* 'd' -> */, -/* pos 00a7: 39 */ 0xE5 /* 'e' -> */, -/* pos 00a8: 40 */ 0xBA /* ':' -> */, -/* pos 00a9: 41 */ 0x00, 0x05 /* - terminal marker 5 - */, -/* pos 00ab: 42 */ 0xE9 /* 'i' -> */, -/* pos 00ac: 43 */ 0xE7 /* 'g' -> */, -/* pos 00ad: 44 */ 0xE9 /* 'i' -> */, -/* pos 00ae: 45 */ 0xEE /* 'n' -> */, -/* pos 00af: 46 */ 0xBA /* ':' -> */, -/* pos 00b0: 47 */ 0x00, 0x06 /* - terminal marker 6 - */, -/* pos 00b2: 48 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x00B9 state 49) */, - 0x74 /* 't' */, 0x13, 0x03 /* (to 0x03C8 state 553) */, - 0x08, /* fail */ -/* pos 00b9: 49 */ 0x63 /* 'c' */, 0x0A, 0x00 /* (to 0x00C3 state 50) */, - 0x72 /* 'r' */, 0xFC, 0x02 /* (to 0x03B8 state 539) */, - 0x74 /* 't' */, 0xFF, 0x02 /* (to 0x03BE state 544) */, - 0x08, /* fail */ -/* pos 00c3: 50 */ 0xAD /* '-' -> */, -/* pos 00c4: 51 */ 0xF7 /* 'w' -> */, -/* pos 00c5: 52 */ 0xE5 /* 'e' -> */, -/* pos 00c6: 53 */ 0xE2 /* 'b' -> */, -/* pos 00c7: 54 */ 0xF3 /* 's' -> */, -/* pos 00c8: 55 */ 0xEF /* 'o' -> */, -/* pos 00c9: 56 */ 0xE3 /* 'c' -> */, -/* pos 00ca: 57 */ 0xEB /* 'k' -> */, -/* pos 00cb: 58 */ 0xE5 /* 'e' -> */, -/* pos 00cc: 59 */ 0xF4 /* 't' -> */, -/* pos 00cd: 60 */ 0xAD /* '-' -> */, -/* pos 00ce: 61 */ 0x64 /* 'd' */, 0x19, 0x00 /* (to 0x00E7 state 62) */, - 0x65 /* 'e' */, 0x20, 0x00 /* (to 0x00F1 state 70) */, - 0x6B /* 'k' */, 0x29, 0x00 /* (to 0x00FD state 81) */, - 0x70 /* 'p' */, 0x38, 0x00 /* (to 0x010F state 88) */, - 0x61 /* 'a' */, 0x3F, 0x00 /* (to 0x0119 state 97) */, - 0x6E /* 'n' */, 0x44, 0x00 /* (to 0x0121 state 104) */, - 0x76 /* 'v' */, 0x86, 0x01 /* (to 0x0266 state 284) */, - 0x6F /* 'o' */, 0x8C, 0x01 /* (to 0x026F state 292) */, - 0x08, /* fail */ -/* pos 00e7: 62 */ 0xF2 /* 'r' -> */, -/* pos 00e8: 63 */ 0xE1 /* 'a' -> */, -/* pos 00e9: 64 */ 0xE6 /* 'f' -> */, -/* pos 00ea: 65 */ 0xF4 /* 't' -> */, -/* pos 00eb: 66 */ 0xBA /* ':' -> */, -/* pos 00ec: 67 */ 0x00, 0x07 /* - terminal marker 7 - */, -/* pos 00ee: 68 */ 0x8A /* '.' -> */, -/* pos 00ef: 69 */ 0x00, 0x08 /* - terminal marker 8 - */, -/* pos 00f1: 70 */ 0xF8 /* 'x' -> */, -/* pos 00f2: 71 */ 0xF4 /* 't' -> */, -/* pos 00f3: 72 */ 0xE5 /* 'e' -> */, -/* pos 00f4: 73 */ 0xEE /* 'n' -> */, -/* pos 00f5: 74 */ 0xF3 /* 's' -> */, -/* pos 00f6: 75 */ 0xE9 /* 'i' -> */, -/* pos 00f7: 76 */ 0xEF /* 'o' -> */, -/* pos 00f8: 77 */ 0xEE /* 'n' -> */, -/* pos 00f9: 78 */ 0xF3 /* 's' -> */, -/* pos 00fa: 79 */ 0xBA /* ':' -> */, -/* pos 00fb: 80 */ 0x00, 0x09 /* - terminal marker 9 - */, -/* pos 00fd: 81 */ 0xE5 /* 'e' -> */, -/* pos 00fe: 82 */ 0xF9 /* 'y' -> */, -/* pos 00ff: 83 */ 0x31 /* '1' */, 0x0A, 0x00 /* (to 0x0109 state 84) */, - 0x32 /* '2' */, 0x0A, 0x00 /* (to 0x010C state 86) */, - 0x3A /* ':' */, 0x5F, 0x01 /* (to 0x0264 state 283) */, - 0x08, /* fail */ -/* pos 0109: 84 */ 0xBA /* ':' -> */, -/* pos 010a: 85 */ 0x00, 0x0A /* - terminal marker 10 - */, -/* pos 010c: 86 */ 0xBA /* ':' -> */, -/* pos 010d: 87 */ 0x00, 0x0B /* - terminal marker 11 - */, -/* pos 010f: 88 */ 0xF2 /* 'r' -> */, -/* pos 0110: 89 */ 0xEF /* 'o' -> */, -/* pos 0111: 90 */ 0xF4 /* 't' -> */, -/* pos 0112: 91 */ 0xEF /* 'o' -> */, -/* pos 0113: 92 */ 0xE3 /* 'c' -> */, -/* pos 0114: 93 */ 0xEF /* 'o' -> */, -/* pos 0115: 94 */ 0xEC /* 'l' -> */, -/* pos 0116: 95 */ 0xBA /* ':' -> */, -/* pos 0117: 96 */ 0x00, 0x0C /* - terminal marker 12 - */, -/* pos 0119: 97 */ 0xE3 /* 'c' -> */, -/* pos 011a: 98 */ 0xE3 /* 'c' -> */, -/* pos 011b: 99 */ 0xE5 /* 'e' -> */, -/* pos 011c: 100 */ 0xF0 /* 'p' -> */, -/* pos 011d: 101 */ 0xF4 /* 't' -> */, -/* pos 011e: 102 */ 0xBA /* ':' -> */, -/* pos 011f: 103 */ 0x00, 0x0D /* - terminal marker 13 - */, -/* pos 0121: 104 */ 0xEF /* 'o' -> */, -/* pos 0122: 105 */ 0xEE /* 'n' -> */, -/* pos 0123: 106 */ 0xE3 /* 'c' -> */, -/* pos 0124: 107 */ 0xE5 /* 'e' -> */, -/* pos 0125: 108 */ 0xBA /* ':' -> */, -/* pos 0126: 109 */ 0x00, 0x0E /* - terminal marker 14 - */, -/* pos 0128: 110 */ 0xF4 /* 't' -> */, -/* pos 0129: 111 */ 0xF0 /* 'p' -> */, -/* pos 012a: 112 */ 0x2F /* '/' */, 0x07, 0x00 /* (to 0x0131 state 113) */, - 0x32 /* '2' */, 0x10, 0x00 /* (to 0x013D state 118) */, - 0x08, /* fail */ -/* pos 0131: 113 */ 0xB1 /* '1' -> */, -/* pos 0132: 114 */ 0xAE /* '.' -> */, -/* pos 0133: 115 */ 0x31 /* '1' */, 0x07, 0x00 /* (to 0x013A state 116) */, - 0x30 /* '0' */, 0x1B, 0x03 /* (to 0x0451 state 660) */, - 0x08, /* fail */ -/* pos 013a: 116 */ 0xA0 /* ' ' -> */, -/* pos 013b: 117 */ 0x00, 0x0F /* - terminal marker 15 - */, -/* pos 013d: 118 */ 0xAD /* '-' -> */, -/* pos 013e: 119 */ 0xF3 /* 's' -> */, -/* pos 013f: 120 */ 0xE5 /* 'e' -> */, -/* pos 0140: 121 */ 0xF4 /* 't' -> */, -/* pos 0141: 122 */ 0xF4 /* 't' -> */, -/* pos 0142: 123 */ 0xE9 /* 'i' -> */, -/* pos 0143: 124 */ 0xEE /* 'n' -> */, -/* pos 0144: 125 */ 0xE7 /* 'g' -> */, -/* pos 0145: 126 */ 0xF3 /* 's' -> */, -/* pos 0146: 127 */ 0xBA /* ':' -> */, -/* pos 0147: 128 */ 0x00, 0x10 /* - terminal marker 16 - */, -/* pos 0149: 129 */ 0x63 /* 'c' */, 0x0D, 0x00 /* (to 0x0156 state 130) */, - 0x75 /* 'u' */, 0xAC, 0x00 /* (to 0x01F8 state 230) */, - 0x67 /* 'g' */, 0x7D, 0x01 /* (to 0x02CC state 358) */, - 0x6C /* 'l' */, 0x7E, 0x01 /* (to 0x02D0 state 361) */, - 0x08, /* fail */ -/* pos 0156: 130 */ 0xE3 /* 'c' -> */, -/* pos 0157: 131 */ 0xE5 /* 'e' -> */, -/* pos 0158: 132 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x015F state 133) */, - 0x73 /* 's' */, 0x0E, 0x00 /* (to 0x0169 state 136) */, - 0x08, /* fail */ -/* pos 015f: 133 */ 0xF4 /* 't' -> */, -/* pos 0160: 134 */ 0x3A /* ':' */, 0x07, 0x00 /* (to 0x0167 state 135) */, - 0x2D /* '-' */, 0x59, 0x00 /* (to 0x01BC state 192) */, - 0x08, /* fail */ -/* pos 0167: 135 */ 0x00, 0x11 /* - terminal marker 17 - */, -/* pos 0169: 136 */ 0xF3 /* 's' -> */, -/* pos 016a: 137 */ 0xAD /* '-' -> */, -/* pos 016b: 138 */ 0xE3 /* 'c' -> */, -/* pos 016c: 139 */ 0xEF /* 'o' -> */, -/* pos 016d: 140 */ 0xEE /* 'n' -> */, -/* pos 016e: 141 */ 0xF4 /* 't' -> */, -/* pos 016f: 142 */ 0xF2 /* 'r' -> */, -/* pos 0170: 143 */ 0xEF /* 'o' -> */, -/* pos 0171: 144 */ 0xEC /* 'l' -> */, -/* pos 0172: 145 */ 0xAD /* '-' -> */, -/* pos 0173: 146 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x017A state 147) */, - 0x61 /* 'a' */, 0x48, 0x01 /* (to 0x02BE state 345) */, - 0x08, /* fail */ -/* pos 017a: 147 */ 0xE5 /* 'e' -> */, -/* pos 017b: 148 */ 0xF1 /* 'q' -> */, -/* pos 017c: 149 */ 0xF5 /* 'u' -> */, -/* pos 017d: 150 */ 0xE5 /* 'e' -> */, -/* pos 017e: 151 */ 0xF3 /* 's' -> */, -/* pos 017f: 152 */ 0xF4 /* 't' -> */, -/* pos 0180: 153 */ 0xAD /* '-' -> */, -/* pos 0181: 154 */ 0xE8 /* 'h' -> */, -/* pos 0182: 155 */ 0xE5 /* 'e' -> */, -/* pos 0183: 156 */ 0xE1 /* 'a' -> */, -/* pos 0184: 157 */ 0xE4 /* 'd' -> */, -/* pos 0185: 158 */ 0xE5 /* 'e' -> */, -/* pos 0186: 159 */ 0xF2 /* 'r' -> */, -/* pos 0187: 160 */ 0xF3 /* 's' -> */, -/* pos 0188: 161 */ 0xBA /* ':' -> */, -/* pos 0189: 162 */ 0x00, 0x12 /* - terminal marker 18 - */, -/* pos 018b: 163 */ 0xE6 /* 'f' -> */, -/* pos 018c: 164 */ 0xAD /* '-' -> */, -/* pos 018d: 165 */ 0x6D /* 'm' */, 0x0D, 0x00 /* (to 0x019A state 166) */, - 0x6E /* 'n' */, 0x20, 0x00 /* (to 0x01B0 state 181) */, - 0x72 /* 'r' */, 0x9E, 0x01 /* (to 0x0331 state 435) */, - 0x75 /* 'u' */, 0xA2, 0x01 /* (to 0x0338 state 441) */, - 0x08, /* fail */ -/* pos 019a: 166 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x01A1 state 167) */, - 0x61 /* 'a' */, 0x8E, 0x01 /* (to 0x032B state 430) */, - 0x08, /* fail */ -/* pos 01a1: 167 */ 0xE4 /* 'd' -> */, -/* pos 01a2: 168 */ 0xE9 /* 'i' -> */, -/* pos 01a3: 169 */ 0xE6 /* 'f' -> */, -/* pos 01a4: 170 */ 0xE9 /* 'i' -> */, -/* pos 01a5: 171 */ 0xE5 /* 'e' -> */, -/* pos 01a6: 172 */ 0xE4 /* 'd' -> */, -/* pos 01a7: 173 */ 0xAD /* '-' -> */, -/* pos 01a8: 174 */ 0xF3 /* 's' -> */, -/* pos 01a9: 175 */ 0xE9 /* 'i' -> */, -/* pos 01aa: 176 */ 0xEE /* 'n' -> */, -/* pos 01ab: 177 */ 0xE3 /* 'c' -> */, -/* pos 01ac: 178 */ 0xE5 /* 'e' -> */, -/* pos 01ad: 179 */ 0xBA /* ':' -> */, -/* pos 01ae: 180 */ 0x00, 0x13 /* - terminal marker 19 - */, -/* pos 01b0: 181 */ 0xEF /* 'o' -> */, -/* pos 01b1: 182 */ 0xEE /* 'n' -> */, -/* pos 01b2: 183 */ 0xE5 /* 'e' -> */, -/* pos 01b3: 184 */ 0xAD /* '-' -> */, -/* pos 01b4: 185 */ 0xED /* 'm' -> */, -/* pos 01b5: 186 */ 0xE1 /* 'a' -> */, -/* pos 01b6: 187 */ 0xF4 /* 't' -> */, -/* pos 01b7: 188 */ 0xE3 /* 'c' -> */, -/* pos 01b8: 189 */ 0xE8 /* 'h' -> */, -/* pos 01b9: 190 */ 0xBA /* ':' -> */, -/* pos 01ba: 191 */ 0x00, 0x14 /* - terminal marker 20 - */, -/* pos 01bc: 192 */ 0x65 /* 'e' */, 0x0D, 0x00 /* (to 0x01C9 state 193) */, - 0x6C /* 'l' */, 0x14, 0x00 /* (to 0x01D3 state 202) */, - 0x63 /* 'c' */, 0xEB, 0x00 /* (to 0x02AD state 330) */, - 0x72 /* 'r' */, 0xF1, 0x00 /* (to 0x02B6 state 338) */, - 0x08, /* fail */ -/* pos 01c9: 193 */ 0xEE /* 'n' -> */, -/* pos 01ca: 194 */ 0xE3 /* 'c' -> */, -/* pos 01cb: 195 */ 0xEF /* 'o' -> */, -/* pos 01cc: 196 */ 0xE4 /* 'd' -> */, -/* pos 01cd: 197 */ 0xE9 /* 'i' -> */, -/* pos 01ce: 198 */ 0xEE /* 'n' -> */, -/* pos 01cf: 199 */ 0xE7 /* 'g' -> */, -/* pos 01d0: 200 */ 0xBA /* ':' -> */, -/* pos 01d1: 201 */ 0x00, 0x15 /* - terminal marker 21 - */, -/* pos 01d3: 202 */ 0xE1 /* 'a' -> */, -/* pos 01d4: 203 */ 0xEE /* 'n' -> */, -/* pos 01d5: 204 */ 0xE7 /* 'g' -> */, -/* pos 01d6: 205 */ 0xF5 /* 'u' -> */, -/* pos 01d7: 206 */ 0xE1 /* 'a' -> */, -/* pos 01d8: 207 */ 0xE7 /* 'g' -> */, -/* pos 01d9: 208 */ 0xE5 /* 'e' -> */, -/* pos 01da: 209 */ 0xBA /* ':' -> */, -/* pos 01db: 210 */ 0x00, 0x16 /* - terminal marker 22 - */, -/* pos 01dd: 211 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x01E4 state 212) */, - 0x6F /* 'o' */, 0x9E, 0x01 /* (to 0x037E state 497) */, - 0x08, /* fail */ -/* pos 01e4: 212 */ 0xE7 /* 'g' -> */, -/* pos 01e5: 213 */ 0xED /* 'm' -> */, -/* pos 01e6: 214 */ 0xE1 /* 'a' -> */, -/* pos 01e7: 215 */ 0xBA /* ':' -> */, -/* pos 01e8: 216 */ 0x00, 0x17 /* - terminal marker 23 - */, -/* pos 01ea: 217 */ 0xE3 /* 'c' -> */, -/* pos 01eb: 218 */ 0xE8 /* 'h' -> */, -/* pos 01ec: 219 */ 0xE5 /* 'e' -> */, -/* pos 01ed: 220 */ 0xAD /* '-' -> */, -/* pos 01ee: 221 */ 0xE3 /* 'c' -> */, -/* pos 01ef: 222 */ 0xEF /* 'o' -> */, -/* pos 01f0: 223 */ 0xEE /* 'n' -> */, -/* pos 01f1: 224 */ 0xF4 /* 't' -> */, -/* pos 01f2: 225 */ 0xF2 /* 'r' -> */, -/* pos 01f3: 226 */ 0xEF /* 'o' -> */, -/* pos 01f4: 227 */ 0xEC /* 'l' -> */, -/* pos 01f5: 228 */ 0xBA /* ':' -> */, -/* pos 01f6: 229 */ 0x00, 0x18 /* - terminal marker 24 - */, -/* pos 01f8: 230 */ 0xF4 /* 't' -> */, -/* pos 01f9: 231 */ 0xE8 /* 'h' -> */, -/* pos 01fa: 232 */ 0xEF /* 'o' -> */, -/* pos 01fb: 233 */ 0xF2 /* 'r' -> */, -/* pos 01fc: 234 */ 0xE9 /* 'i' -> */, -/* pos 01fd: 235 */ 0xFA /* 'z' -> */, -/* pos 01fe: 236 */ 0xE1 /* 'a' -> */, -/* pos 01ff: 237 */ 0xF4 /* 't' -> */, -/* pos 0200: 238 */ 0xE9 /* 'i' -> */, -/* pos 0201: 239 */ 0xEF /* 'o' -> */, -/* pos 0202: 240 */ 0xEE /* 'n' -> */, -/* pos 0203: 241 */ 0xBA /* ':' -> */, -/* pos 0204: 242 */ 0x00, 0x19 /* - terminal marker 25 - */, -/* pos 0206: 243 */ 0xEB /* 'k' -> */, -/* pos 0207: 244 */ 0xE9 /* 'i' -> */, -/* pos 0208: 245 */ 0xE5 /* 'e' -> */, -/* pos 0209: 246 */ 0xBA /* ':' -> */, -/* pos 020a: 247 */ 0x00, 0x1A /* - terminal marker 26 - */, -/* pos 020c: 248 */ 0xE5 /* 'e' -> */, -/* pos 020d: 249 */ 0xEE /* 'n' -> */, -/* pos 020e: 250 */ 0xF4 /* 't' -> */, -/* pos 020f: 251 */ 0xAD /* '-' -> */, -/* pos 0210: 252 */ 0x6C /* 'l' */, 0x10, 0x00 /* (to 0x0220 state 253) */, - 0x74 /* 't' */, 0x1E, 0x00 /* (to 0x0231 state 260) */, - 0x64 /* 'd' */, 0xC0, 0x00 /* (to 0x02D6 state 366) */, - 0x65 /* 'e' */, 0xCA, 0x00 /* (to 0x02E3 state 378) */, - 0x72 /* 'r' */, 0xE3, 0x00 /* (to 0x02FF state 403) */, - 0x08, /* fail */ -/* pos 0220: 253 */ 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x022A state 254) */, - 0x61 /* 'a' */, 0xCA, 0x00 /* (to 0x02ED state 387) */, - 0x6F /* 'o' */, 0xD0, 0x00 /* (to 0x02F6 state 395) */, - 0x08, /* fail */ -/* pos 022a: 254 */ 0xEE /* 'n' -> */, -/* pos 022b: 255 */ 0xE7 /* 'g' -> */, -/* pos 022c: 256 */ 0xF4 /* 't' -> */, -/* pos 022d: 257 */ 0xE8 /* 'h' -> */, -/* pos 022e: 258 */ 0xBA /* ':' -> */, -/* pos 022f: 259 */ 0x00, 0x1B /* - terminal marker 27 - */, -/* pos 0231: 260 */ 0xF9 /* 'y' -> */, -/* pos 0232: 261 */ 0xF0 /* 'p' -> */, -/* pos 0233: 262 */ 0xE5 /* 'e' -> */, -/* pos 0234: 263 */ 0xBA /* ':' -> */, -/* pos 0235: 264 */ 0x00, 0x1C /* - terminal marker 28 - */, -/* pos 0237: 265 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x023E state 266) */, - 0x65 /* 'e' */, 0xF6, 0x01 /* (to 0x0430 state 637) */, - 0x08, /* fail */ -/* pos 023e: 266 */ 0xF4 /* 't' -> */, -/* pos 023f: 267 */ 0xE5 /* 'e' -> */, -/* pos 0240: 268 */ 0xBA /* ':' -> */, -/* pos 0241: 269 */ 0x00, 0x1D /* - terminal marker 29 - */, -/* pos 0243: 270 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x024A state 271) */, - 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x0250 state 276) */, - 0x08, /* fail */ -/* pos 024a: 271 */ 0xEE /* 'n' -> */, -/* pos 024b: 272 */ 0xE7 /* 'g' -> */, -/* pos 024c: 273 */ 0xE5 /* 'e' -> */, -/* pos 024d: 274 */ 0xBA /* ':' -> */, -/* pos 024e: 275 */ 0x00, 0x1E /* - terminal marker 30 - */, -/* pos 0250: 276 */ 0x66 /* 'f' */, 0x07, 0x00 /* (to 0x0257 state 277) */, - 0x74 /* 't' */, 0x5A, 0x01 /* (to 0x03AD state 529) */, - 0x08, /* fail */ -/* pos 0257: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x025E state 278) */, - 0x72 /* 'r' */, 0x4D, 0x01 /* (to 0x03A7 state 524) */, - 0x08, /* fail */ -/* pos 025e: 278 */ 0xF2 /* 'r' -> */, -/* pos 025f: 279 */ 0xE5 /* 'e' -> */, -/* pos 0260: 280 */ 0xF2 /* 'r' -> */, -/* pos 0261: 281 */ 0xBA /* ':' -> */, -/* pos 0262: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, -/* pos 0264: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, -/* pos 0266: 284 */ 0xE5 /* 'e' -> */, -/* pos 0267: 285 */ 0xF2 /* 'r' -> */, -/* pos 0268: 286 */ 0xF3 /* 's' -> */, -/* pos 0269: 287 */ 0xE9 /* 'i' -> */, -/* pos 026a: 288 */ 0xEF /* 'o' -> */, -/* pos 026b: 289 */ 0xEE /* 'n' -> */, -/* pos 026c: 290 */ 0xBA /* ':' -> */, -/* pos 026d: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, -/* pos 026f: 292 */ 0xF2 /* 'r' -> */, -/* pos 0270: 293 */ 0xE9 /* 'i' -> */, -/* pos 0271: 294 */ 0xE7 /* 'g' -> */, -/* pos 0272: 295 */ 0xE9 /* 'i' -> */, -/* pos 0273: 296 */ 0xEE /* 'n' -> */, -/* pos 0274: 297 */ 0xBA /* ':' -> */, -/* pos 0275: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, -/* pos 0277: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0284 state 300) */, - 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x028E state 309) */, - 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0295 state 315) */, - 0x73 /* 's' */, 0x1A, 0x00 /* (to 0x029A state 319) */, - 0x08, /* fail */ -/* pos 0284: 300 */ 0xF5 /* 'u' -> */, -/* pos 0285: 301 */ 0xF4 /* 't' -> */, -/* pos 0286: 302 */ 0xE8 /* 'h' -> */, -/* pos 0287: 303 */ 0xEF /* 'o' -> */, -/* pos 0288: 304 */ 0xF2 /* 'r' -> */, -/* pos 0289: 305 */ 0xE9 /* 'i' -> */, -/* pos 028a: 306 */ 0xF4 /* 't' -> */, -/* pos 028b: 307 */ 0xF9 /* 'y' -> */, -/* pos 028c: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, -/* pos 028e: 309 */ 0xE5 /* 'e' -> */, -/* pos 028f: 310 */ 0xF4 /* 't' -> */, -/* pos 0290: 311 */ 0xE8 /* 'h' -> */, -/* pos 0291: 312 */ 0xEF /* 'o' -> */, -/* pos 0292: 313 */ 0xE4 /* 'd' -> */, -/* pos 0293: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, -/* pos 0295: 315 */ 0xE1 /* 'a' -> */, -/* pos 0296: 316 */ 0xF4 /* 't' -> */, -/* pos 0297: 317 */ 0xE8 /* 'h' -> */, -/* pos 0298: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, -/* pos 029a: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x02A1 state 320) */, - 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02A7 state 325) */, - 0x08, /* fail */ -/* pos 02a1: 320 */ 0xE8 /* 'h' -> */, -/* pos 02a2: 321 */ 0xE5 /* 'e' -> */, -/* pos 02a3: 322 */ 0xED /* 'm' -> */, -/* pos 02a4: 323 */ 0xE5 /* 'e' -> */, -/* pos 02a5: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, -/* pos 02a7: 325 */ 0xE1 /* 'a' -> */, -/* pos 02a8: 326 */ 0xF4 /* 't' -> */, -/* pos 02a9: 327 */ 0xF5 /* 'u' -> */, -/* pos 02aa: 328 */ 0xF3 /* 's' -> */, -/* pos 02ab: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, -/* pos 02ad: 330 */ 0xE8 /* 'h' -> */, -/* pos 02ae: 331 */ 0xE1 /* 'a' -> */, -/* pos 02af: 332 */ 0xF2 /* 'r' -> */, -/* pos 02b0: 333 */ 0xF3 /* 's' -> */, -/* pos 02b1: 334 */ 0xE5 /* 'e' -> */, -/* pos 02b2: 335 */ 0xF4 /* 't' -> */, -/* pos 02b3: 336 */ 0xBA /* ':' -> */, -/* pos 02b4: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, -/* pos 02b6: 338 */ 0xE1 /* 'a' -> */, -/* pos 02b7: 339 */ 0xEE /* 'n' -> */, -/* pos 02b8: 340 */ 0xE7 /* 'g' -> */, -/* pos 02b9: 341 */ 0xE5 /* 'e' -> */, -/* pos 02ba: 342 */ 0xF3 /* 's' -> */, -/* pos 02bb: 343 */ 0xBA /* ':' -> */, -/* pos 02bc: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, -/* pos 02be: 345 */ 0xEC /* 'l' -> */, -/* pos 02bf: 346 */ 0xEC /* 'l' -> */, -/* pos 02c0: 347 */ 0xEF /* 'o' -> */, -/* pos 02c1: 348 */ 0xF7 /* 'w' -> */, -/* pos 02c2: 349 */ 0xAD /* '-' -> */, -/* pos 02c3: 350 */ 0xEF /* 'o' -> */, -/* pos 02c4: 351 */ 0xF2 /* 'r' -> */, -/* pos 02c5: 352 */ 0xE9 /* 'i' -> */, -/* pos 02c6: 353 */ 0xE7 /* 'g' -> */, -/* pos 02c7: 354 */ 0xE9 /* 'i' -> */, -/* pos 02c8: 355 */ 0xEE /* 'n' -> */, -/* pos 02c9: 356 */ 0xBA /* ':' -> */, -/* pos 02ca: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, -/* pos 02cc: 358 */ 0xE5 /* 'e' -> */, -/* pos 02cd: 359 */ 0xBA /* ':' -> */, -/* pos 02ce: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, -/* pos 02d0: 361 */ 0xEC /* 'l' -> */, -/* pos 02d1: 362 */ 0xEF /* 'o' -> */, -/* pos 02d2: 363 */ 0xF7 /* 'w' -> */, -/* pos 02d3: 364 */ 0xBA /* ':' -> */, -/* pos 02d4: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, -/* pos 02d6: 366 */ 0xE9 /* 'i' -> */, -/* pos 02d7: 367 */ 0xF3 /* 's' -> */, -/* pos 02d8: 368 */ 0xF0 /* 'p' -> */, -/* pos 02d9: 369 */ 0xEF /* 'o' -> */, -/* pos 02da: 370 */ 0xF3 /* 's' -> */, -/* pos 02db: 371 */ 0xE9 /* 'i' -> */, -/* pos 02dc: 372 */ 0xF4 /* 't' -> */, -/* pos 02dd: 373 */ 0xE9 /* 'i' -> */, -/* pos 02de: 374 */ 0xEF /* 'o' -> */, -/* pos 02df: 375 */ 0xEE /* 'n' -> */, -/* pos 02e0: 376 */ 0xBA /* ':' -> */, -/* pos 02e1: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, -/* pos 02e3: 378 */ 0xEE /* 'n' -> */, -/* pos 02e4: 379 */ 0xE3 /* 'c' -> */, -/* pos 02e5: 380 */ 0xEF /* 'o' -> */, -/* pos 02e6: 381 */ 0xE4 /* 'd' -> */, -/* pos 02e7: 382 */ 0xE9 /* 'i' -> */, -/* pos 02e8: 383 */ 0xEE /* 'n' -> */, -/* pos 02e9: 384 */ 0xE7 /* 'g' -> */, -/* pos 02ea: 385 */ 0xBA /* ':' -> */, -/* pos 02eb: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, -/* pos 02ed: 387 */ 0xEE /* 'n' -> */, -/* pos 02ee: 388 */ 0xE7 /* 'g' -> */, -/* pos 02ef: 389 */ 0xF5 /* 'u' -> */, -/* pos 02f0: 390 */ 0xE1 /* 'a' -> */, -/* pos 02f1: 391 */ 0xE7 /* 'g' -> */, -/* pos 02f2: 392 */ 0xE5 /* 'e' -> */, -/* pos 02f3: 393 */ 0xBA /* ':' -> */, -/* pos 02f4: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, -/* pos 02f6: 395 */ 0xE3 /* 'c' -> */, -/* pos 02f7: 396 */ 0xE1 /* 'a' -> */, -/* pos 02f8: 397 */ 0xF4 /* 't' -> */, -/* pos 02f9: 398 */ 0xE9 /* 'i' -> */, -/* pos 02fa: 399 */ 0xEF /* 'o' -> */, -/* pos 02fb: 400 */ 0xEE /* 'n' -> */, -/* pos 02fc: 401 */ 0xBA /* ':' -> */, -/* pos 02fd: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, -/* pos 02ff: 403 */ 0xE1 /* 'a' -> */, -/* pos 0300: 404 */ 0xEE /* 'n' -> */, -/* pos 0301: 405 */ 0xE7 /* 'g' -> */, -/* pos 0302: 406 */ 0xE5 /* 'e' -> */, -/* pos 0303: 407 */ 0xBA /* ':' -> */, -/* pos 0304: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, -/* pos 0306: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x030D state 410) */, - 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x0312 state 414) */, - 0x08, /* fail */ -/* pos 030d: 410 */ 0xE1 /* 'a' -> */, -/* pos 030e: 411 */ 0xE7 /* 'g' -> */, -/* pos 030f: 412 */ 0xBA /* ':' -> */, -/* pos 0310: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, -/* pos 0312: 414 */ 0xF0 /* 'p' -> */, -/* pos 0313: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x031A state 416) */, - 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x031F state 420) */, - 0x08, /* fail */ -/* pos 031a: 416 */ 0xE3 /* 'c' -> */, -/* pos 031b: 417 */ 0xF4 /* 't' -> */, -/* pos 031c: 418 */ 0xBA /* ':' -> */, -/* pos 031d: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, -/* pos 031f: 420 */ 0xF2 /* 'r' -> */, -/* pos 0320: 421 */ 0xE5 /* 'e' -> */, -/* pos 0321: 422 */ 0xF3 /* 's' -> */, -/* pos 0322: 423 */ 0xBA /* ':' -> */, -/* pos 0323: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, -/* pos 0325: 425 */ 0xF2 /* 'r' -> */, -/* pos 0326: 426 */ 0xEF /* 'o' -> */, -/* pos 0327: 427 */ 0xED /* 'm' -> */, -/* pos 0328: 428 */ 0xBA /* ':' -> */, -/* pos 0329: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, -/* pos 032b: 430 */ 0xF4 /* 't' -> */, -/* pos 032c: 431 */ 0xE3 /* 'c' -> */, -/* pos 032d: 432 */ 0xE8 /* 'h' -> */, -/* pos 032e: 433 */ 0xBA /* ':' -> */, -/* pos 032f: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, -/* pos 0331: 435 */ 0xE1 /* 'a' -> */, -/* pos 0332: 436 */ 0xEE /* 'n' -> */, -/* pos 0333: 437 */ 0xE7 /* 'g' -> */, -/* pos 0334: 438 */ 0xE5 /* 'e' -> */, -/* pos 0335: 439 */ 0xBA /* ':' -> */, -/* pos 0336: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, -/* pos 0338: 441 */ 0xEE /* 'n' -> */, -/* pos 0339: 442 */ 0xED /* 'm' -> */, -/* pos 033a: 443 */ 0xEF /* 'o' -> */, -/* pos 033b: 444 */ 0xE4 /* 'd' -> */, -/* pos 033c: 445 */ 0xE9 /* 'i' -> */, -/* pos 033d: 446 */ 0xE6 /* 'f' -> */, -/* pos 033e: 447 */ 0xE9 /* 'i' -> */, -/* pos 033f: 448 */ 0xE5 /* 'e' -> */, -/* pos 0340: 449 */ 0xE4 /* 'd' -> */, -/* pos 0341: 450 */ 0xAD /* '-' -> */, -/* pos 0342: 451 */ 0xF3 /* 's' -> */, -/* pos 0343: 452 */ 0xE9 /* 'i' -> */, -/* pos 0344: 453 */ 0xEE /* 'n' -> */, -/* pos 0345: 454 */ 0xE3 /* 'c' -> */, -/* pos 0346: 455 */ 0xE5 /* 'e' -> */, -/* pos 0347: 456 */ 0xBA /* ':' -> */, -/* pos 0348: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, -/* pos 034a: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x0354 state 459) */, - 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x0362 state 472) */, - 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x0367 state 476) */, - 0x08, /* fail */ -/* pos 0354: 459 */ 0xF3 /* 's' -> */, -/* pos 0355: 460 */ 0xF4 /* 't' -> */, -/* pos 0356: 461 */ 0xAD /* '-' -> */, -/* pos 0357: 462 */ 0xED /* 'm' -> */, -/* pos 0358: 463 */ 0xEF /* 'o' -> */, -/* pos 0359: 464 */ 0xE4 /* 'd' -> */, -/* pos 035a: 465 */ 0xE9 /* 'i' -> */, -/* pos 035b: 466 */ 0xE6 /* 'f' -> */, -/* pos 035c: 467 */ 0xE9 /* 'i' -> */, -/* pos 035d: 468 */ 0xE5 /* 'e' -> */, -/* pos 035e: 469 */ 0xE4 /* 'd' -> */, -/* pos 035f: 470 */ 0xBA /* ':' -> */, -/* pos 0360: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, -/* pos 0362: 472 */ 0xEE /* 'n' -> */, -/* pos 0363: 473 */ 0xEB /* 'k' -> */, -/* pos 0364: 474 */ 0xBA /* ':' -> */, -/* pos 0365: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, -/* pos 0367: 476 */ 0xE3 /* 'c' -> */, -/* pos 0368: 477 */ 0xE1 /* 'a' -> */, -/* pos 0369: 478 */ 0xF4 /* 't' -> */, -/* pos 036a: 479 */ 0xE9 /* 'i' -> */, -/* pos 036b: 480 */ 0xEF /* 'o' -> */, -/* pos 036c: 481 */ 0xEE /* 'n' -> */, -/* pos 036d: 482 */ 0xBA /* ':' -> */, -/* pos 036e: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, -/* pos 0370: 484 */ 0xE1 /* 'a' -> */, -/* pos 0371: 485 */ 0xF8 /* 'x' -> */, -/* pos 0372: 486 */ 0xAD /* '-' -> */, -/* pos 0373: 487 */ 0xE6 /* 'f' -> */, -/* pos 0374: 488 */ 0xEF /* 'o' -> */, -/* pos 0375: 489 */ 0xF2 /* 'r' -> */, -/* pos 0376: 490 */ 0xF7 /* 'w' -> */, -/* pos 0377: 491 */ 0xE1 /* 'a' -> */, -/* pos 0378: 492 */ 0xF2 /* 'r' -> */, -/* pos 0379: 493 */ 0xE4 /* 'd' -> */, -/* pos 037a: 494 */ 0xF3 /* 's' -> */, -/* pos 037b: 495 */ 0xBA /* ':' -> */, -/* pos 037c: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, -/* pos 037e: 497 */ 0xF8 /* 'x' -> */, -/* pos 037f: 498 */ 0xF9 /* 'y' -> */, -/* pos 0380: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x0387 state 500) */, - 0x20 /* ' ' */, 0xBB, 0x00 /* (to 0x043E state 649) */, - 0x08, /* fail */ -/* pos 0387: 500 */ 0xE1 /* 'a' -> */, -/* pos 0388: 501 */ 0xF5 /* 'u' -> */, -/* pos 0389: 502 */ 0xF4 /* 't' -> */, -/* pos 038a: 503 */ 0xE8 /* 'h' -> */, -/* pos 038b: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0392 state 505) */, - 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x039C state 514) */, - 0x08, /* fail */ -/* pos 0392: 505 */ 0xEE /* 'n' -> */, -/* pos 0393: 506 */ 0xF4 /* 't' -> */, -/* pos 0394: 507 */ 0xE9 /* 'i' -> */, -/* pos 0395: 508 */ 0xE3 /* 'c' -> */, -/* pos 0396: 509 */ 0xE1 /* 'a' -> */, -/* pos 0397: 510 */ 0xF4 /* 't' -> */, -/* pos 0398: 511 */ 0xE5 /* 'e' -> */, -/* pos 0399: 512 */ 0xBA /* ':' -> */, -/* pos 039a: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, -/* pos 039c: 514 */ 0xF2 /* 'r' -> */, -/* pos 039d: 515 */ 0xE9 /* 'i' -> */, -/* pos 039e: 516 */ 0xFA /* 'z' -> */, -/* pos 039f: 517 */ 0xE1 /* 'a' -> */, -/* pos 03a0: 518 */ 0xF4 /* 't' -> */, -/* pos 03a1: 519 */ 0xE9 /* 'i' -> */, -/* pos 03a2: 520 */ 0xEF /* 'o' -> */, -/* pos 03a3: 521 */ 0xEE /* 'n' -> */, -/* pos 03a4: 522 */ 0xBA /* ':' -> */, -/* pos 03a5: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, -/* pos 03a7: 524 */ 0xE5 /* 'e' -> */, -/* pos 03a8: 525 */ 0xF3 /* 's' -> */, -/* pos 03a9: 526 */ 0xE8 /* 'h' -> */, -/* pos 03aa: 527 */ 0xBA /* ':' -> */, -/* pos 03ab: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, -/* pos 03ad: 529 */ 0xF2 /* 'r' -> */, -/* pos 03ae: 530 */ 0xF9 /* 'y' -> */, -/* pos 03af: 531 */ 0xAD /* '-' -> */, -/* pos 03b0: 532 */ 0xE1 /* 'a' -> */, -/* pos 03b1: 533 */ 0xE6 /* 'f' -> */, -/* pos 03b2: 534 */ 0xF4 /* 't' -> */, -/* pos 03b3: 535 */ 0xE5 /* 'e' -> */, -/* pos 03b4: 536 */ 0xF2 /* 'r' -> */, -/* pos 03b5: 537 */ 0xBA /* ':' -> */, -/* pos 03b6: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, -/* pos 03b8: 539 */ 0xF6 /* 'v' -> */, -/* pos 03b9: 540 */ 0xE5 /* 'e' -> */, -/* pos 03ba: 541 */ 0xF2 /* 'r' -> */, -/* pos 03bb: 542 */ 0xBA /* ':' -> */, -/* pos 03bc: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, -/* pos 03be: 544 */ 0xAD /* '-' -> */, -/* pos 03bf: 545 */ 0xE3 /* 'c' -> */, -/* pos 03c0: 546 */ 0xEF /* 'o' -> */, -/* pos 03c1: 547 */ 0xEF /* 'o' -> */, -/* pos 03c2: 548 */ 0xEB /* 'k' -> */, -/* pos 03c3: 549 */ 0xE9 /* 'i' -> */, -/* pos 03c4: 550 */ 0xE5 /* 'e' -> */, -/* pos 03c5: 551 */ 0xBA /* ':' -> */, -/* pos 03c6: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, -/* pos 03c8: 553 */ 0xF2 /* 'r' -> */, -/* pos 03c9: 554 */ 0xE9 /* 'i' -> */, -/* pos 03ca: 555 */ 0xE3 /* 'c' -> */, -/* pos 03cb: 556 */ 0xF4 /* 't' -> */, -/* pos 03cc: 557 */ 0xAD /* '-' -> */, -/* pos 03cd: 558 */ 0xF4 /* 't' -> */, -/* pos 03ce: 559 */ 0xF2 /* 'r' -> */, -/* pos 03cf: 560 */ 0xE1 /* 'a' -> */, -/* pos 03d0: 561 */ 0xEE /* 'n' -> */, -/* pos 03d1: 562 */ 0xF3 /* 's' -> */, -/* pos 03d2: 563 */ 0xF0 /* 'p' -> */, -/* pos 03d3: 564 */ 0xEF /* 'o' -> */, -/* pos 03d4: 565 */ 0xF2 /* 'r' -> */, -/* pos 03d5: 566 */ 0xF4 /* 't' -> */, -/* pos 03d6: 567 */ 0xAD /* '-' -> */, -/* pos 03d7: 568 */ 0xF3 /* 's' -> */, -/* pos 03d8: 569 */ 0xE5 /* 'e' -> */, -/* pos 03d9: 570 */ 0xE3 /* 'c' -> */, -/* pos 03da: 571 */ 0xF5 /* 'u' -> */, -/* pos 03db: 572 */ 0xF2 /* 'r' -> */, -/* pos 03dc: 573 */ 0xE9 /* 'i' -> */, -/* pos 03dd: 574 */ 0xF4 /* 't' -> */, -/* pos 03de: 575 */ 0xF9 /* 'y' -> */, -/* pos 03df: 576 */ 0xBA /* ':' -> */, -/* pos 03e0: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, -/* pos 03e2: 578 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x03E9 state 579) */, - 0x65 /* 'e' */, 0x84, 0x00 /* (to 0x0469 state 680) */, - 0x08, /* fail */ -/* pos 03e9: 579 */ 0xE1 /* 'a' -> */, -/* pos 03ea: 580 */ 0xEE /* 'n' -> */, -/* pos 03eb: 581 */ 0xF3 /* 's' -> */, -/* pos 03ec: 582 */ 0xE6 /* 'f' -> */, -/* pos 03ed: 583 */ 0xE5 /* 'e' -> */, -/* pos 03ee: 584 */ 0xF2 /* 'r' -> */, -/* pos 03ef: 585 */ 0xAD /* '-' -> */, -/* pos 03f0: 586 */ 0xE5 /* 'e' -> */, -/* pos 03f1: 587 */ 0xEE /* 'n' -> */, -/* pos 03f2: 588 */ 0xE3 /* 'c' -> */, -/* pos 03f3: 589 */ 0xEF /* 'o' -> */, -/* pos 03f4: 590 */ 0xE4 /* 'd' -> */, -/* pos 03f5: 591 */ 0xE9 /* 'i' -> */, -/* pos 03f6: 592 */ 0xEE /* 'n' -> */, -/* pos 03f7: 593 */ 0xE7 /* 'g' -> */, -/* pos 03f8: 594 */ 0xBA /* ':' -> */, -/* pos 03f9: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, -/* pos 03fb: 596 */ 0xE5 /* 'e' -> */, -/* pos 03fc: 597 */ 0xF2 /* 'r' -> */, -/* pos 03fd: 598 */ 0xAD /* '-' -> */, -/* pos 03fe: 599 */ 0xE1 /* 'a' -> */, -/* pos 03ff: 600 */ 0xE7 /* 'g' -> */, -/* pos 0400: 601 */ 0xE5 /* 'e' -> */, -/* pos 0401: 602 */ 0xEE /* 'n' -> */, -/* pos 0402: 603 */ 0xF4 /* 't' -> */, -/* pos 0403: 604 */ 0xBA /* ':' -> */, -/* pos 0404: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, -/* pos 0406: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x040D state 607) */, - 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0412 state 611) */, - 0x08, /* fail */ -/* pos 040d: 607 */ 0xF2 /* 'r' -> */, -/* pos 040e: 608 */ 0xF9 /* 'y' -> */, -/* pos 040f: 609 */ 0xBA /* ':' -> */, -/* pos 0410: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, -/* pos 0412: 611 */ 0xE1 /* 'a' -> */, -/* pos 0413: 612 */ 0xBA /* ':' -> */, -/* pos 0414: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, -/* pos 0416: 614 */ 0xF7 /* 'w' -> */, -/* pos 0417: 615 */ 0xF7 /* 'w' -> */, -/* pos 0418: 616 */ 0xAD /* '-' -> */, -/* pos 0419: 617 */ 0xE1 /* 'a' -> */, -/* pos 041a: 618 */ 0xF5 /* 'u' -> */, -/* pos 041b: 619 */ 0xF4 /* 't' -> */, -/* pos 041c: 620 */ 0xE8 /* 'h' -> */, -/* pos 041d: 621 */ 0xE5 /* 'e' -> */, -/* pos 041e: 622 */ 0xEE /* 'n' -> */, -/* pos 041f: 623 */ 0xF4 /* 't' -> */, -/* pos 0420: 624 */ 0xE9 /* 'i' -> */, -/* pos 0421: 625 */ 0xE3 /* 'c' -> */, -/* pos 0422: 626 */ 0xE1 /* 'a' -> */, -/* pos 0423: 627 */ 0xF4 /* 't' -> */, -/* pos 0424: 628 */ 0xE5 /* 'e' -> */, -/* pos 0425: 629 */ 0xBA /* ':' -> */, -/* pos 0426: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, -/* pos 0428: 631 */ 0xF4 /* 't' -> */, -/* pos 0429: 632 */ 0xE3 /* 'c' -> */, -/* pos 042a: 633 */ 0xE8 /* 'h' -> */, -/* pos 042b: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, -/* pos 042d: 635 */ 0xF4 /* 't' -> */, -/* pos 042e: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, -/* pos 0430: 637 */ 0xEC /* 'l' -> */, -/* pos 0431: 638 */ 0xE5 /* 'e' -> */, -/* pos 0432: 639 */ 0xF4 /* 't' -> */, -/* pos 0433: 640 */ 0xE5 /* 'e' -> */, -/* pos 0434: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, -/* pos 0436: 642 */ 0xE9 /* 'i' -> */, -/* pos 0437: 643 */ 0xAD /* '-' -> */, -/* pos 0438: 644 */ 0xE1 /* 'a' -> */, -/* pos 0439: 645 */ 0xF2 /* 'r' -> */, -/* pos 043a: 646 */ 0xE7 /* 'g' -> */, -/* pos 043b: 647 */ 0xF3 /* 's' -> */, -/* pos 043c: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, -/* pos 043e: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, -/* pos 0440: 650 */ 0xAD /* '-' -> */, -/* pos 0441: 651 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x0448 state 652) */, - 0x66 /* 'f' */, 0x10, 0x00 /* (to 0x0454 state 662) */, - 0x08, /* fail */ -/* pos 0448: 652 */ 0xE5 /* 'e' -> */, -/* pos 0449: 653 */ 0xE1 /* 'a' -> */, -/* pos 044a: 654 */ 0xEC /* 'l' -> */, -/* pos 044b: 655 */ 0xAD /* '-' -> */, -/* pos 044c: 656 */ 0xE9 /* 'i' -> */, -/* pos 044d: 657 */ 0xF0 /* 'p' -> */, -/* pos 044e: 658 */ 0xBA /* ':' -> */, -/* pos 044f: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, -/* pos 0451: 660 */ 0xA0 /* ' ' -> */, -/* pos 0452: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, -/* pos 0454: 662 */ 0xEF /* 'o' -> */, -/* pos 0455: 663 */ 0xF2 /* 'r' -> */, -/* pos 0456: 664 */ 0xF7 /* 'w' -> */, -/* pos 0457: 665 */ 0xE1 /* 'a' -> */, -/* pos 0458: 666 */ 0xF2 /* 'r' -> */, -/* pos 0459: 667 */ 0xE4 /* 'd' -> */, -/* pos 045a: 668 */ 0xE5 /* 'e' -> */, -/* pos 045b: 669 */ 0xE4 /* 'd' -> */, -/* pos 045c: 670 */ 0xAD /* '-' -> */, -/* pos 045d: 671 */ 0xE6 /* 'f' -> */, -/* pos 045e: 672 */ 0xEF /* 'o' -> */, -/* pos 045f: 673 */ 0xF2 /* 'r' -> */, -/* pos 0460: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, -/* pos 0462: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, -/* pos 0464: 676 */ 0xE1 /* 'a' -> */, -/* pos 0465: 677 */ 0xE4 /* 'd' -> */, -/* pos 0466: 678 */ 0xA0 /* ' ' -> */, -/* pos 0467: 679 */ 0x00, 0x52 /* - terminal marker 82 - */, -/* pos 0469: 680 */ 0xBA /* ':' -> */, -/* pos 046a: 681 */ 0x00, 0x53 /* - terminal marker 83 - */, -/* total size 1132 bytes */ diff --git a/thirdparty/lws/mbedtls_verify.diff b/thirdparty/lws/mbedtls_verify.diff deleted file mode 100644 index d320645d67..0000000000 --- a/thirdparty/lws/mbedtls_verify.diff +++ /dev/null @@ -1,74 +0,0 @@ -diff --git a/thirdparty/lws/client/ssl-client.c b/thirdparty/lws/client/ssl-client.c -index 6626e0844..962c6e3cb 100644 ---- a/thirdparty/lws/client/ssl-client.c -+++ b/thirdparty/lws/client/ssl-client.c -@@ -176,11 +176,7 @@ lws_ssl_client_bio_create(struct lws *wsi) - #endif - #else - #if defined(LWS_WITH_MBEDTLS) -- if (wsi->vhost->x509_client_CA) -- SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); -- else -- SSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, OpenSSL_client_verify_callback); -- -+ SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); - #else - #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - SSL_set_tlsext_host_name(wsi->ssl, hostname); -diff --git a/thirdparty/lws/mbedtls_wrapper/platform/ssl_pm.c b/thirdparty/lws/mbedtls_wrapper/platform/ssl_pm.c -index 63504919c..4e3d61109 100644 ---- a/thirdparty/lws/mbedtls_wrapper/platform/ssl_pm.c -+++ b/thirdparty/lws/mbedtls_wrapper/platform/ssl_pm.c -@@ -218,7 +218,7 @@ static int ssl_pm_reload_crt(SSL *ssl) - struct x509_pm *crt_pm = (struct x509_pm *)ssl->cert->x509->x509_pm; - - if (ssl->verify_mode == SSL_VERIFY_PEER) -- mode = MBEDTLS_SSL_VERIFY_REQUIRED; -+ mode = MBEDTLS_SSL_VERIFY_OPTIONAL; - else if (ssl->verify_mode == SSL_VERIFY_FAIL_IF_NO_PEER_CERT) - mode = MBEDTLS_SSL_VERIFY_OPTIONAL; - else if (ssl->verify_mode == SSL_VERIFY_CLIENT_ONCE) -@@ -712,11 +712,39 @@ long ssl_pm_get_verify_result(const SSL *ssl) - struct ssl_pm *ssl_pm = (struct ssl_pm *)ssl->ssl_pm; - - ret = mbedtls_ssl_get_verify_result(&ssl_pm->ssl); -- if (ret) { -- SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_get_verify_result() return 0x%x", ret); -+ -+ if (!ret) -+ return X509_V_OK; -+ -+ if (ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED || -+ (ret & MBEDTLS_X509_BADCRL_NOT_TRUSTED)) -+ // Allows us to use LCCSCF_ALLOW_SELFSIGNED to skip verification -+ verify_result = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN; -+ -+ else if (ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) -+ verify_result = X509_V_ERR_HOSTNAME_MISMATCH; -+ -+ else if ((ret & MBEDTLS_X509_BADCERT_BAD_KEY) || -+ (ret & MBEDTLS_X509_BADCRL_BAD_KEY)) -+ verify_result = X509_V_ERR_CA_KEY_TOO_SMALL; -+ -+ else if ((ret & MBEDTLS_X509_BADCERT_BAD_MD) || -+ (ret & MBEDTLS_X509_BADCRL_BAD_MD)) -+ verify_result = X509_V_ERR_CA_MD_TOO_WEAK; -+ -+ else if ((ret & MBEDTLS_X509_BADCERT_FUTURE) || -+ (ret & MBEDTLS_X509_BADCRL_FUTURE)) -+ verify_result = X509_V_ERR_CERT_NOT_YET_VALID; -+ -+ else if ((ret & MBEDTLS_X509_BADCERT_EXPIRED) || -+ (ret & MBEDTLS_X509_BADCRL_EXPIRED)) -+ verify_result = X509_V_ERR_CERT_HAS_EXPIRED; -+ -+ else - verify_result = X509_V_ERR_UNSPECIFIED; -- } else -- verify_result = X509_V_OK; -+ -+ SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, -+ "mbedtls_ssl_get_verify_result() return 0x%x", ret); - - return verify_result; - } diff --git a/thirdparty/lws/misc/lejp.h b/thirdparty/lws/misc/lejp.h deleted file mode 100644 index 0b37bb3e42..0000000000 --- a/thirdparty/lws/misc/lejp.h +++ /dev/null @@ -1,232 +0,0 @@ -#include "libwebsockets.h" -struct lejp_ctx; - -#ifndef ARRAY_SIZE -#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0])) -#endif -#define LEJP_FLAG_WS_KEEP 64 -#define LEJP_FLAG_WS_COMMENTLINE 32 - -enum lejp_states { - LEJP_IDLE = 0, - LEJP_MEMBERS = 1, - LEJP_M_P = 2, - LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3, - LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4, - LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5, - LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6, - LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7, - LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8, - LEJP_MP_DELIM = 9, - LEJP_MP_VALUE = 10, - LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11, - LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12, - LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13, - LEJP_MP_COMMA_OR_END = 14, - LEJP_MP_ARRAY_END = 15, -}; - -enum lejp_reasons { - LEJP_CONTINUE = -1, - LEJP_REJECT_IDLE_NO_BRACE = -2, - LEJP_REJECT_MEMBERS_NO_CLOSE = -3, - LEJP_REJECT_MP_NO_OPEN_QUOTE = -4, - LEJP_REJECT_MP_STRING_UNDERRUN = -5, - LEJP_REJECT_MP_ILLEGAL_CTRL = -6, - LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7, - LEJP_REJECT_ILLEGAL_HEX = -8, - LEJP_REJECT_MP_DELIM_MISSING_COLON = -9, - LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10, - LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11, - LEJP_REJECT_MP_VAL_NUM_FORMAT = -12, - LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13, - LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14, - LEJP_REJECT_MP_C_OR_E_UNDERF = -15, - LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16, - LEJP_REJECT_MP_ARRAY_END_MISSING = -17, - LEJP_REJECT_STACK_OVERFLOW = -18, - LEJP_REJECT_MP_DELIM_ISTACK = -19, - LEJP_REJECT_NUM_TOO_LONG = -20, - LEJP_REJECT_MP_C_OR_E_NEITHER = -21, - LEJP_REJECT_UNKNOWN = -22, - LEJP_REJECT_CALLBACK = -23 -}; - -#define LEJP_FLAG_CB_IS_VALUE 64 - -enum lejp_callbacks { - LEJPCB_CONSTRUCTED = 0, - LEJPCB_DESTRUCTED = 1, - - LEJPCB_START = 2, - LEJPCB_COMPLETE = 3, - LEJPCB_FAILED = 4, - - LEJPCB_PAIR_NAME = 5, - - LEJPCB_VAL_TRUE = LEJP_FLAG_CB_IS_VALUE | 6, - LEJPCB_VAL_FALSE = LEJP_FLAG_CB_IS_VALUE | 7, - LEJPCB_VAL_NULL = LEJP_FLAG_CB_IS_VALUE | 8, - LEJPCB_VAL_NUM_INT = LEJP_FLAG_CB_IS_VALUE | 9, - LEJPCB_VAL_NUM_FLOAT = LEJP_FLAG_CB_IS_VALUE | 10, - LEJPCB_VAL_STR_START = 11, /* notice handle separately */ - LEJPCB_VAL_STR_CHUNK = LEJP_FLAG_CB_IS_VALUE | 12, - LEJPCB_VAL_STR_END = LEJP_FLAG_CB_IS_VALUE | 13, - - LEJPCB_ARRAY_START = 14, - LEJPCB_ARRAY_END = 15, - - LEJPCB_OBJECT_START = 16, - LEJPCB_OBJECT_END = 17 -}; - -/** - * _lejp_callback() - User parser actions - * \param ctx: LEJP context - * \param reason: Callback reason - * - * Your user callback is associated with the context at construction time, - * and receives calls as the parsing progresses. - * - * All of the callbacks may be ignored and just return 0. - * - * The reasons it might get called, found in @reason, are: - * - * LEJPCB_CONSTRUCTED: The context was just constructed... you might want to - * perform one-time allocation for the life of the context. - * - * LEJPCB_DESTRUCTED: The context is being destructed... if you made any - * allocations at construction-time, you can free them now - * - * LEJPCB_START: Parsing is beginning at the first byte of input - * - * LEJPCB_COMPLETE: Parsing has completed successfully. You'll get a 0 or - * positive return code from lejp_parse indicating the - * amount of unused bytes left in the input buffer - * - * LEJPCB_FAILED: Parsing failed. You'll get a negative error code - * returned from lejp_parse - * - * LEJPCB_PAIR_NAME: When a "name":"value" pair has had the name parsed, - * this callback occurs. You can find the new name at - * the end of ctx->path[] - * - * LEJPCB_VAL_TRUE: The "true" value appeared - * - * LEJPCB_VAL_FALSE: The "false" value appeared - * - * LEJPCB_VAL_NULL: The "null" value appeared - * - * LEJPCB_VAL_NUM_INT: A string representing an integer is in ctx->buf - * - * LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf - * - * LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet - * - * LEJPCB_VAL_STR_CHUNK: We parsed LEJP_STRING_CHUNK -1 bytes of string data in - * ctx->buf, which is as much as we can buffer, so we are - * spilling it. If all your strings are less than - * LEJP_STRING_CHUNK - 1 bytes, you will never see this - * callback. - * - * LEJPCB_VAL_STR_END: String parsing has completed, the last chunk of the - * string is in ctx->buf. - * - * LEJPCB_ARRAY_START: An array started - * - * LEJPCB_ARRAY_END: An array ended - * - * LEJPCB_OBJECT_START: An object started - * - * LEJPCB_OBJECT_END: An object ended - */ -LWS_EXTERN signed char _lejp_callback(struct lejp_ctx *ctx, char reason); - -typedef signed char (*lejp_callback)(struct lejp_ctx *ctx, char reason); - -#ifndef LEJP_MAX_DEPTH -#define LEJP_MAX_DEPTH 12 -#endif -#ifndef LEJP_MAX_INDEX_DEPTH -#define LEJP_MAX_INDEX_DEPTH 5 -#endif -#ifndef LEJP_MAX_PATH -#define LEJP_MAX_PATH 128 -#endif -#ifndef LEJP_STRING_CHUNK -/* must be >= 30 to assemble floats */ -#define LEJP_STRING_CHUNK 255 -#endif - -enum num_flags { - LEJP_SEEN_MINUS = (1 << 0), - LEJP_SEEN_POINT = (1 << 1), - LEJP_SEEN_POST_POINT = (1 << 2), - LEJP_SEEN_EXP = (1 << 3) -}; - -struct _lejp_stack { - char s; /* lejp_state stack*/ - char p; /* path length */ - char i; /* index array length */ - char b; /* user bitfield */ -}; - -struct lejp_ctx { - - /* sorted by type for most compact alignment - * - * pointers - */ - - signed char (*callback)(struct lejp_ctx *ctx, char reason); - void *user; - const char * const *paths; - - /* arrays */ - - struct _lejp_stack st[LEJP_MAX_DEPTH]; - unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */ - unsigned short wild[LEJP_MAX_INDEX_DEPTH]; /* index array */ - char path[LEJP_MAX_PATH]; - char buf[LEJP_STRING_CHUNK]; - - /* int */ - - unsigned int line; - - /* short */ - - unsigned short uni; - - /* char */ - - unsigned char npos; - unsigned char dcount; - unsigned char f; - unsigned char sp; /* stack head */ - unsigned char ipos; /* index stack depth */ - unsigned char ppos; - unsigned char count_paths; - unsigned char path_match; - unsigned char path_match_len; - unsigned char wildcount; -}; - -LWS_VISIBLE LWS_EXTERN void -lejp_construct(struct lejp_ctx *ctx, - signed char (*callback)(struct lejp_ctx *ctx, char reason), - void *user, const char * const *paths, unsigned char paths_count); - -LWS_VISIBLE LWS_EXTERN void -lejp_destruct(struct lejp_ctx *ctx); - -LWS_VISIBLE LWS_EXTERN int -lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len); - -LWS_VISIBLE LWS_EXTERN void -lejp_change_callback(struct lejp_ctx *ctx, - signed char (*callback)(struct lejp_ctx *ctx, char reason)); - -LWS_VISIBLE LWS_EXTERN int -lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len); diff --git a/thirdparty/lws/output.c b/thirdparty/lws/output.c deleted file mode 100644 index 375ff3ef99..0000000000 --- a/thirdparty/lws/output.c +++ /dev/null @@ -1,883 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -static int -lws_0405_frame_mask_generate(struct lws *wsi) -{ -#if 0 - wsi->u.ws.mask[0] = 0; - wsi->u.ws.mask[1] = 0; - wsi->u.ws.mask[2] = 0; - wsi->u.ws.mask[3] = 0; -#else - int n; - /* fetch the per-frame nonce */ - - n = lws_get_random(lws_get_context(wsi), wsi->u.ws.mask, 4); - if (n != 4) { - lwsl_parser("Unable to read from random device %s %d\n", - SYSTEM_RANDOM_FILEPATH, n); - return 1; - } -#endif - /* start masking from first byte of masking key buffer */ - wsi->u.ws.mask_idx = 0; - - return 0; -} - -/* - * notice this returns number of bytes consumed, or -1 - */ -int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) -{ - struct lws_context *context = lws_get_context(wsi); - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - size_t real_len = len; - unsigned int n; - int m; - - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_WRITE, 1); - - if (!len) - return 0; - /* just ignore sends after we cleared the truncation buffer */ - if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && - !wsi->trunc_len) - return len; - - if (wsi->trunc_len && (buf < wsi->trunc_alloc || - buf > (wsi->trunc_alloc + wsi->trunc_len + wsi->trunc_offset))) { - char dump[20]; - strncpy(dump, (char *)buf, sizeof(dump) - 1); - dump[sizeof(dump) - 1] = '\0'; -#if defined(LWS_WITH_ESP8266) - lwsl_err("****** %p: Sending new %lu (%s), pending truncated ...\n", - wsi, (unsigned long)len, dump); -#else - lwsl_err("****** %p: Sending new %lu (%s), pending truncated ...\n" - " It's illegal to do an lws_write outside of\n" - " the writable callback: fix your code\n", - wsi, (unsigned long)len, dump); -#endif - assert(0); - - return -1; - } - - m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_DO_SEND, &buf, len); - if (m < 0) - return -1; - if (m) /* handled */ { - n = m; - goto handle_truncated_send; - } - - if (!wsi->http2_substream && !lws_socket_is_valid(wsi->desc.sockfd)) - lwsl_warn("** error invalid sock but expected to send\n"); - - /* limit sending */ - if (wsi->protocol->tx_packet_size) - n = wsi->protocol->tx_packet_size; - else { - n = wsi->protocol->rx_buffer_size; - if (!n) - n = context->pt_serv_buf_size; - } - n += LWS_PRE + 4; - if (n > len) - n = len; -#if defined(LWS_WITH_ESP8266) - if (wsi->pending_send_completion) { - n = 0; - goto handle_truncated_send; - } -#endif - - /* nope, send it on the socket directly */ - lws_latency_pre(context, wsi); - n = lws_ssl_capable_write(wsi, buf, n); - lws_latency(context, wsi, "send lws_issue_raw", n, n == len); - - switch (n) { - case LWS_SSL_CAPABLE_ERROR: - /* we're going to close, let close know sends aren't possible */ - wsi->socket_is_permanently_unusable = 1; - return -1; - case LWS_SSL_CAPABLE_MORE_SERVICE: - /* nothing got sent, not fatal, retry the whole thing later */ - n = 0; - break; - } - -handle_truncated_send: - /* - * we were already handling a truncated send? - */ - if (wsi->trunc_len) { - lwsl_info("%p partial adv %d (vs %ld)\n", wsi, n, (long)real_len); - wsi->trunc_offset += n; - wsi->trunc_len -= n; - - if (!wsi->trunc_len) { - lwsl_info("***** %p partial send completed\n", wsi); - /* done with it, but don't free it */ - n = real_len; - if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { - lwsl_info("***** %p signalling to close now\n", wsi); - return -1; /* retry closing now */ - } - } - /* always callback on writeable */ - lws_callback_on_writable(wsi); - - return n; - } - - if ((unsigned int)n == real_len) - /* what we just sent went out cleanly */ - return n; - - /* - * Newly truncated send. Buffer the remainder (it will get - * first priority next time the socket is writable) - */ - lwsl_debug("%p new partial sent %d from %lu total\n", wsi, n, - (unsigned long)real_len); - - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITE_PARTIALS, 1); - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, n); - - /* - * - if we still have a suitable malloc lying around, use it - * - or, if too small, reallocate it - * - or, if no buffer, create it - */ - if (!wsi->trunc_alloc || real_len - n > wsi->trunc_alloc_len) { - lws_free(wsi->trunc_alloc); - - wsi->trunc_alloc_len = real_len - n; - wsi->trunc_alloc = lws_malloc(real_len - n, "truncated send alloc"); - if (!wsi->trunc_alloc) { - lwsl_err("truncated send: unable to malloc %lu\n", - (unsigned long)(real_len - n)); - return -1; - } - } - wsi->trunc_offset = 0; - wsi->trunc_len = real_len - n; - memcpy(wsi->trunc_alloc, buf + n, real_len - n); - - /* since something buffered, force it to get another chance to send */ - lws_callback_on_writable(wsi); - - return real_len; -} - -LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, - enum lws_write_protocol wp) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - int masked7 = (wsi->mode == LWSCM_WS_CLIENT); - unsigned char is_masked_bit = 0; - unsigned char *dropmask = NULL; - struct lws_tokens eff_buf; - size_t orig_len = len; - int pre = 0, n; - - if (wsi->parent_carries_io) { - struct lws_write_passthru pas; - - pas.buf = buf; - pas.len = len; - pas.wp = wp; - pas.wsi = wsi; - - if (wsi->parent->protocol->callback(wsi->parent, - LWS_CALLBACK_CHILD_WRITE_VIA_PARENT, - wsi->parent->user_space, - (void *)&pas, 0)) - return 1; - - return len; - } - - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_LWS_WRITE, 1); - - if ((int)len < 0) { - lwsl_err("%s: suspicious len int %d, ulong %lu\n", __func__, - (int)len, (unsigned long)len); - return -1; - } - - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_WRITE, len); - -#ifdef LWS_WITH_ACCESS_LOG - wsi->access_log.sent += len; -#endif - if (wsi->vhost) - wsi->vhost->conn_stats.tx += len; - - if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) { - /* remove us from the list */ - struct lws **w = &pt->tx_draining_ext_list; - - wsi->u.ws.tx_draining_ext = 0; - /* remove us from context draining ext list */ - while (*w) { - if (*w == wsi) { - *w = wsi->u.ws.tx_draining_ext_list; - break; - } - w = &((*w)->u.ws.tx_draining_ext_list); - } - wsi->u.ws.tx_draining_ext_list = NULL; - wp = (wsi->u.ws.tx_draining_stashed_wp & 0xc0) | - LWS_WRITE_CONTINUATION; - - lwsl_ext("FORCED draining wp to 0x%02X\n", wp); - } - - lws_restart_ws_ping_pong_timer(wsi); - - if ((wp & 0x1f) == LWS_WRITE_HTTP || - (wp & 0x1f) == LWS_WRITE_HTTP_FINAL || - (wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION || - (wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) - goto send_raw; - - /* if not in a state to send stuff, then just send nothing */ - - if (wsi->state != LWSS_ESTABLISHED && - ((wsi->state != LWSS_RETURNED_CLOSE_ALREADY && - wsi->state != LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION && - wsi->state != LWSS_AWAITING_CLOSE_ACK) || - wp != LWS_WRITE_CLOSE)) { - lwsl_debug("binning\n"); - return 0; - } - - /* if we are continuing a frame that already had its header done */ - - if (wsi->u.ws.inside_frame) { - lwsl_debug("INSIDE FRAME\n"); - goto do_more_inside_frame; - } - - wsi->u.ws.clean_buffer = 1; - - /* - * give a chance to the extensions to modify payload - * the extension may decide to produce unlimited payload erratically - * (eg, compression extension), so we require only that if he produces - * something, it will be a complete fragment of the length known at - * the time (just the fragment length known), and if he has - * more we will come back next time he is writeable and allow him to - * produce more fragments until he's drained. - * - * This allows what is sent each time it is writeable to be limited to - * a size that can be sent without partial sends or blocking, allows - * interleaving of control frames and other connection service. - */ - eff_buf.token = (char *)buf; - eff_buf.token_len = len; - - switch ((int)wp) { - case LWS_WRITE_PING: - case LWS_WRITE_PONG: - case LWS_WRITE_CLOSE: - break; - default: - lwsl_debug("LWS_EXT_CB_PAYLOAD_TX\n"); - n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &eff_buf, wp); - if (n < 0) - return -1; - - if (n && eff_buf.token_len) { - lwsl_debug("drain len %d\n", (int)eff_buf.token_len); - /* extension requires further draining */ - wsi->u.ws.tx_draining_ext = 1; - wsi->u.ws.tx_draining_ext_list = pt->tx_draining_ext_list; - pt->tx_draining_ext_list = wsi; - /* we must come back to do more */ - lws_callback_on_writable(wsi); - /* - * keep a copy of the write type for the overall - * action that has provoked generation of these - * fragments, so the last guy can use its FIN state. - */ - wsi->u.ws.tx_draining_stashed_wp = wp; - /* this is definitely not actually the last fragment - * because the extension asserted he has more coming - * So make sure this intermediate one doesn't go out - * with a FIN. - */ - wp |= LWS_WRITE_NO_FIN; - } - - if (eff_buf.token_len && wsi->u.ws.stashed_write_pending) { - wsi->u.ws.stashed_write_pending = 0; - wp = (wp &0xc0) | (int)wsi->u.ws.stashed_write_type; - } - } - - /* - * an extension did something we need to keep... for example, if - * compression extension, it has already updated its state according - * to this being issued - */ - if ((char *)buf != eff_buf.token) { - /* - * ext might eat it, but not have anything to issue yet. - * In that case we have to follow his lead, but stash and - * replace the write type that was lost here the first time. - */ - if (len && !eff_buf.token_len) { - if (!wsi->u.ws.stashed_write_pending) - wsi->u.ws.stashed_write_type = (char)wp & 0x3f; - wsi->u.ws.stashed_write_pending = 1; - return len; - } - /* - * extension recreated it: - * need to buffer this if not all sent - */ - wsi->u.ws.clean_buffer = 0; - } - - buf = (unsigned char *)eff_buf.token; - len = eff_buf.token_len; - - if (!buf) { - lwsl_err("null buf (%d)\n", (int)len); - return -1; - } - - switch (wsi->ietf_spec_revision) { - case 13: - if (masked7) { - pre += 4; - dropmask = &buf[0 - pre]; - is_masked_bit = 0x80; - } - - switch (wp & 0xf) { - case LWS_WRITE_TEXT: - n = LWSWSOPC_TEXT_FRAME; - break; - case LWS_WRITE_BINARY: - n = LWSWSOPC_BINARY_FRAME; - break; - case LWS_WRITE_CONTINUATION: - n = LWSWSOPC_CONTINUATION; - break; - - case LWS_WRITE_CLOSE: - n = LWSWSOPC_CLOSE; - break; - case LWS_WRITE_PING: - n = LWSWSOPC_PING; - break; - case LWS_WRITE_PONG: - n = LWSWSOPC_PONG; - break; - default: - lwsl_warn("lws_write: unknown write opc / wp\n"); - return -1; - } - - if (!(wp & LWS_WRITE_NO_FIN)) - n |= 1 << 7; - - if (len < 126) { - pre += 2; - buf[-pre] = n; - buf[-pre + 1] = (unsigned char)(len | is_masked_bit); - } else { - if (len < 65536) { - pre += 4; - buf[-pre] = n; - buf[-pre + 1] = 126 | is_masked_bit; - buf[-pre + 2] = (unsigned char)(len >> 8); - buf[-pre + 3] = (unsigned char)len; - } else { - pre += 10; - buf[-pre] = n; - buf[-pre + 1] = 127 | is_masked_bit; -#if defined __LP64__ - buf[-pre + 2] = (len >> 56) & 0x7f; - buf[-pre + 3] = len >> 48; - buf[-pre + 4] = len >> 40; - buf[-pre + 5] = len >> 32; -#else - buf[-pre + 2] = 0; - buf[-pre + 3] = 0; - buf[-pre + 4] = 0; - buf[-pre + 5] = 0; -#endif - buf[-pre + 6] = (unsigned char)(len >> 24); - buf[-pre + 7] = (unsigned char)(len >> 16); - buf[-pre + 8] = (unsigned char)(len >> 8); - buf[-pre + 9] = (unsigned char)len; - } - } - break; - } - -do_more_inside_frame: - - /* - * Deal with masking if we are in client -> server direction and - * the wp demands it - */ - - if (masked7) { - if (!wsi->u.ws.inside_frame) - if (lws_0405_frame_mask_generate(wsi)) { - lwsl_err("frame mask generation failed\n"); - return -1; - } - - /* - * in v7, just mask the payload - */ - if (dropmask) { /* never set if already inside frame */ - for (n = 4; n < (int)len + 4; n++) - dropmask[n] = dropmask[n] ^ wsi->u.ws.mask[ - (wsi->u.ws.mask_idx++) & 3]; - - /* copy the frame nonce into place */ - memcpy(dropmask, wsi->u.ws.mask, 4); - } - } - -send_raw: - switch ((int)(wp & 0x1f)) { - case LWS_WRITE_CLOSE: -/* lwsl_hexdump(&buf[-pre], len); */ - case LWS_WRITE_HTTP: - case LWS_WRITE_HTTP_FINAL: - case LWS_WRITE_HTTP_HEADERS: - case LWS_WRITE_HTTP_HEADERS_CONTINUATION: - case LWS_WRITE_PONG: - case LWS_WRITE_PING: -#ifdef LWS_WITH_HTTP2 - if (wsi->mode == LWSCM_HTTP2_SERVING) { - unsigned char flags = 0; - - n = LWS_H2_FRAME_TYPE_DATA; - if ((wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) { - n = LWS_H2_FRAME_TYPE_HEADERS; - if (!(wp & LWS_WRITE_NO_FIN)) - flags = LWS_H2_FLAG_END_HEADERS; - if (wsi->u.h2.send_END_STREAM || (wp & LWS_WRITE_H2_STREAM_END)) { - flags |= LWS_H2_FLAG_END_STREAM; - wsi->u.h2.send_END_STREAM = 1; - } - } - - if ((wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION) { - n = LWS_H2_FRAME_TYPE_CONTINUATION; - if (!(wp & LWS_WRITE_NO_FIN)) - flags = LWS_H2_FLAG_END_HEADERS; - if (wsi->u.h2.send_END_STREAM || (wp & LWS_WRITE_H2_STREAM_END)) { - flags |= LWS_H2_FLAG_END_STREAM; - wsi->u.h2.send_END_STREAM = 1; - } - } - - if (((wp & 0x1f) == LWS_WRITE_HTTP || - (wp & 0x1f) == LWS_WRITE_HTTP_FINAL) && - wsi->u.http.tx_content_length) { - wsi->u.http.tx_content_remain -= len; - lwsl_info("%s: wsi %p: tx_content_remain = %llu\n", __func__, wsi, - (unsigned long long)wsi->u.http.tx_content_remain); - if (!wsi->u.http.tx_content_remain) { - lwsl_info("%s: selecting final write mode\n", __func__); - wp = LWS_WRITE_HTTP_FINAL; - } - } - - if ((wp & 0x1f) == LWS_WRITE_HTTP_FINAL || (wp & LWS_WRITE_H2_STREAM_END)) { - //lws_get_network_wsi(wsi)->u.h2.END_STREAM) { - lwsl_info("%s: setting END_STREAM\n", __func__); - flags |= LWS_H2_FLAG_END_STREAM; - wsi->u.h2.send_END_STREAM = 1; - } - - return lws_h2_frame_write(wsi, n, flags, - wsi->u.h2.my_sid, len, buf); - } -#endif - return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre); - default: - break; - } - - /* - * give any active extensions a chance to munge the buffer - * before send. We pass in a pointer to an lws_tokens struct - * prepared with the default buffer and content length that's in - * there. Rather than rewrite the default buffer, extensions - * that expect to grow the buffer can adapt .token to - * point to their own per-connection buffer in the extension - * user allocation. By default with no extensions or no - * extension callback handling, just the normal input buffer is - * used then so it is efficient. - * - * callback returns 1 in case it wants to spill more buffers - * - * This takes care of holding the buffer if send is incomplete, ie, - * if wsi->u.ws.clean_buffer is 0 (meaning an extension meddled with - * the buffer). If wsi->u.ws.clean_buffer is 1, it will instead - * return to the user code how much OF THE USER BUFFER was consumed. - */ - - n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre); - wsi->u.ws.inside_frame = 1; - if (n <= 0) - return n; - - if (n == (int)len + pre) { - /* everything in the buffer was handled (or rebuffered...) */ - wsi->u.ws.inside_frame = 0; - return orig_len; - } - - /* - * it is how many bytes of user buffer got sent... may be < orig_len - * in which case callback when writable has already been arranged - * and user code can call lws_write() again with the rest - * later. - */ - - return n - pre; -} - -LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - struct lws_process_html_args args; - lws_filepos_t amount, poss; - unsigned char *p, *pstart; -#if defined(LWS_WITH_RANGES) - unsigned char finished = 0; -#endif - int n, m; - - lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream); - - while (!lws_send_pipe_choked(wsi)) { - - if (wsi->trunc_len) { - if (lws_issue_raw(wsi, wsi->trunc_alloc + - wsi->trunc_offset, - wsi->trunc_len) < 0) { - lwsl_info("%s: closing\n", __func__); - goto file_had_it; - } - continue; - } - - if (wsi->u.http.filepos == wsi->u.http.filelen) - goto all_sent; - - n = 0; - - pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH; - - p = pstart; - -#if defined(LWS_WITH_RANGES) - if (wsi->u.http.range.count_ranges && !wsi->u.http.range.inside) { - - lwsl_notice("%s: doing range start %llu\n", __func__, wsi->u.http.range.start); - - if ((long long)lws_vfs_file_seek_cur(wsi->u.http.fop_fd, - wsi->u.http.range.start - - wsi->u.http.filepos) < 0) - goto file_had_it; - - wsi->u.http.filepos = wsi->u.http.range.start; - - if (wsi->u.http.range.count_ranges > 1) { - n = lws_snprintf((char *)p, context->pt_serv_buf_size - LWS_H2_FRAME_HEADER_LENGTH, - "_lws\x0d\x0a" - "Content-Type: %s\x0d\x0a" - "Content-Range: bytes %llu-%llu/%llu\x0d\x0a" - "\x0d\x0a", - wsi->u.http.multipart_content_type, - wsi->u.http.range.start, - wsi->u.http.range.end, - wsi->u.http.range.extent); - p += n; - } - - wsi->u.http.range.budget = wsi->u.http.range.end - - wsi->u.http.range.start + 1; - wsi->u.http.range.inside = 1; - } -#endif - - poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH; - - if (poss > wsi->u.http.tx_content_remain) - poss = wsi->u.http.tx_content_remain; - - /* - * if there is a hint about how much we will do well to send at one time, - * restrict ourselves to only trying to send that. - */ - if (wsi->protocol->tx_packet_size && - poss > wsi->protocol->tx_packet_size) - poss = wsi->protocol->tx_packet_size; - -#if defined(LWS_WITH_HTTP2) - m = lws_h2_tx_cr_get(wsi); - if (!m) { - lwsl_info("%s: came here with no tx credit", __func__); - return 0; - } - if (m < poss) - poss = m; - /* - * consumption of the actual payload amount sent will be handled - * when the http2 data frame is sent - */ -#endif - -#if defined(LWS_WITH_RANGES) - if (wsi->u.http.range.count_ranges) { - if (wsi->u.http.range.count_ranges > 1) - poss -= 7; /* allow for final boundary */ - if (poss > wsi->u.http.range.budget) - poss = wsi->u.http.range.budget; - } -#endif - if (wsi->sending_chunked) { - /* we need to drop the chunk size in here */ - p += 10; - /* allow for the chunk to grow by 128 in translation */ - poss -= 10 + 128; - } - - if (lws_vfs_file_read(wsi->u.http.fop_fd, &amount, p, poss) < 0) - goto file_had_it; /* caller will close */ - - if (wsi->sending_chunked) - n = (int)amount; - else - n = (p - pstart) + (int)amount; - - lwsl_debug("%s: sending %d\n", __func__, n); - - if (n) { - lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, - context->timeout_secs); - - if (wsi->sending_chunked) { - args.p = (char *)p; - args.len = n; - args.max_len = (unsigned int)poss + 128; - args.final = wsi->u.http.filepos + n == - wsi->u.http.filelen; - if (user_callback_handle_rxflow( - wsi->vhost->protocols[(int)wsi->protocol_interpret_idx].callback, wsi, - LWS_CALLBACK_PROCESS_HTML, - wsi->user_space, &args, 0) < 0) - goto file_had_it; - n = args.len; - p = (unsigned char *)args.p; - } else - p = pstart; - -#if defined(LWS_WITH_RANGES) - if (wsi->u.http.range.send_ctr + 1 == - wsi->u.http.range.count_ranges && // last range - wsi->u.http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart) - wsi->u.http.range.budget - amount == 0) {// final part - n += lws_snprintf((char *)pstart + n, 6, - "_lws\x0d\x0a"); // append trailing boundary - lwsl_debug("added trailing boundary\n"); - } -#endif - m = lws_write(wsi, p, n, - wsi->u.http.filepos == wsi->u.http.filelen ? - LWS_WRITE_HTTP_FINAL : - LWS_WRITE_HTTP - ); - if (m < 0) - goto file_had_it; - - wsi->u.http.filepos += amount; - -#if defined(LWS_WITH_RANGES) - if (wsi->u.http.range.count_ranges >= 1) { - wsi->u.http.range.budget -= amount; - if (wsi->u.http.range.budget == 0) { - lwsl_notice("range budget exhausted\n"); - wsi->u.http.range.inside = 0; - wsi->u.http.range.send_ctr++; - - if (lws_ranges_next(&wsi->u.http.range) < 1) { - finished = 1; - goto all_sent; - } - } - } -#endif - - if (m != n) { - /* adjust for what was not sent */ - if (lws_vfs_file_seek_cur(wsi->u.http.fop_fd, - m - n) == - (unsigned long)-1) - goto file_had_it; - } - } - -all_sent: - if ((!wsi->trunc_len && wsi->u.http.filepos >= wsi->u.http.filelen) -#if defined(LWS_WITH_RANGES) - || finished) -#else - ) -#endif - { - wsi->state = LWSS_HTTP; - /* we might be in keepalive, so close it off here */ - lws_vfs_file_close(&wsi->u.http.fop_fd); - - lwsl_debug("file completed\n"); - - if (wsi->protocol->callback && - user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, - wsi->user_space, NULL, - 0) < 0) { - /* - * For http/1.x, the choices from - * transaction_completed are either - * 0 to use the connection for pipelined - * or nonzero to hang it up. - * - * However for http/2. while we are - * still interested in hanging up the - * nwsi if there was a network-level - * fatal error, simply completing the - * transaction is a matter of the stream - * state, not the root connection at the - * network level - */ - if (wsi->http2_substream) - return 1; - else - return -1; - } - - return 1; /* >0 indicates completed */ - } - } - - lws_callback_on_writable(wsi); - - return 0; /* indicates further processing must be done */ - -file_had_it: - lws_vfs_file_close(&wsi->u.http.fop_fd); - - return -1; -} - -#if LWS_POSIX -LWS_VISIBLE int -lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - int n; - - lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); - - n = recv(wsi->desc.sockfd, (char *)buf, len, 0); - if (n >= 0) { - if (wsi->vhost) - wsi->vhost->conn_stats.rx += n; - lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); - lws_restart_ws_ping_pong_timer(wsi); - return n; - } -#if LWS_POSIX - if (LWS_ERRNO == LWS_EAGAIN || - LWS_ERRNO == LWS_EWOULDBLOCK || - LWS_ERRNO == LWS_EINTR) - return LWS_SSL_CAPABLE_MORE_SERVICE; -#endif - lwsl_notice("error on reading from skt : %d\n", LWS_ERRNO); - return LWS_SSL_CAPABLE_ERROR; -} - -LWS_VISIBLE int -lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) -{ - int n = 0; - -#if LWS_POSIX - n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL); -// lwsl_info("%s: sent len %d result %d", __func__, len, n); - if (n >= 0) - return n; - - if (LWS_ERRNO == LWS_EAGAIN || - LWS_ERRNO == LWS_EWOULDBLOCK || - LWS_ERRNO == LWS_EINTR) { - if (LWS_ERRNO == LWS_EWOULDBLOCK) { - lws_set_blocking_send(wsi); - } - - return LWS_SSL_CAPABLE_MORE_SERVICE; - } -#else - (void)n; - (void)wsi; - (void)buf; - (void)len; - // !!! -#endif - - lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", - len, wsi->desc.sockfd, n, LWS_ERRNO); - return LWS_SSL_CAPABLE_ERROR; -} -#endif -LWS_VISIBLE int -lws_ssl_pending_no_ssl(struct lws *wsi) -{ - (void)wsi; -#if defined(LWS_WITH_ESP32) - return 100; -#else - return 0; -#endif -} diff --git a/thirdparty/lws/private-libwebsockets.h b/thirdparty/lws/private-libwebsockets.h deleted file mode 100644 index 535fa0be57..0000000000 --- a/thirdparty/lws/private-libwebsockets.h +++ /dev/null @@ -1,2615 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010 - 2016 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "lws_config.h" -#include "lws_config_private.h" - - -#if defined(LWS_WITH_CGI) && defined(LWS_HAVE_VFORK) -#define _GNU_SOURCE -#endif - -#if defined(__COVERITY__) -typedef struct { long double x, y; } _Float128; -#endif - -#ifdef LWS_HAVE_SYS_TYPES_H -#include <sys/types.h> -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <ctype.h> -#include <limits.h> -#include <stdarg.h> -#include <inttypes.h> - -#if defined(LWS_WITH_ESP32) -#define MSG_NOSIGNAL 0 -#define SOMAXCONN 3 -#endif - -#if defined(LWS_WITH_ESP8266) -#include <user_interface.h> -#define assert(n) - -/* rom-provided stdc functions for free, ensure use these instead of libc ones */ - -int ets_vsprintf(char *str, const char *format, va_list argptr); -int ets_vsnprintf(char *buffer, size_t sizeOfBuffer, const char *format, va_list argptr); -int ets_snprintf(char *str, size_t size, const char *format, ...); -int ets_sprintf(char *str, const char *format, ...); -int os_printf_plus(const char *format, ...); -#undef malloc -#undef realloc -#undef free -void *pvPortMalloc(size_t s, const char *f, int line); -#define malloc(s) pvPortMalloc(s, "", 0) -void *pvPortRealloc(void *p, size_t s, const char *f, int line); -#define realloc(p, s) pvPortRealloc(p, s, "", 0) -void vPortFree(void *p, const char *f, int line); -#define free(p) vPortFree(p, "", 0) -#undef memcpy -void *ets_memcpy(void *dest, const void *src, size_t n); -#define memcpy ets_memcpy -void *ets_memset(void *dest, int v, size_t n); -#define memset ets_memset -char *ets_strcpy(char *dest, const char *src); -#define strcpy ets_strcpy -char *ets_strncpy(char *dest, const char *src, size_t n); -#define strncpy ets_strncpy -char *ets_strstr(const char *haystack, const char *needle); -#define strstr ets_strstr -int ets_strcmp(const char *s1, const char *s2); -int ets_strncmp(const char *s1, const char *s2, size_t n); -#define strcmp ets_strcmp -#define strncmp ets_strncmp -size_t ets_strlen(const char *s); -#define strlen ets_strlen -void *ets_memmove(void *dest, const void *src, size_t n); -#define memmove ets_memmove -char *ets_strchr(const char *s, int c); -#define strchr_ets_strchr -#undef _DEBUG -#include <osapi.h> - -#else -#define STORE_IN_ROM -#include <assert.h> -#endif -#if LWS_MAX_SMP > 1 -#include <pthread.h> -#endif - -#ifdef LWS_HAVE_SYS_STAT_H -#include <sys/stat.h> -#endif - -#if defined(WIN32) || defined(_WIN32) - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#if (WINVER < 0x0501) -#undef WINVER -#undef _WIN32_WINNT -#define WINVER 0x0501 -#define _WIN32_WINNT WINVER -#endif -#define LWS_NO_DAEMONIZE -#define LWS_ERRNO WSAGetLastError() -#define LWS_EAGAIN WSAEWOULDBLOCK -#define LWS_EALREADY WSAEALREADY -#define LWS_EINPROGRESS WSAEINPROGRESS -#define LWS_EINTR WSAEINTR -#define LWS_EISCONN WSAEISCONN -#define LWS_EWOULDBLOCK WSAEWOULDBLOCK -#define MSG_NOSIGNAL 0 -#define SHUT_RDWR SD_BOTH -#define SOL_TCP IPPROTO_TCP -#define SHUT_WR SD_SEND - -#define compatible_close(fd) closesocket(fd) -#define lws_set_blocking_send(wsi) wsi->sock_send_blocking = 1 -#define lws_socket_is_valid(x) (!!x) -#define LWS_SOCK_INVALID 0 -#include <winsock2.h> -#include <ws2tcpip.h> -#include <windows.h> -#include <tchar.h> -#ifdef LWS_HAVE_IN6ADDR_H -#include <in6addr.h> -#endif -#include <mstcpip.h> -#include <io.h> - -#if !defined(LWS_HAVE_ATOLL) -#if defined(LWS_HAVE__ATOI64) -#define atoll _atoi64 -#else -#warning No atoll or _atoi64 available, using atoi -#define atoll atoi -#endif -#endif - -#ifndef __func__ -#define __func__ __FUNCTION__ -#endif - -#ifdef LWS_HAVE__VSNPRINTF -#define vsnprintf _vsnprintf -#endif - -/* we don't have an implementation for this on windows... */ -int kill(int pid, int sig); -int fork(void); -#ifndef SIGINT -#define SIGINT 2 -#endif - -#else /* not windows --> */ - -#include <fcntl.h> -#include <strings.h> -#include <unistd.h> -#include <sys/types.h> - -#ifndef __cplusplus -#include <errno.h> -#endif -#include <netdb.h> -#include <signal.h> -#ifdef LWS_WITH_ESP8266 -#include <sockets.h> -#define vsnprintf ets_vsnprintf -#define snprintf ets_snprintf -#define sprintf ets_sprintf - -int kill(int pid, int sig); - -#else -#include <sys/socket.h> -#endif -#ifdef LWS_WITH_HTTP_PROXY -#include <hubbub/hubbub.h> -#include <hubbub/parser.h> -#endif -#if defined(LWS_BUILTIN_GETIFADDRS) - #include "./misc/getifaddrs.h" -#else - #if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) - #if defined(__HAIKU__) - #define _BSD_SOURCE - #endif - #include <ifaddrs.h> - #endif -#endif -#if defined (__ANDROID__) -#include <syslog.h> -#include <sys/resource.h> -#elif defined (__sun) || defined(__HAIKU__) -#include <syslog.h> -#else -#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) -#include <sys/syslog.h> -#endif -#endif -#include <netdb.h> -#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) -#include <sys/mman.h> -#include <sys/un.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <arpa/inet.h> -#include <poll.h> -#endif -#ifdef LWS_WITH_LIBEV -#include <ev.h> -#endif -#ifdef LWS_WITH_LIBUV -#include <uv.h> -#endif -#ifdef LWS_WITH_LIBEVENT -#include <event2/event.h> -#endif - -#ifndef LWS_NO_FORK -#ifdef LWS_HAVE_SYS_PRCTL_H -#include <sys/prctl.h> -#endif -#endif - -#include <sys/time.h> - -#define LWS_ERRNO errno -#define LWS_EAGAIN EAGAIN -#define LWS_EALREADY EALREADY -#define LWS_EINPROGRESS EINPROGRESS -#define LWS_EINTR EINTR -#define LWS_EISCONN EISCONN -#define LWS_EWOULDBLOCK EWOULDBLOCK - -#define lws_set_blocking_send(wsi) - -#if defined(LWS_WITH_ESP8266) -#define lws_socket_is_valid(x) ((x) != NULL) -#define LWS_SOCK_INVALID (NULL) -struct lws; -const char * -lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen); -#else -#define lws_socket_is_valid(x) (x >= 0) -#define LWS_SOCK_INVALID (-1) -#endif -#endif - -#ifndef LWS_HAVE_BZERO -#ifndef bzero -#define bzero(b, len) (memset((b), '\0', (len)), (void) 0) -#endif -#endif - -#ifndef LWS_HAVE_STRERROR -#define strerror(x) "" -#endif - -#ifdef LWS_OPENSSL_SUPPORT - -#ifdef USE_WOLFSSL -#ifdef USE_OLD_CYASSL -#include <cyassl/openssl/ssl.h> -#include <cyassl/error-ssl.h> -#else -#include <wolfssl/openssl/ssl.h> -#include <wolfssl/error-ssl.h> -#define OPENSSL_NO_TLSEXT -#endif /* not USE_OLD_CYASSL */ -#else -#if defined(LWS_WITH_ESP32) -#define OPENSSL_NO_TLSEXT -#else -#if defined(LWS_WITH_MBEDTLS) -#include <mbedtls/ssl.h> -#include <mbedtls/x509_crt.h> -#else -#include <openssl/ssl.h> -#include <openssl/evp.h> -#include <openssl/err.h> -#include <openssl/md5.h> -#include <openssl/sha.h> -#ifdef LWS_HAVE_OPENSSL_ECDH_H -#include <openssl/ecdh.h> -#endif -#include <openssl/x509v3.h> -#endif -#if defined(OPENSSL_VERSION_NUMBER) -#if (OPENSSL_VERSION_NUMBER < 0x0009080afL) -/* later openssl defines this to negate the presence of tlsext... but it was only - * introduced at 0.9.8j. Earlier versions don't know it exists so don't - * define it... making it look like the feature exists... - */ -#define OPENSSL_NO_TLSEXT -#endif -#endif -#endif /* not ESP32 */ -#endif /* not USE_WOLFSSL */ -#endif - -#include "libwebsockets.h" -#if defined(WIN32) || defined(_WIN32) -#else -static inline int compatible_close(int fd) { return close(fd); } -#endif - -#if defined(WIN32) || defined(_WIN32) -#include <gettimeofday.h> -#endif - -#if defined(LWS_WITH_ESP8266) -#undef compatible_close -#define compatible_close(fd) { fd->state=ESPCONN_CLOSE; espconn_delete(fd); } -lws_sockfd_type -esp8266_create_tcp_stream_socket(void); -void -esp8266_tcp_stream_bind(lws_sockfd_type fd, int port, struct lws *wsi); -#ifndef BIG_ENDIAN -#define BIG_ENDIAN 4321 /* to show byte order (taken from gcc) */ -#endif -#ifndef LITTLE_ENDIAN -#define LITTLE_ENDIAN 1234 -#endif -#ifndef BYTE_ORDER -#define BYTE_ORDER LITTLE_ENDIAN -#endif -#endif - - -#if defined(WIN32) || defined(_WIN32) - -#ifndef BIG_ENDIAN -#define BIG_ENDIAN 4321 /* to show byte order (taken from gcc) */ -#endif -#ifndef LITTLE_ENDIAN -#define LITTLE_ENDIAN 1234 -#endif -#ifndef BYTE_ORDER -#define BYTE_ORDER LITTLE_ENDIAN -#endif - -#undef __P -#ifndef __P -#if __STDC__ -#define __P(protos) protos -#else -#define __P(protos) () -#endif -#endif - -#else - -#include <sys/stat.h> -#include <sys/time.h> - -#if defined(__APPLE__) -#include <machine/endian.h> -#elif defined(__FreeBSD__) -#include <sys/endian.h> -#elif defined(__linux__) -#include <endian.h> -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(__QNX__) - #include <gulliver.h> - #if defined(__LITTLEENDIAN__) - #define BYTE_ORDER __LITTLEENDIAN__ - #define LITTLE_ENDIAN __LITTLEENDIAN__ - #define BIG_ENDIAN 4321 /* to show byte order (taken from gcc); for suppres warning that BIG_ENDIAN is not defined. */ - #endif - #if defined(__BIGENDIAN__) - #define BYTE_ORDER __BIGENDIAN__ - #define LITTLE_ENDIAN 1234 /* to show byte order (taken from gcc); for suppres warning that LITTLE_ENDIAN is not defined. */ - #define BIG_ENDIAN __BIGENDIAN__ - #endif -#endif - -#if defined(__sun) && defined(__GNUC__) - -#include <arpa/nameser_compat.h> - -#if !defined (BYTE_ORDER) -# define BYTE_ORDER __BYTE_ORDER__ -#endif - -#if !defined(LITTLE_ENDIAN) -# define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ -#endif - -#if !defined(BIG_ENDIAN) -# define BIG_ENDIAN __ORDER_BIG_ENDIAN__ -#endif - -#endif /* sun + GNUC */ - -#if !defined(BYTE_ORDER) -# define BYTE_ORDER __BYTE_ORDER -#endif -#if !defined(LITTLE_ENDIAN) -# define LITTLE_ENDIAN __LITTLE_ENDIAN -#endif -#if !defined(BIG_ENDIAN) -# define BIG_ENDIAN __BIG_ENDIAN -#endif - -#endif - -/* - * Mac OSX as well as iOS do not define the MSG_NOSIGNAL flag, - * but happily have something equivalent in the SO_NOSIGPIPE flag. - */ -#ifdef __APPLE__ -#define MSG_NOSIGNAL SO_NOSIGPIPE -#endif - -/* - * Solaris 11.X only supports POSIX 2001, MSG_NOSIGNAL appears in - * POSIX 2008. - */ -#ifdef __sun -#define MSG_NOSIGNAL 0 -#endif - -#ifdef _WIN32 -#ifndef FD_HASHTABLE_MODULUS -#define FD_HASHTABLE_MODULUS 32 -#endif -#endif - -#ifndef LWS_DEF_HEADER_LEN -#define LWS_DEF_HEADER_LEN 4096 -#endif -#ifndef LWS_DEF_HEADER_POOL -#define LWS_DEF_HEADER_POOL 4 -#endif -#ifndef LWS_MAX_PROTOCOLS -#define LWS_MAX_PROTOCOLS 5 -#endif -#ifndef LWS_MAX_EXTENSIONS_ACTIVE -#define LWS_MAX_EXTENSIONS_ACTIVE 2 -#endif -#ifndef LWS_MAX_EXT_OFFERS -#define LWS_MAX_EXT_OFFERS 8 -#endif -#ifndef SPEC_LATEST_SUPPORTED -#define SPEC_LATEST_SUPPORTED 13 -#endif -#ifndef AWAITING_TIMEOUT -#define AWAITING_TIMEOUT 20 -#endif -#ifndef CIPHERS_LIST_STRING -#define CIPHERS_LIST_STRING "DEFAULT" -#endif -#ifndef LWS_SOMAXCONN -#define LWS_SOMAXCONN SOMAXCONN -#endif - -#define MAX_WEBSOCKET_04_KEY_LEN 128 - -#ifndef SYSTEM_RANDOM_FILEPATH -#define SYSTEM_RANDOM_FILEPATH "/dev/urandom" -#endif - -enum lws_websocket_opcodes_07 { - LWSWSOPC_CONTINUATION = 0, - LWSWSOPC_TEXT_FRAME = 1, - LWSWSOPC_BINARY_FRAME = 2, - - LWSWSOPC_NOSPEC__MUX = 7, - - /* control extensions 8+ */ - - LWSWSOPC_CLOSE = 8, - LWSWSOPC_PING = 9, - LWSWSOPC_PONG = 0xa, -}; - - -enum lws_connection_states { - LWSS_HTTP, - LWSS_HTTP_ISSUING_FILE, - LWSS_HTTP_HEADERS, - LWSS_HTTP_BODY, - LWSS_DEAD_SOCKET, - LWSS_ESTABLISHED, - LWSS_CLIENT_HTTP_ESTABLISHED, - LWSS_CLIENT_UNCONNECTED, - LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION, - LWSS_RETURNED_CLOSE_ALREADY, - LWSS_AWAITING_CLOSE_ACK, - LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE, - LWSS_SHUTDOWN, - - LWSS_HTTP2_AWAIT_CLIENT_PREFACE, - LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS, - LWSS_HTTP2_ESTABLISHED, - - LWSS_CGI, -}; - -enum http_version { - HTTP_VERSION_1_0, - HTTP_VERSION_1_1, - HTTP_VERSION_2 -}; - -enum http_connection_type { - HTTP_CONNECTION_CLOSE, - HTTP_CONNECTION_KEEP_ALIVE -}; - -enum lws_rx_parse_state { - LWS_RXPS_NEW, - - LWS_RXPS_04_mask_1, - LWS_RXPS_04_mask_2, - LWS_RXPS_04_mask_3, - - LWS_RXPS_04_FRAME_HDR_1, - LWS_RXPS_04_FRAME_HDR_LEN, - LWS_RXPS_04_FRAME_HDR_LEN16_2, - LWS_RXPS_04_FRAME_HDR_LEN16_1, - LWS_RXPS_04_FRAME_HDR_LEN64_8, - LWS_RXPS_04_FRAME_HDR_LEN64_7, - LWS_RXPS_04_FRAME_HDR_LEN64_6, - LWS_RXPS_04_FRAME_HDR_LEN64_5, - LWS_RXPS_04_FRAME_HDR_LEN64_4, - LWS_RXPS_04_FRAME_HDR_LEN64_3, - LWS_RXPS_04_FRAME_HDR_LEN64_2, - LWS_RXPS_04_FRAME_HDR_LEN64_1, - - LWS_RXPS_07_COLLECT_FRAME_KEY_1, - LWS_RXPS_07_COLLECT_FRAME_KEY_2, - LWS_RXPS_07_COLLECT_FRAME_KEY_3, - LWS_RXPS_07_COLLECT_FRAME_KEY_4, - - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED -}; - -#define LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP 32 - -enum connection_mode { - LWSCM_HTTP_SERVING, - LWSCM_HTTP_SERVING_ACCEPTED, /* actual HTTP service going on */ - LWSCM_PRE_WS_SERVING_ACCEPT, - - LWSCM_WS_SERVING, - LWSCM_WS_CLIENT, - - LWSCM_HTTP2_SERVING, - - /* transient, ssl delay hiding */ - LWSCM_SSL_ACK_PENDING, - LWSCM_SSL_INIT, - /* as above, but complete into LWSCM_RAW */ - LWSCM_SSL_ACK_PENDING_RAW, - LWSCM_SSL_INIT_RAW, - - /* special internal types */ - LWSCM_SERVER_LISTENER, - LWSCM_CGI, /* stdin, stdout, stderr for another cgi master wsi */ - LWSCM_RAW, /* raw with bulk handling */ - LWSCM_RAW_FILEDESC, /* raw without bulk handling */ - - /* HTTP Client related */ - LWSCM_HTTP_CLIENT = LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP, - LWSCM_HTTP_CLIENT_ACCEPTED, /* actual HTTP service going on */ - LWSCM_WSCL_WAITING_CONNECT, - LWSCM_WSCL_WAITING_PROXY_REPLY, - LWSCM_WSCL_ISSUE_HANDSHAKE, - LWSCM_WSCL_ISSUE_HANDSHAKE2, - LWSCM_WSCL_ISSUE_HTTP_BODY, - LWSCM_WSCL_WAITING_SSL, - LWSCM_WSCL_WAITING_SERVER_REPLY, - LWSCM_WSCL_WAITING_EXTENSION_CONNECT, - LWSCM_WSCL_PENDING_CANDIDATE_CHILD, - LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY, - LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY, - LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY, - - /****** add new things just above ---^ ******/ - - -}; - -/* enums of socks version */ -enum socks_version { - SOCKS_VERSION_4 = 4, - SOCKS_VERSION_5 = 5 -}; - -/* enums of subnegotiation version */ -enum socks_subnegotiation_version { - SOCKS_SUBNEGOTIATION_VERSION_1 = 1, -}; - -/* enums of socks commands */ -enum socks_command { - SOCKS_COMMAND_CONNECT = 1, - SOCKS_COMMAND_BIND = 2, - SOCKS_COMMAND_UDP_ASSOCIATE = 3 -}; - -/* enums of socks address type */ -enum socks_atyp { - SOCKS_ATYP_IPV4 = 1, - SOCKS_ATYP_DOMAINNAME = 3, - SOCKS_ATYP_IPV6 = 4 -}; - -/* enums of socks authentication methods */ -enum socks_auth_method { - SOCKS_AUTH_NO_AUTH = 0, - SOCKS_AUTH_GSSAPI = 1, - SOCKS_AUTH_USERNAME_PASSWORD = 2 -}; - -/* enums of subnegotiation status */ -enum socks_subnegotiation_status { - SOCKS_SUBNEGOTIATION_STATUS_SUCCESS = 0, -}; - -/* enums of socks request reply */ -enum socks_request_reply { - SOCKS_REQUEST_REPLY_SUCCESS = 0, - SOCKS_REQUEST_REPLY_FAILURE_GENERAL = 1, - SOCKS_REQUEST_REPLY_CONNECTION_NOT_ALLOWED = 2, - SOCKS_REQUEST_REPLY_NETWORK_UNREACHABLE = 3, - SOCKS_REQUEST_REPLY_HOST_UNREACHABLE = 4, - SOCKS_REQUEST_REPLY_CONNECTION_REFUSED = 5, - SOCKS_REQUEST_REPLY_TTL_EXPIRED = 6, - SOCKS_REQUEST_REPLY_COMMAND_NOT_SUPPORTED = 7, - SOCKS_REQUEST_REPLY_ATYP_NOT_SUPPORTED = 8 -}; - -/* enums used to generate socks messages */ -enum socks_msg_type { - /* greeting */ - SOCKS_MSG_GREETING, - /* credential, user name and password */ - SOCKS_MSG_USERNAME_PASSWORD, - /* connect command */ - SOCKS_MSG_CONNECT -}; - -enum { - LWS_RXFLOW_ALLOW = (1 << 0), - LWS_RXFLOW_PENDING_CHANGE = (1 << 1), -}; - -struct lws_ring { - void *buf; - void (*destroy_element)(void *element); - size_t buflen; - size_t element_len; - uint32_t head; - uint32_t oldest_tail; -}; - -/* this is not usable directly by user code any more, lws_close_reason() */ -#define LWS_WRITE_CLOSE 4 - -struct lws_protocols; -struct lws; - -#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) - -struct lws_io_watcher { -#ifdef LWS_WITH_LIBEV - ev_io ev_watcher; -#endif -#ifdef LWS_WITH_LIBUV - uv_poll_t uv_watcher; -#endif -#ifdef LWS_WITH_LIBEVENT - struct event *event_watcher; -#endif - struct lws_context *context; - - uint8_t actual_events; -}; - -struct lws_signal_watcher { -#ifdef LWS_WITH_LIBEV - ev_signal ev_watcher; -#endif -#ifdef LWS_WITH_LIBUV - uv_signal_t uv_watcher; -#endif -#ifdef LWS_WITH_LIBEVENT - struct event *event_watcher; -#endif - struct lws_context *context; -}; -#endif - -#ifdef _WIN32 -#define LWS_FD_HASH(fd) ((fd ^ (fd >> 8) ^ (fd >> 16)) % FD_HASHTABLE_MODULUS) -struct lws_fd_hashtable { - struct lws **wsi; - int length; -}; -#endif - -/* - * This is totally opaque to code using the library. It's exported as a - * forward-reference pointer-only declaration; the user can use the pointer with - * other APIs to get information out of it. - */ - -#if defined(LWS_WITH_ESP32) -typedef uint16_t ah_data_idx_t; -#else -typedef uint32_t ah_data_idx_t; -#endif - -struct lws_fragments { - ah_data_idx_t offset; - uint16_t len; - uint8_t nfrag; /* which ah->frag[] continues this content, or 0 */ - uint8_t flags; /* only http2 cares */ -}; - -/* - * these are assigned from a pool held in the context. - * Both client and server mode uses them for http header analysis - */ - -struct allocated_headers { - struct allocated_headers *next; /* linked list */ - struct lws *wsi; /* owner */ - char *data; /* prepared by context init to point to dedicated storage */ - ah_data_idx_t data_length; - /* - * the randomly ordered fragments, indexed by frag_index and - * lws_fragments->nfrag for continuation. - */ - struct lws_fragments frags[WSI_TOKEN_COUNT]; - time_t assigned; - /* - * for each recognized token, frag_index says which frag[] his data - * starts in (0 means the token did not appear) - * the actual header data gets dumped as it comes in, into data[] - */ - uint8_t frag_index[WSI_TOKEN_COUNT]; -#if defined(LWS_WITH_ESP32) - uint8_t rx[256]; -#else - uint8_t rx[2048]; -#endif - - int16_t rxpos; - int16_t rxlen; - uint32_t pos; - uint32_t http_response; - int hdr_token_idx; - -#ifndef LWS_NO_CLIENT - char initial_handshake_hash_base64[30]; -#endif - - uint8_t in_use; - uint8_t nfrag; -}; - -/* - * so we can have n connections being serviced simultaneously, - * these things need to be isolated per-thread. - */ - -struct lws_context_per_thread { -#if LWS_MAX_SMP > 1 - pthread_mutex_t lock; -#endif - struct lws_pollfd *fds; -#if defined(LWS_WITH_ESP8266) - struct lws **lws_vs_fds_index; -#endif - struct lws *rx_draining_ext_list; - struct lws *tx_draining_ext_list; - struct lws *timeout_list; -#if defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) - struct lws_context *context; -#endif -#ifdef LWS_WITH_CGI - struct lws_cgi *cgi_list; -#endif - void *http_header_data; - struct allocated_headers *ah_list; - struct lws *ah_wait_list; - int ah_wait_list_length; -#ifdef LWS_OPENSSL_SUPPORT - struct lws *pending_read_list; /* linked list */ -#endif -#if defined(LWS_WITH_LIBEV) - struct ev_loop *io_loop_ev; -#endif -#if defined(LWS_WITH_LIBUV) - uv_loop_t *io_loop_uv; - uv_signal_t signals[8]; - uv_timer_t uv_timeout_watcher; - uv_idle_t uv_idle; -#endif -#if defined(LWS_WITH_LIBEVENT) - struct event_base *io_loop_event_base; -#endif -#if defined(LWS_WITH_LIBEV) - struct lws_io_watcher w_accept; -#endif -#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) - struct lws_signal_watcher w_sigint; - unsigned char ev_loop_foreign:1; -#endif - - unsigned long count_conns; - /* - * usable by anything in the service code, but only if the scope - * does not last longer than the service action (since next service - * of any socket can likewise use it and overwrite) - */ - unsigned char *serv_buf; -#ifdef _WIN32 - WSAEVENT *events; -#else - lws_sockfd_type dummy_pipe_fds[2]; -#endif - unsigned int fds_count; - uint32_t ah_pool_length; - - short ah_count_in_use; - unsigned char tid; - unsigned char lock_depth; -}; - -struct lws_conn_stats { - unsigned long long rx, tx; - unsigned long h1_conn, h1_trans, h2_trans, ws_upg, h2_alpn, h2_subs, - h2_upg, rejected; -}; - -void -lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs); - - -enum lws_h2_settings { - H2SET_HEADER_TABLE_SIZE = 1, - H2SET_ENABLE_PUSH, - H2SET_MAX_CONCURRENT_STREAMS, - H2SET_INITIAL_WINDOW_SIZE, - H2SET_MAX_FRAME_SIZE, - H2SET_MAX_HEADER_LIST_SIZE, - - H2SET_COUNT /* always last */ -}; - -struct http2_settings { - uint32_t s[H2SET_COUNT]; -}; - -/* - * virtual host -related context information - * vhostwide SSL context - * vhostwide proxy - * - * hierarchy: - * - * context -> vhost -> wsi - * - * incoming connection non-SSL vhost binding: - * - * listen socket -> wsi -> select vhost after first headers - * - * incoming connection SSL vhost binding: - * - * SSL SNI -> wsi -> bind after SSL negotiation - */ - -struct lws_vhost { -#if !defined(LWS_WITH_ESP8266) - char http_proxy_address[128]; - char proxy_basic_auth_token[128]; -#if defined(LWS_WITH_HTTP2) - struct http2_settings set; -#endif -#if defined(LWS_WITH_SOCKS5) - char socks_proxy_address[128]; - char socks_user[96]; - char socks_password[96]; -#endif -#endif -#if defined(LWS_WITH_ESP8266) - /* listen sockets need a place to hang their hat */ - esp_tcp tcp; -#endif - struct lws_conn_stats conn_stats; - struct lws_context *context; - struct lws_vhost *vhost_next; - const struct lws_http_mount *mount_list; - struct lws *lserv_wsi; - const char *name; - const char *iface; -#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) - int bind_iface; -#endif - const struct lws_protocols *protocols; - void **protocol_vh_privs; - const struct lws_protocol_vhost_options *pvo; - const struct lws_protocol_vhost_options *headers; - struct lws **same_vh_protocol_list; -#ifdef LWS_OPENSSL_SUPPORT - SSL_CTX *ssl_ctx; - SSL_CTX *ssl_client_ctx; -#endif -#if defined(LWS_WITH_MBEDTLS) - X509 *x509_client_CA; -#endif -#ifndef LWS_NO_EXTENSIONS - const struct lws_extension *extensions; -#endif - void *user; - - int listen_port; - unsigned int http_proxy_port; -#if defined(LWS_WITH_SOCKS5) - unsigned int socks_proxy_port; -#endif - unsigned int options; - int count_protocols; - int ka_time; - int ka_probes; - int ka_interval; - int keepalive_timeout; - int timeout_secs_ah_idle; - int ssl_info_event_mask; -#ifdef LWS_WITH_ACCESS_LOG - int log_fd; -#endif - -#ifdef LWS_OPENSSL_SUPPORT - int use_ssl; - int allow_non_ssl_on_ssl_port; - unsigned int user_supplied_ssl_ctx:1; -#endif - - unsigned int created_vhost_protocols:1; - unsigned int being_destroyed:1; - - unsigned char default_protocol_index; - unsigned char raw_protocol_index; -}; - -struct lws_deferred_free -{ - struct lws_deferred_free *next; - time_t deadline; - void *payload; -}; - -typedef union { -#ifdef LWS_WITH_IPV6 - struct sockaddr_in6 sa6; -#endif - struct sockaddr_in sa4; -} sockaddr46; - - -#if defined(LWS_WITH_PEER_LIMITS) -struct lws_peer { - struct lws_peer *next; - struct lws_peer *peer_wait_list; - - time_t time_created; - time_t time_closed_all; - - uint8_t addr[32]; - uint32_t hash; - uint32_t count_wsi; - uint32_t count_ah; - - uint32_t total_wsi; - uint32_t total_ah; - - uint8_t af; -}; -#endif - -/* - * the rest is managed per-context, that includes - * - * - processwide single fd -> wsi lookup - * - contextwide headers pool - */ - -struct lws_context { - time_t last_timeout_check_s; - time_t last_ws_ping_pong_check_s; - time_t time_up; - const struct lws_plat_file_ops *fops; - struct lws_plat_file_ops fops_platform; -#if defined(LWS_WITH_HTTP2) - struct http2_settings set; -#endif -#if defined(LWS_WITH_ZIP_FOPS) - struct lws_plat_file_ops fops_zip; -#endif - struct lws_context_per_thread pt[LWS_MAX_SMP]; - struct lws_conn_stats conn_stats; -#if LWS_MAX_SMP > 1 - pthread_mutex_t lock; - int lock_depth; -#endif -#ifdef _WIN32 -/* different implementation between unix and windows */ - struct lws_fd_hashtable fd_hashtable[FD_HASHTABLE_MODULUS]; -#else -#if defined(LWS_WITH_ESP8266) - struct espconn **connpool; /* .reverse points to the wsi */ - void *rxd; - int rxd_len; - os_timer_t to_timer; -#else - struct lws **lws_lookup; /* fd to wsi */ -#endif -#endif - struct lws_vhost *vhost_list; - struct lws_vhost *vhost_pending_destruction_list; - struct lws_plugin *plugin_list; - struct lws_deferred_free *deferred_free_list; -#if defined(LWS_WITH_PEER_LIMITS) - struct lws_peer **pl_hash_table; - struct lws_peer *peer_wait_list; - time_t next_cull; -#endif - - void *external_baggage_free_on_destroy; - const struct lws_token_limits *token_limits; - void *user_space; - const char *server_string; - const struct lws_protocol_vhost_options *reject_service_keywords; - lws_reload_func deprecation_cb; - -#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) - cap_value_t caps[4]; - char count_caps; -#endif - -#if defined(LWS_WITH_LIBEV) - lws_ev_signal_cb_t * lws_ev_sigint_cb; -#endif -#if defined(LWS_WITH_LIBUV) - uv_signal_cb lws_uv_sigint_cb; - uv_loop_t pu_loop; -#endif -#if defined(LWS_WITH_LIBEVENT) - lws_event_signal_cb_t * lws_event_sigint_cb; -#endif - char canonical_hostname[128]; -#ifdef LWS_LATENCY - unsigned long worst_latency; - char worst_latency_info[256]; -#endif - -#if defined(LWS_WITH_STATS) - uint64_t lws_stats[LWSSTATS_SIZE]; - uint64_t last_dump; - int updated; -#endif -#if defined(LWS_WITH_ESP32) - unsigned long time_last_state_dump; - uint32_t last_free_heap; -#endif - - int max_fds; -#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) - int use_ev_sigint; -#endif - int started_with_parent; - int uid, gid; - - int fd_random; - - int count_wsi_allocated; - int count_cgi_spawned; - unsigned int options; - unsigned int fd_limit_per_thread; - unsigned int timeout_secs; - unsigned int pt_serv_buf_size; - int max_http_header_data; - int simultaneous_ssl_restriction; - int simultaneous_ssl; -#if defined(LWS_WITH_PEER_LIMITS) - uint32_t pl_hash_elements; /* protected by context->lock */ - uint32_t count_peers; /* protected by context->lock */ - unsigned short ip_limit_ah; - unsigned short ip_limit_wsi; -#endif - unsigned int deprecated:1; - unsigned int being_destroyed:1; - unsigned int being_destroyed1:1; - unsigned int requested_kill:1; - unsigned int protocol_init_done:1; - unsigned int ssl_gate_accepts:1; - unsigned int doing_protocol_init; - /* - * set to the Thread ID that's doing the service loop just before entry - * to poll indicates service thread likely idling in poll() - * volatile because other threads may check it as part of processing - * for pollfd event change. - */ - volatile int service_tid; - int service_tid_detected; - - short max_http_header_pool; - short count_threads; - short plugin_protocol_count; - short plugin_extension_count; - short server_string_len; - unsigned short ws_ping_pong_interval; - unsigned short deprecation_pending_listen_close_count; - - uint8_t max_fi; -}; - -int -lws_check_deferred_free(struct lws_context *context, int force); - -#define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x] -#define lws_get_vh_protocol(vh, x) vh->protocols[x] - -LWS_EXTERN void -lws_close_free_wsi_final(struct lws *wsi); -LWS_EXTERN void -lws_libuv_closehandle(struct lws *wsi); -LWS_EXTERN void -lws_libuv_closehandle_manually(struct lws *wsi); -LWS_EXTERN int -lws_libuv_check_watcher_active(struct lws *wsi); - -LWS_VISIBLE LWS_EXTERN int -lws_plat_plugins_init(struct lws_context * context, const char * const *d); - -LWS_VISIBLE LWS_EXTERN int -lws_plat_plugins_destroy(struct lws_context * context); - -LWS_EXTERN void -lws_restart_ws_ping_pong_timer(struct lws *wsi); - -struct lws * -lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd); - - -enum { - LWS_EV_READ = (1 << 0), - LWS_EV_WRITE = (1 << 1), - LWS_EV_START = (1 << 2), - LWS_EV_STOP = (1 << 3), - - LWS_EV_PREPARE_DELETION = (1 << 31), -}; - -#if defined(LWS_WITH_LIBEV) -LWS_EXTERN void -lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc); -LWS_EXTERN void -lws_libev_io(struct lws *wsi, int flags); -LWS_EXTERN int -lws_libev_init_fd_table(struct lws_context *context); -LWS_EXTERN void -lws_libev_destroyloop(struct lws_context *context, int tsi); -LWS_EXTERN void -lws_libev_run(const struct lws_context *context, int tsi); -#define LWS_LIBEV_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV) -LWS_EXTERN void lws_feature_status_libev(struct lws_context_creation_info *info); -#else -#define lws_libev_accept(_a, _b) ((void) 0) -#define lws_libev_io(_a, _b) ((void) 0) -#define lws_libev_init_fd_table(_a) (0) -#define lws_libev_run(_a, _b) ((void) 0) -#define lws_libev_destroyloop(_a, _b) ((void) 0) -#define LWS_LIBEV_ENABLED(context) (0) -#if LWS_POSIX && !defined(LWS_WITH_ESP32) -#define lws_feature_status_libev(_a) \ - lwsl_info("libev support not compiled in\n") -#else -#define lws_feature_status_libev(_a) -#endif -#endif - -#if defined(LWS_WITH_LIBUV) -LWS_EXTERN void -lws_libuv_accept(struct lws *new_wsi, lws_sock_file_fd_type desc); -LWS_EXTERN void -lws_libuv_io(struct lws *wsi, int flags); -LWS_EXTERN int -lws_libuv_init_fd_table(struct lws_context *context); -LWS_EXTERN void -lws_libuv_run(const struct lws_context *context, int tsi); -LWS_EXTERN void -lws_libuv_destroyloop(struct lws_context *context, int tsi); -LWS_EXTERN int -lws_uv_initvhost(struct lws_vhost* vh, struct lws*); -#define LWS_LIBUV_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV) -LWS_EXTERN void lws_feature_status_libuv(struct lws_context_creation_info *info); -#else -#define lws_libuv_accept(_a, _b) ((void) 0) -#define lws_libuv_io(_a, _b) ((void) 0) -#define lws_libuv_init_fd_table(_a) (0) -#define lws_libuv_run(_a, _b) ((void) 0) -#define lws_libuv_destroyloop(_a, _b) ((void) 0) -#define LWS_LIBUV_ENABLED(context) (0) -#if LWS_POSIX && !defined(LWS_WITH_ESP32) -#define lws_feature_status_libuv(_a) \ - lwsl_notice("libuv support not compiled in\n") -#else -#define lws_feature_status_libuv(_a) -#endif -#endif - -#if defined(LWS_WITH_LIBEVENT) -LWS_EXTERN void -lws_libevent_accept(struct lws *new_wsi, lws_sock_file_fd_type desc); -LWS_EXTERN void -lws_libevent_io(struct lws *wsi, int flags); -LWS_EXTERN int -lws_libevent_init_fd_table(struct lws_context *context); -LWS_EXTERN void -lws_libevent_destroyloop(struct lws_context *context, int tsi); -LWS_EXTERN void -lws_libevent_run(const struct lws_context *context, int tsi); -#define LWS_LIBEVENT_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT) -LWS_EXTERN void lws_feature_status_libevent(struct lws_context_creation_info *info); -#else -#define lws_libevent_accept(_a, _b) ((void) 0) -#define lws_libevent_io(_a, _b) ((void) 0) -#define lws_libevent_init_fd_table(_a) (0) -#define lws_libevent_run(_a, _b) ((void) 0) -#define lws_libevent_destroyloop(_a, _b) ((void) 0) -#define LWS_LIBEVENT_ENABLED(context) (0) -#if LWS_POSIX && !defined(LWS_WITH_ESP32) -#define lws_feature_status_libevent(_a) \ - lwsl_notice("libevent support not compiled in\n") -#else -#define lws_feature_status_libevent(_a) -#endif -#endif - - -#ifdef LWS_WITH_IPV6 -#define LWS_IPV6_ENABLED(vh) \ - (!lws_check_opt(vh->context->options, LWS_SERVER_OPTION_DISABLE_IPV6) && \ - !lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_IPV6)) -#else -#define LWS_IPV6_ENABLED(context) (0) -#endif - -#ifdef LWS_WITH_UNIX_SOCK -#define LWS_UNIX_SOCK_ENABLED(vhost) \ - (vhost->options & LWS_SERVER_OPTION_UNIX_SOCK) -#else -#define LWS_UNIX_SOCK_ENABLED(vhost) (0) -#endif - -enum uri_path_states { - URIPS_IDLE, - URIPS_SEEN_SLASH, - URIPS_SEEN_SLASH_DOT, - URIPS_SEEN_SLASH_DOT_DOT, -}; - -enum uri_esc_states { - URIES_IDLE, - URIES_SEEN_PERCENT, - URIES_SEEN_PERCENT_H1, -}; - -/* notice that these union members: - * - * hdr - * http - * http2 - * - * all have a pointer to allocated_headers struct as their first member. - * - * It means for allocated_headers access, the three union paths can all be - * used interchangeably to access the same data - */ - - -#ifndef LWS_NO_CLIENT -struct client_info_stash { - char address[256]; - char path[4096]; - char host[256]; - char origin[256]; - char protocol[256]; - char method[16]; - char iface[16]; -}; -#endif - -struct _lws_header_related { - /* MUST be first in struct */ - struct allocated_headers *ah; - struct lws *ah_wait_list; - unsigned char *preamble_rx; -#ifndef LWS_NO_CLIENT - struct client_info_stash *stash; -#endif - unsigned int preamble_rx_len; - enum uri_path_states ups; - enum uri_esc_states ues; - short lextable_pos; - unsigned int current_token_limit; - - char esc_stash; - char post_literal_equal; - unsigned char parser_state; /* enum lws_token_indexes */ -}; - -#if defined(LWS_WITH_RANGES) -enum range_states { - LWSRS_NO_ACTIVE_RANGE, - LWSRS_BYTES_EQ, - LWSRS_FIRST, - LWSRS_STARTING, - LWSRS_ENDING, - LWSRS_COMPLETED, - LWSRS_SYNTAX, -}; - -struct lws_range_parsing { - unsigned long long start, end, extent, agg, budget; - const char buf[128]; - int pos; - enum range_states state; - char start_valid, end_valid, ctr, count_ranges, did_try, inside, send_ctr; -}; - -int -lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp, unsigned long long extent); -int -lws_ranges_next(struct lws_range_parsing *rp); -void -lws_ranges_reset(struct lws_range_parsing *rp); -#endif - -struct _lws_http_mode_related { - /* MUST be first in struct */ - struct allocated_headers *ah; /* mirroring _lws_header_related */ - struct lws *ah_wait_list; - unsigned char *preamble_rx; -#ifndef LWS_NO_CLIENT - struct client_info_stash *stash; -#endif - unsigned int preamble_rx_len; - struct lws *new_wsi_list; - lws_filepos_t filepos; - lws_filepos_t filelen; - lws_fop_fd_t fop_fd; - -#if defined(LWS_WITH_RANGES) - struct lws_range_parsing range; - char multipart_content_type[64]; -#endif - - enum http_version request_version; - enum http_connection_type connection_type; - lws_filepos_t tx_content_length; - lws_filepos_t tx_content_remain; - lws_filepos_t rx_content_length; - lws_filepos_t rx_content_remain; -}; - -#define LWS_H2_FRAME_HEADER_LENGTH 9 - -#ifdef LWS_WITH_HTTP2 - -enum lws_h2_wellknown_frame_types { - LWS_H2_FRAME_TYPE_DATA, - LWS_H2_FRAME_TYPE_HEADERS, - LWS_H2_FRAME_TYPE_PRIORITY, - LWS_H2_FRAME_TYPE_RST_STREAM, - LWS_H2_FRAME_TYPE_SETTINGS, - LWS_H2_FRAME_TYPE_PUSH_PROMISE, - LWS_H2_FRAME_TYPE_PING, - LWS_H2_FRAME_TYPE_GOAWAY, - LWS_H2_FRAME_TYPE_WINDOW_UPDATE, - LWS_H2_FRAME_TYPE_CONTINUATION, - - LWS_H2_FRAME_TYPE_COUNT /* always last */ -}; - -enum lws_h2_flags { - LWS_H2_FLAG_END_STREAM = 1, - LWS_H2_FLAG_END_HEADERS = 4, - LWS_H2_FLAG_PADDED = 8, - LWS_H2_FLAG_PRIORITY = 0x20, - - LWS_H2_FLAG_SETTINGS_ACK = 1, -}; - -enum lws_h2_errors { - H2_ERR_NO_ERROR, /* Graceful shutdown */ - H2_ERR_PROTOCOL_ERROR, /* Protocol error detected */ - H2_ERR_INTERNAL_ERROR, /* Implementation fault */ - H2_ERR_FLOW_CONTROL_ERROR, /* Flow-control limits exceeded */ - H2_ERR_SETTINGS_TIMEOUT, /* Settings not acknowledged */ - H2_ERR_STREAM_CLOSED, /* Frame received for closed stream */ - H2_ERR_FRAME_SIZE_ERROR, /* Frame size incorrect */ - H2_ERR_REFUSED_STREAM, /* Stream not processed */ - H2_ERR_CANCEL, /* Stream cancelled */ - H2_ERR_COMPRESSION_ERROR, /* Compression state not updated */ - H2_ERR_CONNECT_ERROR, /* TCP connection error for CONNECT method */ - H2_ERR_ENHANCE_YOUR_CALM, /* Processing capacity exceeded */ - H2_ERR_INADEQUATE_SECURITY, /* Negotiated TLS parameters not acceptable */ - H2_ERR_HTTP_1_1_REQUIRED, /* Use HTTP/1.1 for the request */ -}; - -enum lws_h2_states { - LWS_H2_STATE_IDLE, - /* - * Send PUSH_PROMISE -> LWS_H2_STATE_RESERVED_LOCAL - * Recv PUSH_PROMISE -> LWS_H2_STATE_RESERVED_REMOTE - * Send HEADERS -> LWS_H2_STATE_OPEN - * Recv HEADERS -> LWS_H2_STATE_OPEN - * - * - Only PUSH_PROMISE + HEADERS valid to send - * - Only HEADERS or PRIORITY valid to receive - */ - LWS_H2_STATE_RESERVED_LOCAL, - /* - * Send RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv RST_STREAM -> LWS_H2_STATE_CLOSED - * Send HEADERS -> LWS_H2_STATE_HALF_CLOSED_REMOTE - * - * - Only HEADERS, RST_STREAM, or PRIORITY valid to send - * - Only RST_STREAM, PRIORITY, or WINDOW_UPDATE valid to receive - */ - LWS_H2_STATE_RESERVED_REMOTE, - /* - * Send RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv HEADERS -> LWS_H2_STATE_HALF_CLOSED_LOCAL - * - * - Only RST_STREAM, WINDOW_UPDATE, or PRIORITY valid to send - * - Only HEADERS, RST_STREAM, or PRIORITY valid to receive - */ - LWS_H2_STATE_OPEN, - /* - * Send RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv RST_STREAM -> LWS_H2_STATE_CLOSED - * Send END_STREAM flag -> LWS_H2_STATE_HALF_CLOSED_LOCAL - * Recv END_STREAM flag -> LWS_H2_STATE_HALF_CLOSED_REMOTE - */ - LWS_H2_STATE_HALF_CLOSED_REMOTE, - /* - * Send RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv RST_STREAM -> LWS_H2_STATE_CLOSED - * Send END_STREAM flag -> LWS_H2_STATE_CLOSED - * - * - Any frame valid to send - * - Only WINDOW_UPDATE, PRIORITY, or RST_STREAM valid to receive - */ - LWS_H2_STATE_HALF_CLOSED_LOCAL, - /* - * Send RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv RST_STREAM -> LWS_H2_STATE_CLOSED - * Recv END_STREAM flag -> LWS_H2_STATE_CLOSED - * - * - Only WINDOW_UPDATE, PRIORITY, and RST_STREAM valid to send - * - Any frame valid to receive - */ - LWS_H2_STATE_CLOSED, - /* - * - Only PRIORITY, WINDOW_UPDATE (IGNORE) and RST_STREAM (IGNORE) - * may be received - * - * - Only PRIORITY valid to send - */ -}; - -#define LWS_H2_STREAM_ID_MASTER 0 -#define LWS_H2_SETTINGS_LEN 6 - -enum http2_hpack_state { - HPKS_TYPE, - - HPKS_IDX_EXT, - - HPKS_HLEN, - HPKS_HLEN_EXT, - - HPKS_DATA, -}; - -/* - * lws general parsimonious header strategy is only store values from known - * headers, and refer to them by index. - * - * That means if we can't map the peer header name to one that lws knows, we - * will drop the content but track the indexing with associated_lws_hdr_idx = - * LWS_HPACK_IGNORE_ENTRY. - */ - -enum http2_hpack_type { - HPKT_INDEXED_HDR_7, /* 1xxxxxxx: just "header field" */ - HPKT_INDEXED_HDR_6_VALUE_INCR, /* 01xxxxxx: NEW indexed hdr with value */ - HPKT_LITERAL_HDR_VALUE_INCR, /* 01000000: NEW literal hdr with value */ - HPKT_INDEXED_HDR_4_VALUE, /* 0000xxxx: indexed hdr with value */ - HPKT_INDEXED_HDR_4_VALUE_NEVER, /* 0001xxxx: indexed hdr with value NEVER NEW */ - HPKT_LITERAL_HDR_VALUE, /* 00000000: literal hdr with value */ - HPKT_LITERAL_HDR_VALUE_NEVER, /* 00010000: literal hdr with value NEVER NEW */ - HPKT_SIZE_5 -}; - -#define LWS_HPACK_IGNORE_ENTRY 0xffff - - -struct hpack_dt_entry { - char *value; /* malloc'd */ - uint16_t value_len; - uint16_t hdr_len; /* virtual, for accounting */ - uint16_t lws_hdr_idx; /* LWS_HPACK_IGNORE_ENTRY = IGNORE */ -}; - -struct hpack_dynamic_table { - struct hpack_dt_entry *entries; /* malloc'd */ - uint32_t virtual_payload_usage; - uint32_t virtual_payload_max; - uint16_t pos; - uint16_t used_entries; - uint16_t num_entries; -}; - -enum lws_h2_protocol_send_type { - LWS_PPS_NONE, - LWS_H2_PPS_MY_SETTINGS, - LWS_H2_PPS_ACK_SETTINGS, - LWS_H2_PPS_PONG, - LWS_H2_PPS_GOAWAY, - LWS_H2_PPS_RST_STREAM, - LWS_H2_PPS_UPDATE_WINDOW, -}; - -struct lws_h2_protocol_send { - struct lws_h2_protocol_send *next; /* linked list */ - enum lws_h2_protocol_send_type type; - - union uu { - struct { - char str[32]; - uint32_t highest_sid; - uint32_t err; - } ga; - struct { - uint32_t sid; - uint32_t err; - } rs; - struct { - uint8_t ping_payload[8]; - } ping; - struct { - uint32_t sid; - uint32_t credit; - } update_window; - } u; -}; - -struct lws_h2_ghost_sid { - struct lws_h2_ghost_sid *next; - uint32_t sid; -}; - -#define LWS_H2_RX_SCRATCH_SIZE 512 - -/* - * http/2 connection info that is only used by the root connection that has - * the network connection. - * - * h2 tends to spawn many child connections from one network connection, so - * it's necessary to make members only needed by the network connection - * distinct and only malloc'd on network connections. - * - * There's only one HPACK parser per network connection. - * - * But there is an ah per logical child connection... the network connection - * fills it but it belongs to the logical child. - */ -struct lws_h2_netconn { - struct http2_settings set; - struct hpack_dynamic_table hpack_dyn_table; - uint8_t ping_payload[8]; - uint8_t one_setting[LWS_H2_SETTINGS_LEN]; - char goaway_str[32]; /* for rx */ - struct lws *swsi; - struct lws_h2_protocol_send *pps; /* linked list */ - char *rx_scratch; - - enum http2_hpack_state hpack; - enum http2_hpack_type hpack_type; - - unsigned int huff:1; - unsigned int value:1; - unsigned int unknown_header:1; - unsigned int cont_exp:1; - unsigned int cont_exp_headers:1; - unsigned int we_told_goaway:1; - unsigned int pad_length:1; - unsigned int collected_priority:1; - unsigned int is_first_header_char:1; - unsigned int zero_huff_padding:1; - unsigned int last_action_dyntable_resize:1; - - uint32_t hdr_idx; - uint32_t hpack_len; - uint32_t hpack_e_dep; - uint32_t count; - uint32_t preamble; - uint32_t length; - uint32_t sid; - uint32_t inside; - uint32_t highest_sid; - uint32_t highest_sid_opened; - uint32_t cont_exp_sid; - uint32_t dep; - uint32_t goaway_last_sid; - uint32_t goaway_err; - uint32_t hpack_hdr_len; - - uint32_t rx_scratch_pos; - uint32_t rx_scratch_len; - - uint16_t hpack_pos; - - uint8_t frame_state; - uint8_t type; - uint8_t flags; - uint8_t padding; - uint8_t weight_temp; - uint8_t huff_pad; - char first_hdr_char; - uint8_t hpack_m; - uint8_t ext_count; -}; - -struct _lws_h2_related { - /* - * having this first lets us also re-use all HTTP union code - * and in turn, http_mode_related has allocated headers in right - * place so we can use the header apis on the wsi directly still - */ - struct _lws_http_mode_related http; /* MUST BE FIRST IN STRUCT */ - - struct lws_h2_netconn *h2n; /* malloc'd for root net conn */ - struct lws *parent_wsi; - struct lws *child_list; - struct lws *sibling_list; - - char *pending_status_body; - - int tx_cr; - int peer_tx_cr_est; - unsigned int my_sid; - unsigned int child_count; - int my_priority; - uint32_t dependent_on; - - unsigned int END_STREAM:1; - unsigned int END_HEADERS:1; - unsigned int send_END_STREAM:1; - unsigned int GOING_AWAY; - unsigned int requested_POLLOUT:1; - unsigned int skint:1; - - uint16_t round_robin_POLLOUT; - uint16_t count_POLLOUT_children; - uint8_t h2_state; /* the RFC7540 state of the connection */ - uint8_t weight; - - uint8_t initialized; -}; - -#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->u.h2.parent_wsi) - -#endif - -struct _lws_websocket_related { - /* cheapest way to deal with ah overlap with ws union transition */ - struct _lws_header_related hdr; - char *rx_ubuf; - unsigned int rx_ubuf_alloc; - struct lws *rx_draining_ext_list; - struct lws *tx_draining_ext_list; - time_t time_next_ping_check; - size_t rx_packet_length; - unsigned int rx_ubuf_head; - unsigned char mask[4]; - /* Also used for close content... control opcode == < 128 */ - unsigned char ping_payload_buf[128 - 3 + LWS_PRE]; - - unsigned char ping_payload_len; - unsigned char mask_idx; - unsigned char opcode; - unsigned char rsv; - unsigned char rsv_first_msg; - /* zero if no info, or length including 2-byte close code */ - unsigned char close_in_ping_buffer_len; - unsigned char utf8; - unsigned char stashed_write_type; - unsigned char tx_draining_stashed_wp; - - unsigned int final:1; - unsigned int frame_is_binary:1; - unsigned int all_zero_nonce:1; - unsigned int this_frame_masked:1; - unsigned int inside_frame:1; /* next write will be more of frame */ - unsigned int clean_buffer:1; /* buffer not rewritten by extension */ - unsigned int payload_is_close:1; /* process as PONG, but it is close */ - unsigned int ping_pending_flag:1; - unsigned int continuation_possible:1; - unsigned int owed_a_fin:1; - unsigned int check_utf8:1; - unsigned int defeat_check_utf8:1; - unsigned int pmce_compressed_message:1; - unsigned int stashed_write_pending:1; - unsigned int rx_draining_ext:1; - unsigned int tx_draining_ext:1; - unsigned int send_check_ping:1; - unsigned int first_fragment:1; -}; - -#ifdef LWS_WITH_CGI - -#define LWS_HTTP_CHUNK_HDR_SIZE 16 - -enum { - SIGNIFICANT_HDR_CONTENT_LENGTH, - SIGNIFICANT_HDR_LOCATION, - SIGNIFICANT_HDR_STATUS, - SIGNIFICANT_HDR_TRANSFER_ENCODING, - - SIGNIFICANT_HDR_COUNT -}; - -/* wsi who is master of the cgi points to an lws_cgi */ - -struct lws_cgi { - struct lws_cgi *cgi_list; - struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */ - struct lws *wsi; /* owner */ - unsigned char *headers_buf; - unsigned char *headers_start; - unsigned char *headers_pos; - unsigned char *headers_dumped; - unsigned char *headers_end; - lws_filepos_t content_length; - lws_filepos_t content_length_seen; - int pipe_fds[3][2]; - int match[SIGNIFICANT_HDR_COUNT]; - int pid; - int response_code; - int lp; - char l[12]; - - unsigned int being_closed:1; - unsigned int explicitly_chunked:1; - - unsigned char chunked_grace; -}; -#endif - -signed char char_to_hex(const char c); - -#ifndef LWS_NO_CLIENT -enum lws_chunk_parser { - ELCP_HEX, - ELCP_CR, - ELCP_CONTENT, - ELCP_POST_CR, - ELCP_POST_LF, -}; -#endif - -enum lws_parse_urldecode_results { - LPUR_CONTINUE, - LPUR_SWALLOW, - LPUR_FORBID, - LPUR_EXCESSIVE, -}; - -struct lws_rewrite; - -#ifdef LWS_WITH_ACCESS_LOG -struct lws_access_log { - char *header_log; - char *user_agent; - char *referrer; - unsigned long sent; - int response; -}; -#endif - -struct lws { - - /* structs */ - /* members with mutually exclusive lifetimes are unionized */ - - union u { - struct _lws_http_mode_related http; -#ifdef LWS_WITH_HTTP2 - struct _lws_h2_related h2; -#endif - struct _lws_header_related hdr; - struct _lws_websocket_related ws; - } u; - - /* lifetime members */ - -#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) - struct lws_io_watcher w_read; -#endif -#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBEVENT) - struct lws_io_watcher w_write; -#endif -#ifdef LWS_WITH_ACCESS_LOG - struct lws_access_log access_log; -#endif - time_t pending_timeout_limit; - - /* pointers */ - - struct lws_context *context; - struct lws_vhost *vhost; - struct lws *parent; /* points to parent, if any */ - struct lws *child_list; /* points to first child */ - struct lws *sibling_list; /* subsequent children at same level */ -#ifdef LWS_WITH_CGI - struct lws_cgi *cgi; /* wsi being cgi master have one of these */ -#endif - const struct lws_protocols *protocol; - struct lws **same_vh_protocol_prev, *same_vh_protocol_next; - struct lws *timeout_list; - struct lws **timeout_list_prev; -#if defined(LWS_WITH_PEER_LIMITS) - struct lws_peer *peer; -#endif - - void *user_space; - void *opaque_parent_data; - /* rxflow handling */ - unsigned char *rxflow_buffer; - /* truncated send handling */ - unsigned char *trunc_alloc; /* non-NULL means buffering in progress */ - -#if defined (LWS_WITH_ESP8266) - void *premature_rx; - unsigned short prem_rx_size, prem_rx_pos; -#endif - -#ifndef LWS_NO_EXTENSIONS - const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE]; - void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE]; -#endif -#ifdef LWS_OPENSSL_SUPPORT - SSL *ssl; - BIO *client_bio; - struct lws *pending_read_list_prev, *pending_read_list_next; -#if defined(LWS_WITH_STATS) - uint64_t accept_start_us; - char seen_rx; -#endif -#endif -#ifdef LWS_WITH_HTTP_PROXY - struct lws_rewrite *rw; -#endif -#ifdef LWS_LATENCY - unsigned long action_start; - unsigned long latency_start; -#endif - lws_sock_file_fd_type desc; /* .filefd / .sockfd */ -#if defined(LWS_WITH_STATS) - uint64_t active_writable_req_us; -#endif - /* ints */ - int position_in_fds_table; - uint32_t rxflow_len; - uint32_t rxflow_pos; - unsigned int trunc_alloc_len; /* size of malloc */ - unsigned int trunc_offset; /* where we are in terms of spilling */ - unsigned int trunc_len; /* how much is buffered */ -#ifndef LWS_NO_CLIENT - int chunk_remaining; -#endif - unsigned int cache_secs; - - unsigned int hdr_parsing_completed:1; - unsigned int http2_substream:1; - unsigned int upgraded_to_http2:1; - unsigned int seen_nonpseudoheader:1; - unsigned int listener:1; - unsigned int user_space_externally_allocated:1; - unsigned int socket_is_permanently_unusable:1; - unsigned int rxflow_change_to:2; - unsigned int more_rx_waiting:1; /* has to live here since ah may stick to end */ - unsigned int conn_stat_done:1; - unsigned int cache_reuse:1; - unsigned int cache_revalidate:1; - unsigned int cache_intermediaries:1; - unsigned int favoured_pollin:1; - unsigned int sending_chunked:1; - unsigned int already_did_cce:1; - unsigned int told_user_closed:1; - unsigned int waiting_to_send_close_frame:1; - unsigned int ipv6:1; - unsigned int parent_carries_io:1; - unsigned int parent_pending_cb_on_writable:1; - unsigned int cgi_stdout_zero_length:1; - unsigned int seen_zero_length_recv:1; - unsigned int rxflow_will_be_applied:1; - -#if defined(LWS_WITH_ESP8266) - unsigned int pending_send_completion:3; - unsigned int close_is_pending_send_completion:1; -#endif -#ifdef LWS_WITH_ACCESS_LOG - unsigned int access_log_pending:1; -#endif -#ifndef LWS_NO_CLIENT - unsigned int do_ws:1; /* whether we are doing http or ws flow */ - unsigned int chunked:1; /* if the clientside connection is chunked */ - unsigned int client_rx_avail:1; - unsigned int client_http_body_pending:1; -#endif -#ifdef LWS_WITH_HTTP_PROXY - unsigned int perform_rewrite:1; -#endif -#ifndef LWS_NO_EXTENSIONS - unsigned int extension_data_pending:1; -#endif -#ifdef LWS_OPENSSL_SUPPORT - unsigned int use_ssl:4; -#endif -#ifdef _WIN32 - unsigned int sock_send_blocking:1; -#endif -#ifdef LWS_OPENSSL_SUPPORT - unsigned int redirect_to_https:1; -#endif - - /* volatile to make sure code is aware other thread can change */ - volatile unsigned int handling_pollout:1; - volatile unsigned int leave_pollout_active:1; - -#ifndef LWS_NO_CLIENT - unsigned short c_port; -#endif - - /* chars */ -#ifndef LWS_NO_EXTENSIONS - unsigned char count_act_ext; -#endif - uint8_t ietf_spec_revision; - char mode; /* enum connection_mode */ - char state; /* enum lws_connection_states */ - char state_pre_close; - char lws_rx_parse_state; /* enum lws_rx_parse_state */ - char rx_frame_type; /* enum lws_write_protocol */ - char pending_timeout; /* enum pending_timeout */ - char tsi; /* thread service index we belong to */ - char protocol_interpret_idx; - char redirects; - uint8_t rxflow_bitmap; -#ifdef LWS_WITH_CGI - char cgi_channel; /* which of stdin/out/err */ - char hdr_state; -#endif -#ifndef LWS_NO_CLIENT - char chunk_parser; /* enum lws_chunk_parser */ -#endif -#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT) - char reason_bf; /* internal writeable callback reason bitfield */ -#endif -}; - -#define lws_is_flowcontrolled(w) (!!(wsi->rxflow_bitmap)) - -LWS_EXTERN int log_level; - -LWS_EXTERN int -lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, - const char *iface); - -#if defined(LWS_WITH_IPV6) -LWS_EXTERN unsigned long -lws_get_addr_scope(const char *ipaddr); -#endif - -LWS_EXTERN void -lws_close_free_wsi(struct lws *wsi, enum lws_close_status); - -LWS_EXTERN void -lws_free_wsi(struct lws *wsi); - -LWS_EXTERN int -remove_wsi_socket_from_fds(struct lws *wsi); -LWS_EXTERN int -lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len); - -#ifndef LWS_LATENCY -static inline void -lws_latency(struct lws_context *context, struct lws *wsi, const char *action, - int ret, int completion) { - do { - (void)context; (void)wsi; (void)action; (void)ret; - (void)completion; - } while (0); -} -static inline void -lws_latency_pre(struct lws_context *context, struct lws *wsi) { - do { (void)context; (void)wsi; } while (0); -} -#else -#define lws_latency_pre(_context, _wsi) lws_latency(_context, _wsi, NULL, 0, 0) -extern void -lws_latency(struct lws_context *context, struct lws *wsi, const char *action, - int ret, int completion); -#endif - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_client_rx_sm(struct lws *wsi, unsigned char c); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_parse(struct lws *wsi, unsigned char c); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_parse_urldecode(struct lws *wsi, uint8_t *_c); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_http_action(struct lws *wsi); - -LWS_EXTERN int -lws_b64_selftest(void); - -LWS_EXTERN int -lws_service_flag_pending(struct lws_context *context, int tsi); - -#if defined(_WIN32) || defined(LWS_WITH_ESP8266) -LWS_EXTERN struct lws * -wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd); - -LWS_EXTERN int -insert_wsi(struct lws_context *context, struct lws *wsi); - -LWS_EXTERN int -delete_from_fd(struct lws_context *context, lws_sockfd_type fd); -#else -#define wsi_from_fd(A,B) A->lws_lookup[B] -#define insert_wsi(A,B) assert(A->lws_lookup[B->desc.sockfd] == 0); A->lws_lookup[B->desc.sockfd]=B -#define delete_from_fd(A,B) A->lws_lookup[B]=0 -#endif - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len); - - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_service_timeout_check(struct lws *wsi, unsigned int sec); - -LWS_EXTERN void -lws_remove_from_timeout_list(struct lws *wsi); - -LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT -lws_client_connect_2(struct lws *wsi); - -LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT -lws_client_reset(struct lws **wsi, int ssl, const char *address, int port, - const char *path, const char *host); - -LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT -lws_create_new_server_wsi(struct lws_vhost *vhost); - -LWS_EXTERN char * LWS_WARN_UNUSED_RESULT -lws_generate_client_handshake(struct lws *wsi, char *pkt); - -LWS_EXTERN int -lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd); - -LWS_EXTERN struct lws * -lws_client_connect_via_info2(struct lws *wsi); - -LWS_EXTERN int -_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah); - -/* - * EXTENSIONS - */ - -#ifndef LWS_NO_EXTENSIONS -LWS_VISIBLE void -lws_context_init_extensions(struct lws_context_creation_info *info, - struct lws_context *context); -LWS_EXTERN int -lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r, - void *v, size_t len); - -LWS_EXTERN int -lws_ext_cb_active(struct lws *wsi, int reason, void *buf, int len); -LWS_EXTERN int -lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, int reason, - void *arg, int len); - -#else -#define lws_any_extension_handled(_a, _b, _c, _d) (0) -#define lws_ext_cb_active(_a, _b, _c, _d) (0) -#define lws_ext_cb_all_exts(_a, _b, _c, _d, _e) (0) -#define lws_issue_raw_ext_access lws_issue_raw -#define lws_context_init_extensions(_a, _b) -#endif - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_client_interpret_server_handshake(struct lws *wsi); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_rx_sm(struct lws *wsi, unsigned char c); - -LWS_EXTERN int -lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, size_t *len); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len); - -LWS_EXTERN void -lws_union_transition(struct lws *wsi, enum connection_mode mode); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, - enum lws_callback_reasons reason, void *user, - void *in, size_t len); -#ifdef LWS_WITH_HTTP2 -struct lws * lws_h2_get_nth_child(struct lws *wsi, int n); -LWS_EXTERN void lws_h2_init(struct lws *wsi); -LWS_EXTERN int -lws_h2_settings(struct lws *nwsi, struct http2_settings *settings, - unsigned char *buf, int len); -LWS_EXTERN int -lws_h2_parser(struct lws *wsi, unsigned char c); -LWS_EXTERN int lws_h2_do_pps_send(struct lws *wsi); -LWS_EXTERN int lws_h2_frame_write(struct lws *wsi, int type, int flags, - unsigned int sid, unsigned int len, - unsigned char *buf); -LWS_EXTERN struct lws * -lws_h2_wsi_from_id(struct lws *wsi, unsigned int sid); -LWS_EXTERN int lws_hpack_interpret(struct lws *wsi, - unsigned char c); -LWS_EXTERN int -lws_add_http2_header_by_name(struct lws *wsi, - const unsigned char *name, - const unsigned char *value, int length, - unsigned char **p, unsigned char *end); -LWS_EXTERN int -lws_add_http2_header_by_token(struct lws *wsi, - enum lws_token_indexes token, - const unsigned char *value, int length, - unsigned char **p, unsigned char *end); -LWS_EXTERN int -lws_add_http2_header_status(struct lws *wsi, - unsigned int code, unsigned char **p, - unsigned char *end); -LWS_EXTERN int -lws_h2_configure_if_upgraded(struct lws *wsi); -LWS_EXTERN void -lws_hpack_destroy_dynamic_header(struct lws *wsi); -LWS_EXTERN int -lws_hpack_dynamic_size(struct lws *wsi, int size); -LWS_EXTERN int -lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason); -LWS_EXTERN int -lws_h2_tx_cr_get(struct lws *wsi); -LWS_EXTERN void -lws_h2_tx_cr_consume(struct lws *wsi, int consumed); -LWS_EXTERN int -lws_hdr_extant(struct lws *wsi, enum lws_token_indexes h); -LWS_EXTERN void -lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pss); - -LWS_EXTERN const struct http2_settings lws_h2_defaults; -#else -#define lws_h2_configure_if_upgraded(x) -#endif - -LWS_EXTERN int -lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd); - -LWS_EXTERN int -lws_plat_check_connection_error(struct lws *wsi); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_header_table_attach(struct lws *wsi, int autoservice); - -LWS_EXTERN int -lws_header_table_detach(struct lws *wsi, int autoservice); - -LWS_EXTERN void -lws_header_table_reset(struct lws *wsi, int autoservice); -void -_lws_header_table_reset(struct allocated_headers *ah); - -void -lws_header_table_force_to_detachable_state(struct lws *wsi); -int -lws_header_table_is_in_detachable_state(struct lws *wsi); - -LWS_EXTERN char * LWS_WARN_UNUSED_RESULT -lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ensure_user_space(struct lws *wsi); - -LWS_EXTERN int -lws_change_pollfd(struct lws *wsi, int _and, int _or); - -#ifndef LWS_NO_SERVER -int lws_context_init_server(struct lws_context_creation_info *info, - struct lws_vhost *vhost); -LWS_EXTERN struct lws_vhost * -lws_select_vhost(struct lws_context *context, int port, const char *servername); -LWS_EXTERN int -handshake_0405(struct lws_context *context, struct lws *wsi); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len); -LWS_EXTERN void -lws_server_get_canonical_hostname(struct lws_context *context, - struct lws_context_creation_info *info); -#else -#define lws_context_init_server(_a, _b) (0) -#define lws_interpret_incoming_packet(_a, _b, _c) (0) -#define lws_server_get_canonical_hostname(_a, _b) -#endif - -#ifndef LWS_NO_DAEMONIZE -LWS_EXTERN int get_daemonize_pid(); -#else -#define get_daemonize_pid() (0) -#endif - -#if !defined(LWS_WITH_ESP8266) -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -interface_to_sa(struct lws_vhost *vh, const char *ifname, - struct sockaddr_in *addr, size_t addrlen); -#endif -LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); - -enum lws_ssl_capable_status { - LWS_SSL_CAPABLE_ERROR = -1, - LWS_SSL_CAPABLE_MORE_SERVICE = -2, -}; - -#ifndef LWS_OPENSSL_SUPPORT -#define LWS_SSL_ENABLED(context) (0) -#define lws_context_init_server_ssl(_a, _b) (0) -#define lws_ssl_destroy(_a) -#define lws_context_init_http2_ssl(_a) -#define lws_ssl_capable_read lws_ssl_capable_read_no_ssl -#define lws_ssl_capable_write lws_ssl_capable_write_no_ssl -#define lws_ssl_pending lws_ssl_pending_no_ssl -#define lws_server_socket_service_ssl(_b, _c) (0) -#define lws_ssl_close(_a) (0) -#define lws_ssl_context_destroy(_a) -#define lws_ssl_SSL_CTX_destroy(_a) -#define lws_ssl_remove_wsi_from_buffered_list(_a) -#define lws_context_init_ssl_library(_a) -#define lws_ssl_anybody_has_buffered_read_tsi(_a, _b) (0) -#else -#define LWS_SSL_ENABLED(context) (context->use_ssl) -LWS_EXTERN int openssl_websocket_private_data_index; -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ssl_pending(struct lws *wsi); -LWS_EXTERN int -lws_context_init_ssl_library(struct lws_context_creation_info *info); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_server_socket_service_ssl(struct lws *new_wsi, lws_sockfd_type accept_fd); -LWS_EXTERN int -lws_ssl_close(struct lws *wsi); -LWS_EXTERN void -lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost); -LWS_EXTERN void -lws_ssl_context_destroy(struct lws_context *context); -LWS_VISIBLE void -lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi); -LWS_EXTERN int -lws_ssl_client_bio_create(struct lws *wsi); -LWS_EXTERN int -lws_ssl_client_connect1(struct lws *wsi); -LWS_EXTERN int -lws_ssl_client_connect2(struct lws *wsi); -LWS_EXTERN void -lws_ssl_elaborate_error(void); -LWS_EXTERN int -lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi); -#ifndef LWS_NO_SERVER -LWS_EXTERN int -lws_context_init_server_ssl(struct lws_context_creation_info *info, - struct lws_vhost *vhost); -#else -#define lws_context_init_server_ssl(_a, _b) (0) -#endif -LWS_EXTERN void -lws_ssl_destroy(struct lws_vhost *vhost); -/* HTTP2-related */ - -#ifdef LWS_WITH_HTTP2 -LWS_EXTERN void -lws_context_init_http2_ssl(struct lws_vhost *vhost); -#else -#define lws_context_init_http2_ssl(_a) -#endif -#endif - -#if LWS_MAX_SMP > 1 -static LWS_INLINE void -lws_pt_mutex_init(struct lws_context_per_thread *pt) -{ - pthread_mutex_init(&pt->lock, NULL); -} - -static LWS_INLINE void -lws_pt_mutex_destroy(struct lws_context_per_thread *pt) -{ - pthread_mutex_destroy(&pt->lock); -} - -static LWS_INLINE void -lws_pt_lock(struct lws_context_per_thread *pt) -{ - if (!pt->lock_depth++) - pthread_mutex_lock(&pt->lock); -} - -static LWS_INLINE void -lws_pt_unlock(struct lws_context_per_thread *pt) -{ - if (!(--pt->lock_depth)) - pthread_mutex_unlock(&pt->lock); -} -static LWS_INLINE void -lws_context_lock(struct lws_context *context) -{ - if (!context->lock_depth++) - pthread_mutex_lock(&context->lock); -} - -static LWS_INLINE void -lws_context_unlock(struct lws_context *context) -{ - if (!(--context->lock_depth)) - pthread_mutex_unlock(&context->lock); -} - -#else -#define lws_pt_mutex_init(_a) (void)(_a) -#define lws_pt_mutex_destroy(_a) (void)(_a) -#define lws_pt_lock(_a) (void)(_a) -#define lws_pt_unlock(_a) (void)(_a) -#define lws_context_lock(_a) (void)(_a) -#define lws_context_unlock(_a) (void)(_a) -#endif - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_ssl_pending_no_ssl(struct lws *wsi); - -#ifdef LWS_WITH_HTTP_PROXY -struct lws_rewrite { - hubbub_parser *parser; - hubbub_parser_optparams params; - const char *from, *to; - int from_len, to_len; - unsigned char *p, *end; - struct lws *wsi; -}; -static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len) -{ - if (s->len != len) - return 1; - - return strncmp((const char *)s->ptr, p, len); -} -typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw); -LWS_EXTERN struct lws_rewrite * -lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to); -LWS_EXTERN void -lws_rewrite_destroy(struct lws_rewrite *r); -LWS_EXTERN int -lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len); -#endif - -#ifndef LWS_NO_CLIENT -LWS_EXTERN int lws_client_socket_service(struct lws_context *context, - struct lws *wsi, - struct lws_pollfd *pollfd); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_http_transaction_completed_client(struct lws *wsi); -#ifdef LWS_OPENSSL_SUPPORT -LWS_EXTERN int -lws_context_init_client_ssl(struct lws_context_creation_info *info, - struct lws_vhost *vhost); - -LWS_EXTERN void -lws_ssl_info_callback(const SSL *ssl, int where, int ret); - -#else - #define lws_context_init_client_ssl(_a, _b) (0) -#endif -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len); -LWS_EXTERN void -lws_decode_ssl_error(void); -#else -#define lws_context_init_client_ssl(_a, _b) (0) -#define lws_handshake_client(_a, _b, _c) (0) -#endif - -LWS_EXTERN int -_lws_rx_flow_control(struct lws *wsi); - -LWS_EXTERN int -_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa); - -#ifndef LWS_NO_SERVER -LWS_EXTERN int -lws_server_socket_service(struct lws_context *context, struct lws *wsi, - struct lws_pollfd *pollfd); -LWS_EXTERN int -lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); -#else -#define lws_server_socket_service(_a, _b, _c) (0) -#define lws_handshake_server(_a, _b, _c) (0) -#endif - -#ifdef LWS_WITH_ACCESS_LOG -LWS_EXTERN int -lws_access_log(struct lws *wsi); -LWS_EXTERN void -lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth); -#else -#define lws_access_log(_a) -#endif - -LWS_EXTERN int -lws_cgi_kill_terminated(struct lws_context_per_thread *pt); - -LWS_EXTERN void -lws_cgi_remove_and_kill(struct lws *wsi); - -int -lws_protocol_init(struct lws_context *context); - -int -lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p); - -const struct lws_http_mount * -lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len); - -/* - * custom allocator - */ -LWS_EXTERN void * -lws_realloc(void *ptr, size_t size, const char *reason); - -LWS_EXTERN void * LWS_WARN_UNUSED_RESULT -lws_zalloc(size_t size, const char *reason); - -#ifdef LWS_PLAT_OPTEE -void *lws_malloc(size_t size, const char *reason); -void lws_free(void *p); -#define lws_free_set_NULL(P) do { lws_free(P); (P) = NULL; } while(0) -#else -#define lws_malloc(S, R) lws_realloc(NULL, S, R) -#define lws_free(P) lws_realloc(P, 0, "lws_free") -#define lws_free_set_NULL(P) do { lws_realloc(P, 0, "free"); (P) = NULL; } while(0) -#endif - -const struct lws_plat_file_ops * -lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path, - const char **vpath); - -/* lws_plat_ */ -LWS_EXTERN void -lws_plat_delete_socket_from_fds(struct lws_context *context, - struct lws *wsi, int m); -LWS_EXTERN void -lws_plat_insert_socket_into_fds(struct lws_context *context, - struct lws *wsi); -LWS_EXTERN void -lws_plat_service_periodic(struct lws_context *context); - -LWS_EXTERN int -lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi, - struct lws_pollfd *pfd); -LWS_EXTERN void -lws_add_wsi_to_draining_ext_list(struct lws *wsi); -LWS_EXTERN void -lws_remove_wsi_from_draining_ext_list(struct lws *wsi); -LWS_EXTERN int -lws_plat_context_early_init(void); -LWS_EXTERN void -lws_plat_context_early_destroy(struct lws_context *context); -LWS_EXTERN void -lws_plat_context_late_destroy(struct lws_context *context); -LWS_EXTERN int -lws_poll_listen_fd(struct lws_pollfd *fd); -LWS_EXTERN int -lws_plat_service(struct lws_context *context, int timeout_ms); -LWS_EXTERN LWS_VISIBLE int -_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi); -LWS_EXTERN int -lws_plat_init(struct lws_context *context, - struct lws_context_creation_info *info); -LWS_EXTERN void -lws_plat_drop_app_privileges(struct lws_context_creation_info *info); -LWS_EXTERN unsigned long long -time_in_microseconds(void); -LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT -lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt); -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_plat_inet_pton(int af, const char *src, void *dst); - -LWS_EXTERN int LWS_WARN_UNUSED_RESULT -lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len); -LWS_EXTERN int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount); -LWS_EXTERN int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount); - -LWS_EXTERN void -lws_same_vh_protocol_remove(struct lws *wsi); -LWS_EXTERN void -lws_same_vh_protocol_insert(struct lws *wsi, int n); - -#if defined(LWS_WITH_STATS) -void -lws_stats_atomic_bump(struct lws_context * context, - struct lws_context_per_thread *pt, int index, uint64_t bump); -void -lws_stats_atomic_max(struct lws_context * context, - struct lws_context_per_thread *pt, int index, uint64_t val); -#else -static inline uint64_t lws_stats_atomic_bump(struct lws_context * context, - struct lws_context_per_thread *pt, int index, uint64_t bump) { - (void)context; (void)pt; (void)index; (void)bump; return 0; } -static inline uint64_t lws_stats_atomic_max(struct lws_context * context, - struct lws_context_per_thread *pt, int index, uint64_t val) { - (void)context; (void)pt; (void)index; (void)val; return 0; } -#endif - -/* socks */ -void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, - ssize_t *msg_len); - -#if defined(LWS_WITH_PEER_LIMITS) -void -lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer); -int -lws_peer_confirm_ah_attach_ok(struct lws_context *context, struct lws_peer *peer); -void -lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer); -void -lws_peer_cull_peer_wait_list(struct lws_context *context); -struct lws_peer * -lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd); -void -lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer, - struct lws *wsi); -#endif - -#ifdef __cplusplus -}; -#endif diff --git a/thirdparty/lws/server/parsers.c b/thirdparty/lws/server/parsers.c deleted file mode 100644 index fb345ab04c..0000000000 --- a/thirdparty/lws/server/parsers.c +++ /dev/null @@ -1,1783 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -const unsigned char lextable[] = { - #include "lextable.h" -}; - -#define FAIL_CHAR 0x08 - -int LWS_WARN_UNUSED_RESULT -lextable_decode(int pos, char c) -{ - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - - while (1) { - if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */ - if ((lextable[pos] & 0x7f) != c) - return -1; - /* fall thru */ - pos++; - if (lextable[pos] == FAIL_CHAR) - return -1; - return pos; - } - - if (lextable[pos] == FAIL_CHAR) - return -1; - - /* b7 = 0, end or 3-byte */ - if (lextable[pos] < FAIL_CHAR) /* terminal marker */ - return pos; - - if (lextable[pos] == c) /* goto */ - return pos + (lextable[pos + 1]) + - (lextable[pos + 2] << 8); - /* fall thru goto */ - pos += 3; - /* continue */ - } -} - -static struct allocated_headers * -_lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size) -{ - struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct"); - - if (!ah) - return NULL; - - ah->data = lws_malloc(data_size, "ah data"); - if (!ah->data) { - lws_free(ah); - - return NULL; - } - ah->next = pt->ah_list; - pt->ah_list = ah; - ah->data_length = data_size; - pt->ah_pool_length++; - - lwsl_info("%s: created ah %p (size %d): pool length %d\n", __func__, - ah, (int)data_size, pt->ah_pool_length); - - return ah; -} - -int -_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah) -{ - lws_start_foreach_llp(struct allocated_headers **, a, pt->ah_list) { - if ((*a) == ah) { - *a = ah->next; - pt->ah_pool_length--; - lwsl_info("%s: freed ah %p : pool length %d\n", - __func__, ah, pt->ah_pool_length); - if (ah->data) - lws_free(ah->data); - lws_free(ah); - - return 0; - } - } lws_end_foreach_llp(a, next); - - return 1; -} - -void -_lws_header_table_reset(struct allocated_headers *ah) -{ - /* init the ah to reflect no headers or data have appeared yet */ - memset(ah->frag_index, 0, sizeof(ah->frag_index)); - memset(ah->frags, 0, sizeof(ah->frags)); - ah->nfrag = 0; - ah->pos = 0; - ah->http_response = 0; -} - -// doesn't scrub the ah rxbuffer by default, parent must do if needed - -void -lws_header_table_reset(struct lws *wsi, int autoservice) -{ - struct allocated_headers *ah = wsi->u.hdr.ah; - struct lws_context_per_thread *pt; - struct lws_pollfd *pfd; - - /* if we have the idea we're resetting 'our' ah, must be bound to one */ - assert(ah); - /* ah also concurs with ownership */ - assert(ah->wsi == wsi); - - _lws_header_table_reset(ah); - - wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; - wsi->u.hdr.lextable_pos = 0; - - /* since we will restart the ah, our new headers are not completed */ - wsi->hdr_parsing_completed = 0; - - /* while we hold the ah, keep a timeout on the wsi */ - lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH, - wsi->vhost->timeout_secs_ah_idle); - - time(&ah->assigned); - - /* - * if we inherited pending rx (from socket adoption deferred - * processing), apply and free it. - */ - if (wsi->u.hdr.preamble_rx) { - memcpy(ah->rx, wsi->u.hdr.preamble_rx, - wsi->u.hdr.preamble_rx_len); - ah->rxlen = wsi->u.hdr.preamble_rx_len; - lws_free_set_NULL(wsi->u.hdr.preamble_rx); - - if (autoservice) { - lwsl_debug("%s: service on readbuf ah\n", __func__); - - pt = &wsi->context->pt[(int)wsi->tsi]; - /* - * Unlike a normal connect, we have the headers already - * (or the first part of them anyway) - */ - pfd = &pt->fds[wsi->position_in_fds_table]; - pfd->revents |= LWS_POLLIN; - lwsl_err("%s: calling service\n", __func__); - lws_service_fd_tsi(wsi->context, pfd, wsi->tsi); - } - } -} - -static void -_lws_header_ensure_we_are_on_waiting_list(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - struct lws_pollargs pa; - struct lws **pwsi = &pt->ah_wait_list; - - while (*pwsi) { - if (*pwsi == wsi) - return; - pwsi = &(*pwsi)->u.hdr.ah_wait_list; - } - - lwsl_info("%s: wsi: %p\n", __func__, wsi); - wsi->u.hdr.ah_wait_list = pt->ah_wait_list; - pt->ah_wait_list = wsi; - pt->ah_wait_list_length++; - - /* we cannot accept input then */ - - _lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa); -} - -static int -__lws_remove_from_ah_waiting_list(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - struct lws **pwsi =&pt->ah_wait_list; - - while (*pwsi) { - if (*pwsi == wsi) { - lwsl_info("%s: wsi %p\n", __func__, wsi); - /* point prev guy to our next */ - *pwsi = wsi->u.hdr.ah_wait_list; - /* we shouldn't point anywhere now */ - wsi->u.hdr.ah_wait_list = NULL; - pt->ah_wait_list_length--; - - return 1; - } - pwsi = &(*pwsi)->u.hdr.ah_wait_list; - } - - return 0; -} - -int LWS_WARN_UNUSED_RESULT -lws_header_table_attach(struct lws *wsi, int autoservice) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - struct lws_pollargs pa; - int n; - - lwsl_info("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__, - (void *)wsi, (void *)wsi->u.hdr.ah, wsi->tsi, - pt->ah_count_in_use); - - /* if we are already bound to one, just clear it down */ - if (wsi->u.hdr.ah) { - lwsl_info("%s: cleardown\n", __func__); - goto reset; - } - - lws_pt_lock(pt); - - n = pt->ah_count_in_use == context->max_http_header_pool; -#if defined(LWS_WITH_PEER_LIMITS) - if (!n) { - n = lws_peer_confirm_ah_attach_ok(context, wsi->peer); - if (n) - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1); - } -#endif - if (n) { - /* - * Pool is either all busy, or we don't want to give this - * particular guy an ah right now... - * - * Make sure we are on the waiting list, and return that we - * weren't able to provide the ah - */ - _lws_header_ensure_we_are_on_waiting_list(wsi); - - goto bail; - } - - __lws_remove_from_ah_waiting_list(wsi); - - wsi->u.hdr.ah = _lws_create_ah(pt, context->max_http_header_data); - if (!wsi->u.hdr.ah) { /* we could not create an ah */ - _lws_header_ensure_we_are_on_waiting_list(wsi); - - goto bail; - } - - wsi->u.hdr.ah->in_use = 1; - wsi->u.hdr.ah->wsi = wsi; /* mark our owner */ - pt->ah_count_in_use++; - -#if defined(LWS_WITH_PEER_LIMITS) - if (wsi->peer) - wsi->peer->count_ah++; -#endif - - _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); - - lwsl_info("%s: did attach wsi %p: ah %p: count %d (on exit)\n", __func__, - (void *)wsi, (void *)wsi->u.hdr.ah, pt->ah_count_in_use); - - lws_pt_unlock(pt); - -reset: - - /* and reset the rx state */ - wsi->u.hdr.ah->rxpos = 0; - wsi->u.hdr.ah->rxlen = 0; - - lws_header_table_reset(wsi, autoservice); - -#ifndef LWS_NO_CLIENT - if (wsi->state == LWSS_CLIENT_UNCONNECTED) - if (!lws_client_connect_via_info2(wsi)) - /* our client connect has failed, the wsi - * has been closed - */ - return -1; -#endif - - return 0; - -bail: - lws_pt_unlock(pt); - - return 1; -} - -void -lws_header_table_force_to_detachable_state(struct lws *wsi) -{ - if (wsi->u.hdr.ah) { - wsi->u.hdr.ah->rxpos = -1; - wsi->u.hdr.ah->rxlen = -1; - wsi->hdr_parsing_completed = 1; - } -} - -int -lws_header_table_is_in_detachable_state(struct lws *wsi) -{ - struct allocated_headers *ah = wsi->u.hdr.ah; - - return ah && ah->rxpos == ah->rxlen && wsi->hdr_parsing_completed; -} - -int lws_header_table_detach(struct lws *wsi, int autoservice) -{ - struct lws_context *context = wsi->context; - struct allocated_headers *ah = wsi->u.hdr.ah; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - struct lws_pollargs pa; - struct lws **pwsi, **pwsi_eligible; - time_t now; - - lws_pt_lock(pt); - __lws_remove_from_ah_waiting_list(wsi); - lws_pt_unlock(pt); - - if (!ah) - return 0; - - lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__, - (void *)wsi, (void *)ah, wsi->tsi, - pt->ah_count_in_use); - - if (wsi->u.hdr.preamble_rx) - lws_free_set_NULL(wsi->u.hdr.preamble_rx); - - /* may not be detached while he still has unprocessed rx */ - if (!lws_header_table_is_in_detachable_state(wsi)) { - lwsl_err("%s: %p: CANNOT DETACH rxpos:%d, rxlen:%d, " - "wsi->hdr_parsing_completed = %d\n", __func__, wsi, - ah->rxpos, ah->rxlen, wsi->hdr_parsing_completed); - return 0; - } - - lws_pt_lock(pt); - - /* we did have an ah attached */ - time(&now); - if (ah->assigned && now - ah->assigned > 3) { - /* - * we're detaching the ah, but it was held an - * unreasonably long time - */ - lwsl_debug("%s: wsi %p: ah held %ds, " - "ah.rxpos %d, ah.rxlen %d, mode/state %d %d," - "wsi->more_rx_waiting %d\n", __func__, wsi, - (int)(now - ah->assigned), - ah->rxpos, ah->rxlen, wsi->mode, wsi->state, - wsi->more_rx_waiting); - } - - ah->assigned = 0; - - /* if we think we're detaching one, there should be one in use */ - assert(pt->ah_count_in_use > 0); - /* and this specific one should have been in use */ - assert(ah->in_use); - wsi->u.hdr.ah = NULL; - ah->wsi = NULL; /* no owner */ -#if defined(LWS_WITH_PEER_LIMITS) - lws_peer_track_ah_detach(context, wsi->peer); -#endif - - pwsi = &pt->ah_wait_list; - - /* oh there is nobody on the waiting list... leave the ah unattached */ - if (!*pwsi) - goto nobody_usable_waiting; - - /* - * at least one wsi on the same tsi is waiting, give it to oldest guy - * who is allowed to take it (if any) - */ - lwsl_info("pt wait list %p\n", *pwsi); - wsi = NULL; - pwsi_eligible = NULL; - - while (*pwsi) { -#if defined(LWS_WITH_PEER_LIMITS) - /* are we willing to give this guy an ah? */ - if (!lws_peer_confirm_ah_attach_ok(context, (*pwsi)->peer)) -#endif - { - wsi = *pwsi; - pwsi_eligible = pwsi; - } -#if defined(LWS_WITH_PEER_LIMITS) - else - if (!(*pwsi)->u.hdr.ah_wait_list) - lws_stats_atomic_bump(context, pt, - LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1); -#endif - pwsi = &(*pwsi)->u.hdr.ah_wait_list; - } - - if (!wsi) /* everybody waiting already has too many ah... */ - goto nobody_usable_waiting; - - lwsl_info("%s: last eligible wsi in wait list %p\n", __func__, wsi); - - wsi->u.hdr.ah = ah; - ah->wsi = wsi; /* new owner */ - - /* and reset the rx state */ - ah->rxpos = 0; - ah->rxlen = 0; - lws_header_table_reset(wsi, autoservice); -#if defined(LWS_WITH_PEER_LIMITS) - if (wsi->peer) - wsi->peer->count_ah++; -#endif - - /* clients acquire the ah and then insert themselves in fds table... */ - if (wsi->position_in_fds_table != -1) { - lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi); - - /* he has been stuck waiting for an ah, but now his wait is - * over, let him progress */ - - _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); - } - - /* point prev guy to next guy in list instead */ - *pwsi_eligible = wsi->u.hdr.ah_wait_list; - /* the guy who got one is out of the list */ - wsi->u.hdr.ah_wait_list = NULL; - pt->ah_wait_list_length--; - -#ifndef LWS_NO_CLIENT - if (wsi->state == LWSS_CLIENT_UNCONNECTED) { - lws_pt_unlock(pt); - - if (!lws_client_connect_via_info2(wsi)) { - /* our client connect has failed, the wsi - * has been closed - */ - - return -1; - } - return 0; - } -#endif - - assert(!!pt->ah_wait_list_length == !!(lws_intptr_t)pt->ah_wait_list); -bail: - lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__, - (void *)wsi, (void *)ah, pt->tid, pt->ah_count_in_use); - - lws_pt_unlock(pt); - - return 0; - -nobody_usable_waiting: - lwsl_info("%s: nobody usable waiting\n", __func__); - _lws_destroy_ah(pt, ah); - pt->ah_count_in_use--; - - goto bail; -} - -LWS_VISIBLE int -lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx) -{ - int n; - - if (!wsi->u.hdr.ah) - return 0; - - n = wsi->u.hdr.ah->frag_index[h]; - if (!n) - return 0; - do { - if (!frag_idx) - return wsi->u.hdr.ah->frags[n].len; - n = wsi->u.hdr.ah->frags[n].nfrag; - } while (frag_idx-- && n); - - return 0; -} - -LWS_VISIBLE int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h) -{ - int n; - int len = 0; - - if (!wsi->u.hdr.ah) - return 0; - - n = wsi->u.hdr.ah->frag_index[h]; - if (!n) - return 0; - do { - len += wsi->u.hdr.ah->frags[n].len; - n = wsi->u.hdr.ah->frags[n].nfrag; - } while (n); - - return len; -} - -LWS_VISIBLE int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len, - enum lws_token_indexes h, int frag_idx) -{ - int n = 0; - int f; - - if (!wsi->u.hdr.ah) - return -1; - - f = wsi->u.hdr.ah->frag_index[h]; - - if (!f) - return -1; - - while (n < frag_idx) { - f = wsi->u.hdr.ah->frags[f].nfrag; - if (!f) - return -1; - n++; - } - - if (wsi->u.hdr.ah->frags[f].len >= len) - return -1; - - memcpy(dst, wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[f].offset, - wsi->u.hdr.ah->frags[f].len); - dst[wsi->u.hdr.ah->frags[f].len] = '\0'; - - return wsi->u.hdr.ah->frags[f].len; -} - -LWS_VISIBLE int lws_hdr_copy(struct lws *wsi, char *dst, int len, - enum lws_token_indexes h) -{ - int toklen = lws_hdr_total_length(wsi, h); - int n; - - if (toklen >= len) - return -1; - - if (!wsi->u.hdr.ah) - return -1; - - n = wsi->u.hdr.ah->frag_index[h]; - if (!n) - return 0; - - do { - if (wsi->u.hdr.ah->frags[n].len >= len) - return -1; - strncpy(dst, &wsi->u.hdr.ah->data[wsi->u.hdr.ah->frags[n].offset], - wsi->u.hdr.ah->frags[n].len); - dst += wsi->u.hdr.ah->frags[n].len; - len -= wsi->u.hdr.ah->frags[n].len; - n = wsi->u.hdr.ah->frags[n].nfrag; - } while (n); - *dst = '\0'; - - return toklen; -} - -char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h) -{ - int n; - - n = wsi->u.hdr.ah->frag_index[h]; - if (!n) - return NULL; - - return wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[n].offset; -} - -int LWS_WARN_UNUSED_RESULT -lws_pos_in_bounds(struct lws *wsi) -{ - if (wsi->u.hdr.ah->pos < - (unsigned int)wsi->context->max_http_header_data) - return 0; - - if (wsi->u.hdr.ah->pos == wsi->context->max_http_header_data) { - lwsl_err("Ran out of header data space\n"); - return 1; - } - - /* - * with these tests everywhere, it should never be able to exceed - * the limit, only meet it - */ - lwsl_err("%s: pos %d, limit %d\n", __func__, wsi->u.hdr.ah->pos, - wsi->context->max_http_header_data); - assert(0); - - return 1; -} - -int LWS_WARN_UNUSED_RESULT -lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s) -{ - wsi->u.hdr.ah->nfrag++; - if (wsi->u.hdr.ah->nfrag == ARRAY_SIZE(wsi->u.hdr.ah->frags)) { - lwsl_warn("More hdr frags than we can deal with, dropping\n"); - return -1; - } - - wsi->u.hdr.ah->frag_index[h] = wsi->u.hdr.ah->nfrag; - - wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].offset = wsi->u.hdr.ah->pos; - wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len = 0; - wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].nfrag = 0; - - do { - if (lws_pos_in_bounds(wsi)) - return -1; - - wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = *s; - if (*s) - wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len++; - } while (*s++); - - return 0; -} - -signed char char_to_hex(const char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -1; -} - -static int LWS_WARN_UNUSED_RESULT -issue_char(struct lws *wsi, unsigned char c) -{ - unsigned short frag_len; - - if (lws_pos_in_bounds(wsi)) - return -1; - - frag_len = wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len; - /* - * If we haven't hit the token limit, just copy the character into - * the header - */ - if (frag_len < wsi->u.hdr.current_token_limit) { - wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c; - if (c) - wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len++; - return 0; - } - - /* Insert a null character when we *hit* the limit: */ - if (frag_len == wsi->u.hdr.current_token_limit) { - if (lws_pos_in_bounds(wsi)) - return -1; - - wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = '\0'; - lwsl_warn("header %i exceeds limit %d\n", - wsi->u.hdr.parser_state, - wsi->u.hdr.current_token_limit); - } - - return 1; -} - -int -lws_parse_urldecode(struct lws *wsi, uint8_t *_c) -{ - struct allocated_headers *ah = wsi->u.hdr.ah; - unsigned int enc = 0; - uint8_t c = *_c; - - /* - * PRIORITY 1 - * special URI processing... convert %xx - */ - switch (wsi->u.hdr.ues) { - case URIES_IDLE: - if (c == '%') { - wsi->u.hdr.ues = URIES_SEEN_PERCENT; - goto swallow; - } - break; - case URIES_SEEN_PERCENT: - if (char_to_hex(c) < 0) - /* illegal post-% char */ - goto forbid; - - wsi->u.hdr.esc_stash = c; - wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1; - goto swallow; - - case URIES_SEEN_PERCENT_H1: - if (char_to_hex(c) < 0) - /* illegal post-% char */ - goto forbid; - - *_c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) | - char_to_hex(c); - c = *_c; - enc = 1; - wsi->u.hdr.ues = URIES_IDLE; - break; - } - - /* - * PRIORITY 2 - * special URI processing... - * convert /.. or /... or /../ etc to / - * convert /./ to / - * convert // or /// etc to / - * leave /.dir or whatever alone - */ - - switch (wsi->u.hdr.ups) { - case URIPS_IDLE: - if (!c) - return -1; - /* genuine delimiter */ - if ((c == '&' || c == ';') && !enc) { - if (issue_char(wsi, c) < 0) - return -1; - /* swallow the terminator */ - ah->frags[ah->nfrag].len--; - /* link to next fragment */ - ah->frags[ah->nfrag].nfrag = ah->nfrag + 1; - ah->nfrag++; - if (ah->nfrag >= ARRAY_SIZE(ah->frags)) - goto excessive; - /* start next fragment after the & */ - wsi->u.hdr.post_literal_equal = 0; - ah->frags[ah->nfrag].offset = ah->pos; - ah->frags[ah->nfrag].len = 0; - ah->frags[ah->nfrag].nfrag = 0; - goto swallow; - } - /* uriencoded = in the name part, disallow */ - if (c == '=' && enc && - ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] && - !wsi->u.hdr.post_literal_equal) { - c = '_'; - *_c =c; - } - - /* after the real =, we don't care how many = */ - if (c == '=' && !enc) - wsi->u.hdr.post_literal_equal = 1; - - /* + to space */ - if (c == '+' && !enc) { - c = ' '; - *_c = c; - } - /* issue the first / always */ - if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) - wsi->u.hdr.ups = URIPS_SEEN_SLASH; - break; - case URIPS_SEEN_SLASH: - /* swallow subsequent slashes */ - if (c == '/') - goto swallow; - /* track and swallow the first . after / */ - if (c == '.') { - wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT; - goto swallow; - } - wsi->u.hdr.ups = URIPS_IDLE; - break; - case URIPS_SEEN_SLASH_DOT: - /* swallow second . */ - if (c == '.') { - wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT; - goto swallow; - } - /* change /./ to / */ - if (c == '/') { - wsi->u.hdr.ups = URIPS_SEEN_SLASH; - goto swallow; - } - /* it was like /.dir ... regurgitate the . */ - wsi->u.hdr.ups = URIPS_IDLE; - if (issue_char(wsi, '.') < 0) - return -1; - break; - - case URIPS_SEEN_SLASH_DOT_DOT: - - /* /../ or /..[End of URI] --> backup to last / */ - if (c == '/' || c == '?') { - /* - * back up one dir level if possible - * safe against header fragmentation because - * the method URI can only be in 1 fragment - */ - if (ah->frags[ah->nfrag].len > 2) { - ah->pos--; - ah->frags[ah->nfrag].len--; - do { - ah->pos--; - ah->frags[ah->nfrag].len--; - } while (ah->frags[ah->nfrag].len > 1 && - ah->data[ah->pos] != '/'); - } - wsi->u.hdr.ups = URIPS_SEEN_SLASH; - if (ah->frags[ah->nfrag].len > 1) - break; - goto swallow; - } - - /* /..[^/] ... regurgitate and allow */ - - if (issue_char(wsi, '.') < 0) - return -1; - if (issue_char(wsi, '.') < 0) - return -1; - wsi->u.hdr.ups = URIPS_IDLE; - break; - } - - if (c == '?' && !enc && - !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI arguments */ - if (wsi->u.hdr.ues != URIES_IDLE) - goto forbid; - - /* seal off uri header */ - if (issue_char(wsi, '\0') < 0) - return -1; - - /* move to using WSI_TOKEN_HTTP_URI_ARGS */ - ah->nfrag++; - if (ah->nfrag >= ARRAY_SIZE(ah->frags)) - goto excessive; - ah->frags[ah->nfrag].offset = ah->pos; - ah->frags[ah->nfrag].len = 0; - ah->frags[ah->nfrag].nfrag = 0; - - wsi->u.hdr.post_literal_equal = 0; - ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; - wsi->u.hdr.ups = URIPS_IDLE; - goto swallow; - } - - return LPUR_CONTINUE; - -swallow: - return LPUR_SWALLOW; - -forbid: - return LPUR_FORBID; - -excessive: - return LPUR_EXCESSIVE; -} - -static const unsigned char methods[] = { - WSI_TOKEN_GET_URI, - WSI_TOKEN_POST_URI, - WSI_TOKEN_OPTIONS_URI, - WSI_TOKEN_PUT_URI, - WSI_TOKEN_PATCH_URI, - WSI_TOKEN_DELETE_URI, - WSI_TOKEN_CONNECT, - WSI_TOKEN_HEAD_URI, -}; - -int LWS_WARN_UNUSED_RESULT -lws_parse(struct lws *wsi, unsigned char c) -{ - struct allocated_headers *ah = wsi->u.hdr.ah; - struct lws_context *context = wsi->context; - unsigned int n, m; - int r; - - assert(wsi->u.hdr.ah); - - switch (wsi->u.hdr.parser_state) { - default: - - lwsl_parser("WSI_TOK_(%d) '%c'\n", wsi->u.hdr.parser_state, c); - - /* collect into malloc'd buffers */ - /* optional initial space swallow */ - if (!ah->frags[ah->frag_index[wsi->u.hdr.parser_state]].len && - c == ' ') - break; - - for (m = 0; m < ARRAY_SIZE(methods); m++) - if (wsi->u.hdr.parser_state == methods[m]) - break; - if (m == ARRAY_SIZE(methods)) - /* it was not any of the methods */ - goto check_eol; - - /* special URI processing... end at space */ - - if (c == ' ') { - /* enforce starting with / */ - if (!ah->frags[ah->nfrag].len) - if (issue_char(wsi, '/') < 0) - return -1; - - if (wsi->u.hdr.ups == URIPS_SEEN_SLASH_DOT_DOT) { - /* - * back up one dir level if possible - * safe against header fragmentation because - * the method URI can only be in 1 fragment - */ - if (ah->frags[ah->nfrag].len > 2) { - ah->pos--; - ah->frags[ah->nfrag].len--; - do { - ah->pos--; - ah->frags[ah->nfrag].len--; - } while (ah->frags[ah->nfrag].len > 1 && - ah->data[ah->pos] != '/'); - } - } - - /* begin parsing HTTP version: */ - if (issue_char(wsi, '\0') < 0) - return -1; - wsi->u.hdr.parser_state = WSI_TOKEN_HTTP; - goto start_fragment; - } - - r = lws_parse_urldecode(wsi, &c); - switch (r) { - case LPUR_CONTINUE: - break; - case LPUR_SWALLOW: - goto swallow; - case LPUR_FORBID: - goto forbid; - case LPUR_EXCESSIVE: - goto excessive; - default: - return -1; - } -check_eol: - /* bail at EOL */ - if (wsi->u.hdr.parser_state != WSI_TOKEN_CHALLENGE && - c == '\x0d') { - if (wsi->u.hdr.ues != URIES_IDLE) - goto forbid; - - c = '\0'; - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR; - lwsl_parser("*\n"); - } - - n = issue_char(wsi, c); - if ((int)n < 0) - return -1; - if (n > 0) - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; - -swallow: - /* per-protocol end of headers management */ - - if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE) - goto set_parsing_complete; - break; - - /* collecting and checking a name part */ - case WSI_TOKEN_NAME_PART: - lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X (mode=%d) wsi->u.hdr.lextable_pos=%d\n", c, c, wsi->mode, wsi->u.hdr.lextable_pos); - - wsi->u.hdr.lextable_pos = - lextable_decode(wsi->u.hdr.lextable_pos, c); - /* - * Server needs to look out for unknown methods... - */ - if (wsi->u.hdr.lextable_pos < 0 && - (wsi->mode == LWSCM_HTTP_SERVING)) { - /* this is not a header we know about */ - for (m = 0; m < ARRAY_SIZE(methods); m++) - if (ah->frag_index[methods[m]]) { - /* - * already had the method, no idea what - * this crap from the client is, ignore - */ - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; - break; - } - /* - * hm it's an unknown http method from a client in fact, - * it cannot be valid http - */ - if (m == ARRAY_SIZE(methods)) { - /* - * are we set up to accept raw in these cases? - */ - if (lws_check_opt(wsi->vhost->options, - LWS_SERVER_OPTION_FALLBACK_TO_RAW)) - return 2; /* transition to raw */ - - lwsl_info("Unknown method - dropping\n"); - goto forbid; - } - break; - } - /* - * ...otherwise for a client, let him ignore unknown headers - * coming from the server - */ - if (wsi->u.hdr.lextable_pos < 0) { - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; - break; - } - - if (lextable[wsi->u.hdr.lextable_pos] < FAIL_CHAR) { - /* terminal state */ - - n = ((unsigned int)lextable[wsi->u.hdr.lextable_pos] << 8) | - lextable[wsi->u.hdr.lextable_pos + 1]; - - lwsl_parser("known hdr %d\n", n); - for (m = 0; m < ARRAY_SIZE(methods); m++) - if (n == methods[m] && - ah->frag_index[methods[m]]) { - lwsl_warn("Duplicated method\n"); - return -1; - } - - /* - * WSORIGIN is protocol equiv to ORIGIN, - * JWebSocket likes to send it, map to ORIGIN - */ - if (n == WSI_TOKEN_SWORIGIN) - n = WSI_TOKEN_ORIGIN; - - wsi->u.hdr.parser_state = (enum lws_token_indexes) - (WSI_TOKEN_GET_URI + n); - - if (context->token_limits) - wsi->u.hdr.current_token_limit = - context->token_limits->token_limit[ - wsi->u.hdr.parser_state]; - else - wsi->u.hdr.current_token_limit = - wsi->context->max_http_header_data; - - if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE) - goto set_parsing_complete; - - goto start_fragment; - } - break; - -start_fragment: - ah->nfrag++; -excessive: - if (ah->nfrag == ARRAY_SIZE(ah->frags)) { - lwsl_warn("More hdr frags than we can deal with\n"); - return -1; - } - - ah->frags[ah->nfrag].offset = ah->pos; - ah->frags[ah->nfrag].len = 0; - ah->frags[ah->nfrag].nfrag = 0; - ah->frags[ah->nfrag].flags = 2; - - n = ah->frag_index[wsi->u.hdr.parser_state]; - if (!n) { /* first fragment */ - ah->frag_index[wsi->u.hdr.parser_state] = ah->nfrag; - ah->hdr_token_idx = wsi->u.hdr.parser_state; - break; - } - /* continuation */ - while (ah->frags[n].nfrag) - n = ah->frags[n].nfrag; - ah->frags[n].nfrag = ah->nfrag; - - if (issue_char(wsi, ' ') < 0) - return -1; - break; - - /* skipping arg part of a name we didn't recognize */ - case WSI_TOKEN_SKIPPING: - lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c); - - if (c == '\x0d') - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR; - break; - - case WSI_TOKEN_SKIPPING_SAW_CR: - lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c); - if (wsi->u.hdr.ues != URIES_IDLE) - goto forbid; - if (c == '\x0a') { - wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; - wsi->u.hdr.lextable_pos = 0; - } else - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; - break; - /* we're done, ignore anything else */ - - case WSI_PARSING_COMPLETE: - lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c); - break; - } - - return 0; - -set_parsing_complete: - if (wsi->u.hdr.ues != URIES_IDLE) - goto forbid; - if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { - if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION)) - wsi->ietf_spec_revision = - atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION)); - - lwsl_parser("v%02d hdrs completed\n", wsi->ietf_spec_revision); - } - wsi->u.hdr.parser_state = WSI_PARSING_COMPLETE; - wsi->hdr_parsing_completed = 1; - - return 0; - -forbid: - lwsl_notice(" forbidding on uri sanitation\n"); - lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); - - return -1; -} - -LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi) -{ - return wsi->u.ws.frame_is_binary; -} - -void -lws_add_wsi_to_draining_ext_list(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - - if (wsi->u.ws.rx_draining_ext) - return; - - lwsl_ext("%s: RX EXT DRAINING: Adding to list\n", __func__); - - wsi->u.ws.rx_draining_ext = 1; - wsi->u.ws.rx_draining_ext_list = pt->rx_draining_ext_list; - pt->rx_draining_ext_list = wsi; -} - -void -lws_remove_wsi_from_draining_ext_list(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - struct lws **w = &pt->rx_draining_ext_list; - - if (!wsi->u.ws.rx_draining_ext) - return; - - lwsl_ext("%s: RX EXT DRAINING: Removing from list\n", __func__); - - wsi->u.ws.rx_draining_ext = 0; - - /* remove us from context draining ext list */ - while (*w) { - if (*w == wsi) { - /* if us, point it instead to who we were pointing to */ - *w = wsi->u.ws.rx_draining_ext_list; - break; - } - w = &((*w)->u.ws.rx_draining_ext_list); - } - wsi->u.ws.rx_draining_ext_list = NULL; -} - -/* - * client-parser.c: lws_client_rx_sm() needs to be roughly kept in - * sync with changes here, esp related to ext draining - */ - -int -lws_rx_sm(struct lws *wsi, unsigned char c) -{ - int callback_action = LWS_CALLBACK_RECEIVE; - int ret = 0, n, rx_draining_ext = 0; - struct lws_tokens eff_buf; - - eff_buf.token = NULL; - eff_buf.token_len = 0; - if (wsi->socket_is_permanently_unusable) - return -1; - - switch (wsi->lws_rx_parse_state) { - case LWS_RXPS_NEW: - if (wsi->u.ws.rx_draining_ext) { - eff_buf.token = NULL; - eff_buf.token_len = 0; - lws_remove_wsi_from_draining_ext_list(wsi); - rx_draining_ext = 1; - lwsl_debug("%s: doing draining flow\n", __func__); - - goto drain_extension; - } - switch (wsi->ietf_spec_revision) { - case 13: - /* - * no prepended frame key any more - */ - wsi->u.ws.all_zero_nonce = 1; - goto handle_first; - - default: - lwsl_warn("lws_rx_sm: unknown spec version %d\n", - wsi->ietf_spec_revision); - break; - } - break; - case LWS_RXPS_04_mask_1: - wsi->u.ws.mask[1] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - wsi->lws_rx_parse_state = LWS_RXPS_04_mask_2; - break; - case LWS_RXPS_04_mask_2: - wsi->u.ws.mask[2] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - wsi->lws_rx_parse_state = LWS_RXPS_04_mask_3; - break; - case LWS_RXPS_04_mask_3: - wsi->u.ws.mask[3] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - - /* - * start from the zero'th byte in the XOR key buffer since - * this is the start of a frame with a new key - */ - - wsi->u.ws.mask_idx = 0; - - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1; - break; - - /* - * 04 logical framing from the spec (all this is masked when incoming - * and has to be unmasked) - * - * We ignore the possibility of extension data because we don't - * negotiate any extensions at the moment. - * - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-------+-+-------------+-------------------------------+ - * |F|R|R|R| opcode|R| Payload len | Extended payload length | - * |I|S|S|S| (4) |S| (7) | (16/63) | - * |N|V|V|V| |V| | (if payload len==126/127) | - * | |1|2|3| |4| | | - * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - * | Extended payload length continued, if payload len == 127 | - * + - - - - - - - - - - - - - - - +-------------------------------+ - * | | Extension data | - * +-------------------------------+ - - - - - - - - - - - - - - - + - * : : - * +---------------------------------------------------------------+ - * : Application data : - * +---------------------------------------------------------------+ - * - * We pass payload through to userland as soon as we get it, ignoring - * FIN. It's up to userland to buffer it up if it wants to see a - * whole unfragmented block of the original size (which may be up to - * 2^63 long!) - */ - - case LWS_RXPS_04_FRAME_HDR_1: -handle_first: - - wsi->u.ws.opcode = c & 0xf; - wsi->u.ws.rsv = c & 0x70; - wsi->u.ws.final = !!((c >> 7) & 1); - - switch (wsi->u.ws.opcode) { - case LWSWSOPC_TEXT_FRAME: - case LWSWSOPC_BINARY_FRAME: - wsi->u.ws.rsv_first_msg = (c & 0x70); - wsi->u.ws.frame_is_binary = - wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME; - wsi->u.ws.first_fragment = 1; - break; - case 3: - case 4: - case 5: - case 6: - case 7: - case 0xb: - case 0xc: - case 0xd: - case 0xe: - case 0xf: - lwsl_info("illegal opcode\n"); - return -1; - } - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN: - - wsi->u.ws.this_frame_masked = !!(c & 0x80); - - switch (c & 0x7f) { - case 126: - /* control frames are not allowed to have big lengths */ - if (wsi->u.ws.opcode & 8) - goto illegal_ctl_length; - - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; - break; - case 127: - /* control frames are not allowed to have big lengths */ - if (wsi->u.ws.opcode & 8) - goto illegal_ctl_length; - - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; - break; - default: - wsi->u.ws.rx_packet_length = c & 0x7f; - if (wsi->u.ws.this_frame_masked) - wsi->lws_rx_parse_state = - LWS_RXPS_07_COLLECT_FRAME_KEY_1; - else - if (wsi->u.ws.rx_packet_length) - wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - else { - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - goto spill; - } - break; - } - break; - - case LWS_RXPS_04_FRAME_HDR_LEN16_2: - wsi->u.ws.rx_packet_length = c << 8; - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN16_1: - wsi->u.ws.rx_packet_length |= c; - if (wsi->u.ws.this_frame_masked) - wsi->lws_rx_parse_state = - LWS_RXPS_07_COLLECT_FRAME_KEY_1; - else - wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_8: - if (c & 0x80) { - lwsl_warn("b63 of length must be zero\n"); - /* kill the connection */ - return -1; - } -#if defined __LP64__ - wsi->u.ws.rx_packet_length = ((size_t)c) << 56; -#else - wsi->u.ws.rx_packet_length = 0; -#endif - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_7: -#if defined __LP64__ - wsi->u.ws.rx_packet_length |= ((size_t)c) << 48; -#endif - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_6: -#if defined __LP64__ - wsi->u.ws.rx_packet_length |= ((size_t)c) << 40; -#endif - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_5: -#if defined __LP64__ - wsi->u.ws.rx_packet_length |= ((size_t)c) << 32; -#endif - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_4: - wsi->u.ws.rx_packet_length |= ((size_t)c) << 24; - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_3: - wsi->u.ws.rx_packet_length |= ((size_t)c) << 16; - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_2: - wsi->u.ws.rx_packet_length |= ((size_t)c) << 8; - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; - break; - - case LWS_RXPS_04_FRAME_HDR_LEN64_1: - wsi->u.ws.rx_packet_length |= ((size_t)c); - if (wsi->u.ws.this_frame_masked) - wsi->lws_rx_parse_state = - LWS_RXPS_07_COLLECT_FRAME_KEY_1; - else - wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - break; - - case LWS_RXPS_07_COLLECT_FRAME_KEY_1: - wsi->u.ws.mask[0] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; - break; - - case LWS_RXPS_07_COLLECT_FRAME_KEY_2: - wsi->u.ws.mask[1] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; - break; - - case LWS_RXPS_07_COLLECT_FRAME_KEY_3: - wsi->u.ws.mask[2] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; - break; - - case LWS_RXPS_07_COLLECT_FRAME_KEY_4: - wsi->u.ws.mask[3] = c; - if (c) - wsi->u.ws.all_zero_nonce = 0; - wsi->lws_rx_parse_state = - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; - wsi->u.ws.mask_idx = 0; - if (wsi->u.ws.rx_packet_length == 0) { - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - goto spill; - } - break; - - - case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: - assert(wsi->u.ws.rx_ubuf); - - if (wsi->u.ws.rx_draining_ext) - goto drain_extension; - - if (wsi->u.ws.rx_ubuf_head + LWS_PRE >= - wsi->u.ws.rx_ubuf_alloc) { - lwsl_err("Attempted overflow \n"); - return -1; - } - if (wsi->u.ws.all_zero_nonce) - wsi->u.ws.rx_ubuf[LWS_PRE + - (wsi->u.ws.rx_ubuf_head++)] = c; - else - wsi->u.ws.rx_ubuf[LWS_PRE + - (wsi->u.ws.rx_ubuf_head++)] = - c ^ wsi->u.ws.mask[ - (wsi->u.ws.mask_idx++) & 3]; - - if (--wsi->u.ws.rx_packet_length == 0) { - /* spill because we have the whole frame */ - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - goto spill; - } - - /* - * if there's no protocol max frame size given, we are - * supposed to default to context->pt_serv_buf_size - */ - if (!wsi->protocol->rx_buffer_size && - wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size) - break; - - if (wsi->protocol->rx_buffer_size && - wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size) - break; - - /* spill because we filled our rx buffer */ -spill: - /* - * is this frame a control packet we should take care of at this - * layer? If so service it and hide it from the user callback - */ - - lwsl_parser("spill on %s\n", wsi->protocol->name); - - switch (wsi->u.ws.opcode) { - case LWSWSOPC_CLOSE: - - /* is this an acknowledgement of our close? */ - if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { - /* - * fine he has told us he is closing too, let's - * finish our close - */ - lwsl_parser("seen client close ack\n"); - return -1; - } - if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) - /* if he sends us 2 CLOSE, kill him */ - return -1; - - if (lws_partial_buffered(wsi)) { - /* - * if we're in the middle of something, - * we can't do a normal close response and - * have to just close our end. - */ - wsi->socket_is_permanently_unusable = 1; - lwsl_parser("Closing on peer close due to Pending tx\n"); - return -1; - } - - if (user_callback_handle_rxflow( - wsi->protocol->callback, wsi, - LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, - wsi->user_space, - &wsi->u.ws.rx_ubuf[LWS_PRE], - wsi->u.ws.rx_ubuf_head)) - return -1; - - lwsl_parser("server sees client close packet\n"); - wsi->state = LWSS_RETURNED_CLOSE_ALREADY; - /* deal with the close packet contents as a PONG */ - wsi->u.ws.payload_is_close = 1; - goto process_as_ping; - - case LWSWSOPC_PING: - lwsl_info("received %d byte ping, sending pong\n", - wsi->u.ws.rx_ubuf_head); - - if (wsi->u.ws.ping_pending_flag) { - /* - * there is already a pending ping payload - * we should just log and drop - */ - lwsl_parser("DROP PING since one pending\n"); - goto ping_drop; - } -process_as_ping: - /* control packets can only be < 128 bytes long */ - if (wsi->u.ws.rx_ubuf_head > 128 - 3) { - lwsl_parser("DROP PING payload too large\n"); - goto ping_drop; - } - - /* stash the pong payload */ - memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE, - &wsi->u.ws.rx_ubuf[LWS_PRE], - wsi->u.ws.rx_ubuf_head); - - wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head; - wsi->u.ws.ping_pending_flag = 1; - - /* get it sent as soon as possible */ - lws_callback_on_writable(wsi); -ping_drop: - wsi->u.ws.rx_ubuf_head = 0; - return 0; - - case LWSWSOPC_PONG: - lwsl_info("received pong\n"); - lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE], - wsi->u.ws.rx_ubuf_head); - - if (wsi->pending_timeout == PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { - lwsl_info("received expected PONG on wsi %p\n", wsi); - lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - } - - /* issue it */ - callback_action = LWS_CALLBACK_RECEIVE_PONG; - break; - - case LWSWSOPC_TEXT_FRAME: - case LWSWSOPC_BINARY_FRAME: - case LWSWSOPC_CONTINUATION: - break; - - default: - lwsl_parser("passing opc %x up to exts\n", - wsi->u.ws.opcode); - /* - * It's something special we can't understand here. - * Pass the payload up to the extension's parsing - * state machine. - */ - - eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; - eff_buf.token_len = wsi->u.ws.rx_ubuf_head; - - if (lws_ext_cb_active(wsi, LWS_EXT_CB_EXTENDED_PAYLOAD_RX, - &eff_buf, 0) <= 0) - /* not handle or fail */ - lwsl_ext("ext opc opcode 0x%x unknown\n", - wsi->u.ws.opcode); - - wsi->u.ws.rx_ubuf_head = 0; - return 0; - } - - /* - * No it's real payload, pass it up to the user callback. - * It's nicely buffered with the pre-padding taken care of - * so it can be sent straight out again using lws_write - */ - - eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; - eff_buf.token_len = wsi->u.ws.rx_ubuf_head; - - if (wsi->u.ws.opcode == LWSWSOPC_PONG && !eff_buf.token_len) - goto already_done; - -drain_extension: - lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); - - if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || - wsi->state == LWSS_AWAITING_CLOSE_ACK) - goto already_done; - - n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); - /* - * eff_buf may be pointing somewhere completely different now, - * it's the output - */ - wsi->u.ws.first_fragment = 0; - if (n < 0) { - /* - * we may rely on this to get RX, just drop connection - */ - wsi->socket_is_permanently_unusable = 1; - return -1; - } - - if (rx_draining_ext && eff_buf.token_len == 0) - goto already_done; - - if (n && eff_buf.token_len) - /* extension had more... main loop will come back */ - lws_add_wsi_to_draining_ext_list(wsi); - else - lws_remove_wsi_from_draining_ext_list(wsi); - - if (eff_buf.token_len > 0 || - callback_action == LWS_CALLBACK_RECEIVE_PONG) { - eff_buf.token[eff_buf.token_len] = '\0'; - - if (wsi->protocol->callback) { - - if (callback_action == LWS_CALLBACK_RECEIVE_PONG) - lwsl_info("Doing pong callback\n"); - - ret = user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, - (enum lws_callback_reasons)callback_action, - wsi->user_space, - eff_buf.token, - eff_buf.token_len); - } - else - lwsl_err("No callback on payload spill!\n"); - } - -already_done: - wsi->u.ws.rx_ubuf_head = 0; - break; - } - - return ret; - -illegal_ctl_length: - - lwsl_warn("Control frame with xtended length is illegal\n"); - /* kill the connection */ - return -1; -} - -LWS_VISIBLE size_t -lws_remaining_packet_payload(struct lws *wsi) -{ - return wsi->u.ws.rx_packet_length; -} - -/* Once we reach LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED, we know how much - * to expect in that state and can deal with it in bulk more efficiently. - */ - -int -lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, - size_t *len) -{ - unsigned char *buffer = *buf, mask[4]; - int buffer_size, n; - unsigned int avail; - char *rx_ubuf; - - if (wsi->protocol->rx_buffer_size) - buffer_size = wsi->protocol->rx_buffer_size; - else - buffer_size = wsi->context->pt_serv_buf_size; - avail = buffer_size - wsi->u.ws.rx_ubuf_head; - - /* do not consume more than we should */ - if (avail > wsi->u.ws.rx_packet_length) - avail = wsi->u.ws.rx_packet_length; - - /* do not consume more than what is in the buffer */ - if (avail > *len) - avail = *len; - - /* we want to leave 1 byte for the parser to handle properly */ - if (avail <= 1) - return 0; - - avail--; - rx_ubuf = wsi->u.ws.rx_ubuf + LWS_PRE + wsi->u.ws.rx_ubuf_head; - if (wsi->u.ws.all_zero_nonce) - memcpy(rx_ubuf, buffer, avail); - else { - - for (n = 0; n < 4; n++) - mask[n] = wsi->u.ws.mask[(wsi->u.ws.mask_idx + n) & 3]; - - /* deal with 4-byte chunks using unwrapped loop */ - n = avail >> 2; - while (n--) { - *(rx_ubuf++) = *(buffer++) ^ mask[0]; - *(rx_ubuf++) = *(buffer++) ^ mask[1]; - *(rx_ubuf++) = *(buffer++) ^ mask[2]; - *(rx_ubuf++) = *(buffer++) ^ mask[3]; - } - /* and the remaining bytes bytewise */ - for (n = 0; n < (int)(avail & 3); n++) - *(rx_ubuf++) = *(buffer++) ^ mask[n]; - - wsi->u.ws.mask_idx = (wsi->u.ws.mask_idx + avail) & 3; - } - - (*buf) += avail; - wsi->u.ws.rx_ubuf_head += avail; - wsi->u.ws.rx_packet_length -= avail; - *len -= avail; - - return avail; -} diff --git a/thirdparty/lws/server/ranges.c b/thirdparty/lws/server/ranges.c deleted file mode 100644 index bc1578d733..0000000000 --- a/thirdparty/lws/server/ranges.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * RFC7233 ranges parser - * - * Copyright (C) 2016 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -/* - * RFC7233 examples - * - * o The first 500 bytes (byte offsets 0-499, inclusive): - * - * bytes=0-499 - * - * o The second 500 bytes (byte offsets 500-999, inclusive): - * - * bytes=500-999 - * - * o The final 500 bytes (byte offsets 9500-9999, inclusive): - * - * bytes=-500 - * - * Or: - * - * bytes=9500- - * - * o The first and last bytes only (bytes 0 and 9999): - * - * bytes=0-0,-1 - * - * o Other valid (but not canonical) specifications of the second 500 - * bytes (byte offsets 500-999, inclusive): - * - * bytes=500-600,601-999 - * bytes=500-700,601-999 - */ - -/* - * returns 1 if the range struct represents a usable range - * if no ranges header, you get one of these for the whole - * file. Otherwise you get one for each valid range in the - * header. - * - * returns 0 if no further valid range forthcoming; rp->state - * may be LWSRS_SYNTAX or LWSRS_COMPLETED - */ - -int -lws_ranges_next(struct lws_range_parsing *rp) -{ - static const char * const beq = "bytes="; - char c; - - while (1) { - - c = rp->buf[rp->pos]; - - switch (rp->state) { - case LWSRS_SYNTAX: - case LWSRS_COMPLETED: - return 0; - - case LWSRS_NO_ACTIVE_RANGE: - rp->state = LWSRS_COMPLETED; - return 0; - - case LWSRS_BYTES_EQ: // looking for "bytes=" - if (c != beq[rp->pos]) { - rp->state = LWSRS_SYNTAX; - return -1; - } - if (rp->pos == 5) - rp->state = LWSRS_FIRST; - break; - - case LWSRS_FIRST: - rp->start = 0; - rp->end = 0; - rp->start_valid = 0; - rp->end_valid = 0; - - rp->state = LWSRS_STARTING; - - // fallthru - - case LWSRS_STARTING: - if (c == '-') { - rp->state = LWSRS_ENDING; - break; - } - - if (!(c >= '0' && c <= '9')) { - rp->state = LWSRS_SYNTAX; - return 0; - } - rp->start = (rp->start * 10) + (c - '0'); - rp->start_valid = 1; - break; - - case LWSRS_ENDING: - if (c == ',' || c == '\0') { - rp->state = LWSRS_FIRST; - if (c == ',') - rp->pos++; - - /* - * By the end of this, start and end are - * always valid if the range still is - */ - - if (!rp->start_valid) { /* eg, -500 */ - if (rp->end > rp->extent) - rp->end = rp->extent; - - rp->start = rp->extent - rp->end; - rp->end = rp->extent - 1; - } else - if (!rp->end_valid) - rp->end = rp->extent - 1; - - rp->did_try = 1; - - /* end must be >= start or ignore it */ - if (rp->end < rp->start) { - if (c == ',') - break; - rp->state = LWSRS_COMPLETED; - return 0; - } - - return 1; /* issue range */ - } - - if (!(c >= '0' && c <= '9')) { - rp->state = LWSRS_SYNTAX; - return 0; - } - rp->end = (rp->end * 10) + (c - '0'); - rp->end_valid = 1; - break; - } - - rp->pos++; - } -} - -void -lws_ranges_reset(struct lws_range_parsing *rp) -{ - rp->pos = 0; - rp->ctr = 0; - rp->start = 0; - rp->end = 0; - rp->start_valid = 0; - rp->end_valid = 0; - rp->state = LWSRS_BYTES_EQ; -} - -/* - * returns count of valid ranges - */ -int -lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp, - unsigned long long extent) -{ - rp->agg = 0; - rp->send_ctr = 0; - rp->inside = 0; - rp->count_ranges = 0; - rp->did_try = 0; - lws_ranges_reset(rp); - rp->state = LWSRS_COMPLETED; - - rp->extent = extent; - - if (lws_hdr_copy(wsi, (char *)rp->buf, sizeof(rp->buf), - WSI_TOKEN_HTTP_RANGE) <= 0) - return 0; - - rp->state = LWSRS_BYTES_EQ; - - while (lws_ranges_next(rp)) { - rp->count_ranges++; - rp->agg += rp->end - rp->start + 1; - } - - lwsl_debug("%s: count %d\n", __func__, rp->count_ranges); - lws_ranges_reset(rp); - - if (rp->did_try && !rp->count_ranges) - return -1; /* "not satisfiable */ - - lws_ranges_next(rp); - - return rp->count_ranges; -} diff --git a/thirdparty/lws/server/server-handshake.c b/thirdparty/lws/server/server-handshake.c deleted file mode 100644 index 3d319c35d6..0000000000 --- a/thirdparty/lws/server/server-handshake.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } - -#ifndef LWS_NO_EXTENSIONS -static int -lws_extension_server_handshake(struct lws *wsi, char **p, int budget) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - char ext_name[64], *args, *end = (*p) + budget - 1; - const struct lws_ext_options *opts, *po; - const struct lws_extension *ext; - struct lws_ext_option_arg oa; - int n, m, more = 1; - int ext_count = 0; - char ignore; - char *c; - - /* - * Figure out which extensions the client has that we want to - * enable on this connection, and give him back the list - */ - if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) - return 0; - - /* - * break down the list of client extensions - * and go through them - */ - - if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size, - WSI_TOKEN_EXTENSIONS) < 0) - return 1; - - c = (char *)pt->serv_buf; - lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c); - wsi->count_act_ext = 0; - ignore = 0; - n = 0; - args = NULL; - - /* - * We may get a simple request - * - * Sec-WebSocket-Extensions: permessage-deflate - * - * or an elaborated one with requested options - * - * Sec-WebSocket-Extensions: permessage-deflate; \ - * server_no_context_takeover; \ - * client_no_context_takeover - */ - - while (more) { - - if (*c && (*c != ',' && *c != '\t')) { - if (*c == ';') { - ignore = 1; - args = c + 1; - } - if (ignore || *c == ' ') { - c++; - continue; - } - ext_name[n] = *c++; - if (n < sizeof(ext_name) - 1) - n++; - continue; - } - ext_name[n] = '\0'; - - ignore = 0; - if (!*c) - more = 0; - else { - c++; - if (!n) - continue; - } - - while (args && *args && *args == ' ') - args++; - - /* check a client's extension against our support */ - - ext = wsi->vhost->extensions; - - while (ext && ext->callback) { - - if (strcmp(ext_name, ext->name)) { - ext++; - continue; - } - - /* - * oh, we do support this one he asked for... but let's - * confirm he only gave it once - */ - for (m = 0; m < wsi->count_act_ext; m++) - if (wsi->active_extensions[m] == ext) { - lwsl_info("extension mentioned twice\n"); - return 1; /* shenanigans */ - } - - /* - * ask user code if it's OK to apply it on this - * particular connection + protocol - */ - m = (wsi->protocol->callback)(wsi, - LWS_CALLBACK_CONFIRM_EXTENSION_OKAY, - wsi->user_space, ext_name, 0); - - /* - * zero return from callback means go ahead and allow - * the extension, it's what we get if the callback is - * unhandled - */ - if (m) { - ext++; - continue; - } - - /* apply it */ - - ext_count++; - - /* instantiate the extension on this conn */ - - wsi->active_extensions[wsi->count_act_ext] = ext; - - /* allow him to construct his context */ - - if (ext->callback(lws_get_context(wsi), ext, wsi, - LWS_EXT_CB_CONSTRUCT, - (void *)&wsi->act_ext_user[ - wsi->count_act_ext], - (void *)&opts, 0)) { - lwsl_info("ext %s failed construction\n", - ext_name); - ext_count--; - ext++; - - continue; - } - - if (ext_count > 1) - *(*p)++ = ','; - else - LWS_CPYAPP(*p, - "\x0d\x0aSec-WebSocket-Extensions: "); - *p += lws_snprintf(*p, (end - *p), "%s", ext_name); - - /* - * go through the options trying to apply the - * recognized ones - */ - - lwsl_debug("ext args %s", args); - - while (args && *args && *args != ',') { - while (*args == ' ') - args++; - po = opts; - while (po->name) { - lwsl_debug("'%s' '%s'\n", po->name, args); - /* only support arg-less options... */ - if (po->type == EXTARG_NONE && - !strncmp(args, po->name, - strlen(po->name))) { - oa.option_name = NULL; - oa.option_index = po - opts; - oa.start = NULL; - lwsl_debug("setting %s\n", po->name); - if (!ext->callback( - lws_get_context(wsi), ext, wsi, - LWS_EXT_CB_OPTION_SET, - wsi->act_ext_user[ - wsi->count_act_ext], - &oa, (end - *p))) { - - *p += lws_snprintf(*p, (end - *p), "; %s", po->name); - lwsl_debug("adding option %s\n", po->name); - } - } - po++; - } - while (*args && *args != ',' && *args != ';') - args++; - } - - wsi->count_act_ext++; - lwsl_parser("count_act_ext <- %d\n", - wsi->count_act_ext); - - ext++; - } - - n = 0; - args = NULL; - } - - return 0; -} -#endif -int -handshake_0405(struct lws_context *context, struct lws *wsi) -{ - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - struct lws_process_html_args args; - unsigned char hash[20]; - int n, accept_len; - char *response; - char *p; - - if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) || - !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) { - lwsl_parser("handshake_04 missing pieces\n"); - /* completed header processing, but missing some bits */ - goto bail; - } - - if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >= MAX_WEBSOCKET_04_KEY_LEN) { - lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN); - goto bail; - } - - /* - * since key length is restricted above (currently 128), cannot - * overflow - */ - n = sprintf((char *)pt->serv_buf, - "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", - lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY)); - - lws_SHA1(pt->serv_buf, n, hash); - - accept_len = lws_b64_encode_string((char *)hash, 20, - (char *)pt->serv_buf, context->pt_serv_buf_size); - if (accept_len < 0) { - lwsl_warn("Base64 encoded hash too long\n"); - goto bail; - } - - /* allocate the per-connection user memory (if any) */ - if (lws_ensure_user_space(wsi)) - goto bail; - - /* create the response packet */ - - /* make a buffer big enough for everything */ - - response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + LWS_PRE; - p = response; - LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" - "Upgrade: WebSocket\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Accept: "); - strcpy(p, (char *)pt->serv_buf); - p += accept_len; - - /* we can only return the protocol header if: - * - one came in, and ... */ - if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && - /* - it is not an empty string */ - wsi->protocol->name && - wsi->protocol->name[0]) { - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); - p += lws_snprintf(p, 128, "%s", wsi->protocol->name); - } - -#ifndef LWS_NO_EXTENSIONS - /* - * Figure out which extensions the client has that we want to - * enable on this connection, and give him back the list. - * - * Give him a limited write bugdet - */ - if (lws_extension_server_handshake(wsi, &p, 192)) - goto bail; -#endif - LWS_CPYAPP(p, "\x0d\x0a"); - - args.p = p; - args.max_len = ((char *)pt->serv_buf + context->pt_serv_buf_size) - p; - if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, - LWS_CALLBACK_ADD_HEADERS, - wsi->user_space, &args, 0)) - goto bail; - - p = args.p; - - /* end of response packet */ - - LWS_CPYAPP(p, "\x0d\x0a"); - - if (!lws_any_extension_handled(wsi, LWS_EXT_CB_HANDSHAKE_REPLY_TX, - response, p - response)) { - - /* okay send the handshake response accepting the connection */ - - lwsl_parser("issuing resp pkt %d len\n", (int)(p - response)); -#if defined(DEBUG) && ! defined(LWS_WITH_ESP8266) - fwrite(response, 1, p - response, stderr); -#endif - n = lws_write(wsi, (unsigned char *)response, - p - response, LWS_WRITE_HTTP_HEADERS); - if (n != (p - response)) { - lwsl_debug("handshake_0405: ERROR writing to socket\n"); - goto bail; - } - - } - - /* alright clean up and set ourselves into established state */ - - wsi->state = LWSS_ESTABLISHED; - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - - { - const char * uri_ptr = - lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); - int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); - const struct lws_http_mount *hit = - lws_find_mount(wsi, uri_ptr, uri_len); - if (hit && hit->cgienv && - wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, - wsi->user_space, (void *)hit->cgienv, 0)) - return 1; - } - - return 0; - - -bail: - /* caller will free up his parsing allocations */ - return -1; -} - diff --git a/thirdparty/lws/server/ssl-server.c b/thirdparty/lws/server/ssl-server.c deleted file mode 100644 index c4362824bf..0000000000 --- a/thirdparty/lws/server/ssl-server.c +++ /dev/null @@ -1,477 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -extern int openssl_websocket_private_data_index, - openssl_SSL_CTX_private_data_index; - -extern void -lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info); - -#if !defined(LWS_WITH_MBEDTLS) -static int -OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) -{ - SSL *ssl; - int n; - struct lws *wsi; - - ssl = X509_STORE_CTX_get_ex_data(x509_ctx, - SSL_get_ex_data_X509_STORE_CTX_idx()); - - /* - * !!! nasty openssl requires the index to come as a library-scope - * static - */ - wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); - - n = wsi->vhost->protocols[0].callback(wsi, - LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION, - x509_ctx, ssl, preverify_ok); - - /* convert return code from 0 = OK to 1 = OK */ - return !n; -} -#endif - -static int -lws_context_ssl_init_ecdh(struct lws_vhost *vhost) -{ -#ifdef LWS_SSL_SERVER_WITH_ECDH_CERT - EC_KEY *EC_key = NULL; - EVP_PKEY *pkey; - int KeyType; - X509 *x; - - if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH)) - return 0; - - lwsl_notice(" Using ECDH certificate support\n"); - - /* Get X509 certificate from ssl context */ - x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0); - if (!x) { - lwsl_err("%s: x is NULL\n", __func__); - return 1; - } - /* Get the public key from certificate */ - pkey = X509_get_pubkey(x); - if (!pkey) { - lwsl_err("%s: pkey is NULL\n", __func__); - - return 1; - } - /* Get the key type */ - KeyType = EVP_PKEY_type(pkey->type); - - if (EVP_PKEY_EC != KeyType) { - lwsl_notice("Key type is not EC\n"); - return 0; - } - /* Get the key */ - EC_key = EVP_PKEY_get1_EC_KEY(pkey); - /* Set ECDH parameter */ - if (!EC_key) { - lwsl_err("%s: ECDH key is NULL \n", __func__); - return 1; - } - SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, EC_key); - EC_KEY_free(EC_key); -#endif - return 0; -} - -static int -lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info, - struct lws_vhost *vhost) -{ -#if defined(LWS_HAVE_OPENSSL_ECDH_H) && !defined(LWS_WITH_MBEDTLS) - EC_KEY *ecdh; - int ecdh_nid; - const char *ecdh_curve = "prime256v1"; - - if (info->ecdh_curve) - ecdh_curve = info->ecdh_curve; - - ecdh_nid = OBJ_sn2nid(ecdh_curve); - if (NID_undef == ecdh_nid) { - lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve); - return 1; - } - - ecdh = EC_KEY_new_by_curve_name(ecdh_nid); - if (NULL == ecdh) { - lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve); - return 1; - } - SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, ecdh); - EC_KEY_free(ecdh); - - SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_ECDH_USE); - - lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve); -#else -#if !defined(LWS_WITH_MBEDTLS) - lwsl_notice(" OpenSSL doesn't support ECDH\n"); -#endif -#endif - return 0; -} - -#if !defined(LWS_WITH_MBEDTLS) && defined(SSL_TLSEXT_ERR_NOACK) && !defined(OPENSSL_NO_TLSEXT) -static int -lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) -{ - struct lws_context *context = (struct lws_context *)arg; - struct lws_vhost *vhost, *vh; - const char *servername; - - if (!ssl) - return SSL_TLSEXT_ERR_NOACK; - - /* - * We can only get ssl accepted connections by using a vhost's ssl_ctx - * find out which listening one took us and only match vhosts on the - * same port. - */ - vh = context->vhost_list; - while (vh) { - if (!vh->being_destroyed && ssl && vh->ssl_ctx == SSL_get_SSL_CTX(ssl)) - break; - vh = vh->vhost_next; - } - - if (!vh) { - assert(vh); /* can't match the incoming vh? */ - return SSL_TLSEXT_ERR_OK; - } - - servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (!servername) { - /* the client doesn't know what hostname it wants */ - lwsl_info("SNI: Unknown ServerName: %s\n", servername); - - return SSL_TLSEXT_ERR_OK; - } - - vhost = lws_select_vhost(context, vh->listen_port, servername); - if (!vhost) { - lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port); - - return SSL_TLSEXT_ERR_OK; - } - - lwsl_info("SNI: Found: %s:%d\n", servername, vh->listen_port); - - /* select the ssl ctx from the selected vhost for this conn */ - SSL_set_SSL_CTX(ssl, vhost->ssl_ctx); - - return SSL_TLSEXT_ERR_OK; -} -#endif - -LWS_VISIBLE int -lws_context_init_server_ssl(struct lws_context_creation_info *info, - struct lws_vhost *vhost) -{ - struct lws_context *context = vhost->context; - struct lws wsi; - unsigned long error; - int n; - - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) { - vhost->use_ssl = 0; - return 0; - } - - /* - * If he is giving a cert filepath, take it as a sign he wants to use - * it on this vhost. User code can leave the cert filepath NULL and - * set the LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX option itself, in - * which case he's expected to set up the cert himself at - * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which - * provides the vhost SSL_CTX * in the user parameter. - */ - if (info->ssl_cert_filepath) - info->options |= LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; - - if (info->port != CONTEXT_PORT_NO_LISTEN) { - - vhost->use_ssl = lws_check_opt(info->options, - LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX); - - if (vhost->use_ssl && info->ssl_cipher_list) - lwsl_notice(" SSL ciphers: '%s'\n", info->ssl_cipher_list); - - if (vhost->use_ssl) - lwsl_notice(" Using SSL mode\n"); - else - lwsl_notice(" Using non-SSL mode\n"); - } - - /* - * give him a fake wsi with context + vhost set, so he can use - * lws_get_context() in the callback - */ - memset(&wsi, 0, sizeof(wsi)); - wsi.vhost = vhost; - wsi.context = context; - - (void)n; - (void)error; - - /* - * Firefox insists on SSLv23 not SSLv3 - * Konq disables SSLv2 by default now, SSLv23 works - * - * SSLv23_server_method() is the openssl method for "allow all TLS - * versions", compared to e.g. TLSv1_2_server_method() which only allows - * tlsv1.2. Unwanted versions must be disabled using SSL_CTX_set_options() - */ -#if !defined(LWS_WITH_MBEDTLS) - { - SSL_METHOD *method; - - method = (SSL_METHOD *)SSLv23_server_method(); - if (!method) { - error = ERR_get_error(); - lwsl_err("problem creating ssl method %lu: %s\n", - error, ERR_error_string(error, - (char *)context->pt[0].serv_buf)); - return 1; - } - vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ - if (!vhost->ssl_ctx) { - error = ERR_get_error(); - lwsl_err("problem creating ssl context %lu: %s\n", - error, ERR_error_string(error, - (char *)context->pt[0].serv_buf)); - return 1; - } - } -#else - { - const SSL_METHOD *method = TLSv1_2_server_method(); - - vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ - if (!vhost->ssl_ctx) { - lwsl_err("problem creating ssl context\n"); - return 1; - } - - } -#endif -#if !defined(LWS_WITH_MBEDTLS) - - /* associate the lws context with the SSL_CTX */ - - SSL_CTX_set_ex_data(vhost->ssl_ctx, - openssl_SSL_CTX_private_data_index, (char *)vhost->context); - /* Disable SSLv2 and SSLv3 */ - SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); -#ifdef SSL_OP_NO_COMPRESSION - SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_COMPRESSION); -#endif - SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_DH_USE); - SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - - if (info->ssl_cipher_list) - SSL_CTX_set_cipher_list(vhost->ssl_ctx, - info->ssl_cipher_list); -#endif - - /* as a server, are we requiring clients to identify themselves? */ - - if (lws_check_opt(info->options, - LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) { - int verify_options = SSL_VERIFY_PEER; - - if (!lws_check_opt(info->options, - LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED)) - verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - -#if !defined(LWS_WITH_MBEDTLS) - SSL_CTX_set_session_id_context(vhost->ssl_ctx, - (unsigned char *)context, sizeof(void *)); - - /* absolutely require the client cert */ - - SSL_CTX_set_verify(vhost->ssl_ctx, - verify_options, OpenSSL_verify_callback); -#endif - } - -#if !defined(LWS_WITH_MBEDTLS) && !defined(OPENSSL_NO_TLSEXT) - SSL_CTX_set_tlsext_servername_callback(vhost->ssl_ctx, - lws_ssl_server_name_cb); - SSL_CTX_set_tlsext_servername_arg(vhost->ssl_ctx, context); -#endif - - /* - * give user code a chance to load certs into the server - * allowing it to verify incoming client certs - */ -#if !defined(LWS_WITH_MBEDTLS) - if (info->ssl_ca_filepath && - !SSL_CTX_load_verify_locations(vhost->ssl_ctx, - info->ssl_ca_filepath, NULL)) { - lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n", __func__); - } -#endif - if (vhost->use_ssl) { - if (lws_context_ssl_init_ecdh_curve(info, vhost)) - return -1; - - vhost->protocols[0].callback(&wsi, - LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, - vhost->ssl_ctx, NULL, 0); - } - - if (lws_check_opt(info->options, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT)) - /* Normally SSL listener rejects non-ssl, optionally allow */ - vhost->allow_non_ssl_on_ssl_port = 1; - - if (info->ssl_options_set) - SSL_CTX_set_options(vhost->ssl_ctx, info->ssl_options_set); - -/* SSL_clear_options introduced in 0.9.8m */ -#if !defined(LWS_WITH_MBEDTLS) -#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL) - if (info->ssl_options_clear) - SSL_CTX_clear_options(vhost->ssl_ctx, info->ssl_options_clear); -#endif -#endif - - lwsl_info(" SSL options 0x%lX\n", SSL_CTX_get_options(vhost->ssl_ctx)); - - if (vhost->use_ssl && info->ssl_cert_filepath) { - /* - * The user code can choose to either pass the cert and - * key filepaths using the info members like this, or it can - * leave them NULL; force the vhost SSL_CTX init using the info - * options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and - * set up the cert himself using the user callback - * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which - * happened just above and has the vhost SSL_CTX * in the user - * parameter. - */ -#if !defined(LWS_WITH_MBEDTLS) - /* set the local certificate from CertFile */ - n = SSL_CTX_use_certificate_chain_file(vhost->ssl_ctx, - info->ssl_cert_filepath); - if (n != 1) { - error = ERR_get_error(); - lwsl_err("problem getting cert '%s' %lu: %s\n", - info->ssl_cert_filepath, - error, - ERR_error_string(error, - (char *)context->pt[0].serv_buf)); - return 1; - } - lws_ssl_bind_passphrase(vhost->ssl_ctx, info); -#else - uint8_t *p; - lws_filepos_t flen; - int err; - - if (alloc_pem_to_der_file(vhost->context, info->ssl_cert_filepath, &p, - &flen)) { - lwsl_err("couldn't find cert file %s\n", - info->ssl_cert_filepath); - - return 1; - } - err = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, flen, p); - if (!err) { - lwsl_err("Problem loading cert\n"); - return 1; - } -#if !defined(LWS_WITH_ESP32) - free(p); - p = NULL; -#endif - - if (info->ssl_private_key_filepath) { - if (alloc_pem_to_der_file(vhost->context, - info->ssl_private_key_filepath, &p, &flen)) { - lwsl_err("couldn't find cert file %s\n", - info->ssl_cert_filepath); - - return 1; - } - err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen); - if (!err) { - lwsl_err("Problem loading key\n"); - - return 1; - } - } - -#if !defined(LWS_WITH_ESP32) - free(p); - p = NULL; -#endif -#endif - if (info->ssl_private_key_filepath != NULL) { -#if !defined(LWS_WITH_MBEDTLS) - /* set the private key from KeyFile */ - if (SSL_CTX_use_PrivateKey_file(vhost->ssl_ctx, - info->ssl_private_key_filepath, - SSL_FILETYPE_PEM) != 1) { - error = ERR_get_error(); - lwsl_err("ssl problem getting key '%s' %lu: %s\n", - info->ssl_private_key_filepath, error, - ERR_error_string(error, - (char *)context->pt[0].serv_buf)); - return 1; - } -#endif - } else - if (vhost->protocols[0].callback(&wsi, - LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY, - vhost->ssl_ctx, NULL, 0)) { - lwsl_err("ssl private key not set\n"); - - return 1; - } -#if !defined(LWS_WITH_MBEDTLS) - /* verify private key */ - if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) { - lwsl_err("Private SSL key doesn't match cert\n"); - return 1; - } -#endif - } - if (vhost->use_ssl) { - if (lws_context_ssl_init_ecdh(vhost)) - return 1; - - /* - * SSL is happy and has a cert it's content with - * If we're supporting HTTP2, initialize that - */ - lws_context_init_http2_ssl(vhost); - } - - return 0; -} - diff --git a/thirdparty/lws/service.c b/thirdparty/lws/service.c deleted file mode 100644 index 8cf455e2c9..0000000000 --- a/thirdparty/lws/service.c +++ /dev/null @@ -1,1714 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" - -static int -lws_calllback_as_writeable(struct lws *wsi) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - int n; - - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1); -#if defined(LWS_WITH_STATS) - if (wsi->active_writable_req_us) { - uint64_t ul = time_in_microseconds() - - wsi->active_writable_req_us; - - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_MS_WRITABLE_DELAY, ul); - lws_stats_atomic_max(wsi->context, pt, - LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); - wsi->active_writable_req_us = 0; - } -#endif - - switch (wsi->mode) { - case LWSCM_RAW: - n = LWS_CALLBACK_RAW_WRITEABLE; - break; - case LWSCM_RAW_FILEDESC: - n = LWS_CALLBACK_RAW_WRITEABLE_FILE; - break; - case LWSCM_WS_CLIENT: - n = LWS_CALLBACK_CLIENT_WRITEABLE; - break; - case LWSCM_WSCL_ISSUE_HTTP_BODY: - n = LWS_CALLBACK_CLIENT_HTTP_WRITEABLE; - break; - case LWSCM_WS_SERVING: - n = LWS_CALLBACK_SERVER_WRITEABLE; - break; - default: - n = LWS_CALLBACK_HTTP_WRITEABLE; - break; - } - - return user_callback_handle_rxflow(wsi->protocol->callback, - wsi, (enum lws_callback_reasons) n, - wsi->user_space, NULL, 0); -} - -LWS_VISIBLE int -lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) -{ - int write_type = LWS_WRITE_PONG; - struct lws_tokens eff_buf; -#ifdef LWS_WITH_HTTP2 - struct lws **wsi2, *wsi2a; -#endif - int ret, m, n; - - wsi->leave_pollout_active = 0; - wsi->handling_pollout = 1; - /* - * if another thread wants POLLOUT on us, from here on while - * handling_pollout is set, he will only set leave_pollout_active. - * If we are going to disable POLLOUT, we will check that first. - */ - - /* - * user callback is lowest priority to get these notifications - * actually, since other pending things cannot be disordered - */ - - /* Priority 1: pending truncated sends are incomplete ws fragments - * If anything else sent first the protocol would be - * corrupted. - */ - if (wsi->trunc_len) { - //lwsl_notice("%s: completing partial\n", __func__); - if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, - wsi->trunc_len) < 0) { - lwsl_info("%s signalling to close\n", __func__); - goto bail_die; - } - /* leave POLLOUT active either way */ - goto bail_ok; - } else - if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { - wsi->socket_is_permanently_unusable = 1; - goto bail_die; /* retry closing now */ - } - - if (wsi->mode == LWSCM_WSCL_ISSUE_HTTP_BODY) - goto user_service; - -#ifdef LWS_WITH_HTTP2 - /* - * Priority 2: protocol packets - */ - if (wsi->upgraded_to_http2 && wsi->u.h2.h2n->pps) { - lwsl_info("servicing pps\n"); - if (lws_h2_do_pps_send(wsi)) { - wsi->socket_is_permanently_unusable = 1; - goto bail_die; - } - if (wsi->u.h2.h2n->pps) - goto bail_ok; - - /* we can resume whatever we were doing */ - lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_ENABLE | - LWS_RXFLOW_REASON_H2_PPS_PENDING); - - goto bail_ok; /* leave POLLOUT active */ - } -#endif - -#ifdef LWS_WITH_CGI - if (wsi->cgi) { - /* also one shot */ - if (pollfd) - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - return 1; - } - goto user_service_go_again; - } -#endif - - /* Priority 3: pending control packets (pong or close) - * - * 3a: close notification packet requested from close api - */ - - if (wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION) { - lwsl_debug("sending close packet\n"); - wsi->waiting_to_send_close_frame = 0; - n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], - wsi->u.ws.close_in_ping_buffer_len, - LWS_WRITE_CLOSE); - if (n >= 0) { - wsi->state = LWSS_AWAITING_CLOSE_ACK; - lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 1); - lwsl_debug("sent close indication, awaiting ack\n"); - - goto bail_ok; - } - - goto bail_die; - } - - /* else, the send failed and we should just hang up */ - - if ((wsi->state == LWSS_ESTABLISHED && - wsi->u.ws.ping_pending_flag) || - (wsi->state == LWSS_RETURNED_CLOSE_ALREADY && - wsi->u.ws.payload_is_close)) { - - if (wsi->u.ws.payload_is_close) - write_type = LWS_WRITE_CLOSE; - - n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], - wsi->u.ws.ping_payload_len, write_type); - if (n < 0) - goto bail_die; - - /* well he is sent, mark him done */ - wsi->u.ws.ping_pending_flag = 0; - if (wsi->u.ws.payload_is_close) - /* oh... a close frame was it... then we are done */ - goto bail_die; - - /* otherwise for PING, leave POLLOUT active either way */ - goto bail_ok; - } - - if (wsi->state == LWSS_ESTABLISHED && - !wsi->socket_is_permanently_unusable && - wsi->u.ws.send_check_ping) { - - lwsl_info("issuing ping on wsi %p\n", wsi); - wsi->u.ws.send_check_ping = 0; - n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], - 0, LWS_WRITE_PING); - if (n < 0) - goto bail_die; - - /* - * we apparently were able to send the PING in a reasonable time - * now reset the clock on our peer to be able to send the - * PONG in a reasonable time. - */ - - lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG, - wsi->context->timeout_secs); - - goto bail_ok; - } - - /* Priority 4: if we are closing, not allowed to send more data frags - * which means user callback or tx ext flush banned now - */ - if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) - goto user_service; - - /* Priority 5: Tx path extension with more to send - * - * These are handled as new fragments each time around - * So while we must block new writeable callback to enforce - * payload ordering, but since they are always complete - * fragments control packets can interleave OK. - */ - if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) { - lwsl_ext("SERVICING TX EXT DRAINING\n"); - if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0) - goto bail_die; - /* leave POLLOUT active */ - goto bail_ok; - } - - /* Priority 6: user can get the callback - */ - m = lws_ext_cb_active(wsi, LWS_EXT_CB_IS_WRITEABLE, NULL, 0); - if (m) - goto bail_die; -#ifndef LWS_NO_EXTENSIONS - if (!wsi->extension_data_pending) - goto user_service; -#endif - /* - * check in on the active extensions, see if they - * had pending stuff to spill... they need to get the - * first look-in otherwise sequence will be disordered - * - * NULL, zero-length eff_buf means just spill pending - */ - - ret = 1; - if (wsi->mode == LWSCM_RAW || wsi->mode == LWSCM_RAW_FILEDESC) - ret = 0; - - while (ret == 1) { - - /* default to nobody has more to spill */ - - ret = 0; - eff_buf.token = NULL; - eff_buf.token_len = 0; - - /* give every extension a chance to spill */ - - m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND, - &eff_buf, 0); - if (m < 0) { - lwsl_err("ext reports fatal error\n"); - goto bail_die; - } - if (m) - /* - * at least one extension told us he has more - * to spill, so we will go around again after - */ - ret = 1; - - /* assuming they gave us something to send, send it */ - - if (eff_buf.token_len) { - n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, - eff_buf.token_len); - if (n < 0) { - lwsl_info("closing from POLLOUT spill\n"); - goto bail_die; - } - /* - * Keep amount spilled small to minimize chance of this - */ - if (n != eff_buf.token_len) { - lwsl_err("Unable to spill ext %d vs %d\n", - eff_buf.token_len, n); - goto bail_die; - } - } else - continue; - - /* no extension has more to spill */ - - if (!ret) - continue; - - /* - * There's more to spill from an extension, but we just sent - * something... did that leave the pipe choked? - */ - - if (!lws_send_pipe_choked(wsi)) - /* no we could add more */ - continue; - - lwsl_info("choked in POLLOUT service\n"); - - /* - * Yes, he's choked. Leave the POLLOUT masked on so we will - * come back here when he is unchoked. Don't call the user - * callback to enforce ordering of spilling, he'll get called - * when we come back here and there's nothing more to spill. - */ - - goto bail_ok; - } -#ifndef LWS_NO_EXTENSIONS - wsi->extension_data_pending = 0; -#endif -user_service: - /* one shot */ - - if (wsi->parent_carries_io) { - wsi->handling_pollout = 0; - wsi->leave_pollout_active = 0; - - return lws_calllback_as_writeable(wsi); - } - - if (pollfd) { - int eff = wsi->leave_pollout_active; - - if (!eff) - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - goto bail_die; - } - - wsi->handling_pollout = 0; - - /* cannot get leave_pollout_active set after the above */ - if (!eff && wsi->leave_pollout_active) - /* got set inbetween sampling eff and clearing - * handling_pollout, force POLLOUT on */ - lws_calllback_as_writeable(wsi); - - wsi->leave_pollout_active = 0; - } - - if (wsi->mode != LWSCM_WSCL_ISSUE_HTTP_BODY && - !wsi->hdr_parsing_completed) - goto bail_ok; - - -#ifdef LWS_WITH_CGI -user_service_go_again: -#endif - -#ifdef LWS_WITH_HTTP2 - /* - * we are the 'network wsi' for potentially many muxed child wsi with - * no network connection of their own, who have to use us for all their - * network actions. So we use a round-robin scheme to share out the - * POLLOUT notifications to our children. - * - * But because any child could exhaust the socket's ability to take - * writes, we can only let one child get notified each time. - * - * In addition children may be closed / deleted / added between POLLOUT - * notifications, so we can't hold pointers - */ - - if (wsi->mode != LWSCM_HTTP2_SERVING) { - lwsl_info("%s: non http2\n", __func__); - goto notify; - } - - wsi->u.h2.requested_POLLOUT = 0; - if (!wsi->u.h2.initialized) { - lwsl_info("pollout on uninitialized http2 conn\n"); - goto bail_ok; - } - -// if (SSL_want_read(wsi->ssl) || SSL_want_write(wsi->ssl)) { -// lws_callback_on_writable(wsi); -// goto bail_ok; -// } - - lwsl_info("%s: %p: children waiting for POLLOUT service:\n", __func__, wsi); - wsi2a = wsi->u.h2.child_list; - while (wsi2a) { - if (wsi2a->u.h2.requested_POLLOUT) - lwsl_debug(" * %p\n", wsi2a); - else - lwsl_debug(" %p\n", wsi2a); - - wsi2a = wsi2a->u.h2.sibling_list; - } - - wsi2 = &wsi->u.h2.child_list; - if (!*wsi2) - goto bail_ok; - - do { - struct lws *w, **wa; - - wa = &(*wsi2)->u.h2.sibling_list; - if (!(*wsi2)->u.h2.requested_POLLOUT) { - lwsl_debug(" child %p doesn't want POLLOUT\n", *wsi2); - goto next_child; - } - - /* - * we're going to do writable callback for this child. - * move him to be the last child - */ - - lwsl_debug("servicing child %p\n", *wsi2); - - w = *wsi2; - while (w) { - if (!w->u.h2.sibling_list) { /* w is the current last */ - lwsl_debug("w=%p, *wsi2 = %p\n", w, *wsi2); - if (w == *wsi2) /* we are already last */ - break; - w->u.h2.sibling_list = *wsi2; /* last points to us as new last */ - *wsi2 = (*wsi2)->u.h2.sibling_list; /* guy pointing to us until now points to our old next */ - w->u.h2.sibling_list->u.h2.sibling_list = NULL; /* we point to nothing because we are last */ - w = w->u.h2.sibling_list; /* w becomes us */ - break; - } - w = w->u.h2.sibling_list; - } - - w->u.h2.requested_POLLOUT = 0; - lwsl_info("%s: child %p (state %d)\n", __func__, (*wsi2), (*wsi2)->state); - - if (w->u.h2.pending_status_body) { - w->u.h2.send_END_STREAM = 1; - n = lws_write(w, - (uint8_t *)w->u.h2.pending_status_body + LWS_PRE, - strlen(w->u.h2.pending_status_body + LWS_PRE), - LWS_WRITE_HTTP_FINAL); - lws_free_set_NULL(w->u.h2.pending_status_body); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); - wa = &wsi->u.h2.child_list; - goto next_child; - } - - if (w->state == LWSS_HTTP_ISSUING_FILE) { - - w->leave_pollout_active = 0; - - /* >0 == completion, <0 == error - * - * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when - * it's done. That's the case even if we just completed the - * send, so wait for that. - */ - n = lws_serve_http_file_fragment(w); - lwsl_debug("lws_serve_http_file_fragment says %d\n", n); - - /* - * We will often hear about out having sent the final - * DATA here... if so close the actual wsi - */ - if (n < 0 || w->u.h2.send_END_STREAM) { - lwsl_debug("Closing POLLOUT child %p\n", w); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); - wa = &wsi->u.h2.child_list; - goto next_child; - } - if (n > 0) - if (lws_http_transaction_completed(w)) - goto bail_die; - if (!n) { - lws_callback_on_writable(w); - (w)->u.h2.requested_POLLOUT = 1; - } - - goto next_child; - } - - if (lws_calllback_as_writeable(w) || w->u.h2.send_END_STREAM) { - lwsl_debug("Closing POLLOUT child\n"); - lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); - wa = &wsi->u.h2.child_list; - } - -next_child: - wsi2 = wa; - } while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi)); - - lwsl_info("%s: %p: children waiting for POLLOUT service: %p\n", __func__, wsi, wsi->u.h2.child_list); - wsi2a = wsi->u.h2.child_list; - while (wsi2a) { - if (wsi2a->u.h2.requested_POLLOUT) - lwsl_debug(" * %p\n", wsi2a); - else - lwsl_debug(" %p\n", wsi2a); - - wsi2a = wsi2a->u.h2.sibling_list; - } - - - wsi2a = wsi->u.h2.child_list; - while (wsi2a) { - if (wsi2a->u.h2.requested_POLLOUT) { - lws_change_pollfd(wsi, 0, LWS_POLLOUT); - break; - } - wsi2a = wsi2a->u.h2.sibling_list; - } - - goto bail_ok; - - -notify: -#endif - wsi->leave_pollout_active = 0; - - n = lws_calllback_as_writeable(wsi); - wsi->handling_pollout = 0; - - if (wsi->leave_pollout_active) - lws_change_pollfd(wsi, 0, LWS_POLLOUT); - - return n; - - /* - * since these don't disable the POLLOUT, they are always doing the - * right thing for leave_pollout_active whether it was set or not. - */ - -bail_ok: - wsi->handling_pollout = 0; - wsi->leave_pollout_active = 0; - - return 0; - -bail_die: - wsi->handling_pollout = 0; - wsi->leave_pollout_active = 0; - - return -1; -} - -int -lws_service_timeout_check(struct lws *wsi, unsigned int sec) -{ - struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - int n = 0; - - (void)n; - - /* - * if extensions want in on it (eg, we are a mux parent) - * give them a chance to service child timeouts - */ - if (lws_ext_cb_active(wsi, LWS_EXT_CB_1HZ, NULL, sec) < 0) - return 0; - - if (!wsi->pending_timeout) - return 0; - - /* - * if we went beyond the allowed time, kill the - * connection - */ - if ((time_t)sec > wsi->pending_timeout_limit) { - - if (wsi->desc.sockfd != LWS_SOCK_INVALID && - wsi->position_in_fds_table >= 0) - n = pt->fds[wsi->position_in_fds_table].events; - - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_TIMEOUTS, 1); - - /* no need to log normal idle keepalive timeout */ - if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE) - lwsl_info("wsi %p: TIMEDOUT WAITING on %d " - "(did hdr %d, ah %p, wl %d, pfd " - "events %d) %llu vs %llu\n", - (void *)wsi, wsi->pending_timeout, - wsi->hdr_parsing_completed, wsi->u.hdr.ah, - pt->ah_wait_list_length, n, - (unsigned long long)sec, - (unsigned long long)wsi->pending_timeout_limit); - - /* - * Since he failed a timeout, he already had a chance to do - * something and was unable to... that includes situations like - * half closed connections. So process this "failed timeout" - * close as a violent death and don't try to do protocol - * cleanup like flush partials. - */ - wsi->socket_is_permanently_unusable = 1; - if (wsi->mode == LWSCM_WSCL_WAITING_SSL) - wsi->vhost->protocols[0].callback(wsi, - LWS_CALLBACK_CLIENT_CONNECTION_ERROR, - wsi->user_space, - (void *)"Timed out waiting SSL", 21); - - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - - return 1; - } - - return 0; -} - -int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len) -{ -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2) { - struct lws_h2_netconn *h2n = wsi->u.h2.h2n; - - assert(h2n->rx_scratch); - buf += n; - len -= n; - assert ((char *)buf >= (char *)h2n->rx_scratch && - (char *)&buf[len] <= (char *)&h2n->rx_scratch[LWS_H2_RX_SCRATCH_SIZE]); - - h2n->rx_scratch_pos = ((char *)buf - (char *)h2n->rx_scratch); - h2n->rx_scratch_len = len; - - lwsl_info("%s: %p: pausing h2 rx_scratch\n", __func__, wsi); - - return 0; - } -#endif - /* his RX is flowcontrolled, don't send remaining now */ - if (wsi->rxflow_buffer) { - if (buf >= wsi->rxflow_buffer && - &buf[len - 1] < &wsi->rxflow_buffer[wsi->rxflow_len]) { - /* rxflow while we were spilling prev rxflow */ - lwsl_info("%s: staying in rxflow buf\n", __func__); - return 1; - } else { - lwsl_err("%s: conflicting rxflow buf, " - "current %p len %d, new %p len %d\n", __func__, - wsi->rxflow_buffer, wsi->rxflow_len, buf, len); - assert(0); - return 1; - } - } - - /* a new rxflow, buffer it and warn caller */ - lwsl_info("%s: new rxflow input buffer len %d\n", __func__, len - n); - wsi->rxflow_buffer = lws_malloc(len - n, "rxflow buf"); - if (!wsi->rxflow_buffer) - return -1; - - wsi->rxflow_len = len - n; - wsi->rxflow_pos = 0; - memcpy(wsi->rxflow_buffer, buf + n, len - n); - - return 0; -} - -/* this is used by the platform service code to stop us waiting for network - * activity in poll() when we have something that already needs service - */ - -LWS_VISIBLE LWS_EXTERN int -lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) -{ - struct lws_context_per_thread *pt = &context->pt[tsi]; - struct allocated_headers *ah; - - /* Figure out if we really want to wait in poll() - * We only need to wait if really nothing already to do and we have - * to wait for something from network - */ - - /* 1) if we know we are draining rx ext, do not wait in poll */ - if (pt->rx_draining_ext_list) - return 0; - -#ifdef LWS_OPENSSL_SUPPORT - /* 2) if we know we have non-network pending data, do not wait in poll */ - if (lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) { - lwsl_info("ssl buffered read\n"); - return 0; - } -#endif - - /* 3) if any ah has pending rx, do not wait in poll */ - ah = pt->ah_list; - while (ah) { - if (ah->rxpos != ah->rxlen) { - if (!ah->wsi) { - assert(0); - } - return 0; - } - ah = ah->next; - } - - return timeout_ms; -} - -/* - * guys that need POLLIN service again without waiting for network action - * can force POLLIN here if not flowcontrolled, so they will get service. - * - * Return nonzero if anybody got their POLLIN faked - */ -int -lws_service_flag_pending(struct lws_context *context, int tsi) -{ - struct lws_context_per_thread *pt = &context->pt[tsi]; - struct allocated_headers *ah; -#ifdef LWS_OPENSSL_SUPPORT - struct lws *wsi_next; -#endif - struct lws *wsi; - int forced = 0; - - /* POLLIN faking */ - - /* - * 1) For all guys with already-available ext data to drain, if they are - * not flowcontrolled, fake their POLLIN status - */ - wsi = pt->rx_draining_ext_list; - while (wsi) { - pt->fds[wsi->position_in_fds_table].revents |= - pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; - if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) { - forced = 1; - break; - } - wsi = wsi->u.ws.rx_draining_ext_list; - } - -#ifdef LWS_OPENSSL_SUPPORT - /* - * 2) For all guys with buffered SSL read data already saved up, if they - * are not flowcontrolled, fake their POLLIN status so they'll get - * service to use up the buffered incoming data, even though their - * network socket may have nothing - */ - wsi = pt->pending_read_list; - while (wsi) { - wsi_next = wsi->pending_read_list_next; - pt->fds[wsi->position_in_fds_table].revents |= - pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; - if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) { - forced = 1; - /* - * he's going to get serviced now, take him off the - * list of guys with buffered SSL. If he still has some - * at the end of the service, he'll get put back on the - * list then. - */ - lws_ssl_remove_wsi_from_buffered_list(wsi); - } - - wsi = wsi_next; - } -#endif - /* - * 3) For any wsi who have an ah with pending RX who did not - * complete their current headers, and are not flowcontrolled, - * fake their POLLIN status so they will be able to drain the - * rx buffered in the ah - */ - ah = pt->ah_list; - while (ah) { - if (ah->rxpos != ah->rxlen && !ah->wsi->hdr_parsing_completed) { - pt->fds[ah->wsi->position_in_fds_table].revents |= - pt->fds[ah->wsi->position_in_fds_table].events & - LWS_POLLIN; - if (pt->fds[ah->wsi->position_in_fds_table].revents & - LWS_POLLIN) { - forced = 1; - break; - } - } - ah = ah->next; - } - - return forced; -} - -#ifndef LWS_NO_CLIENT - -LWS_VISIBLE int -lws_http_client_read(struct lws *wsi, char **buf, int *len) -{ - int rlen, n; - - rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); - *len = 0; - - /* allow the source to signal he has data again next time */ - lws_change_pollfd(wsi, 0, LWS_POLLIN); - - if (rlen == LWS_SSL_CAPABLE_ERROR) { - lwsl_notice("%s: SSL capable error\n", __func__); - return -1; - } - - if (rlen == 0) - return -1; - - if (rlen < 0) - return 0; - - *len = rlen; - wsi->client_rx_avail = 0; - - /* - * server may insist on transfer-encoding: chunked, - * so http client must deal with it - */ -spin_chunks: - while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { - switch (wsi->chunk_parser) { - case ELCP_HEX: - if ((*buf)[0] == '\x0d') { - wsi->chunk_parser = ELCP_CR; - break; - } - n = char_to_hex((*buf)[0]); - if (n < 0) { - lwsl_debug("chunking failure\n"); - return -1; - } - wsi->chunk_remaining <<= 4; - wsi->chunk_remaining |= n; - break; - case ELCP_CR: - if ((*buf)[0] != '\x0a') { - lwsl_debug("chunking failure\n"); - return -1; - } - wsi->chunk_parser = ELCP_CONTENT; - lwsl_info("chunk %d\n", wsi->chunk_remaining); - if (wsi->chunk_remaining) - break; - lwsl_info("final chunk\n"); - goto completed; - - case ELCP_CONTENT: - break; - - case ELCP_POST_CR: - if ((*buf)[0] != '\x0d') { - lwsl_debug("chunking failure\n"); - - return -1; - } - - wsi->chunk_parser = ELCP_POST_LF; - break; - - case ELCP_POST_LF: - if ((*buf)[0] != '\x0a') - return -1; - - wsi->chunk_parser = ELCP_HEX; - wsi->chunk_remaining = 0; - break; - } - (*buf)++; - (*len)--; - } - - if (wsi->chunked && !wsi->chunk_remaining) - return 0; - - if (wsi->u.http.rx_content_remain && - wsi->u.http.rx_content_remain < *len) - n = (int)wsi->u.http.rx_content_remain; - else - n = *len; - - if (wsi->chunked && wsi->chunk_remaining && - wsi->chunk_remaining < n) - n = wsi->chunk_remaining; - -#ifdef LWS_WITH_HTTP_PROXY - /* hubbub */ - if (wsi->perform_rewrite) - lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n); - else -#endif - if (user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, - wsi->user_space, *buf, n)) { - lwsl_debug("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ returned -1\n", __func__); - - return -1; - } - - if (wsi->chunked && wsi->chunk_remaining) { - (*buf) += n; - wsi->chunk_remaining -= n; - *len -= n; - } - - if (wsi->chunked && !wsi->chunk_remaining) - wsi->chunk_parser = ELCP_POST_CR; - - if (wsi->chunked && *len) - goto spin_chunks; - - if (wsi->chunked) - return 0; - - /* if we know the content length, decrement the content remaining */ - if (wsi->u.http.rx_content_length > 0) - wsi->u.http.rx_content_remain -= n; - - if (wsi->u.http.rx_content_remain || !wsi->u.http.rx_content_length) - return 0; - -completed: - if (user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, - wsi->user_space, NULL, 0)) { - lwsl_debug("Completed call returned -1\n"); - return -1; - } - - if (lws_http_transaction_completed_client(wsi)) { - lwsl_notice("%s: transaction completed says -1\n", __func__); - return -1; - } - - return 0; -} -#endif - -static int -lws_is_ws_with_ext(struct lws *wsi) -{ -#if defined(LWS_NO_EXTENSIONS) - return 0; -#else - return wsi->state == LWSS_ESTABLISHED && - !!wsi->count_act_ext; -#endif -} - -LWS_VISIBLE int -lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int tsi) -{ - struct lws_context_per_thread *pt = &context->pt[tsi]; - lws_sockfd_type our_fd = 0, tmp_fd; - struct allocated_headers *ah; - struct lws_tokens eff_buf; - unsigned int pending = 0; - struct lws *wsi, *wsi1; - char draining_flow = 0; - int timed_out = 0; - time_t now; - int n = 0, m; - int more; - - if (!context->protocol_init_done) - lws_protocol_init(context); - - time(&now); - - /* - * handle case that system time was uninitialized when lws started - * at boot, and got initialized a little later - */ - if (context->time_up < 1464083026 && now > 1464083026) - context->time_up = now; - - /* TODO: if using libev, we should probably use timeout watchers... */ - if (context->last_timeout_check_s != now) { - context->last_timeout_check_s = now; - -#if defined(LWS_WITH_STATS) - if (!tsi && now - context->last_dump > 10) { - lws_stats_log_dump(context); - context->last_dump = now; - } -#endif - - lws_plat_service_periodic(context); - - lws_check_deferred_free(context, 0); - -#if defined(LWS_WITH_PEER_LIMITS) - lws_peer_cull_peer_wait_list(context); -#endif - - /* retire unused deprecated context */ -#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_ESP32) -#if LWS_POSIX && !defined(_WIN32) - if (context->deprecated && !context->count_wsi_allocated) { - lwsl_notice("%s: ending deprecated context\n", __func__); - kill(getpid(), SIGINT); - return 0; - } -#endif -#endif - /* global timeout check once per second */ - - if (pollfd) - our_fd = pollfd->fd; - - /* - * Phase 1: check every wsi on the timeout check list - */ - - wsi = context->pt[tsi].timeout_list; - while (wsi) { - /* we have to take copies, because he may be deleted */ - wsi1 = wsi->timeout_list; - tmp_fd = wsi->desc.sockfd; - if (lws_service_timeout_check(wsi, (unsigned int)now)) { - /* he did time out... */ - if (tmp_fd == our_fd) - /* it was the guy we came to service! */ - timed_out = 1; - /* he's gone, no need to mark as handled */ - } - wsi = wsi1; - } - - /* - * Phase 2: double-check active ah timeouts independent of wsi - * timeout status - */ - - ah = pt->ah_list; - while (ah) { - int len; - char buf[256]; - const unsigned char *c; - - if (!ah->in_use || !ah->wsi || !ah->assigned || - (ah->wsi->vhost && now - ah->assigned < - ah->wsi->vhost->timeout_secs_ah_idle + 60)) { - ah = ah->next; - continue; - } - - /* - * a single ah session somehow got held for - * an unreasonable amount of time. - * - * Dump info on the connection... - */ - wsi = ah->wsi; - buf[0] = '\0'; - lws_get_peer_simple(wsi, buf, sizeof(buf)); - lwsl_notice("ah excessive hold: wsi %p\n" - " peer address: %s\n" - " ah rxpos %u, rxlen %u, pos %u\n", - wsi, buf, ah->rxpos, ah->rxlen, - ah->pos); - buf[0] = '\0'; - m = 0; - do { - c = lws_token_to_string(m); - if (!c) - break; - if (!(*c)) - break; - - len = lws_hdr_total_length(wsi, m); - if (!len || len > sizeof(buf) - 1) { - m++; - continue; - } - - if (lws_hdr_copy(wsi, buf, - sizeof buf, m) > 0) { - buf[sizeof(buf) - 1] = '\0'; - - lwsl_notice(" %s = %s\n", - (const char *)c, buf); - } - m++; - } while (1); - - /* explicitly detach the ah */ - - lws_header_table_force_to_detachable_state(wsi); - lws_header_table_detach(wsi, 0); - - /* ... and then drop the connection */ - - if (wsi->desc.sockfd == our_fd) - /* it was the guy we came to service! */ - timed_out = 1; - - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - - ah = pt->ah_list; - } - -#ifdef LWS_WITH_CGI - /* - * Phase 3: handle cgi timeouts - */ - lws_cgi_kill_terminated(pt); -#endif -#if 0 - { - char s[300], *p = s; - - for (n = 0; n < context->count_threads; n++) - p += sprintf(p, " %7lu (%5d), ", - context->pt[n].count_conns, - context->pt[n].fds_count); - - lwsl_notice("load: %s\n", s); - } -#endif - } - - /* - * at intervals, check for ws connections needing ping-pong checks - */ - - if (context->ws_ping_pong_interval && - context->last_ws_ping_pong_check_s < now + 10) { - struct lws_vhost *vh = context->vhost_list; - context->last_ws_ping_pong_check_s = now; - - while (vh) { - for (n = 0; n < vh->count_protocols; n++) { - wsi = vh->same_vh_protocol_list[n]; - - while (wsi) { - if (wsi->state == LWSS_ESTABLISHED && - !wsi->socket_is_permanently_unusable && - !wsi->u.ws.send_check_ping && - wsi->u.ws.time_next_ping_check && - wsi->u.ws.time_next_ping_check < now) { - - lwsl_info("requesting ping-pong on wsi %p\n", wsi); - wsi->u.ws.send_check_ping = 1; - lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING, - context->timeout_secs); - lws_callback_on_writable(wsi); - wsi->u.ws.time_next_ping_check = now + - wsi->context->ws_ping_pong_interval; - } - wsi = wsi->same_vh_protocol_next; - } - } - vh = vh->vhost_next; - } - } - - - /* the socket we came to service timed out, nothing to do */ - if (timed_out) - return 0; - - /* just here for timeout management? */ - if (!pollfd) - return 0; - - /* no, here to service a socket descriptor */ - wsi = wsi_from_fd(context, pollfd->fd); - if (!wsi) - /* not lws connection ... leave revents alone and return */ - return 0; - - /* - * so that caller can tell we handled, past here we need to - * zero down pollfd->revents after handling - */ - -#if LWS_POSIX - /* handle session socket closed */ - - if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && - (pollfd->revents & LWS_POLLHUP)) { - wsi->socket_is_permanently_unusable = 1; - lwsl_debug("Session Socket %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); - - goto close_and_handled; - } - -#ifdef _WIN32 - if (pollfd->revents & LWS_POLLOUT) - wsi->sock_send_blocking = FALSE; -#endif - -#endif - - if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && - (pollfd->revents & LWS_POLLHUP)) { - lwsl_debug("pollhup\n"); - wsi->socket_is_permanently_unusable = 1; - goto close_and_handled; - } - -#ifdef LWS_OPENSSL_SUPPORT - if ((wsi->state == LWSS_SHUTDOWN) && lws_is_ssl(wsi) && wsi->ssl) { - n = SSL_shutdown(wsi->ssl); - lwsl_debug("SSL_shutdown=%d for fd %d\n", n, wsi->desc.sockfd); - switch (n) { - case 1: - n = shutdown(wsi->desc.sockfd, SHUT_WR); - goto close_and_handled; - - case 0: - lws_change_pollfd(wsi, 0, LWS_POLLIN); - n = 0; - goto handled; - - default: - n = SSL_get_error(wsi->ssl, n); - if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) { - if (SSL_want_read(wsi->ssl)) { - lwsl_debug("(wants read)\n"); - lws_change_pollfd(wsi, 0, LWS_POLLIN); - n = 0; - goto handled; - } - if (SSL_want_write(wsi->ssl)) { - lwsl_debug("(wants write)\n"); - lws_change_pollfd(wsi, 0, LWS_POLLOUT); - n = 0; - goto handled; - } - } - - /* actual error occurred, just close the connection */ - n = shutdown(wsi->desc.sockfd, SHUT_WR); - goto close_and_handled; - } - } -#endif - - /* okay, what we came here to do... */ - - switch (wsi->mode) { - case LWSCM_HTTP_SERVING: - case LWSCM_HTTP_CLIENT: - case LWSCM_HTTP_SERVING_ACCEPTED: - case LWSCM_SERVER_LISTENER: - case LWSCM_SSL_ACK_PENDING: - case LWSCM_SSL_ACK_PENDING_RAW: - if (wsi->state == LWSS_CLIENT_HTTP_ESTABLISHED) - goto handled; - -#ifdef LWS_WITH_CGI - if (wsi->cgi && (pollfd->revents & LWS_POLLOUT)) { - n = lws_handle_POLLOUT_event(wsi, pollfd); - if (n) - goto close_and_handled; - goto handled; - } -#endif - /* fallthru */ - case LWSCM_RAW: - n = lws_server_socket_service(context, wsi, pollfd); - if (n) /* closed by above */ - return 1; - goto handled; - - case LWSCM_RAW_FILEDESC: - - if (pollfd->revents & LWS_POLLOUT) { - n = lws_calllback_as_writeable(wsi); - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - return 1; - } - if (n) - goto close_and_handled; - } - n = LWS_CALLBACK_RAW_RX; - if (wsi->mode == LWSCM_RAW_FILEDESC) - n = LWS_CALLBACK_RAW_RX_FILE; - - if (pollfd->revents & LWS_POLLIN) { - if (user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, n, - wsi->user_space, NULL, 0)) { - lwsl_debug("raw rx callback closed it\n"); - goto close_and_handled; - } - } - - if (pollfd->revents & LWS_POLLHUP) - goto close_and_handled; - n = 0; - goto handled; - - case LWSCM_WS_SERVING: - case LWSCM_WS_CLIENT: - case LWSCM_HTTP2_SERVING: - case LWSCM_HTTP_CLIENT_ACCEPTED: - - /* 1: something requested a callback when it was OK to write */ - - if ((pollfd->revents & LWS_POLLOUT) && - ((wsi->state == LWSS_ESTABLISHED || - wsi->state == LWSS_HTTP2_ESTABLISHED || - wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS || - wsi->state == LWSS_RETURNED_CLOSE_ALREADY || - wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || - wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE)) && - lws_handle_POLLOUT_event(wsi, pollfd)) { - if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) - wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE; - lwsl_info("lws_service_fd: closing\n"); - goto close_and_handled; - } - - if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || - wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || - wsi->state == LWSS_AWAITING_CLOSE_ACK) { - /* - * we stopped caring about anything except control - * packets. Force flow control off, defeat tx - * draining. - */ - lws_rx_flow_control(wsi, 1); - wsi->u.ws.tx_draining_ext = 0; - } - - if (wsi->u.ws.tx_draining_ext) - /* we cannot deal with new RX until the TX ext - * path has been drained. It's because new - * rx will, eg, crap on the wsi rx buf that - * may be needed to retain state. - * - * TX ext drain path MUST go through event loop - * to avoid blocking. - */ - break; - - if (lws_is_flowcontrolled(wsi)) - /* We cannot deal with any kind of new RX - * because we are RX-flowcontrolled. - */ - break; - -#if defined(LWS_WITH_HTTP2) - if (wsi->http2_substream || wsi->upgraded_to_http2) { - wsi1 = lws_get_network_wsi(wsi); - if (wsi1 && wsi1->trunc_len) - /* We cannot deal with any kind of new RX - * because we are dealing with a partial send - * (new RX may trigger new http_action() that - * expect to be able to send) - */ - break; - } -#endif - - /* 2: RX Extension needs to be drained - */ - - if (wsi->state == LWSS_ESTABLISHED && - wsi->u.ws.rx_draining_ext) { - - lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__); -#ifndef LWS_NO_CLIENT - if (wsi->mode == LWSCM_WS_CLIENT) { - n = lws_client_rx_sm(wsi, 0); - if (n < 0) - /* we closed wsi */ - n = 0; - } else -#endif - n = lws_rx_sm(wsi, 0); - - goto handled; - } - - if (wsi->u.ws.rx_draining_ext) - /* - * We have RX EXT content to drain, but can't do it - * right now. That means we cannot do anything lower - * priority either. - */ - break; - - /* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained - */ - - if (wsi->rxflow_buffer) { - lwsl_info("draining rxflow (len %d)\n", - wsi->rxflow_len - wsi->rxflow_pos); - assert(wsi->rxflow_pos < wsi->rxflow_len); - /* well, drain it */ - eff_buf.token = (char *)wsi->rxflow_buffer + - wsi->rxflow_pos; - eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; - draining_flow = 1; - goto drain; - } - -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2) { - struct lws_h2_netconn *h2n = wsi->u.h2.h2n; - - if (h2n->rx_scratch_len) { - lwsl_info("%s: %p: resuming h2 rx_scratch pos = %d len = %d\n", - __func__, wsi, h2n->rx_scratch_pos, h2n->rx_scratch_len); - eff_buf.token = (char *)h2n->rx_scratch + - h2n->rx_scratch_pos; - eff_buf.token_len = h2n->rx_scratch_len; - - h2n->rx_scratch_len = 0; - goto drain; - } - } -#endif - - /* 4: any incoming (or ah-stashed incoming rx) data ready? - * notice if rx flow going off raced poll(), rx flow wins - */ - - if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) - break; -read: - if (lws_is_flowcontrolled(wsi)) { - lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n", - __func__, wsi, wsi->rxflow_bitmap); - break; - } - - /* all the union members start with hdr, so even in ws mode - * we can deal with the ah via u.hdr - */ - if (wsi->u.hdr.ah) { - lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi); - eff_buf.token_len = wsi->u.hdr.ah->rxlen - - wsi->u.hdr.ah->rxpos; - eff_buf.token = (char *)wsi->u.hdr.ah->rx + - wsi->u.hdr.ah->rxpos; - } else { - if (wsi->mode != LWSCM_HTTP_CLIENT_ACCEPTED) { - /* - * extension may not consume everything (eg, pmd may be constrained - * as to what it can output...) has to go in per-wsi rx buf area. - * Otherwise in large temp serv_buf area. - */ - -#if defined(LWS_WITH_HTTP2) - if (wsi->upgraded_to_http2) { - if (!wsi->u.h2.h2n->rx_scratch) { - wsi->u.h2.h2n->rx_scratch = lws_malloc(LWS_H2_RX_SCRATCH_SIZE, "h2 rx scratch"); - if (!wsi->u.h2.h2n->rx_scratch) - goto close_and_handled; - } - eff_buf.token = wsi->u.h2.h2n->rx_scratch; - eff_buf.token_len = LWS_H2_RX_SCRATCH_SIZE; - } else -#endif - { - eff_buf.token = (char *)pt->serv_buf; - if (lws_is_ws_with_ext(wsi)) { - eff_buf.token_len = wsi->u.ws.rx_ubuf_alloc; - } else { - eff_buf.token_len = context->pt_serv_buf_size; - } - - if ((unsigned int)eff_buf.token_len > context->pt_serv_buf_size) - eff_buf.token_len = context->pt_serv_buf_size; - } - - if ((int)pending > eff_buf.token_len) - pending = eff_buf.token_len; - - eff_buf.token_len = lws_ssl_capable_read(wsi, - (unsigned char *)eff_buf.token, pending ? pending : - eff_buf.token_len); - switch (eff_buf.token_len) { - case 0: - lwsl_info("%s: zero length read\n", __func__); - goto close_and_handled; - case LWS_SSL_CAPABLE_MORE_SERVICE: - lwsl_info("SSL Capable more service\n"); - n = 0; - goto handled; - case LWS_SSL_CAPABLE_ERROR: - lwsl_info("Closing when error\n"); - goto close_and_handled; - } - // lwsl_notice("Actual RX %d\n", eff_buf.token_len); - } - } - -drain: -#ifndef LWS_NO_CLIENT - if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED && - !wsi->told_user_closed) { - - /* - * In SSL mode we get POLLIN notification about - * encrypted data in. - * - * But that is not necessarily related to decrypted - * data out becoming available; in may need to perform - * other in or out before that happens. - * - * simply mark ourselves as having readable data - * and turn off our POLLIN - */ - wsi->client_rx_avail = 1; - lws_change_pollfd(wsi, LWS_POLLIN, 0); - - /* let user code know, he'll usually ask for writeable - * callback and drain / re-enable it there - */ - if (user_callback_handle_rxflow( - wsi->protocol->callback, - wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, - wsi->user_space, NULL, 0)) { - lwsl_info("RECEIVE_CLIENT_HTTP closed it\n"); - goto close_and_handled; - } - - n = 0; - goto handled; - } -#endif - /* - * give any active extensions a chance to munge the buffer - * before parse. We pass in a pointer to an lws_tokens struct - * prepared with the default buffer and content length that's in - * there. Rather than rewrite the default buffer, extensions - * that expect to grow the buffer can adapt .token to - * point to their own per-connection buffer in the extension - * user allocation. By default with no extensions or no - * extension callback handling, just the normal input buffer is - * used then so it is efficient. - */ - do { - more = 0; - - m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE, - &eff_buf, 0); - if (m < 0) - goto close_and_handled; - if (m) - more = 1; - - /* service incoming data */ - - if (eff_buf.token_len) { - /* - * if draining from rxflow buffer, not - * critical to track what was used since at the - * use it bumps wsi->rxflow_pos. If we come - * around again it will pick up from where it - * left off. - */ - n = lws_read(wsi, (unsigned char *)eff_buf.token, - eff_buf.token_len); - if (n < 0) { - /* we closed wsi */ - n = 0; - goto handled; - } - } - - eff_buf.token = NULL; - eff_buf.token_len = 0; - } while (more); - - if (wsi->u.hdr.ah) { - lwsl_debug("%s: %p: detaching\n", __func__, wsi); - lws_header_table_force_to_detachable_state(wsi); - /* we can run the normal ah detach flow despite - * being in ws union mode, since all union members - * start with hdr */ - lws_header_table_detach(wsi, 0); - } - - pending = lws_ssl_pending(wsi); - if (pending) { - if (lws_is_ws_with_ext(wsi)) - pending = pending > wsi->u.ws.rx_ubuf_alloc ? - wsi->u.ws.rx_ubuf_alloc : pending; - else - pending = pending > context->pt_serv_buf_size ? - context->pt_serv_buf_size : pending; - goto read; - } - - if (draining_flow && wsi->rxflow_buffer && - wsi->rxflow_pos == wsi->rxflow_len) { - lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); - lws_free_set_NULL(wsi->rxflow_buffer); - /* having drained the rxflow buffer, can rearm POLLIN */ -#ifdef LWS_NO_SERVER - n = -#endif - _lws_rx_flow_control(wsi); - /* n ignored, needed for NO_SERVER case */ - } - - break; -#ifdef LWS_WITH_CGI - case LWSCM_CGI: /* we exist to handle a cgi's stdin/out/err data... - * do the callback on our master wsi - */ - { - struct lws_cgi_args args; - - if (wsi->cgi_channel >= LWS_STDOUT && - !(pollfd->revents & pollfd->events & LWS_POLLIN)) - break; - if (wsi->cgi_channel == LWS_STDIN && - !(pollfd->revents & pollfd->events & LWS_POLLOUT)) - break; - - if (wsi->cgi_channel == LWS_STDIN) - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_info("failed at set pollfd\n"); - return 1; - } - - args.ch = wsi->cgi_channel; - args.stdwsi = &wsi->parent->cgi->stdwsi[0]; - args.hdr_state = wsi->hdr_state; - - lwsl_debug("CGI LWS_STDOUT %p mode %d state %d\n", - wsi->parent, wsi->parent->mode, - wsi->parent->state); - - if (user_callback_handle_rxflow( - wsi->parent->protocol->callback, - wsi->parent, LWS_CALLBACK_CGI, - wsi->parent->user_space, - (void *)&args, 0)) - return 1; - - break; - } -#endif - /* - * something went wrong with parsing the handshake, and - * we ended up back in the event loop without completing it - */ - case LWSCM_PRE_WS_SERVING_ACCEPT: - wsi->socket_is_permanently_unusable = 1; - goto close_and_handled; - - default: -#ifdef LWS_NO_CLIENT - break; -#else - if ((pollfd->revents & LWS_POLLOUT) && - lws_handle_POLLOUT_event(wsi, pollfd)) { - lwsl_debug("POLLOUT event closed it\n"); - goto close_and_handled; - } - - n = lws_client_socket_service(context, wsi, pollfd); - if (n) - return 1; - goto handled; -#endif - } - - n = 0; - goto handled; - -close_and_handled: - lwsl_debug("%p: Close and handled\n", wsi); - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); - /* - * pollfd may point to something else after the close - * due to pollfd swapping scheme on delete on some platforms - * we can't clear revents now because it'd be the wrong guy's revents - */ - return 1; - -handled: - pollfd->revents = 0; - return n; -} - -LWS_VISIBLE int -lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd) -{ - return lws_service_fd_tsi(context, pollfd, 0); -} - -LWS_VISIBLE int -lws_service(struct lws_context *context, int timeout_ms) -{ - return lws_plat_service(context, timeout_ms); -} - -LWS_VISIBLE int -lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi) -{ - return _lws_plat_service_tsi(context, timeout_ms, tsi); -} - diff --git a/thirdparty/lws/ssl.c b/thirdparty/lws/ssl.c deleted file mode 100644 index 4ff3088ab3..0000000000 --- a/thirdparty/lws/ssl.c +++ /dev/null @@ -1,973 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> - * - * 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: - * version 2.1 of the License. - * - * 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 - */ - -#include "private-libwebsockets.h" -#include <errno.h> - -int lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) -{ - lws_filepos_t len; - lws_fop_flags_t flags = LWS_O_RDONLY; - lws_fop_fd_t fops_fd = lws_vfs_file_open( - lws_get_fops(context), filename, &flags); - int ret = 1; - - if (!fops_fd) - return 1; - - len = lws_vfs_get_length(fops_fd); - - *buf = lws_malloc((size_t)len, "lws_alloc_vfs_file"); - if (!*buf) - goto bail; - - if (lws_vfs_file_read(fops_fd, amount, *buf, len)) - goto bail; - - ret = 0; -bail: - lws_vfs_file_close(&fops_fd); - - return ret; -} - -#if defined(LWS_WITH_MBEDTLS) -#if defined(LWS_WITH_ESP32) -int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) -{ - nvs_handle nvh; - size_t s; - int n = 0; - - ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); - if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) { - n = 1; - goto bail; - } - *buf = lws_malloc(s, "alloc_file"); - if (!*buf) { - n = 2; - goto bail; - } - if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) { - lws_free(*buf); - n = 1; - goto bail; - } - - *amount = s; - -bail: - nvs_close(nvh); - - return n; -} -#else -int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) -{ - FILE *f; - size_t s; - int n = 0; - - f = fopen(filename, "rb"); - if (f == NULL) { - n = 1; - goto bail; - } - - if (fseek(f, 0, SEEK_END) != 0) { - n = 1; - goto bail; - } - - s = ftell(f); - if (s == -1) { - n = 1; - goto bail; - } - - if (fseek(f, 0, SEEK_SET) != 0) { - n = 1; - goto bail; - } - - *buf = lws_malloc(s, "alloc_file"); - if (!*buf) { - n = 2; - goto bail; - } - - if (fread(*buf, s, 1, f) != 1) { - lws_free(*buf); - n = 1; - goto bail; - } - - *amount = s; - -bail: - if (f) - fclose(f); - - return n; - -} -#endif -int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf, - lws_filepos_t *amount) -{ - uint8_t *pem, *p, *q, *end; - lws_filepos_t len; - int n; - - n = alloc_file(context, filename, &pem, &len); - if (n) - return n; - - /* trim the first line */ - - p = pem; - end = p + len; - if (strncmp((char *)p, "-----", 5)) - goto bail; - p += 5; - while (p < end && *p != '\n' && *p != '-') - p++; - - if (*p != '-') - goto bail; - - while (p < end && *p != '\n') - p++; - - if (p >= end) - goto bail; - - p++; - - /* trim the last line */ - - q = end - 2; - - while (q > pem && *q != '\n') - q--; - - if (*q != '\n') - goto bail; - - *q = '\0'; - - *amount = lws_b64_decode_string((char *)p, (char *)pem, len); - *buf = pem; - - return 0; - -bail: - lws_free(pem); - - return 4; -} -#endif - -int openssl_websocket_private_data_index, - openssl_SSL_CTX_private_data_index; - -int lws_ssl_get_error(struct lws *wsi, int n) -{ - int m; - - if (!wsi->ssl) - return 99; - - m = SSL_get_error(wsi->ssl, n); - lwsl_debug("%s: %p %d -> %d\n", __func__, wsi->ssl, n, m); - - return m; -} - -/* Copies a string describing the code returned by lws_ssl_get_error(), - * which may also contain system error information in the case of SSL_ERROR_SYSCALL, - * into buf up to len. - * Returns a pointer to buf. - * - * Note: the lws_ssl_get_error() code is *not* an error code that can be passed - * to ERR_error_string(), - * - * ret is the return value originally passed to lws_ssl_get_error(), needed to disambiguate - * SYS_ERROR_SYSCALL. - * - * See man page for SSL_get_error(). - * - * Not thread safe, uses strerror() - */ -char* lws_ssl_get_error_string(int status, int ret, char *buf, size_t len) { - switch (status) { - case SSL_ERROR_NONE: return strncpy(buf, "SSL_ERROR_NONE", len); - case SSL_ERROR_ZERO_RETURN: return strncpy(buf, "SSL_ERROR_ZERO_RETURN", len); - case SSL_ERROR_WANT_READ: return strncpy(buf, "SSL_ERROR_WANT_READ", len); - case SSL_ERROR_WANT_WRITE: return strncpy(buf, "SSL_ERROR_WANT_WRITE", len); - case SSL_ERROR_WANT_CONNECT: return strncpy(buf, "SSL_ERROR_WANT_CONNECT", len); - case SSL_ERROR_WANT_ACCEPT: return strncpy(buf, "SSL_ERROR_WANT_ACCEPT", len); - case SSL_ERROR_WANT_X509_LOOKUP: return strncpy(buf, "SSL_ERROR_WANT_X509_LOOKUP", len); - case SSL_ERROR_SYSCALL: - switch (ret) { - case 0: - lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: EOF"); - return buf; - case -1: -#ifndef LWS_PLAT_OPTEE - lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %s", strerror(errno)); -#else - lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %d", errno); -#endif - return buf; - default: - return strncpy(buf, "SSL_ERROR_SYSCALL", len); - } - case SSL_ERROR_SSL: return "SSL_ERROR_SSL"; - default: return "SSL_ERROR_UNKNOWN"; - } -} - -void -lws_ssl_elaborate_error(void) -{ -#if defined(LWS_WITH_MBEDTLS) -#else - char buf[256]; - u_long err; - - while ((err = ERR_get_error()) != 0) { - ERR_error_string_n(err, buf, sizeof(buf)); - lwsl_info("*** %s\n", buf); - } -#endif -} - -#if !defined(LWS_WITH_MBEDTLS) - -static int -lws_context_init_ssl_pem_passwd_cb(char * buf, int size, int rwflag, void *userdata) -{ - struct lws_context_creation_info * info = - (struct lws_context_creation_info *)userdata; - - strncpy(buf, info->ssl_private_key_password, size); - buf[size - 1] = '\0'; - - return strlen(buf); -} - -void -lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info) -{ - if (!info->ssl_private_key_password) - return; - /* - * password provided, set ssl callback and user data - * for checking password which will be trigered during - * SSL_CTX_use_PrivateKey_file function - */ - SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)info); - SSL_CTX_set_default_passwd_cb(ssl_ctx, lws_context_init_ssl_pem_passwd_cb); -} -#endif - -int -lws_context_init_ssl_library(struct lws_context_creation_info *info) -{ -#ifdef USE_WOLFSSL -#ifdef USE_OLD_CYASSL - lwsl_info(" Compiled with CyaSSL support\n"); -#else - lwsl_info(" Compiled with wolfSSL support\n"); -#endif -#else -#if defined(LWS_WITH_BORINGSSL) - lwsl_info(" Compiled with BoringSSL support\n"); -#else -#if defined(LWS_WITH_MBEDTLS) - lwsl_info(" Compiled with MbedTLS support\n"); -#else - lwsl_info(" Compiled with OpenSSL support\n"); -#endif -#endif -#endif - if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) { - lwsl_info(" SSL disabled: no LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT\n"); - return 0; - } - - /* basic openssl init */ - - lwsl_info("Doing SSL library init\n"); - -#if !defined(LWS_WITH_MBEDTLS) - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - - openssl_websocket_private_data_index = - SSL_get_ex_new_index(0, "lws", NULL, NULL, NULL); - - openssl_SSL_CTX_private_data_index = SSL_CTX_get_ex_new_index(0, - NULL, NULL, NULL, NULL); -#endif - - return 0; -} - -LWS_VISIBLE void -lws_ssl_destroy(struct lws_vhost *vhost) -{ - if (!lws_check_opt(vhost->context->options, - LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) - return; - - if (vhost->ssl_ctx) - SSL_CTX_free(vhost->ssl_ctx); - if (!vhost->user_supplied_ssl_ctx && vhost->ssl_client_ctx) - SSL_CTX_free(vhost->ssl_client_ctx); - -#if defined(LWS_WITH_MBEDTLS) - if (vhost->x509_client_CA) - X509_free(vhost->x509_client_CA); -#else -// after 1.1.0 no need -#if (OPENSSL_VERSION_NUMBER < 0x10100000) -// <= 1.0.1f = old api, 1.0.1g+ = new api -#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL) - ERR_remove_state(0); -#else -#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \ - !defined(LIBRESSL_VERSION_NUMBER) && \ - !defined(OPENSSL_IS_BORINGSSL) - ERR_remove_thread_state(); -#else - ERR_remove_thread_state(NULL); -#endif -#endif - // after 1.1.0 no need -#if (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000) - SSL_COMP_free_compression_methods(); -#endif - ERR_free_strings(); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); -#endif -#endif -} - -int -lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi) -{ - struct lws_context_per_thread *pt = &context->pt[tsi]; - struct lws *wsi, *wsi_next; - - wsi = pt->pending_read_list; - while (wsi) { - wsi_next = wsi->pending_read_list_next; - pt->fds[wsi->position_in_fds_table].revents |= - pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; - if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) - return 1; - - wsi = wsi_next; - } - - return 0; -} - -LWS_VISIBLE void -lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - - if (!wsi->pending_read_list_prev && - !wsi->pending_read_list_next && - pt->pending_read_list != wsi) - /* we are not on the list */ - return; - - /* point previous guy's next to our next */ - if (!wsi->pending_read_list_prev) - pt->pending_read_list = wsi->pending_read_list_next; - else - wsi->pending_read_list_prev->pending_read_list_next = - wsi->pending_read_list_next; - - /* point next guy's previous to our previous */ - if (wsi->pending_read_list_next) - wsi->pending_read_list_next->pending_read_list_prev = - wsi->pending_read_list_prev; - - wsi->pending_read_list_prev = NULL; - wsi->pending_read_list_next = NULL; -} - -LWS_VISIBLE int -lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len) -{ - struct lws_context *context = wsi->context; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - int n = 0, m; - - if (!wsi->ssl) - return lws_ssl_capable_read_no_ssl(wsi, buf, len); - - lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); - - errno = 0; - n = SSL_read(wsi->ssl, buf, len); -#if defined(LWS_WITH_ESP32) - if (!n && errno == ENOTCONN) { - lwsl_debug("%p: SSL_read ENOTCONN\n", wsi); - return LWS_SSL_CAPABLE_ERROR; - } -#endif -#if defined(LWS_WITH_STATS) - if (!wsi->seen_rx) { - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_RX_DELAY, - time_in_microseconds() - wsi->accept_start_us); - lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1); - wsi->seen_rx = 1; - } -#endif - - - lwsl_debug("%p: SSL_read says %d\n", wsi, n); - /* manpage: returning 0 means connection shut down */ - if (!n || (n == -1 && errno == ENOTCONN)) { - wsi->socket_is_permanently_unusable = 1; - - return LWS_SSL_CAPABLE_ERROR; - } - - if (n < 0) { - m = lws_ssl_get_error(wsi, n); - lwsl_debug("%p: ssl err %d errno %d\n", wsi, m, errno); - if (m == SSL_ERROR_ZERO_RETURN || - m == SSL_ERROR_SYSCALL) - return LWS_SSL_CAPABLE_ERROR; - - if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) { - lwsl_debug("%s: WANT_READ\n", __func__); - lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); - return LWS_SSL_CAPABLE_MORE_SERVICE; - } - if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->ssl)) { - lwsl_debug("%s: WANT_WRITE\n", __func__); - lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); - return LWS_SSL_CAPABLE_MORE_SERVICE; - } - wsi->socket_is_permanently_unusable = 1; - - return LWS_SSL_CAPABLE_ERROR; - } - - lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); - - if (wsi->vhost) - wsi->vhost->conn_stats.rx += n; - - lws_restart_ws_ping_pong_timer(wsi); - - /* - * if it was our buffer that limited what we read, - * check if SSL has additional data pending inside SSL buffers. - * - * Because these won't signal at the network layer with POLLIN - * and if we don't realize, this data will sit there forever - */ - if (n != len) - goto bail; - if (!wsi->ssl) - goto bail; - - if (!SSL_pending(wsi->ssl)) - goto bail; - - if (wsi->pending_read_list_next) - return n; - if (wsi->pending_read_list_prev) - return n; - if (pt->pending_read_list == wsi) - return n; - - /* add us to the linked list of guys with pending ssl */ - if (pt->pending_read_list) - pt->pending_read_list->pending_read_list_prev = wsi; - - wsi->pending_read_list_next = pt->pending_read_list; - wsi->pending_read_list_prev = NULL; - pt->pending_read_list = wsi; - - return n; -bail: - lws_ssl_remove_wsi_from_buffered_list(wsi); - - return n; -} - -LWS_VISIBLE int -lws_ssl_pending(struct lws *wsi) -{ - if (!wsi->ssl) - return 0; - - return SSL_pending(wsi->ssl); -} - -LWS_VISIBLE int -lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len) -{ - int n, m; - - if (!wsi->ssl) - return lws_ssl_capable_write_no_ssl(wsi, buf, len); - - n = SSL_write(wsi->ssl, buf, len); - if (n > 0) - return n; - - m = lws_ssl_get_error(wsi, n); - if (m != SSL_ERROR_SYSCALL) { - - if (SSL_want_read(wsi->ssl)) { - lwsl_notice("%s: want read\n", __func__); - - return LWS_SSL_CAPABLE_MORE_SERVICE; - } - - if (SSL_want_write(wsi->ssl)) { - lws_set_blocking_send(wsi); - - lwsl_notice("%s: want write\n", __func__); - - return LWS_SSL_CAPABLE_MORE_SERVICE; - } - } - - lwsl_debug("%s failed: %s\n",__func__, ERR_error_string(m, NULL)); - lws_ssl_elaborate_error(); - - wsi->socket_is_permanently_unusable = 1; - - return LWS_SSL_CAPABLE_ERROR; -} - -static int -lws_gate_accepts(struct lws_context *context, int on) -{ - struct lws_vhost *v = context->vhost_list; - - lwsl_info("gating accepts %d\n", on); - context->ssl_gate_accepts = !on; -#if defined(LWS_WITH_STATS) - context->updated = 1; -#endif - - while (v) { - if (v->use_ssl && v->lserv_wsi) /* gate ability to accept incoming connections */ - if (lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on, - (LWS_POLLIN) * on)) - lwsl_info("Unable to set accept POLLIN %d\n", on); - - v = v->vhost_next; - } - - return 0; -} - -void -lws_ssl_info_callback(const SSL *ssl, int where, int ret) -{ - struct lws *wsi; - struct lws_context *context; - struct lws_ssl_info si; - - context = (struct lws_context *)SSL_CTX_get_ex_data( - SSL_get_SSL_CTX(ssl), - openssl_SSL_CTX_private_data_index); - if (!context) - return; - wsi = wsi_from_fd(context, SSL_get_fd(ssl)); - if (!wsi) - return; - - if (!(where & wsi->vhost->ssl_info_event_mask)) - return; - - si.where = where; - si.ret = ret; - - if (user_callback_handle_rxflow(wsi->protocol->callback, - wsi, LWS_CALLBACK_SSL_INFO, - wsi->user_space, &si, 0)) - lws_set_timeout(wsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, -1); -} - - -LWS_VISIBLE int -lws_ssl_close(struct lws *wsi) -{ - lws_sockfd_type n; - - if (!wsi->ssl) - return 0; /* not handled */ - -#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) - /* kill ssl callbacks, becausse we will remove the fd from the - * table linking it to the wsi - */ - if (wsi->vhost->ssl_info_event_mask) - SSL_set_info_callback(wsi->ssl, NULL); -#endif - - n = SSL_get_fd(wsi->ssl); - if (!wsi->socket_is_permanently_unusable) - SSL_shutdown(wsi->ssl); - compatible_close(n); - SSL_free(wsi->ssl); - wsi->ssl = NULL; - - if (wsi->context->simultaneous_ssl_restriction && - wsi->context->simultaneous_ssl-- == - wsi->context->simultaneous_ssl_restriction) - /* we made space and can do an accept */ - lws_gate_accepts(wsi->context, 1); -#if defined(LWS_WITH_STATS) - wsi->context->updated = 1; -#endif - - return 1; /* handled */ -} - -/* leave all wsi close processing to the caller */ - -LWS_VISIBLE int -lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) -{ - struct lws_context *context = wsi->context; - struct lws_vhost *vh; - struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - int n, m; -#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) - BIO *bio; -#endif - char buf[256]; - - (void)buf; - - if (!LWS_SSL_ENABLED(wsi->vhost)) - return 0; - - switch (wsi->mode) { - case LWSCM_SSL_INIT: - case LWSCM_SSL_INIT_RAW: - if (wsi->ssl) - lwsl_err("%s: leaking ssl\n", __func__); - if (accept_fd == LWS_SOCK_INVALID) - assert(0); - if (context->simultaneous_ssl_restriction && - context->simultaneous_ssl >= context->simultaneous_ssl_restriction) { - lwsl_notice("unable to deal with SSL connection\n"); - return 1; - } - errno = 0; - wsi->ssl = SSL_new(wsi->vhost->ssl_ctx); - if (wsi->ssl == NULL) { - lwsl_err("SSL_new failed: %d (errno %d)\n", - lws_ssl_get_error(wsi, 0), errno); - - lws_ssl_elaborate_error(); - if (accept_fd != LWS_SOCK_INVALID) - compatible_close(accept_fd); - goto fail; - } -#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) - if (wsi->vhost->ssl_info_event_mask) - SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); -#endif - if (context->simultaneous_ssl_restriction && - ++context->simultaneous_ssl == context->simultaneous_ssl_restriction) - /* that was the last allowed SSL connection */ - lws_gate_accepts(context, 0); -#if defined(LWS_WITH_STATS) - context->updated = 1; -#endif - -#if !defined(LWS_WITH_MBEDTLS) - SSL_set_ex_data(wsi->ssl, - openssl_websocket_private_data_index, wsi); -#endif - SSL_set_fd(wsi->ssl, accept_fd); - -#ifdef USE_WOLFSSL -#ifdef USE_OLD_CYASSL - CyaSSL_set_using_nonblock(wsi->ssl, 1); -#else - wolfSSL_set_using_nonblock(wsi->ssl, 1); -#endif -#else -#if defined(LWS_WITH_MBEDTLS) - lws_plat_set_socket_options(wsi->vhost, accept_fd); -#else - SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - bio = SSL_get_rbio(wsi->ssl); - if (bio) - BIO_set_nbio(bio, 1); /* nonblocking */ - else - lwsl_notice("NULL rbio\n"); - bio = SSL_get_wbio(wsi->ssl); - if (bio) - BIO_set_nbio(bio, 1); /* nonblocking */ - else - lwsl_notice("NULL rbio\n"); -#endif -#endif - - /* - * we are not accepted yet, but we need to enter ourselves - * as a live connection. That way we can retry when more - * pieces come if we're not sorted yet - */ - - if (wsi->mode == LWSCM_SSL_INIT) - wsi->mode = LWSCM_SSL_ACK_PENDING; - else - wsi->mode = LWSCM_SSL_ACK_PENDING_RAW; - - if (insert_wsi_socket_into_fds(context, wsi)) { - lwsl_err("%s: failed to insert into fds\n", __func__); - goto fail; - } - - lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT, - context->timeout_secs); - - lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n"); - - /* fallthru */ - - case LWSCM_SSL_ACK_PENDING: - case LWSCM_SSL_ACK_PENDING_RAW: - if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { - lwsl_err("%s: lws_change_pollfd failed\n", __func__); - goto fail; - } - - lws_latency_pre(context, wsi); - - if (wsi->vhost->allow_non_ssl_on_ssl_port) { - - n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, - context->pt_serv_buf_size, MSG_PEEK); - - /* - * optionally allow non-SSL connect on SSL listening socket - * This is disabled by default, if enabled it goes around any - * SSL-level access control (eg, client-side certs) so leave - * it disabled unless you know it's not a problem for you - */ - - if (n >= 1 && pt->serv_buf[0] >= ' ') { - /* - * TLS content-type for Handshake is 0x16, and - * for ChangeCipherSpec Record, it's 0x14 - * - * A non-ssl session will start with the HTTP - * method in ASCII. If we see it's not a legit - * SSL handshake kill the SSL for this - * connection and try to handle as a HTTP - * connection upgrade directly. - */ - wsi->use_ssl = 0; - - SSL_shutdown(wsi->ssl); - SSL_free(wsi->ssl); - wsi->ssl = NULL; - if (lws_check_opt(context->options, - LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS)) - wsi->redirect_to_https = 1; - goto accepted; - } - if (!n) /* - * connection is gone, or nothing to read - * if it's gone, we will timeout on - * PENDING_TIMEOUT_SSL_ACCEPT - */ - break; - if (n < 0 && (LWS_ERRNO == LWS_EAGAIN || - LWS_ERRNO == LWS_EWOULDBLOCK)) { - /* - * well, we get no way to know ssl or not - * so go around again waiting for something - * to come and give us a hint, or timeout the - * connection. - */ - m = SSL_ERROR_WANT_READ; - goto go_again; - } - } - - /* normal SSL connection processing path */ - -#if defined(LWS_WITH_STATS) - if (!wsi->accept_start_us) - wsi->accept_start_us = time_in_microseconds(); -#endif - errno = 0; - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN, 1); - n = SSL_accept(wsi->ssl); - lws_latency(context, wsi, - "SSL_accept LWSCM_SSL_ACK_PENDING\n", n, n == 1); - lwsl_info("SSL_accept says %d\n", n); - if (n == 1) - goto accepted; - - m = lws_ssl_get_error(wsi, n); - -#if defined(LWS_WITH_MBEDTLS) - if (m == SSL_ERROR_SYSCALL && errno == 11) - m = SSL_ERROR_WANT_READ; -#endif - if (m == SSL_ERROR_SYSCALL || m == SSL_ERROR_SSL) - goto failed; - -go_again: - if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) { - if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { - lwsl_info("%s: WANT_READ change_pollfd failed\n", __func__); - goto fail; - } - - lwsl_info("SSL_ERROR_WANT_READ\n"); - break; - } - if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->ssl)) { - lwsl_debug("%s: WANT_WRITE\n", __func__); - - if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { - lwsl_info("%s: WANT_WRITE change_pollfd failed\n", __func__); - goto fail; - } - - break; - } -failed: - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1); - wsi->socket_is_permanently_unusable = 1; - lwsl_info("SSL_accept failed socket %u: %s\n", wsi->desc.sockfd, - lws_ssl_get_error_string(m, n, buf, sizeof(buf))); - lws_ssl_elaborate_error(); - goto fail; - -accepted: - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1); -#if defined(LWS_WITH_STATS) - lws_stats_atomic_bump(wsi->context, pt, - LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, - time_in_microseconds() - wsi->accept_start_us); - wsi->accept_start_us = time_in_microseconds(); -#endif - - /* adapt our vhost to match the SNI SSL_CTX that was chosen */ - vh = context->vhost_list; - while (vh) { - if (!vh->being_destroyed && wsi->ssl && - vh->ssl_ctx == SSL_get_SSL_CTX(wsi->ssl)) { - lwsl_info("setting wsi to vh %s\n", vh->name); - wsi->vhost = vh; - break; - } - vh = vh->vhost_next; - } - - /* OK, we are accepted... give him some time to negotiate */ - lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, - context->timeout_secs); - - if (wsi->mode == LWSCM_SSL_ACK_PENDING_RAW) - wsi->mode = LWSCM_RAW; - else - wsi->mode = LWSCM_HTTP_SERVING; -#if defined(LWS_WITH_HTTP2) - if (lws_h2_configure_if_upgraded(wsi)) - goto fail; -#endif - lwsl_debug("accepted new SSL conn\n"); - break; - } - - return 0; - -fail: - return 1; -} - -void -lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost) -{ - if (vhost->ssl_ctx) - SSL_CTX_free(vhost->ssl_ctx); - - if (!vhost->user_supplied_ssl_ctx && vhost->ssl_client_ctx) - SSL_CTX_free(vhost->ssl_client_ctx); -} - -void -lws_ssl_context_destroy(struct lws_context *context) -{ - -#if !defined(LWS_WITH_MBEDTLS) - -// after 1.1.0 no need -#if (OPENSSL_VERSION_NUMBER < 0x10100000) -// <= 1.0.1f = old api, 1.0.1g+ = new api -#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL) - ERR_remove_state(0); -#else -#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \ - !defined(LIBRESSL_VERSION_NUMBER) && \ - !defined(OPENSSL_IS_BORINGSSL) - ERR_remove_thread_state(); -#else - ERR_remove_thread_state(NULL); -#endif -#endif - // after 1.1.0 no need -#if (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000) - SSL_COMP_free_compression_methods(); -#endif - ERR_free_strings(); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); -#endif -#endif -} diff --git a/thirdparty/mbedtls/LICENSE b/thirdparty/mbedtls/LICENSE new file mode 100644 index 0000000000..546a8e631f --- /dev/null +++ b/thirdparty/mbedtls/LICENSE @@ -0,0 +1,2 @@ +Unless specifically indicated otherwise in a file, files are licensed +under the Apache 2.0 license, as can be found in: apache-2.0.txt diff --git a/thirdparty/mbedtls/apache-2.0.txt b/thirdparty/mbedtls/apache-2.0.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/thirdparty/mbedtls/apache-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/thirdparty/miniupnpc/LICENSE b/thirdparty/miniupnpc/LICENSE new file mode 100644 index 0000000000..0816733704 --- /dev/null +++ b/thirdparty/miniupnpc/LICENSE @@ -0,0 +1,27 @@ +MiniUPnPc +Copyright (c) 2005-2016, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/thirdparty/miniupnpc/codelength.h b/thirdparty/miniupnpc/codelength.h new file mode 100644 index 0000000000..ea0b005ffe --- /dev/null +++ b/thirdparty/miniupnpc/codelength.h @@ -0,0 +1,54 @@ +/* $Id: codelength.h,v 1.3 2011/07/30 13:10:05 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef CODELENGTH_H_INCLUDED +#define CODELENGTH_H_INCLUDED + +/* Encode length by using 7bit per Byte : + * Most significant bit of each byte specifies that the + * following byte is part of the code */ + +/* n : unsigned + * p : unsigned char * + */ +#define DECODELENGTH(n, p) n = 0; \ + do { n = (n << 7) | (*p & 0x7f); } \ + while((*(p++)&0x80) && (n<(1<<25))); + +/* n : unsigned + * READ : function/macro to read one byte (unsigned char) + */ +#define DECODELENGTH_READ(n, READ) \ + n = 0; \ + do { \ + unsigned char c; \ + READ(c); \ + n = (n << 7) | (c & 0x07f); \ + if(!(c&0x80)) break; \ + } while(n<(1<<25)); + +/* n : unsigned + * p : unsigned char * + * p_limit : unsigned char * + */ +#define DECODELENGTH_CHECKLIMIT(n, p, p_limit) \ + n = 0; \ + do { \ + if((p) >= (p_limit)) break; \ + n = (n << 7) | (*(p) & 0x7f); \ + } while((*((p)++)&0x80) && (n<(1<<25))); + + +/* n : unsigned + * p : unsigned char * + */ +#define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ + if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ + if(n>=16384) *(p++) = (n >> 14) | 0x80; \ + if(n>=128) *(p++) = (n >> 7) | 0x80; \ + *(p++) = n & 0x7f; + +#endif /* CODELENGTH_H_INCLUDED */ diff --git a/thirdparty/miniupnpc/connecthostport.c b/thirdparty/miniupnpc/connecthostport.c new file mode 100644 index 0000000000..ea6e4e5943 --- /dev/null +++ b/thirdparty/miniupnpc/connecthostport.c @@ -0,0 +1,264 @@ +/* $Id: connecthostport.c,v 1.15 2015/10/09 16:26:19 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2010-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +/* use getaddrinfo() or gethostbyname() + * uncomment the following line in order to use gethostbyname() */ +#ifdef NO_GETADDRINFO +#define USE_GETHOSTBYNAME +#endif + +#include <string.h> +#include <stdio.h> +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#define MAXHOSTNAMELEN 64 +#define snprintf _snprintf +#define herror +#define socklen_t int +#else /* #ifdef _WIN32 */ +#include <unistd.h> +#include <sys/types.h> +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#include <sys/time.h> +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ +#include <sys/param.h> +#include <sys/select.h> +#include <errno.h> +#define closesocket close +#include <netdb.h> +#include <netinet/in.h> +/* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions + * during the connect() call */ +#define MINIUPNPC_IGNORE_EINTR +#include <sys/socket.h> +#include <sys/select.h> +#endif /* #else _WIN32 */ + +/* definition of PRINT_SOCKET_ERROR */ +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#if defined(__amigaos__) || defined(__amigaos4__) +#define herror(A) printf("%s\n", A) +#endif + +#include "connecthostport.h" + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or -1 in case of error */ +SOCKET connecthostport(const char * host, unsigned short port, + unsigned int scope_id) +{ + SOCKET s; + int n; +#ifdef USE_GETHOSTBYNAME + struct sockaddr_in dest; + struct hostent *hp; +#else /* #ifdef USE_GETHOSTBYNAME */ + char tmp_host[MAXHOSTNAMELEN+1]; + char port_str[8]; + struct addrinfo *ai, *p; + struct addrinfo hints; +#endif /* #ifdef USE_GETHOSTBYNAME */ +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + struct timeval timeout; +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + +#ifdef USE_GETHOSTBYNAME + hp = gethostbyname(host); + if(hp == NULL) + { + herror(host); + return INVALID_SOCKET; + } + memcpy(&dest.sin_addr, hp->h_addr, sizeof(dest.sin_addr)); + memset(dest.sin_zero, 0, sizeof(dest.sin_zero)); + s = socket(PF_INET, SOCK_STREAM, 0); + if(ISINVALID(s)) + { + PRINT_SOCKET_ERROR("socket"); + return INVALID_SOCKET; + } +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + /* setting a 3 seconds timeout for the connect() call */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt SO_RCVTIMEO"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt SO_SNDTIMEO"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + dest.sin_family = AF_INET; + dest.sin_port = htons(port); + n = connect(s, (struct sockaddr *)&dest, sizeof(struct sockaddr_in)); +#ifdef MINIUPNPC_IGNORE_EINTR + /* EINTR The system call was interrupted by a signal that was caught + * EINPROGRESS The socket is nonblocking and the connection cannot + * be completed immediately. */ + while(n < 0 && (errno == EINTR || errno == EINPROGRESS)) + { + socklen_t len; + fd_set wset; + int err; + FD_ZERO(&wset); + FD_SET(s, &wset); + if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) + continue; + /*len = 0;*/ + /*n = getpeername(s, NULL, &len);*/ + len = sizeof(err); + if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + closesocket(s); + return INVALID_SOCKET; + } + if(err != 0) { + errno = err; + n = -1; + } + } +#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ + if(n<0) + { + PRINT_SOCKET_ERROR("connect"); + closesocket(s); + return INVALID_SOCKET; + } +#else /* #ifdef USE_GETHOSTBYNAME */ + /* use getaddrinfo() instead of gethostbyname() */ + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_ADDRCONFIG; */ +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; +#endif + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; /* AF_INET, AF_INET6 or AF_UNSPEC */ + /* hints.ai_protocol = IPPROTO_TCP; */ + snprintf(port_str, sizeof(port_str), "%hu", port); + if(host[0] == '[') + { + /* literal ip v6 address */ + int i, j; + for(i = 0, j = 1; host[j] && (host[j] != ']') && i < MAXHOSTNAMELEN; i++, j++) + { + tmp_host[i] = host[j]; + if(0 == memcmp(host+j, "%25", 3)) /* %25 is just url encoding for '%' */ + j+=2; /* skip "25" */ + } + tmp_host[i] = '\0'; + } + else + { + strncpy(tmp_host, host, MAXHOSTNAMELEN); + } + tmp_host[MAXHOSTNAMELEN] = '\0'; + n = getaddrinfo(tmp_host, port_str, &hints, &ai); + if(n != 0) + { +#ifdef _WIN32 + fprintf(stderr, "getaddrinfo() error : %d\n", n); +#else + fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n)); +#endif + return INVALID_SOCKET; + } + s = -1; + for(p = ai; p; p = p->ai_next) + { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if(ISINVALID(s)) + continue; + if(p->ai_addr->sa_family == AF_INET6 && scope_id > 0) { + struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *)p->ai_addr; + addr6->sin6_scope_id = scope_id; + } +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + /* setting a 3 seconds timeout for the connect() call */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + n = connect(s, p->ai_addr, p->ai_addrlen); +#ifdef MINIUPNPC_IGNORE_EINTR + /* EINTR The system call was interrupted by a signal that was caught + * EINPROGRESS The socket is nonblocking and the connection cannot + * be completed immediately. */ + while(n < 0 && (errno == EINTR || errno == EINPROGRESS)) + { + socklen_t len; + fd_set wset; + int err; + FD_ZERO(&wset); + FD_SET(s, &wset); + if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) + continue; + /*len = 0;*/ + /*n = getpeername(s, NULL, &len);*/ + len = sizeof(err); + if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + closesocket(s); + freeaddrinfo(ai); + return INVALID_SOCKET; + } + if(err != 0) { + errno = err; + n = -1; + } + } +#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ + if(n < 0) + { + closesocket(s); + continue; + } + else + { + break; + } + } + freeaddrinfo(ai); + if(ISINVALID(s)) + { + PRINT_SOCKET_ERROR("socket"); + return INVALID_SOCKET; + } + if(n < 0) + { + PRINT_SOCKET_ERROR("connect"); + return INVALID_SOCKET; + } +#endif /* #ifdef USE_GETHOSTBYNAME */ + return s; +} + diff --git a/thirdparty/miniupnpc/connecthostport.h b/thirdparty/miniupnpc/connecthostport.h new file mode 100644 index 0000000000..701816b5b6 --- /dev/null +++ b/thirdparty/miniupnpc/connecthostport.h @@ -0,0 +1,20 @@ +/* $Id: connecthostport.h,v 1.2 2012/06/23 22:32:33 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2010-2018 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef CONNECTHOSTPORT_H_INCLUDED +#define CONNECTHOSTPORT_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or INVALID_SOCKET in case of error */ +SOCKET connecthostport(const char * host, unsigned short port, + unsigned int scope_id); + +#endif + diff --git a/thirdparty/miniupnpc/igd_desc_parse.c b/thirdparty/miniupnpc/igd_desc_parse.c new file mode 100644 index 0000000000..d2999ad011 --- /dev/null +++ b/thirdparty/miniupnpc/igd_desc_parse.c @@ -0,0 +1,123 @@ +/* $Id: igd_desc_parse.c,v 1.17 2015/09/15 13:30:04 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include "igd_desc_parse.h" +#include <stdio.h> +#include <string.h> + +/* Start element handler : + * update nesting level counter and copy element name */ +void IGDstartelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + if(l >= MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(datas->cureltname, name, l); + datas->cureltname[l] = '\0'; + datas->level++; + if( (l==7) && !memcmp(name, "service", l) ) { + datas->tmp.controlurl[0] = '\0'; + datas->tmp.eventsuburl[0] = '\0'; + datas->tmp.scpdurl[0] = '\0'; + datas->tmp.servicetype[0] = '\0'; + } +} + +#define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) + +/* End element handler : + * update nesting level counter and update parser state if + * service element is parsed */ +void IGDendelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + datas->level--; + /*printf("endelt %2d %.*s\n", datas->level, l, name);*/ + if( (l==7) && !memcmp(name, "service", l) ) + { + if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) { + memcpy(&datas->CIF, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPv6FirewallControl:")) { + memcpy(&datas->IPv6FC, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPConnection:") + || COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANPPPConnection:") ) { + if(datas->first.servicetype[0] == '\0') { + memcpy(&datas->first, &datas->tmp, sizeof(struct IGDdatas_service)); + } else { + memcpy(&datas->second, &datas->tmp, sizeof(struct IGDdatas_service)); + } + } + } +} + +/* Data handler : + * copy data depending on the current element name and state */ +void IGDdata(void * d, const char * data, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + char * dstmember = 0; + /*printf("%2d %s : %.*s\n", + datas->level, datas->cureltname, l, data); */ + if( !strcmp(datas->cureltname, "URLBase") ) + dstmember = datas->urlbase; + else if( !strcmp(datas->cureltname, "presentationURL") ) + dstmember = datas->presentationurl; + else if( !strcmp(datas->cureltname, "serviceType") ) + dstmember = datas->tmp.servicetype; + else if( !strcmp(datas->cureltname, "controlURL") ) + dstmember = datas->tmp.controlurl; + else if( !strcmp(datas->cureltname, "eventSubURL") ) + dstmember = datas->tmp.eventsuburl; + else if( !strcmp(datas->cureltname, "SCPDURL") ) + dstmember = datas->tmp.scpdurl; +/* else if( !strcmp(datas->cureltname, "deviceType") ) + dstmember = datas->devicetype_tmp;*/ + if(dstmember) + { + if(l>=MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(dstmember, data, l); + dstmember[l] = '\0'; + } +} + +#ifdef DEBUG +void printIGD(struct IGDdatas * d) +{ + printf("urlbase = '%s'\n", d->urlbase); + printf("WAN Device (Common interface config) :\n"); + /*printf(" deviceType = '%s'\n", d->CIF.devicetype);*/ + printf(" serviceType = '%s'\n", d->CIF.servicetype); + printf(" controlURL = '%s'\n", d->CIF.controlurl); + printf(" eventSubURL = '%s'\n", d->CIF.eventsuburl); + printf(" SCPDURL = '%s'\n", d->CIF.scpdurl); + printf("primary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->first.devicetype);*/ + printf(" servicetype = '%s'\n", d->first.servicetype); + printf(" controlURL = '%s'\n", d->first.controlurl); + printf(" eventSubURL = '%s'\n", d->first.eventsuburl); + printf(" SCPDURL = '%s'\n", d->first.scpdurl); + printf("secondary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->second.devicetype);*/ + printf(" servicetype = '%s'\n", d->second.servicetype); + printf(" controlURL = '%s'\n", d->second.controlurl); + printf(" eventSubURL = '%s'\n", d->second.eventsuburl); + printf(" SCPDURL = '%s'\n", d->second.scpdurl); + printf("WAN IPv6 Firewall Control :\n"); + /*printf(" deviceType = '%s'\n", d->IPv6FC.devicetype);*/ + printf(" servicetype = '%s'\n", d->IPv6FC.servicetype); + printf(" controlURL = '%s'\n", d->IPv6FC.controlurl); + printf(" eventSubURL = '%s'\n", d->IPv6FC.eventsuburl); + printf(" SCPDURL = '%s'\n", d->IPv6FC.scpdurl); +} +#endif /* DEBUG */ + diff --git a/thirdparty/miniupnpc/igd_desc_parse.h b/thirdparty/miniupnpc/igd_desc_parse.h new file mode 100644 index 0000000000..0de546b697 --- /dev/null +++ b/thirdparty/miniupnpc/igd_desc_parse.h @@ -0,0 +1,49 @@ +/* $Id: igd_desc_parse.h,v 1.12 2014/11/17 17:19:13 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2014 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef IGD_DESC_PARSE_H_INCLUDED +#define IGD_DESC_PARSE_H_INCLUDED + +/* Structure to store the result of the parsing of UPnP + * descriptions of Internet Gateway Devices */ +#define MINIUPNPC_URL_MAXSIZE (128) +struct IGDdatas_service { + char controlurl[MINIUPNPC_URL_MAXSIZE]; + char eventsuburl[MINIUPNPC_URL_MAXSIZE]; + char scpdurl[MINIUPNPC_URL_MAXSIZE]; + char servicetype[MINIUPNPC_URL_MAXSIZE]; + /*char devicetype[MINIUPNPC_URL_MAXSIZE];*/ +}; + +struct IGDdatas { + char cureltname[MINIUPNPC_URL_MAXSIZE]; + char urlbase[MINIUPNPC_URL_MAXSIZE]; + char presentationurl[MINIUPNPC_URL_MAXSIZE]; + int level; + /*int state;*/ + /* "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ + struct IGDdatas_service CIF; + /* "urn:schemas-upnp-org:service:WANIPConnection:1" + * "urn:schemas-upnp-org:service:WANPPPConnection:1" */ + struct IGDdatas_service first; + /* if both WANIPConnection and WANPPPConnection are present */ + struct IGDdatas_service second; + /* "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" */ + struct IGDdatas_service IPv6FC; + /* tmp */ + struct IGDdatas_service tmp; +}; + +void IGDstartelt(void *, const char *, int); +void IGDendelt(void *, const char *, int); +void IGDdata(void *, const char *, int); +#ifdef DEBUG +void printIGD(struct IGDdatas *); +#endif /* DEBUG */ + +#endif /* IGD_DESC_PARSE_H_INCLUDED */ diff --git a/thirdparty/miniupnpc/listdevices.c b/thirdparty/miniupnpc/listdevices.c new file mode 100644 index 0000000000..bd9ba57efc --- /dev/null +++ b/thirdparty/miniupnpc/listdevices.c @@ -0,0 +1,197 @@ +/* $Id: listdevices.c,v 1.6 2015/07/23 20:40:08 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2013-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#ifdef _WIN32 +#include <winsock2.h> +#endif /* _WIN32 */ +#include "miniupnpc.h" + +struct upnp_dev_list { + struct upnp_dev_list * next; + char * descURL; + struct UPNPDev * * array; + size_t count; + size_t allocated_count; +}; + +#define ADD_DEVICE_COUNT_STEP 16 + +void add_device(struct upnp_dev_list * * list_head, struct UPNPDev * dev) +{ + struct upnp_dev_list * elt; + size_t i; + + if(dev == NULL) + return; + for(elt = *list_head; elt != NULL; elt = elt->next) { + if(strcmp(elt->descURL, dev->descURL) == 0) { + for(i = 0; i < elt->count; i++) { + if (strcmp(elt->array[i]->st, dev->st) == 0 && strcmp(elt->array[i]->usn, dev->usn) == 0) { + return; /* already found */ + } + } + if(elt->count >= elt->allocated_count) { + struct UPNPDev * * tmp; + elt->allocated_count += ADD_DEVICE_COUNT_STEP; + tmp = realloc(elt->array, elt->allocated_count * sizeof(struct UPNPDev *)); + if(tmp == NULL) { + fprintf(stderr, "Failed to realloc(%p, %lu)\n", elt->array, (unsigned long)(elt->allocated_count * sizeof(struct UPNPDev *))); + return; + } + elt->array = tmp; + } + elt->array[elt->count++] = dev; + return; + } + } + elt = malloc(sizeof(struct upnp_dev_list)); + if(elt == NULL) { + fprintf(stderr, "Failed to malloc(%lu)\n", (unsigned long)sizeof(struct upnp_dev_list)); + return; + } + elt->next = *list_head; + elt->descURL = strdup(dev->descURL); + if(elt->descURL == NULL) { + fprintf(stderr, "Failed to strdup(%s)\n", dev->descURL); + free(elt); + return; + } + elt->allocated_count = ADD_DEVICE_COUNT_STEP; + elt->array = malloc(ADD_DEVICE_COUNT_STEP * sizeof(struct UPNPDev *)); + if(elt->array == NULL) { + fprintf(stderr, "Failed to malloc(%lu)\n", (unsigned long)(ADD_DEVICE_COUNT_STEP * sizeof(struct UPNPDev *))); + free(elt->descURL); + free(elt); + return; + } + elt->array[0] = dev; + elt->count = 1; + *list_head = elt; +} + +void free_device(struct upnp_dev_list * elt) +{ + free(elt->descURL); + free(elt->array); + free(elt); +} + +int main(int argc, char * * argv) +{ + const char * searched_device = NULL; + const char * * searched_devices = NULL; + const char * multicastif = 0; + const char * minissdpdpath = 0; + int ipv6 = 0; + unsigned char ttl = 2; + int error = 0; + struct UPNPDev * devlist = 0; + struct UPNPDev * dev; + struct upnp_dev_list * sorted_list = NULL; + struct upnp_dev_list * dev_array; + int i; + +#ifdef _WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + + for(i = 1; i < argc; i++) { + if(strcmp(argv[i], "-6") == 0) + ipv6 = 1; + else if(strcmp(argv[i], "-d") == 0) { + if(++i >= argc) { + fprintf(stderr, "%s option needs one argument\n", "-d"); + return 1; + } + searched_device = argv[i]; + } else if(strcmp(argv[i], "-t") == 0) { + if(++i >= argc) { + fprintf(stderr, "%s option needs one argument\n", "-t"); + return 1; + } + ttl = (unsigned char)atoi(argv[i]); + } else if(strcmp(argv[i], "-l") == 0) { + if(++i >= argc) { + fprintf(stderr, "-l option needs at least one argument\n"); + return 1; + } + searched_devices = (const char * *)(argv + i); + break; + } else if(strcmp(argv[i], "-m") == 0) { + if(++i >= argc) { + fprintf(stderr, "-m option needs one argument\n"); + return 1; + } + multicastif = argv[i]; + } else { + printf("usage : %s [options] [-l <device1> <device2> ...]\n", argv[0]); + printf("options :\n"); + printf(" -6 : use IPv6\n"); + printf(" -m address/ifname : network interface to use for multicast\n"); + printf(" -d <device string> : search only for this type of device\n"); + printf(" -l <device1> <device2> ... : search only for theses types of device\n"); + printf(" -t ttl : set multicast TTL. Default value is 2.\n"); + printf(" -h : this help\n"); + return 1; + } + } + + if(searched_device) { + printf("searching UPnP device type %s\n", searched_device); + devlist = upnpDiscoverDevice(searched_device, + 2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error); + } else if(searched_devices) { + printf("searching UPnP device types :\n"); + for(i = 0; searched_devices[i]; i++) + printf("\t%s\n", searched_devices[i]); + devlist = upnpDiscoverDevices(searched_devices, + 2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error, 1); + } else { + printf("searching all UPnP devices\n"); + devlist = upnpDiscoverAll(2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error); + } + if(devlist) { + for(dev = devlist, i = 1; dev != NULL; dev = dev->pNext, i++) { + printf("%3d: %-48s\n", i, dev->st); + printf(" %s\n", dev->descURL); + printf(" %s\n", dev->usn); + add_device(&sorted_list, dev); + } + putchar('\n'); + for (dev_array = sorted_list; dev_array != NULL ; dev_array = dev_array->next) { + printf("%s :\n", dev_array->descURL); + for(i = 0; (unsigned)i < dev_array->count; i++) { + printf("%2d: %s\n", i+1, dev_array->array[i]->st); + printf(" %s\n", dev_array->array[i]->usn); + } + putchar('\n'); + } + freeUPNPDevlist(devlist); + while(sorted_list != NULL) { + dev_array = sorted_list; + sorted_list = sorted_list->next; + free_device(dev_array); + } + } else { + printf("no device found.\n"); + } + + return 0; +} + diff --git a/thirdparty/miniupnpc/minisoap.c b/thirdparty/miniupnpc/minisoap.c new file mode 100644 index 0000000000..520c9302e8 --- /dev/null +++ b/thirdparty/miniupnpc/minisoap.c @@ -0,0 +1,124 @@ +/* $Id: minisoap.c,v 1.25 2017/04/21 10:03:24 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * + * Minimal SOAP implementation for UPnP protocol. + */ +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 +#include <io.h> +#include <winsock2.h> +#define snprintf _snprintf +#else +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#endif +#include "minisoap.h" +#include "miniupnpcstrings.h" + +/* only for malloc */ +#include <stdlib.h> + +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +/* httpWrite sends the headers and the body to the socket + * and returns the number of bytes sent */ +static int +httpWrite(SOCKET fd, const char * body, int bodysize, + const char * headers, int headerssize) +{ + int n = 0; + /*n = write(fd, headers, headerssize);*/ + /*if(bodysize>0) + n += write(fd, body, bodysize);*/ + /* Note : my old linksys router only took into account + * soap request that are sent into only one packet */ + char * p; + /* TODO: AVOID MALLOC, we could use writev() for that */ + p = malloc(headerssize+bodysize); + if(!p) + return -1; + memcpy(p, headers, headerssize); + memcpy(p+headerssize, body, bodysize); + /*n = write(fd, p, headerssize+bodysize);*/ + n = send(fd, p, headerssize+bodysize, 0); + if(n<0) { + PRINT_SOCKET_ERROR("send"); + } + /* disable send on the socket */ + /* draytek routers don't seem to like that... */ +#if 0 +#ifdef _WIN32 + if(shutdown(fd, SD_SEND)<0) { +#else + if(shutdown(fd, SHUT_WR)<0) { /*SD_SEND*/ +#endif + PRINT_SOCKET_ERROR("shutdown"); + } +#endif + free(p); + return n; +} + +/* self explanatory */ +int soapPostSubmit(SOCKET fd, + const char * url, + const char * host, + unsigned short port, + const char * action, + const char * body, + const char * httpversion) +{ + int bodysize; + char headerbuf[512]; + int headerssize; + char portstr[8]; + bodysize = (int)strlen(body); + /* We are not using keep-alive HTTP connections. + * HTTP/1.1 needs the header Connection: close to do that. + * This is the default with HTTP/1.0 + * Using HTTP/1.1 means we need to support chunked transfer-encoding : + * When using HTTP/1.1, the router "BiPAC 7404VNOX" always use chunked + * transfer encoding. */ + /* Connection: Close is normally there only in HTTP/1.1 but who knows */ + portstr[0] = '\0'; + if(port != 80) + snprintf(portstr, sizeof(portstr), ":%hu", port); + headerssize = snprintf(headerbuf, sizeof(headerbuf), + "POST %s HTTP/%s\r\n" + "Host: %s%s\r\n" + "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%s\"\r\n" + "Connection: Close\r\n" + "Cache-Control: no-cache\r\n" /* ??? */ + "Pragma: no-cache\r\n" + "\r\n", + url, httpversion, host, portstr, bodysize, action); + if ((unsigned int)headerssize >= sizeof(headerbuf)) + return -1; +#ifdef DEBUG + /*printf("SOAP request : headersize=%d bodysize=%d\n", + headerssize, bodysize); + */ + printf("SOAP request : POST %s HTTP/%s - Host: %s%s\n", + url, httpversion, host, portstr); + printf("SOAPAction: \"%s\" - Content-Length: %d\n", action, bodysize); + printf("Headers :\n%s", headerbuf); + printf("Body :\n%s\n", body); +#endif + return httpWrite(fd, body, bodysize, headerbuf, headerssize); +} + + diff --git a/thirdparty/miniupnpc/minisoap.h b/thirdparty/miniupnpc/minisoap.h new file mode 100644 index 0000000000..d6a45d03ba --- /dev/null +++ b/thirdparty/miniupnpc/minisoap.h @@ -0,0 +1,17 @@ +/* $Id: minisoap.h,v 1.4 2010/04/12 20:39:41 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ +#ifndef MINISOAP_H_INCLUDED +#define MINISOAP_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +/*int httpWrite(int, const char *, int, const char *);*/ +int soapPostSubmit(SOCKET, const char *, const char *, unsigned short, + const char *, const char *, const char *); + +#endif + diff --git a/thirdparty/miniupnpc/minissdpc.c b/thirdparty/miniupnpc/minissdpc.c new file mode 100644 index 0000000000..d76b242ad0 --- /dev/null +++ b/thirdparty/miniupnpc/minissdpc.c @@ -0,0 +1,888 @@ +/* $Id: minissdpc.c,v 1.32 2016/10/07 09:04:36 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2018 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +/*#include <syslog.h>*/ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#if defined (__NetBSD__) +#include <net/if.h> +#endif +#if defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#include <iphlpapi.h> +#define snprintf _snprintf +#if !defined(_MSC_VER) +#include <stdint.h> +#else /* !defined(_MSC_VER) */ +typedef unsigned short uint16_t; +#endif /* !defined(_MSC_VER) */ +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#endif /* _WIN32 */ +#if defined(__amigaos__) || defined(__amigaos4__) +#include <sys/socket.h> +#endif /* defined(__amigaos__) || defined(__amigaos4__) */ +#if defined(__amigaos__) +#define uint16_t unsigned short +#endif /* defined(__amigaos__) */ +/* Hack */ +#define UNIX_PATH_LEN 108 +struct sockaddr_un { + uint16_t sun_family; + char sun_path[UNIX_PATH_LEN]; +}; +#else /* defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) */ +#include <strings.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <net/if.h> +#define closesocket close +#endif + +#include "miniupnpc_socketdef.h" + +#if !defined(__DragonFly__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__sun) && !defined(__GNU__) && !defined(__FreeBSD_kernel__) +#define HAS_IP_MREQN +#endif + +#if !defined(HAS_IP_MREQN) && !defined(_WIN32) +#include <sys/ioctl.h> +#if defined(__sun) +#include <sys/sockio.h> +#endif +#endif + +#if defined(HAS_IP_MREQN) && defined(NEED_STRUCT_IP_MREQN) +/* Several versions of glibc don't define this structure, + * define it here and compile with CFLAGS NEED_STRUCT_IP_MREQN */ +struct ip_mreqn +{ + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_address; /* local IP address of interface */ + int imr_ifindex; /* Interface index */ +}; +#endif + +#if defined(__amigaos__) || defined(__amigaos4__) +/* Amiga OS specific stuff */ +#define TIMEVAL struct timeval +#endif + +#include "minissdpc.h" +#include "miniupnpc.h" +#include "receivedata.h" + +#if !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) + +#include "codelength.h" + +struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath, int * error) +{ + struct UPNPDev * devlist = NULL; + int s; + int res; + + s = connectToMiniSSDPD(socketpath); + if (s < 0) { + if (error) + *error = s; + return NULL; + } + res = requestDevicesFromMiniSSDPD(s, devtype); + if (res < 0) { + if (error) + *error = res; + } else { + devlist = receiveDevicesFromMiniSSDPD(s, error); + } + disconnectFromMiniSSDPD(s); + return devlist; +} + +/* macros used to read from unix socket */ +#define READ_BYTE_BUFFER(c) \ + if((int)bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + c = buffer[bufferindex++]; + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +#define READ_COPY_BUFFER(dst, len) \ + for(l = len, p = (unsigned char *)dst; l > 0; ) { \ + unsigned int lcopy; \ + if((int)bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + lcopy = MIN(l, (n - bufferindex)); \ + memcpy(p, buffer + bufferindex, lcopy); \ + l -= lcopy; \ + p += lcopy; \ + bufferindex += lcopy; \ + } + +#define READ_DISCARD_BUFFER(len) \ + for(l = len; l > 0; ) { \ + unsigned int lcopy; \ + if(bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + lcopy = MIN(l, (n - bufferindex)); \ + l -= lcopy; \ + bufferindex += lcopy; \ + } + +int +connectToMiniSSDPD(const char * socketpath) +{ + int s; + struct sockaddr_un addr; +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) + struct timeval timeout; +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if(s < 0) + { + /*syslog(LOG_ERR, "socket(unix): %m");*/ + perror("socket(unix)"); + return MINISSDPC_SOCKET_ERROR; + } +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) + /* setting a 3 seconds timeout */ + /* not supported for AF_UNIX sockets under Solaris */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + perror("setsockopt SO_RCVTIMEO unix"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + perror("setsockopt SO_SNDTIMEO unix"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + if(!socketpath) + socketpath = "/var/run/minissdpd.sock"; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socketpath, sizeof(addr.sun_path)); + /* TODO : check if we need to handle the EINTR */ + if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) + { + /*syslog(LOG_WARNING, "connect(\"%s\"): %m", socketpath);*/ + close(s); + return MINISSDPC_SOCKET_ERROR; + } + return s; +} + +int +disconnectFromMiniSSDPD(int s) +{ + if (close(s) < 0) + return MINISSDPC_SOCKET_ERROR; + return MINISSDPC_SUCCESS; +} + +int +requestDevicesFromMiniSSDPD(int s, const char * devtype) +{ + unsigned char buffer[256]; + unsigned char * p; + unsigned int stsize, l; + + stsize = strlen(devtype); + if(stsize == 8 && 0 == memcmp(devtype, "ssdp:all", 8)) + { + buffer[0] = 3; /* request type 3 : everything */ + } + else + { + buffer[0] = 1; /* request type 1 : request devices/services by type */ + } + p = buffer + 1; + l = stsize; CODELENGTH(l, p); + if(p + stsize > buffer + sizeof(buffer)) + { + /* devtype is too long ! */ +#ifdef DEBUG + fprintf(stderr, "devtype is too long ! stsize=%u sizeof(buffer)=%u\n", + stsize, (unsigned)sizeof(buffer)); +#endif /* DEBUG */ + return MINISSDPC_INVALID_INPUT; + } + memcpy(p, devtype, stsize); + p += stsize; + if(write(s, buffer, p - buffer) < 0) + { + /*syslog(LOG_ERR, "write(): %m");*/ + perror("minissdpc.c: write()"); + return MINISSDPC_SOCKET_ERROR; + } + return MINISSDPC_SUCCESS; +} + +struct UPNPDev * +receiveDevicesFromMiniSSDPD(int s, int * error) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = NULL; + unsigned char buffer[256]; + ssize_t n; + unsigned char * p; + unsigned char * url; + unsigned char * st; + unsigned int bufferindex; + unsigned int i, ndev; + unsigned int urlsize, stsize, usnsize, l; + + n = read(s, buffer, sizeof(buffer)); + if(n<=0) + { + perror("minissdpc.c: read()"); + if (error) + *error = MINISSDPC_SOCKET_ERROR; + return NULL; + } + ndev = buffer[0]; + bufferindex = 1; + for(i = 0; i < ndev; i++) + { + DECODELENGTH_READ(urlsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + return devlist; + } +#ifdef DEBUG + printf(" urlsize=%u", urlsize); +#endif /* DEBUG */ + url = malloc(urlsize); + if(url == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + return devlist; + } + READ_COPY_BUFFER(url, urlsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_return; + } + DECODELENGTH_READ(stsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_return; + } +#ifdef DEBUG + printf(" stsize=%u", stsize); +#endif /* DEBUG */ + st = malloc(stsize); + if (st == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + goto free_url_and_return; + } + READ_COPY_BUFFER(st, stsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_st_and_return; + } + DECODELENGTH_READ(usnsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_st_and_return; + } +#ifdef DEBUG + printf(" usnsize=%u\n", usnsize); +#endif /* DEBUG */ + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize); + if(tmp == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + goto free_url_and_st_and_return; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + memcpy(tmp->buffer, url, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->st, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + free(url); + free(st); + url = NULL; + st = NULL; + tmp->usn = tmp->buffer + 1 + urlsize + 1 + stsize; + READ_COPY_BUFFER(tmp->usn, usnsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_tmp_and_return; + } + tmp->buffer[urlsize+1+stsize+1+usnsize] = '\0'; + tmp->scope_id = 0; /* default value. scope_id is not available with MiniSSDPd */ + devlist = tmp; + } + if (error) + *error = MINISSDPC_SUCCESS; + return devlist; + +free_url_and_st_and_return: + free(st); +free_url_and_return: + free(url); + return devlist; + +free_tmp_and_return: + free(tmp); + return devlist; +} + +#endif /* !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) */ + +/* parseMSEARCHReply() + * the last 4 arguments are filled during the parsing : + * - location/locationsize : "location:" field of the SSDP reply packet + * - st/stsize : "st:" field of the SSDP reply packet. + * The strings are NOT null terminated */ +static void +parseMSEARCHReply(const char * reply, int size, + const char * * location, int * locationsize, + const char * * st, int * stsize, + const char * * usn, int * usnsize) +{ + int a, b, i; + i = 0; + a = i; /* start of the line */ + b = 0; /* end of the "header" (position of the colon) */ + while(i<size) + { + switch(reply[i]) + { + case ':': + if(b==0) + { + b = i; /* end of the "header" */ + /*for(j=a; j<b; j++) + { + putchar(reply[j]); + } + */ + } + break; + case '\x0a': + case '\x0d': + if(b!=0) + { + /*for(j=b+1; j<i; j++) + { + putchar(reply[j]); + } + putchar('\n');*/ + /* skip the colon and white spaces */ + do { b++; } while(reply[b]==' '); + if(0==strncasecmp(reply+a, "location", 8)) + { + *location = reply+b; + *locationsize = i-b; + } + else if(0==strncasecmp(reply+a, "st", 2)) + { + *st = reply+b; + *stsize = i-b; + } + else if(0==strncasecmp(reply+a, "usn", 3)) + { + *usn = reply+b; + *usnsize = i-b; + } + b = 0; + } + a = i+1; + break; + default: + break; + } + i++; + } +} + +/* port upnp discover : SSDP protocol */ +#define SSDP_PORT 1900 +#define XSTR(s) STR(s) +#define STR(s) #s +#define UPNP_MCAST_ADDR "239.255.255.250" +/* for IPv6 */ +#define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */ +#define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */ + +/* direct discovery if minissdpd responses are not sufficient */ +/* ssdpDiscoverDevices() : + * return a chained list of all devices found or NULL if + * no devices was found. + * It is up to the caller to free the chained list + * delay is in millisecond (poll). + * UDA v1.1 says : + * The TTL for the IP packet SHOULD default to 2 and + * SHOULD be configurable. */ +struct UPNPDev * +ssdpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = 0; + unsigned int scope_id = 0; + int opt = 1; + static const char MSearchMsgFmt[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: %s:" XSTR(SSDP_PORT) "\r\n" + "ST: %s\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: %u\r\n" + "\r\n"; + int deviceIndex; + char bufr[1536]; /* reception and emission buffer */ + SOCKET sudp; + int n; + struct sockaddr_storage sockudp_r; + unsigned int mx; +#ifdef NO_GETADDRINFO + struct sockaddr_storage sockudp_w; +#else + int rv; + struct addrinfo hints, *servinfo, *p; +#endif +#ifdef _WIN32 + MIB_IPFORWARDROW ip_forward; + unsigned long _ttl = (unsigned long)ttl; +#endif + int linklocal = 1; + int sentok; + + if(error) + *error = MINISSDPC_UNKNOWN_ERROR; + + if(localport==UPNP_LOCAL_PORT_SAME) + localport = SSDP_PORT; + +#ifdef _WIN32 + sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP); +#else + sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0); +#endif + if(ISINVALID(sudp)) + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("socket"); + return NULL; + } + /* reception */ + memset(&sockudp_r, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_r; + p->sin6_family = AF_INET6; + if(localport > 0 && localport < 65536) + p->sin6_port = htons((unsigned short)localport); + p->sin6_addr = in6addr_any; /* in6addr_any is not available with MinGW32 3.4.2 */ + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_r; + p->sin_family = AF_INET; + if(localport > 0 && localport < 65536) + p->sin_port = htons((unsigned short)localport); + p->sin_addr.s_addr = INADDR_ANY; + } +#ifdef _WIN32 +/* This code could help us to use the right Network interface for + * SSDP multicast traffic */ +/* Get IP associated with the index given in the ip_forward struct + * in order to give this ip to setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF) */ + if(!ipv6 + && (GetBestRoute(inet_addr("223.255.255.255"), 0, &ip_forward) == NO_ERROR)) { + DWORD dwRetVal = 0; + PMIB_IPADDRTABLE pIPAddrTable; + DWORD dwSize = 0; +#ifdef DEBUG + IN_ADDR IPAddr; +#endif + int i; +#ifdef DEBUG + printf("ifIndex=%lu nextHop=%lx \n", ip_forward.dwForwardIfIndex, ip_forward.dwForwardNextHop); +#endif + pIPAddrTable = (MIB_IPADDRTABLE *) malloc(sizeof (MIB_IPADDRTABLE)); + if(pIPAddrTable) { + if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { + free(pIPAddrTable); + pIPAddrTable = (MIB_IPADDRTABLE *) malloc(dwSize); + } + } + if(pIPAddrTable) { + dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 ); + if (dwRetVal == NO_ERROR) { +#ifdef DEBUG + printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries); +#endif + for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) { +#ifdef DEBUG + printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwIndex); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwAddr; + printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwMask; + printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwBCastAddr; + printf("\tBroadCast[%d]: \t%s (%ld)\n", i, inet_ntoa(IPAddr), pIPAddrTable->table[i].dwBCastAddr); + printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwReasmSize); + printf("\tType and State[%d]:", i); + printf("\n"); +#endif + if (pIPAddrTable->table[i].dwIndex == ip_forward.dwForwardIfIndex) { + /* Set the address of this interface to be used */ + struct in_addr mc_if; + memset(&mc_if, 0, sizeof(mc_if)); + mc_if.s_addr = pIPAddrTable->table[i].dwAddr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { + PRINT_SOCKET_ERROR("setsockopt"); + } + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = pIPAddrTable->table[i].dwAddr; +#ifndef DEBUG + break; +#endif + } + } + } + free(pIPAddrTable); + pIPAddrTable = NULL; + } + } +#endif /* _WIN32 */ + +#ifdef _WIN32 + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof (opt)) < 0) +#else + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) +#endif + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("setsockopt(SO_REUSEADDR,...)"); + return NULL; + } + + if(ipv6) { +#ifdef _WIN32 + DWORD mcastHops = ttl; + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&mcastHops, sizeof(mcastHops)) < 0) +#else /* _WIN32 */ + int mcastHops = ttl; + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &mcastHops, sizeof(mcastHops)) < 0) +#endif /* _WIN32 */ + { + PRINT_SOCKET_ERROR("setsockopt(IPV6_MULTICAST_HOPS,...)"); + } + } else { +#ifdef _WIN32 + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&_ttl, sizeof(_ttl)) < 0) +#else /* _WIN32 */ + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) +#endif /* _WIN32 */ + { + /* not a fatal error */ + PRINT_SOCKET_ERROR("setsockopt(IP_MULTICAST_TTL,...)"); + } + } + + if(multicastif) + { + if(ipv6) { +#if !defined(_WIN32) + /* according to MSDN, if_nametoindex() is supported since + * MS Windows Vista and MS Windows Server 2008. + * http://msdn.microsoft.com/en-us/library/bb408409%28v=vs.85%29.aspx */ + unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IPV6_MULTICAST_IF"); + } +#else +#ifdef DEBUG + printf("Setting of multicast interface not supported in IPv6 under Windows.\n"); +#endif +#endif + } else { + struct in_addr mc_if; + mc_if.s_addr = inet_addr(multicastif); /* ex: 192.168.x.x */ + if(mc_if.s_addr != INADDR_NONE) + { + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } + } else { +#ifdef HAS_IP_MREQN + /* was not an ip address, try with an interface name */ + struct ip_mreqn reqn; /* only defined with -D_BSD_SOURCE or -D_GNU_SOURCE */ + memset(&reqn, 0, sizeof(struct ip_mreqn)); + reqn.imr_ifindex = if_nametoindex(multicastif); + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&reqn, sizeof(reqn)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } +#elif !defined(_WIN32) + struct ifreq ifr; + int ifrlen = sizeof(ifr); + strncpy(ifr.ifr_name, multicastif, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + if(ioctl(sudp, SIOCGIFADDR, &ifr, &ifrlen) < 0) + { + PRINT_SOCKET_ERROR("ioctl(...SIOCGIFADDR...)"); + } + mc_if.s_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } +#else /* _WIN32 */ +#ifdef DEBUG + printf("Setting of multicast interface not supported with interface name.\n"); +#endif +#endif /* #ifdef HAS_IP_MREQN / !defined(_WIN32) */ + } + } + } + + /* Before sending the packed, we first "bind" in order to be able + * to receive the response */ + if (bind(sudp, (const struct sockaddr *)&sockudp_r, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) != 0) + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("bind"); + closesocket(sudp); + return NULL; + } + + if(error) + *error = MINISSDPC_SUCCESS; + /* Calculating maximum response time in seconds */ + mx = ((unsigned int)delay) / 1000u; + if(mx == 0) { + mx = 1; + delay = 1000; + } + /* receiving SSDP response packet */ + for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + sentok = 0; + /* sending the SSDP M-SEARCH packet */ + n = snprintf(bufr, sizeof(bufr), + MSearchMsgFmt, + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceTypes[deviceIndex], mx); + if ((unsigned int)n >= sizeof(bufr)) { + if(error) + *error = MINISSDPC_MEMORY_ERROR; + goto error; + } +#ifdef DEBUG + /*printf("Sending %s", bufr);*/ + printf("Sending M-SEARCH request to %s with ST: %s\n", + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceTypes[deviceIndex]); +#endif +#ifdef NO_GETADDRINFO + /* the following code is not using getaddrinfo */ + /* emission */ + memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; + p->sin6_family = AF_INET6; + p->sin6_port = htons(SSDP_PORT); + inet_pton(AF_INET6, + linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, + &(p->sin6_addr)); + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; + p->sin_family = AF_INET; + p->sin_port = htons(SSDP_PORT); + p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); + } + n = sendto(sudp, bufr, n, 0, &sockudp_w, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (n < 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("sendto"); + } else { + sentok = 1; + } +#else /* #ifdef NO_GETADDRINFO */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* AF_INET6 or AF_INET */ + hints.ai_socktype = SOCK_DGRAM; + /*hints.ai_flags = */ + if ((rv = getaddrinfo(ipv6 + ? (linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR) + : UPNP_MCAST_ADDR, + XSTR(SSDP_PORT), &hints, &servinfo)) != 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; +#ifdef _WIN32 + fprintf(stderr, "getaddrinfo() failed: %d\n", rv); +#else + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); +#endif + break; + } + for(p = servinfo; p; p = p->ai_next) { + n = sendto(sudp, bufr, n, 0, p->ai_addr, p->ai_addrlen); + if (n < 0) { +#ifdef DEBUG + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + if (getnameinfo(p->ai_addr, p->ai_addrlen, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) { + fprintf(stderr, "host:%s port:%s\n", hbuf, sbuf); + } +#endif + PRINT_SOCKET_ERROR("sendto"); + continue; + } else { + sentok = 1; + } + } + freeaddrinfo(servinfo); + if(!sentok) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + } +#endif /* #ifdef NO_GETADDRINFO */ + /* Waiting for SSDP REPLY packet to M-SEARCH + * if searchalltypes is set, enter the loop only + * when the last deviceType is reached */ + if((sentok && !searchalltypes) || !deviceTypes[deviceIndex + 1]) do { + n = receivedata(sudp, bufr, sizeof(bufr), delay, &scope_id); + if (n < 0) { + /* error */ + if(error) + *error = MINISSDPC_SOCKET_ERROR; + goto error; + } else if (n == 0) { + /* no data or Time Out */ +#ifdef DEBUG + printf("NODATA or TIMEOUT\n"); +#endif /* DEBUG */ + if (devlist && !searchalltypes) { + /* found some devices, stop now*/ + if(error) + *error = MINISSDPC_SUCCESS; + goto error; + } + } else { + const char * descURL=NULL; + int urlsize=0; + const char * st=NULL; + int stsize=0; + const char * usn=NULL; + int usnsize=0; + parseMSEARCHReply(bufr, n, &descURL, &urlsize, &st, &stsize, &usn, &usnsize); + if(st&&descURL) { +#ifdef DEBUG + printf("M-SEARCH Reply:\n ST: %.*s\n USN: %.*s\n Location: %.*s\n", + stsize, st, usnsize, (usn?usn:""), urlsize, descURL); +#endif /* DEBUG */ + for(tmp=devlist; tmp; tmp = tmp->pNext) { + if(memcmp(tmp->descURL, descURL, urlsize) == 0 && + tmp->descURL[urlsize] == '\0' && + memcmp(tmp->st, st, stsize) == 0 && + tmp->st[stsize] == '\0' && + (usnsize == 0 || memcmp(tmp->usn, usn, usnsize) == 0) && + tmp->usn[usnsize] == '\0') + break; + } + /* at the exit of the loop above, tmp is null if + * no duplicate device was found */ + if(tmp) + continue; + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize); + if(!tmp) { + /* memory allocation error */ + if(error) + *error = MINISSDPC_MEMORY_ERROR; + goto error; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + tmp->usn = tmp->st + 1 + stsize; + memcpy(tmp->buffer, descURL, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->st, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + if(usn != NULL) + memcpy(tmp->usn, usn, usnsize); + tmp->buffer[urlsize+1+stsize+1+usnsize] = '\0'; + tmp->scope_id = scope_id; + devlist = tmp; + } + } + } while(n > 0); + if(ipv6) { + /* switch linklocal flag */ + if(linklocal) { + linklocal = 0; + --deviceIndex; + } else { + linklocal = 1; + } + } + } +error: + closesocket(sudp); + return devlist; +} + diff --git a/thirdparty/miniupnpc/minissdpc.h b/thirdparty/miniupnpc/minissdpc.h new file mode 100644 index 0000000000..167d897cb6 --- /dev/null +++ b/thirdparty/miniupnpc/minissdpc.h @@ -0,0 +1,58 @@ +/* $Id: minissdpc.h,v 1.6 2015/09/18 12:45:16 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINISSDPC_H_INCLUDED +#define MINISSDPC_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "upnpdev.h" + +/* error codes : */ +#define MINISSDPC_SUCCESS (0) +#define MINISSDPC_UNKNOWN_ERROR (-1) +#define MINISSDPC_SOCKET_ERROR (-101) +#define MINISSDPC_MEMORY_ERROR (-102) +#define MINISSDPC_INVALID_INPUT (-103) +#define MINISSDPC_INVALID_SERVER_REPLY (-104) + +#ifdef __cplusplus +extern "C" { +#endif + +#if !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) + +MINIUPNP_LIBSPEC struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath, int * error); + +MINIUPNP_LIBSPEC int +connectToMiniSSDPD(const char * socketpath); + +MINIUPNP_LIBSPEC int +disconnectFromMiniSSDPD(int fd); + +MINIUPNP_LIBSPEC int +requestDevicesFromMiniSSDPD(int fd, const char * devtype); + +MINIUPNP_LIBSPEC struct UPNPDev * +receiveDevicesFromMiniSSDPD(int fd, int * error); + +#endif /* !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) */ + +MINIUPNP_LIBSPEC struct UPNPDev * +ssdpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/thirdparty/miniupnpc/miniupnpc.c b/thirdparty/miniupnpc/miniupnpc.c new file mode 100644 index 0000000000..5d93ef9933 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc.c @@ -0,0 +1,727 @@ +/* $Id: miniupnpc.c,v 1.149 2016/02/09 09:50:46 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2018 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 +/* Win32 Specific includes and defines */ +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#include <iphlpapi.h> +#define snprintf _snprintf +#define strdup _strdup +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#define MAXHOSTNAMELEN 64 +#else /* #ifdef _WIN32 */ +/* Standard POSIX includes */ +#include <unistd.h> +#if defined(__amigaos__) && !defined(__amigaos4__) +/* Amiga OS 3 specific stuff */ +#define socklen_t int +#else +#include <sys/select.h> +#endif +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <net/if.h> +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include <poll.h> +#endif +#include <strings.h> +#include <errno.h> +#define closesocket close +#endif /* #else _WIN32 */ +#ifdef __GNU__ +#define MAXHOSTNAMELEN 64 +#endif + + +#include "miniupnpc.h" +#include "minissdpc.h" +#include "miniwget.h" +#include "miniwget_private.h" +#include "minisoap.h" +#include "minixml.h" +#include "upnpcommands.h" +#include "connecthostport.h" + +/* compare the beginning of a string with a constant string */ +#define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define SOAPPREFIX "s" +#define SERVICEPREFIX "u" +#define SERVICEPREFIX2 'u' + +/* check if an ip address is a private (LAN) address + * see https://tools.ietf.org/html/rfc1918 */ +static int is_rfc1918addr(const char * addr) +{ + /* 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) */ + if(COMPARE(addr, "192.168.")) + return 1; + /* 10.0.0.0 - 10.255.255.255 (10/8 prefix) */ + if(COMPARE(addr, "10.")) + return 1; + /* 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) */ + if(COMPARE(addr, "172.")) { + int i = atoi(addr + 4); + if((16 <= i) && (i <= 31)) + return 1; + } + return 0; +} + +/* root description parsing */ +MINIUPNP_LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) +{ + struct xmlparser parser; + /* xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parser.attfunc = 0; + parsexml(&parser); +#ifdef DEBUG + printIGD(data); +#endif +} + +/* simpleUPnPcommand2 : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +static char * +simpleUPnPcommand2(SOCKET s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize, const char * httpversion) +{ + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port = 0; + char * path; + char soapact[128]; + char soapbody[2048]; + int soapbodylen; + char * buf; + int n; + int status_code; + + *bufsize = 0; + snprintf(soapact, sizeof(soapact), "%s#%s", service, action); + if(args==NULL) + { + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "<?xml version=\"1.0\"?>\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" + "</" SERVICEPREFIX ":%s>" + "</" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>" + "\r\n", action, service, action); + if ((unsigned int)soapbodylen >= sizeof(soapbody)) + return NULL; + } + else + { + char * p; + const char * pe, * pv; + const char * const pend = soapbody + sizeof(soapbody); + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "<?xml version=\"1.0\"?>\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", + action, service); + if ((unsigned int)soapbodylen >= sizeof(soapbody)) + return NULL; + p = soapbody + soapbodylen; + while(args->elt) + { + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '<'; + + pe = args->elt; + while(p < pend && *pe) + *(p++) = *(pe++); + + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '>'; + + if((pv = args->val)) + { + while(p < pend && *pv) + *(p++) = *(pv++); + } + + if((p+2) > pend) /* check for space to write next 2 bytes */ + return NULL; + *(p++) = '<'; + *(p++) = '/'; + + pe = args->elt; + while(p < pend && *pe) + *(p++) = *(pe++); + + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '>'; + + args++; + } + if((p+4) > pend) /* check for space to write next 4 bytes */ + return NULL; + *(p++) = '<'; + *(p++) = '/'; + *(p++) = SERVICEPREFIX2; + *(p++) = ':'; + + pe = action; + while(p < pend && *pe) + *(p++) = *(pe++); + + strncpy(p, "></" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>\r\n", + pend - p); + if(soapbody[sizeof(soapbody)-1]) /* strncpy pads buffer with 0s, so if it doesn't end in 0, could not fit full string */ + return NULL; + } + if(!parseURL(url, hostname, &port, &path, NULL)) return NULL; + if(ISINVALID(s)) { + s = connecthostport(hostname, port, 0); + if(ISINVALID(s)) { + /* failed to connect */ + return NULL; + } + } + + n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, httpversion); + if(n<=0) { +#ifdef DEBUG + printf("Error sending SOAP request\n"); +#endif + closesocket(s); + return NULL; + } + + buf = getHTTPResponse(s, bufsize, &status_code); +#ifdef DEBUG + if(*bufsize > 0 && buf) + { + printf("HTTP %d SOAP Response :\n%.*s\n", status_code, *bufsize, buf); + } + else + { + printf("HTTP %d, empty SOAP response. size=%d\n", status_code, *bufsize); + } +#endif + closesocket(s); + return buf; +} + +/* simpleUPnPcommand : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +char * +simpleUPnPcommand(int s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize) +{ + char * buf; + +#if 1 + buf = simpleUPnPcommand2((SOCKET)s, url, service, action, args, bufsize, "1.1"); +#else + buf = simpleUPnPcommand2((SOCKET)s, url, service, action, args, bufsize, "1.0"); + if (!buf || *bufsize == 0) + { +#if DEBUG + printf("Error or no result from SOAP request; retrying with HTTP/1.1\n"); +#endif + buf = simpleUPnPcommand2((SOCKET)s, url, service, action, args, bufsize, "1.1"); + } +#endif + return buf; +} + +/* upnpDiscoverDevices() : + * return a chained list of all devices found or NULL if + * no devices was found. + * It is up to the caller to free the chained list + * delay is in millisecond (poll). + * UDA v1.1 says : + * The TTL for the IP packet SHOULD default to 2 and + * SHOULD be configurable. */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = 0; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + int deviceIndex; +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + + if(error) + *error = UPNPDISCOVER_UNKNOWN_ERROR; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* first try to get infos from minissdpd ! */ + if(!minissdpdsock) + minissdpdsock = "/var/run/minissdpd.sock"; + if(minissdpdsock[0] != '\0') { + for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + struct UPNPDev * minissdpd_devlist; + int only_rootdevice = 1; + minissdpd_devlist = getDevicesFromMiniSSDPD(deviceTypes[deviceIndex], + minissdpdsock, 0); + if(minissdpd_devlist) { +#ifdef DEBUG + printf("returned by MiniSSDPD: %s\t%s\n", + minissdpd_devlist->st, minissdpd_devlist->descURL); +#endif /* DEBUG */ + if(!strstr(minissdpd_devlist->st, "rootdevice")) + only_rootdevice = 0; + for(tmp = minissdpd_devlist; tmp->pNext != NULL; tmp = tmp->pNext) { +#ifdef DEBUG + printf("returned by MiniSSDPD: %s\t%s\n", + tmp->pNext->st, tmp->pNext->descURL); +#endif /* DEBUG */ + if(!strstr(tmp->st, "rootdevice")) + only_rootdevice = 0; + } + tmp->pNext = devlist; + devlist = minissdpd_devlist; + if(!searchalltypes && !only_rootdevice) + break; + } + } + } + for(tmp = devlist; tmp != NULL; tmp = tmp->pNext) { + /* We return what we have found if it was not only a rootdevice */ + if(!strstr(tmp->st, "rootdevice")) { + if(error) + *error = UPNPDISCOVER_SUCCESS; + return devlist; + } + } +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + + /* direct discovery if minissdpd responses are not sufficient */ + { + struct UPNPDev * discovered_devlist; + discovered_devlist = ssdpDiscoverDevices(deviceTypes, delay, multicastif, localport, + ipv6, ttl, error, searchalltypes); + if(devlist == NULL) + devlist = discovered_devlist; + else { + for(tmp = devlist; tmp->pNext != NULL; tmp = tmp->pNext); + tmp->pNext = discovered_devlist; + } + } + return devlist; +} + +/* upnpDiscover() Discover IGD device */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + static const char * const deviceList[] = { +#if 0 + "urn:schemas-upnp-org:device:InternetGatewayDevice:2", + "urn:schemas-upnp-org:service:WANIPConnection:2", +#endif + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + "urn:schemas-upnp-org:service:WANIPConnection:1", + "urn:schemas-upnp-org:service:WANPPPConnection:1", + "upnp:rootdevice", + /*"ssdp:all",*/ + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +/* upnpDiscoverAll() Discover all UPnP devices */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + static const char * const deviceList[] = { + /*"upnp:rootdevice",*/ + "ssdp:all", + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +/* upnpDiscoverDevice() Discover a specific device */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + const char * const deviceList[] = { + device, + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +static char * +build_absolute_url(const char * baseurl, const char * descURL, + const char * url, unsigned int scope_id) +{ + int l, n; + char * s; + const char * base; + char * p; +#if defined(IF_NAMESIZE) && !defined(_WIN32) + char ifname[IF_NAMESIZE]; +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + char scope_str[8]; +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + + if( (url[0] == 'h') + &&(url[1] == 't') + &&(url[2] == 't') + &&(url[3] == 'p') + &&(url[4] == ':') + &&(url[5] == '/') + &&(url[6] == '/')) + return strdup(url); + base = (baseurl[0] == '\0') ? descURL : baseurl; + n = strlen(base); + if(n > 7) { + p = strchr(base + 7, '/'); + if(p) + n = p - base; + } + l = n + strlen(url) + 1; + if(url[0] != '/') + l++; + if(scope_id != 0) { +#if defined(IF_NAMESIZE) && !defined(_WIN32) + if(if_indextoname(scope_id, ifname)) { + l += 3 + strlen(ifname); /* 3 == strlen(%25) */ + } +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + /* under windows, scope is numerical */ + l += 3 + snprintf(scope_str, sizeof(scope_str), "%u", scope_id); +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + } + s = malloc(l); + if(s == NULL) return NULL; + memcpy(s, base, n); + if(scope_id != 0) { + s[n] = '\0'; + if(0 == memcmp(s, "http://[fe80:", 13)) { + /* this is a linklocal IPv6 address */ + p = strchr(s, ']'); + if(p) { + /* insert %25<scope> into URL */ +#if defined(IF_NAMESIZE) && !defined(_WIN32) + memmove(p + 3 + strlen(ifname), p, strlen(p) + 1); + memcpy(p, "%25", 3); + memcpy(p + 3, ifname, strlen(ifname)); + n += 3 + strlen(ifname); +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + memmove(p + 3 + strlen(scope_str), p, strlen(p) + 1); + memcpy(p, "%25", 3); + memcpy(p + 3, scope_str, strlen(scope_str)); + n += 3 + strlen(scope_str); +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + } + } + } + if(url[0] != '/') + s[n++] = '/'; + memcpy(s + n, url, l - n); + return s; +} + +/* Prepare the Urls for usage... + */ +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data, + const char * descURL, unsigned int scope_id) +{ + /* strdup descURL */ + urls->rootdescURL = strdup(descURL); + + /* get description of WANIPConnection */ + urls->ipcondescURL = build_absolute_url(data->urlbase, descURL, + data->first.scpdurl, scope_id); + urls->controlURL = build_absolute_url(data->urlbase, descURL, + data->first.controlurl, scope_id); + urls->controlURL_CIF = build_absolute_url(data->urlbase, descURL, + data->CIF.controlurl, scope_id); + urls->controlURL_6FC = build_absolute_url(data->urlbase, descURL, + data->IPv6FC.controlurl, scope_id); + +#ifdef DEBUG + printf("urls->ipcondescURL='%s'\n", urls->ipcondescURL); + printf("urls->controlURL='%s'\n", urls->controlURL); + printf("urls->controlURL_CIF='%s'\n", urls->controlURL_CIF); + printf("urls->controlURL_6FC='%s'\n", urls->controlURL_6FC); +#endif +} + +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls * urls) +{ + if(!urls) + return; + free(urls->controlURL); + urls->controlURL = 0; + free(urls->ipcondescURL); + urls->ipcondescURL = 0; + free(urls->controlURL_CIF); + urls->controlURL_CIF = 0; + free(urls->controlURL_6FC); + urls->controlURL_6FC = 0; + free(urls->rootdescURL); + urls->rootdescURL = 0; +} + +int +UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) +{ + char status[64]; + unsigned int uptime; + status[0] = '\0'; + UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, NULL); + if(0 == strcmp("Connected", status)) + return 1; + else if(0 == strcmp("Up", status)) /* Also accept "Up" */ + return 1; + else + return 0; +} + + +/* UPNP_GetValidIGD() : + * return values : + * -1 = Internal error + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any positive non zero return case, the urls and data structures + * passed as parameters are set. Don't forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +MINIUPNP_LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + struct xml_desc { + char * xml; + int size; + int is_igd; + } * desc = NULL; + struct UPNPDev * dev; + int ndev = 0; + int i; + int state = -1; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ + int n_igd = 0; + char extIpAddr[16]; + char myLanAddr[40]; + int status_code = -1; + + if(!devlist) + { +#ifdef DEBUG + printf("Empty devlist\n"); +#endif + return 0; + } + /* counting total number of devices in the list */ + for(dev = devlist; dev; dev = dev->pNext) + ndev++; + if(ndev > 0) + { + desc = calloc(ndev, sizeof(struct xml_desc)); + if(!desc) + return -1; /* memory allocation error */ + } + /* Step 1 : downloading descriptions and testing type */ + for(dev = devlist, i = 0; dev; dev = dev->pNext, i++) + { + /* we should choose an internet gateway device. + * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ + desc[i].xml = miniwget_getaddr(dev->descURL, &(desc[i].size), + myLanAddr, sizeof(myLanAddr), + dev->scope_id, &status_code); +#ifdef DEBUG + if(!desc[i].xml) + { + printf("error getting XML description %s\n", dev->descURL); + } +#endif + if(desc[i].xml) + { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(desc[i].xml, desc[i].size, data); + if(COMPARE(data->CIF.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) + { + desc[i].is_igd = 1; + n_igd++; + if(lanaddr) + strncpy(lanaddr, myLanAddr, lanaddrlen); + } + } + } + /* iterate the list to find a device depending on state */ + for(state = 1; state <= 3; state++) + { + for(dev = devlist, i = 0; dev; dev = dev->pNext, i++) + { + if(desc[i].xml) + { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(desc[i].xml, desc[i].size, data); + if(desc[i].is_igd || state >= 3 ) + { + int is_connected; + + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + + /* in state 2 and 3 we don't test if device is connected ! */ + if(state >= 2) + goto free_and_return; + is_connected = UPNPIGD_IsConnected(urls, data); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, is_connected); +#endif + /* checks that status is connected AND there is a external IP address assigned */ + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!is_rfc1918addr(extIpAddr) && (extIpAddr[0] != '\0') + && (0 != strcmp(extIpAddr, "0.0.0.0"))) + goto free_and_return; + } + FreeUPNPUrls(urls); + if(data->second.servicetype[0] != '\0') { +#ifdef DEBUG + printf("We tried %s, now we try %s !\n", + data->first.servicetype, data->second.servicetype); +#endif + /* swaping WANPPPConnection and WANIPConnection ! */ + memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service)); + memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); + memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + is_connected = UPNPIGD_IsConnected(urls, data); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, is_connected); +#endif + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!is_rfc1918addr(extIpAddr) && (extIpAddr[0] != '\0') + && (0 != strcmp(extIpAddr, "0.0.0.0"))) + goto free_and_return; + } + FreeUPNPUrls(urls); + } + } + memset(data, 0, sizeof(struct IGDdatas)); + } + } + } + state = 0; +free_and_return: + if(desc) { + for(i = 0; i < ndev; i++) { + if(desc[i].xml) { + free(desc[i].xml); + } + } + free(desc); + } + return state; +} + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * return value : + * 0 - Not ok + * 1 - OK */ +int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + char * descXML; + int descXMLsize = 0; + + descXML = miniwget_getaddr(rootdescurl, &descXMLsize, + lanaddr, lanaddrlen, 0, NULL); + if(descXML) { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(descXML, descXMLsize, data); + free(descXML); + descXML = NULL; + GetUPNPUrls(urls, data, rootdescurl, 0); + return 1; + } else { + return 0; + } +} + diff --git a/thirdparty/miniupnpc/miniupnpc.def b/thirdparty/miniupnpc/miniupnpc.def new file mode 100644 index 0000000000..60e0bbe423 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc.def @@ -0,0 +1,45 @@ +LIBRARY +; miniupnpc library + miniupnpc + +EXPORTS +; miniupnpc + upnpDiscover + freeUPNPDevlist + parserootdesc + UPNP_GetValidIGD + UPNP_GetIGDFromUrl + GetUPNPUrls + FreeUPNPUrls +; miniwget + miniwget + miniwget_getaddr +; upnpcommands + UPNP_GetTotalBytesSent + UPNP_GetTotalBytesReceived + UPNP_GetTotalPacketsSent + UPNP_GetTotalPacketsReceived + UPNP_GetStatusInfo + UPNP_GetConnectionTypeInfo + UPNP_GetExternalIPAddress + UPNP_GetLinkLayerMaxBitRates + UPNP_AddPortMapping + UPNP_AddAnyPortMapping + UPNP_DeletePortMapping + UPNP_DeletePortMappingRange + UPNP_GetPortMappingNumberOfEntries + UPNP_GetSpecificPortMappingEntry + UPNP_GetGenericPortMappingEntry + UPNP_GetListOfPortMappings + UPNP_AddPinhole + UPNP_CheckPinholeWorking + UPNP_UpdatePinhole + UPNP_GetPinholePackets + UPNP_DeletePinhole + UPNP_GetFirewallStatus + UPNP_GetOutboundPinholeTimeout +; upnperrors + strupnperror +; portlistingparse + ParsePortListing + FreePortListing diff --git a/thirdparty/miniupnpc/miniupnpc.h b/thirdparty/miniupnpc/miniupnpc.h new file mode 100644 index 0000000000..8ddc282bd1 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc.h @@ -0,0 +1,153 @@ +/* $Id: miniupnpc.h,v 1.53 2018/05/07 11:05:16 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINIUPNPC_H_INCLUDED +#define MINIUPNPC_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "igd_desc_parse.h" +#include "upnpdev.h" + +/* error codes : */ +#define UPNPDISCOVER_SUCCESS (0) +#define UPNPDISCOVER_UNKNOWN_ERROR (-1) +#define UPNPDISCOVER_SOCKET_ERROR (-101) +#define UPNPDISCOVER_MEMORY_ERROR (-102) + +/* versions : */ +#define MINIUPNPC_VERSION "2.1" +#define MINIUPNPC_API_VERSION 17 + +/* Source port: + Using "1" as an alias for 1900 for backwards compatibility + (presuming one would have used that for the "sameport" parameter) */ +#define UPNP_LOCAL_PORT_ANY 0 +#define UPNP_LOCAL_PORT_SAME 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structures definitions : */ +struct UPNParg { const char * elt; const char * val; }; + +char * +simpleUPnPcommand(int, const char *, const char *, + const char *, struct UPNParg *, + int *); + +/* upnpDiscover() + * discover UPnP devices on the network. + * The discovered devices are returned as a chained list. + * It is up to the caller to free the list with freeUPNPDevlist(). + * delay (in millisecond) is the maximum time for waiting any device + * response. + * If available, device list will be obtained from MiniSSDPd. + * Default path for minissdpd socket will be used if minissdpdsock argument + * is NULL. + * If multicastif is not NULL, it will be used instead of the default + * multicast interface for sending SSDP discover packets. + * If localport is set to UPNP_LOCAL_PORT_SAME(1) SSDP packets will be sent + * from the source port 1900 (same as destination port), if set to + * UPNP_LOCAL_PORT_ANY(0) system assign a source port, any other value will + * be attempted as the source port. + * "searchalltypes" parameter is useful when searching several types, + * if 0, the discovery will stop with the first type returning results. + * TTL should default to 2. */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); + +/* parserootdesc() : + * parse root XML description of a UPnP device and fill the IGDdatas + * structure. */ +MINIUPNP_LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); + +/* structure used to get fast access to urls + * controlURL: controlURL of the WANIPConnection + * ipcondescURL: url of the description of the WANIPConnection + * controlURL_CIF: controlURL of the WANCommonInterfaceConfig + * controlURL_6FC: controlURL of the WANIPv6FirewallControl + */ +struct UPNPUrls { + char * controlURL; + char * ipcondescURL; + char * controlURL_CIF; + char * controlURL_6FC; + char * rootdescURL; +}; + +/* UPNP_GetValidIGD() : + * return values : + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any non zero return case, the urls and data structures + * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +MINIUPNP_LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * When succeding, urls, data, and lanaddr arguments are set. + * return value : + * 0 - Not ok + * 1 - OK */ +MINIUPNP_LIBSPEC int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, + const char *, unsigned int); + +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls *); + +/* return 0 or 1 */ +MINIUPNP_LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/thirdparty/miniupnpc/miniupnpc/miniupnpc.h b/thirdparty/miniupnpc/miniupnpc/miniupnpc.h new file mode 100644 index 0000000000..8ddc282bd1 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc/miniupnpc.h @@ -0,0 +1,153 @@ +/* $Id: miniupnpc.h,v 1.53 2018/05/07 11:05:16 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINIUPNPC_H_INCLUDED +#define MINIUPNPC_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "igd_desc_parse.h" +#include "upnpdev.h" + +/* error codes : */ +#define UPNPDISCOVER_SUCCESS (0) +#define UPNPDISCOVER_UNKNOWN_ERROR (-1) +#define UPNPDISCOVER_SOCKET_ERROR (-101) +#define UPNPDISCOVER_MEMORY_ERROR (-102) + +/* versions : */ +#define MINIUPNPC_VERSION "2.1" +#define MINIUPNPC_API_VERSION 17 + +/* Source port: + Using "1" as an alias for 1900 for backwards compatibility + (presuming one would have used that for the "sameport" parameter) */ +#define UPNP_LOCAL_PORT_ANY 0 +#define UPNP_LOCAL_PORT_SAME 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structures definitions : */ +struct UPNParg { const char * elt; const char * val; }; + +char * +simpleUPnPcommand(int, const char *, const char *, + const char *, struct UPNParg *, + int *); + +/* upnpDiscover() + * discover UPnP devices on the network. + * The discovered devices are returned as a chained list. + * It is up to the caller to free the list with freeUPNPDevlist(). + * delay (in millisecond) is the maximum time for waiting any device + * response. + * If available, device list will be obtained from MiniSSDPd. + * Default path for minissdpd socket will be used if minissdpdsock argument + * is NULL. + * If multicastif is not NULL, it will be used instead of the default + * multicast interface for sending SSDP discover packets. + * If localport is set to UPNP_LOCAL_PORT_SAME(1) SSDP packets will be sent + * from the source port 1900 (same as destination port), if set to + * UPNP_LOCAL_PORT_ANY(0) system assign a source port, any other value will + * be attempted as the source port. + * "searchalltypes" parameter is useful when searching several types, + * if 0, the discovery will stop with the first type returning results. + * TTL should default to 2. */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); + +/* parserootdesc() : + * parse root XML description of a UPnP device and fill the IGDdatas + * structure. */ +MINIUPNP_LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); + +/* structure used to get fast access to urls + * controlURL: controlURL of the WANIPConnection + * ipcondescURL: url of the description of the WANIPConnection + * controlURL_CIF: controlURL of the WANCommonInterfaceConfig + * controlURL_6FC: controlURL of the WANIPv6FirewallControl + */ +struct UPNPUrls { + char * controlURL; + char * ipcondescURL; + char * controlURL_CIF; + char * controlURL_6FC; + char * rootdescURL; +}; + +/* UPNP_GetValidIGD() : + * return values : + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any non zero return case, the urls and data structures + * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +MINIUPNP_LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * When succeding, urls, data, and lanaddr arguments are set. + * return value : + * 0 - Not ok + * 1 - OK */ +MINIUPNP_LIBSPEC int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, + const char *, unsigned int); + +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls *); + +/* return 0 or 1 */ +MINIUPNP_LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/thirdparty/miniupnpc/miniupnpc/miniwget.h b/thirdparty/miniupnpc/miniupnpc/miniwget.h new file mode 100644 index 0000000000..f5572c2544 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc/miniwget.h @@ -0,0 +1,27 @@ +/* $Id: miniwget.h,v 1.12 2016/01/24 17:24:36 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIWGET_H_INCLUDED +#define MINIWGET_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int, int *); + +MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int, int *); + +int parseURL(const char *, char *, unsigned short *, char * *, unsigned int *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/miniupnpc/miniupnpc/upnpcommands.h b/thirdparty/miniupnpc/miniupnpc/upnpcommands.h new file mode 100644 index 0000000000..0c6d501666 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc/upnpcommands.h @@ -0,0 +1,348 @@ +/* $Id: upnpcommands.h,v 1.32 2018/03/13 23:34:47 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef UPNPCOMMANDS_H_INCLUDED +#define UPNPCOMMANDS_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "miniupnpctypes.h" + +/* MiniUPnPc return codes : */ +#define UPNPCOMMAND_SUCCESS (0) +#define UPNPCOMMAND_UNKNOWN_ERROR (-1) +#define UPNPCOMMAND_INVALID_ARGS (-2) +#define UPNPCOMMAND_HTTP_ERROR (-3) +#define UPNPCOMMAND_INVALID_RESPONSE (-4) +#define UPNPCOMMAND_MEM_ALLOC_ERROR (-5) + +#ifdef __cplusplus +extern "C" { +#endif + +struct PortMappingParserData; + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype); + +/* UPNP_GetStatusInfo() + * status and lastconnerror are 64 byte buffers + * Return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +MINIUPNP_LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror); + +/* UPNP_GetConnectionTypeInfo() + * argument connectionType is a 64 character buffer + * Return Values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +MINIUPNP_LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType); + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * possible UPnP Errors : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. */ +MINIUPNP_LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd); + +/* UPNP_GetLinkLayerMaxBitRates() + * call WANCommonInterfaceConfig:1#GetCommonLinkProperties + * + * return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. */ +MINIUPNP_LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char* controlURL, + const char* servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp); + +/* UPNP_AddPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 718 ConflictInMappingEntry - The port mapping entry specified conflicts + * with a mapping assigned previously to another client + * 724 SamePortValuesRequired - Internal and External port values + * must be the same + * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports + * permanent lease times on port mappings + * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard + * and cannot be a specific IP address or DNS name + * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and + * cannot be a specific port value + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration); + +/* UPNP_AddAnyPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort); + +/* UPNP_DeletePortMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost); + +/* UPNP_DeletePortRangeMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 730 PortMappingNotFound - This error message is returned if no port + * mapping is found in the specified range. + * 733 InconsistentParameters - NewStartPort and NewEndPort values are not consistent. */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage); + +/* UPNP_GetPortMappingNumberOfEntries() + * not supported by all routers */ +MINIUPNP_LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char* controlURL, + const char* servicetype, + unsigned int * num); + +/* UPNP_GetSpecificPortMappingEntry() + * retrieves an existing port mapping + * params : + * in extPort + * in proto + * in remoteHost + * out intClient (16 bytes) + * out intPort (6 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out leaseDuration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * List of possible UPnP errors for _GetSpecificPortMappingEntry : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array. + */ +MINIUPNP_LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + const char * remoteHost, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration); + +/* UPNP_GetGenericPortMappingEntry() + * params : + * in index + * out extPort (6 bytes) + * out intClient (16 bytes) + * out intPort (6 bytes) + * out protocol (4 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out rHost (64 bytes) + * out duration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * Possible UPNP Error codes : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds + */ +MINIUPNP_LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration); + +/* UPNP_GetListOfPortMappings() Available in IGD v2 + * + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +MINIUPNP_LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data); + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed); + +MINIUPNP_LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout); + +MINIUPNP_LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID); + +MINIUPNP_LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime); + +MINIUPNP_LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID); + +MINIUPNP_LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking); + +MINIUPNP_LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/thirdparty/miniupnpc/miniupnpc_declspec.h b/thirdparty/miniupnpc/miniupnpc_declspec.h new file mode 100644 index 0000000000..40adb922ec --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc_declspec.h @@ -0,0 +1,21 @@ +#ifndef MINIUPNPC_DECLSPEC_H_INCLUDED +#define MINIUPNPC_DECLSPEC_H_INCLUDED + +#if defined(_WIN32) && !defined(MINIUPNP_STATICLIB) + /* for windows dll */ + #ifdef MINIUPNP_EXPORTS + #define MINIUPNP_LIBSPEC __declspec(dllexport) + #else + #define MINIUPNP_LIBSPEC __declspec(dllimport) + #endif +#else + #if defined(__GNUC__) && __GNUC__ >= 4 + /* fix dynlib for OS X 10.9.2 and Apple LLVM version 5.0 */ + #define MINIUPNP_LIBSPEC __attribute__ ((visibility ("default"))) + #else + #define MINIUPNP_LIBSPEC + #endif +#endif + +#endif /* MINIUPNPC_DECLSPEC_H_INCLUDED */ + diff --git a/thirdparty/miniupnpc/miniupnpc_socketdef.h b/thirdparty/miniupnpc/miniupnpc_socketdef.h new file mode 100644 index 0000000000..965d9151b9 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpc_socketdef.h @@ -0,0 +1,37 @@ +/* $Id: miniupnpc_socketdef.h,v 1.1 2018/03/13 23:44:10 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + * Copyright (c) 2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef MINIUPNPC_SOCKETDEF_H_INCLUDED +#define MINIUPNPC_SOCKETDEF_H_INCLUDED + +#ifdef _MSC_VER + +#define ISINVALID(s) (INVALID_SOCKET==(s)) + +#else + +#ifndef SOCKET +#define SOCKET int +#endif +#ifndef SSIZE_T +#define SSIZE_T ssize_t +#endif +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#ifndef ISINVALID +#define ISINVALID(s) ((s)<0) +#endif + +#endif + +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#endif /* MINIUPNPC_SOCKETDEF_H_INCLUDED */ diff --git a/thirdparty/miniupnpc/miniupnpcmodule.c b/thirdparty/miniupnpc/miniupnpcmodule.c new file mode 100644 index 0000000000..8657a0e002 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpcmodule.c @@ -0,0 +1,703 @@ +/* $Id: miniupnpcmodule.c,v 1.24 2014/06/10 09:48:11 nanard Exp $*/ +/* Project : miniupnp + * Author : Thomas BERNARD + * website : https://miniupnp.tuxfamily.org/ + * copyright (c) 2007-2018 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#include <Python.h> +#define MINIUPNP_STATICLIB +#include "structmember.h" +#include "miniupnpc.h" +#include "upnpcommands.h" +#include "upnperrors.h" + +#ifdef _WIN32 +#include <winsock2.h> +#endif + +/* for compatibility with Python < 2.4 */ +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +/* for compatibility with Python < 3.0 */ +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(type, size) \ + PyObject_HEAD_INIT(type) size, +#endif + +#ifndef Py_TYPE +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif + +typedef struct { + PyObject_HEAD + /* Type-specific fields go here. */ + struct UPNPDev * devlist; + struct UPNPUrls urls; + struct IGDdatas data; + unsigned int discoverdelay; /* value passed to upnpDiscover() */ + unsigned int localport; /* value passed to upnpDiscover() */ + char lanaddr[40]; /* our ip address on the LAN */ + char * multicastif; + char * minissdpdsocket; +} UPnPObject; + +static PyMemberDef UPnP_members[] = { + {"lanaddr", T_STRING_INPLACE, offsetof(UPnPObject, lanaddr), + READONLY, "ip address on the LAN" + }, + {"discoverdelay", T_UINT, offsetof(UPnPObject, discoverdelay), + 0/*READWRITE*/, "value in ms used to wait for SSDP responses" + }, + {"localport", T_UINT, offsetof(UPnPObject, localport), + 0/*READWRITE*/, + "If localport is set to UPNP_LOCAL_PORT_SAME(1) " + "SSDP packets will be sent from the source port " + "1900 (same as destination port), if set to " + "UPNP_LOCAL_PORT_ANY(0) system assign a source " + "port, any other value will be attempted as the " + "source port" + }, + /* T_STRING is allways readonly :( */ + {"multicastif", T_STRING, offsetof(UPnPObject, multicastif), + 0, "IP of the network interface to be used for multicast operations" + }, + {"minissdpdsocket", T_STRING, offsetof(UPnPObject, minissdpdsocket), + 0, "path of the MiniSSDPd unix socket" + }, + {NULL} +}; + + +static int UPnP_init(UPnPObject *self, PyObject *args, PyObject *kwds) +{ + char* multicastif = NULL; + char* minissdpdsocket = NULL; + static char *kwlist[] = { + "multicastif", "minissdpdsocket", "discoverdelay", + "localport", NULL + }; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "|zzII", kwlist, + &multicastif, + &minissdpdsocket, + &self->discoverdelay, + &self->localport)) + return -1; + + if(self->localport>1 && + (self->localport>65534||self->localport<1024)) { + PyErr_SetString(PyExc_Exception, "Invalid localport value"); + return -1; + } + if(multicastif) + self->multicastif = strdup(multicastif); + if(minissdpdsocket) + self->minissdpdsocket = strdup(minissdpdsocket); + + return 0; +} + +static void +UPnPObject_dealloc(UPnPObject *self) +{ + freeUPNPDevlist(self->devlist); + FreeUPNPUrls(&self->urls); + free(self->multicastif); + free(self->minissdpdsocket); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject * +UPnP_discover(UPnPObject *self) +{ + struct UPNPDev * dev; + int i; + PyObject *res = NULL; + if(self->devlist) + { + freeUPNPDevlist(self->devlist); + self->devlist = 0; + } + Py_BEGIN_ALLOW_THREADS + self->devlist = upnpDiscover((int)self->discoverdelay/*timeout in ms*/, + self->multicastif, + self->minissdpdsocket, + (int)self->localport, + 0/*ip v6*/, + 2/* TTL */, + 0/*error */); + Py_END_ALLOW_THREADS + /* Py_RETURN_NONE ??? */ + for(dev = self->devlist, i = 0; dev; dev = dev->pNext) + i++; + res = Py_BuildValue("i", i); + return res; +} + +static PyObject * +UPnP_selectigd(UPnPObject *self) +{ + int r; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetValidIGD(self->devlist, &self->urls, &self->data, + self->lanaddr, sizeof(self->lanaddr)); +Py_END_ALLOW_THREADS + if(r) + { + return Py_BuildValue("s", self->urls.controlURL); + } + else + { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, "No UPnP device discovered"); + return NULL; + } +} + +static PyObject * +UPnP_totalbytesent(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalBytesSent(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_totalbytereceived(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalBytesReceived(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_totalpacketsent(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalPacketsSent(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_totalpacketreceived(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalPacketsReceived(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_statusinfo(UPnPObject *self) +{ + char status[64]; + char lastconnerror[64]; + unsigned int uptime = 0; + int r; + status[0] = '\0'; + lastconnerror[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetStatusInfo(self->urls.controlURL, self->data.first.servicetype, + status, &uptime, lastconnerror); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("(s,I,s)", status, uptime, lastconnerror); +#else + return Py_BuildValue("(s,i,s)", status, (int)uptime, lastconnerror); +#endif + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +static PyObject * +UPnP_connectiontype(UPnPObject *self) +{ + char connectionType[64]; + int r; + connectionType[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetConnectionTypeInfo(self->urls.controlURL, + self->data.first.servicetype, + connectionType); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("s", connectionType); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +static PyObject * +UPnP_externalipaddress(UPnPObject *self) +{ + char externalIPAddress[40]; + int r; + externalIPAddress[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetExternalIPAddress(self->urls.controlURL, + self->data.first.servicetype, + externalIPAddress); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("s", externalIPAddress); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* AddPortMapping(externalPort, protocol, internalHost, internalPort, desc, + * remoteHost) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +UPnP_addportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + char inPort[6]; + unsigned short iPort; + const char * proto; + const char * host; + const char * desc; + const char * remoteHost; + const char * leaseDuration = "0"; + int r; + if (!PyArg_ParseTuple(args, "HssHzz", &ePort, &proto, + &host, &iPort, &desc, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + sprintf(inPort, "%hu", iPort); + r = UPNP_AddPortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, inPort, host, desc, proto, + remoteHost, leaseDuration); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) + { + Py_RETURN_TRUE; + } + else + { + // TODO: RAISE an Exception. See upnpcommands.h for errors codes. + // upnperrors.c + //Py_RETURN_FALSE; + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* AddAnyPortMapping(externalPort, protocol, internalHost, internalPort, desc, + * remoteHost) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +UPnP_addanyportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + char inPort[6]; + unsigned short iPort; + char reservedPort[6]; + const char * proto; + const char * host; + const char * desc; + const char * remoteHost; + const char * leaseDuration = "0"; + int r; + if (!PyArg_ParseTuple(args, "HssHzz", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + sprintf(inPort, "%hu", iPort); + r = UPNP_AddAnyPortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, inPort, host, desc, proto, + remoteHost, leaseDuration, reservedPort); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("i", atoi(reservedPort)); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + + +/* DeletePortMapping(extPort, proto, removeHost='') + * proto = 'UDP', 'TCP' */ +static PyObject * +UPnP_deleteportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + const char * proto; + const char * remoteHost = ""; + int r; + if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + r = UPNP_DeletePortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, proto, remoteHost); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + Py_RETURN_TRUE; + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* DeletePortMappingRange(extPort, proto, removeHost='') + * proto = 'UDP', 'TCP' */ +static PyObject * +UPnP_deleteportmappingrange(UPnPObject *self, PyObject *args) +{ + char extPortStart[6]; + unsigned short ePortStart; + char extPortEnd[6]; + unsigned short ePortEnd; + const char * proto; + unsigned char manage; + char manageStr[6]; + int r; + if(!PyArg_ParseTuple(args, "HHsb", &ePortStart, &ePortEnd, &proto, &manage)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPortStart, "%hu", ePortStart); + sprintf(extPortEnd, "%hu", ePortEnd); + sprintf(manageStr, "%hu", (unsigned short)manage); + r = UPNP_DeletePortMappingRange(self->urls.controlURL, self->data.first.servicetype, + extPortStart, extPortEnd, proto, manageStr); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + Py_RETURN_TRUE; + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +static PyObject * +UPnP_getportmappingnumberofentries(UPnPObject *self) +{ + unsigned int n = 0; + int r; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetPortMappingNumberOfEntries(self->urls.controlURL, + self->data.first.servicetype, + &n); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", n); +#else + return Py_BuildValue("i", (int)n); +#endif + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* GetSpecificPortMapping(ePort, proto, remoteHost='') + * proto = 'UDP' or 'TCP' */ +static PyObject * +UPnP_getspecificportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + const char * proto; + const char * remoteHost = ""; + char intClient[40]; + char intPort[6]; + unsigned short iPort; + char desc[80]; + char enabled[4]; + char leaseDuration[16]; + if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) + return NULL; + extPort[0] = '\0'; intClient[0] = '\0'; intPort[0] = '\0'; + desc[0] = '\0'; enabled[0] = '\0'; leaseDuration[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + UPNP_GetSpecificPortMappingEntry(self->urls.controlURL, + self->data.first.servicetype, + extPort, proto, remoteHost, + intClient, intPort, + desc, enabled, leaseDuration); +Py_END_ALLOW_THREADS + if(intClient[0]) + { + iPort = (unsigned short)atoi(intPort); + return Py_BuildValue("(s,H,s,O,i)", + intClient, iPort, desc, + PyBool_FromLong(atoi(enabled)), + atoi(leaseDuration)); + } + else + { + Py_RETURN_NONE; + } +} + +/* GetGenericPortMapping(index) */ +static PyObject * +UPnP_getgenericportmapping(UPnPObject *self, PyObject *args) +{ + int i, r; + char index[8]; + char intClient[40]; + char intPort[6]; + unsigned short iPort; + char extPort[6]; + unsigned short ePort; + char protocol[4]; + char desc[80]; + char enabled[6]; + char rHost[64]; + char duration[16]; /* lease duration */ + unsigned int dur; + if(!PyArg_ParseTuple(args, "i", &i)) + return NULL; +Py_BEGIN_ALLOW_THREADS + snprintf(index, sizeof(index), "%d", i); + rHost[0] = '\0'; enabled[0] = '\0'; + duration[0] = '\0'; desc[0] = '\0'; + extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; + r = UPNP_GetGenericPortMappingEntry(self->urls.controlURL, + self->data.first.servicetype, + index, + extPort, intClient, intPort, + protocol, desc, enabled, rHost, + duration); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) + { + ePort = (unsigned short)atoi(extPort); + iPort = (unsigned short)atoi(intPort); + dur = (unsigned int)strtoul(duration, 0, 0); +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("(H,s,(s,H),s,s,s,I)", + ePort, protocol, intClient, iPort, + desc, enabled, rHost, dur); +#else + return Py_BuildValue("(i,s,(s,i),s,s,s,i)", + (int)ePort, protocol, intClient, (int)iPort, + desc, enabled, rHost, (int)dur); +#endif + } + else + { + Py_RETURN_NONE; + } +} + +/* miniupnpc.UPnP object Method Table */ +static PyMethodDef UPnP_methods[] = { + {"discover", (PyCFunction)UPnP_discover, METH_NOARGS, + "discover UPnP IGD devices on the network" + }, + {"selectigd", (PyCFunction)UPnP_selectigd, METH_NOARGS, + "select a valid UPnP IGD among discovered devices" + }, + {"totalbytesent", (PyCFunction)UPnP_totalbytesent, METH_NOARGS, + "return the total number of bytes sent by UPnP IGD" + }, + {"totalbytereceived", (PyCFunction)UPnP_totalbytereceived, METH_NOARGS, + "return the total number of bytes received by UPnP IGD" + }, + {"totalpacketsent", (PyCFunction)UPnP_totalpacketsent, METH_NOARGS, + "return the total number of packets sent by UPnP IGD" + }, + {"totalpacketreceived", (PyCFunction)UPnP_totalpacketreceived, METH_NOARGS, + "return the total number of packets received by UPnP IGD" + }, + {"statusinfo", (PyCFunction)UPnP_statusinfo, METH_NOARGS, + "return status and uptime" + }, + {"connectiontype", (PyCFunction)UPnP_connectiontype, METH_NOARGS, + "return IGD WAN connection type" + }, + {"externalipaddress", (PyCFunction)UPnP_externalipaddress, METH_NOARGS, + "return external IP address" + }, + {"addportmapping", (PyCFunction)UPnP_addportmapping, METH_VARARGS, + "add a port mapping" + }, + {"addanyportmapping", (PyCFunction)UPnP_addanyportmapping, METH_VARARGS, + "add a port mapping, IGD to select alternative if necessary" + }, + {"deleteportmapping", (PyCFunction)UPnP_deleteportmapping, METH_VARARGS, + "delete a port mapping" + }, + {"deleteportmappingrange", (PyCFunction)UPnP_deleteportmappingrange, METH_VARARGS, + "delete a range of port mappings" + }, + {"getportmappingnumberofentries", (PyCFunction)UPnP_getportmappingnumberofentries, METH_NOARGS, + "-- non standard --" + }, + {"getspecificportmapping", (PyCFunction)UPnP_getspecificportmapping, METH_VARARGS, + "get details about a specific port mapping entry" + }, + {"getgenericportmapping", (PyCFunction)UPnP_getgenericportmapping, METH_VARARGS, + "get all details about the port mapping at index" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject UPnPType = { + PyVarObject_HEAD_INIT(NULL, + 0) /*ob_size*/ + "miniupnpc.UPnP", /*tp_name*/ + sizeof(UPnPObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)UPnPObject_dealloc,/*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "UPnP objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + UPnP_methods, /* tp_methods */ + UPnP_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)UPnP_init, /* tp_init */ + 0, /* tp_alloc */ +#ifndef _WIN32 + PyType_GenericNew,/*UPnP_new,*/ /* tp_new */ +#else + 0, +#endif +}; + +/* module methods */ +static PyMethodDef miniupnpc_methods[] = { + {NULL} /* Sentinel */ +}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "miniupnpc", /* m_name */ + "miniupnpc module.", /* m_doc */ + -1, /* m_size */ + miniupnpc_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; +#endif + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit_miniupnpc(void) +#else +initminiupnpc(void) +#endif +{ + PyObject* m; + +#ifdef _WIN32 + /* initialize Winsock. */ + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + + UPnPType.tp_new = PyType_GenericNew; +#endif + if (PyType_Ready(&UPnPType) < 0) +#if PY_MAJOR_VERSION >= 3 + return 0; +#else + return; +#endif + +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("miniupnpc", miniupnpc_methods, + "miniupnpc module."); +#endif + + Py_INCREF(&UPnPType); + PyModule_AddObject(m, "UPnP", (PyObject *)&UPnPType); + +#if PY_MAJOR_VERSION >= 3 + return m; +#endif +} + diff --git a/thirdparty/miniupnpc/miniupnpcstrings.h b/thirdparty/miniupnpc/miniupnpcstrings.h new file mode 100644 index 0000000000..1d5c5882fd --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpcstrings.h @@ -0,0 +1,17 @@ +#ifndef MINIUPNPCSTRINGS_H_INCLUDED +#define MINIUPNPCSTRINGS_H_INCLUDED + +#include <version.h> + +#define OS_STRING VERSION_NAME "/1.0" +#define MINIUPNPC_VERSION_STRING "2.1" + +#if 0 +/* according to "UPnP Device Architecture 1.0" */ +#define UPNP_VERSION_STRING "UPnP/1.0" +#else +/* according to "UPnP Device Architecture 1.1" */ +#define UPNP_VERSION_STRING "UPnP/1.1" +#endif + +#endif diff --git a/thirdparty/miniupnpc/miniupnpctypes.h b/thirdparty/miniupnpc/miniupnpctypes.h new file mode 100644 index 0000000000..307ce39699 --- /dev/null +++ b/thirdparty/miniupnpc/miniupnpctypes.h @@ -0,0 +1,19 @@ +/* $Id: miniupnpctypes.h,v 1.1 2011/02/15 11:10:40 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org + * Author : Thomas Bernard + * Copyright (c) 2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef MINIUPNPCTYPES_H_INCLUDED +#define MINIUPNPCTYPES_H_INCLUDED + +#if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) +#define UNSIGNED_INTEGER unsigned long long +#define STRTOUI strtoull +#else +#define UNSIGNED_INTEGER unsigned int +#define STRTOUI strtoul +#endif + +#endif + diff --git a/thirdparty/miniupnpc/miniwget.c b/thirdparty/miniupnpc/miniwget.c new file mode 100644 index 0000000000..a46ba76022 --- /dev/null +++ b/thirdparty/miniupnpc/miniwget.c @@ -0,0 +1,662 @@ +/* $Id: miniwget.c,v 1.78 2018/03/13 23:22:18 nanard Exp $ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#define MAXHOSTNAMELEN 64 +#define snprintf _snprintf +#define socklen_t int +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#else /* #ifdef _WIN32 */ +#include <unistd.h> +#include <sys/param.h> +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/select.h> +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netdb.h> +#define closesocket close +#include <strings.h> +#endif /* #else _WIN32 */ +#ifdef __GNU__ +#define MAXHOSTNAMELEN 64 +#endif /* __GNU__ */ + +#ifndef MIN +#define MIN(x,y) (((x)<(y))?(x):(y)) +#endif /* MIN */ + + +#include "miniupnpcstrings.h" +#include "miniwget.h" +#include "connecthostport.h" +#include "receivedata.h" + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +/* + * Read a HTTP response from a socket. + * Process Content-Length and Transfer-encoding headers. + * return a pointer to the content buffer, which length is saved + * to the length parameter. + */ +void * +getHTTPResponse(SOCKET s, int * size, int * status_code) +{ + char buf[2048]; + int n; + int endofheaders = 0; + int chunked = 0; + int content_length = -1; + unsigned int chunksize = 0; + unsigned int bytestocopy = 0; + /* buffers : */ + char * header_buf; + unsigned int header_buf_len = 2048; + unsigned int header_buf_used = 0; + char * content_buf; + unsigned int content_buf_len = 2048; + unsigned int content_buf_used = 0; + char chunksize_buf[32]; + unsigned int chunksize_buf_index; +#ifdef DEBUG + char * reason_phrase = NULL; + int reason_phrase_len = 0; +#endif + + if(status_code) *status_code = -1; + header_buf = malloc(header_buf_len); + if(header_buf == NULL) + { +#ifdef DEBUG + fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse"); +#endif /* DEBUG */ + *size = -1; + return NULL; + } + content_buf = malloc(content_buf_len); + if(content_buf == NULL) + { + free(header_buf); +#ifdef DEBUG + fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse"); +#endif /* DEBUG */ + *size = -1; + return NULL; + } + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + + while((n = receivedata(s, buf, sizeof(buf), 5000, NULL)) > 0) + { + if(endofheaders == 0) + { + int i; + int linestart=0; + int colon=0; + int valuestart=0; + if(header_buf_used + n > header_buf_len) { + char * tmp = realloc(header_buf, header_buf_used + n); + if(tmp == NULL) { + /* memory allocation error */ + free(header_buf); + free(content_buf); + *size = -1; + return NULL; + } + header_buf = tmp; + header_buf_len = header_buf_used + n; + } + memcpy(header_buf + header_buf_used, buf, n); + header_buf_used += n; + /* search for CR LF CR LF (end of headers) + * recognize also LF LF */ + i = 0; + while(i < ((int)header_buf_used-1) && (endofheaders == 0)) { + if(header_buf[i] == '\r') { + i++; + if(header_buf[i] == '\n') { + i++; + if(i < (int)header_buf_used && header_buf[i] == '\r') { + i++; + if(i < (int)header_buf_used && header_buf[i] == '\n') { + endofheaders = i+1; + } + } + } + } else if(header_buf[i] == '\n') { + i++; + if(header_buf[i] == '\n') { + endofheaders = i+1; + } + } + i++; + } + if(endofheaders == 0) + continue; + /* parse header lines */ + for(i = 0; i < endofheaders - 1; i++) { + if(linestart > 0 && colon <= linestart && header_buf[i]==':') + { + colon = i; + while(i < (endofheaders-1) + && (header_buf[i+1] == ' ' || header_buf[i+1] == '\t')) + i++; + valuestart = i + 1; + } + /* detecting end of line */ + else if(header_buf[i]=='\r' || header_buf[i]=='\n') + { + if(linestart == 0 && status_code) + { + /* Status line + * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ + int sp; + for(sp = 0; sp < i; sp++) + if(header_buf[sp] == ' ') + { + if(*status_code < 0) + *status_code = atoi(header_buf + sp + 1); + else + { +#ifdef DEBUG + reason_phrase = header_buf + sp + 1; + reason_phrase_len = i - sp - 1; +#endif + break; + } + } +#ifdef DEBUG + printf("HTTP status code = %d, Reason phrase = %.*s\n", + *status_code, reason_phrase_len, reason_phrase); +#endif + } + else if(colon > linestart && valuestart > colon) + { +#ifdef DEBUG + printf("header='%.*s', value='%.*s'\n", + colon-linestart, header_buf+linestart, + i-valuestart, header_buf+valuestart); +#endif + if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart)) + { + content_length = atoi(header_buf+valuestart); +#ifdef DEBUG + printf("Content-Length: %d\n", content_length); +#endif + } + else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart) + && 0==strncasecmp(header_buf+valuestart, "chunked", 7)) + { +#ifdef DEBUG + printf("chunked transfer-encoding!\n"); +#endif + chunked = 1; + } + } + while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n')) + i++; + linestart = i; + colon = linestart; + valuestart = 0; + } + } + /* copy the remaining of the received data back to buf */ + n = header_buf_used - endofheaders; + memcpy(buf, header_buf + endofheaders, n); + /* if(headers) */ + } + /* if we get there, endofheaders != 0. + * In the other case, there was a continue above */ + /* content */ + if(chunked) + { + int i = 0; + while(i < n) + { + if(chunksize == 0) + { + /* reading chunk size */ + if(chunksize_buf_index == 0) { + /* skipping any leading CR LF */ + if(i<n && buf[i] == '\r') i++; + if(i<n && buf[i] == '\n') i++; + } + while(i<n && isxdigit(buf[i]) + && chunksize_buf_index < (sizeof(chunksize_buf)-1)) + { + chunksize_buf[chunksize_buf_index++] = buf[i]; + chunksize_buf[chunksize_buf_index] = '\0'; + i++; + } + while(i<n && buf[i] != '\r' && buf[i] != '\n') + i++; /* discarding chunk-extension */ + if(i<n && buf[i] == '\r') i++; + if(i<n && buf[i] == '\n') { + unsigned int j; + for(j = 0; j < chunksize_buf_index; j++) { + if(chunksize_buf[j] >= '0' + && chunksize_buf[j] <= '9') + chunksize = (chunksize << 4) + (chunksize_buf[j] - '0'); + else + chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10); + } + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + i++; + } else { + /* not finished to get chunksize */ + continue; + } +#ifdef DEBUG + printf("chunksize = %u (%x)\n", chunksize, chunksize); +#endif + if(chunksize == 0) + { +#ifdef DEBUG + printf("end of HTTP content - %d %d\n", i, n); + /*printf("'%.*s'\n", n-i, buf+i);*/ +#endif + goto end_of_stream; + } + } + /* it is guaranteed that (n >= i) */ + bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i); + if((content_buf_used + bytestocopy) > content_buf_len) + { + char * tmp; + if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + bytestocopy; + } + tmp = realloc(content_buf, content_buf_len); + if(tmp == NULL) { + /* memory allocation error */ + free(content_buf); + free(header_buf); + *size = -1; + return NULL; + } + content_buf = tmp; + } + memcpy(content_buf + content_buf_used, buf + i, bytestocopy); + content_buf_used += bytestocopy; + i += bytestocopy; + chunksize -= bytestocopy; + } + } + else + { + /* not chunked */ + if(content_length > 0 + && (content_buf_used + n) > (unsigned int)content_length) { + /* skipping additional bytes */ + n = content_length - content_buf_used; + } + if(content_buf_used + n > content_buf_len) + { + char * tmp; + if(content_length >= 0 + && (unsigned int)content_length >= (content_buf_used + n)) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + n; + } + tmp = realloc(content_buf, content_buf_len); + if(tmp == NULL) { + /* memory allocation error */ + free(content_buf); + free(header_buf); + *size = -1; + return NULL; + } + content_buf = tmp; + } + memcpy(content_buf + content_buf_used, buf, n); + content_buf_used += n; + } + /* use the Content-Length header value if available */ + if(content_length > 0 && content_buf_used >= (unsigned int)content_length) + { +#ifdef DEBUG + printf("End of HTTP content\n"); +#endif + break; + } + } +end_of_stream: + free(header_buf); header_buf = NULL; + *size = content_buf_used; + if(content_buf_used == 0) + { + free(content_buf); + content_buf = NULL; + } + return content_buf; +} + +/* miniwget3() : + * do all the work. + * Return NULL if something failed. */ +static void * +miniwget3(const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + const char * httpversion, unsigned int scope_id, + int * status_code) +{ + char buf[2048]; + SOCKET s; + int n; + int len; + int sent; + void * content; + + *size = 0; + s = connecthostport(host, port, scope_id); + if(ISINVALID(s)) + return NULL; + + /* get address for caller ! */ + if(addr_str) + { + struct sockaddr_storage saddr; + socklen_t saddrlen; + + saddrlen = sizeof(saddr); + if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0) + { + perror("getsockname"); + } + else + { +#if defined(__amigaos__) && !defined(__amigaos4__) + /* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD); + * But his function make a string with the port : nn.nn.nn.nn:port */ +/* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr), + NULL, addr_str, (DWORD *)&addr_str_len)) + { + printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError()); + }*/ + /* the following code is only compatible with ip v4 addresses */ + strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len); +#else +#if 0 + if(saddr.sa_family == AF_INET6) { + inet_ntop(AF_INET6, + &(((struct sockaddr_in6 *)&saddr)->sin6_addr), + addr_str, addr_str_len); + } else { + inet_ntop(AF_INET, + &(((struct sockaddr_in *)&saddr)->sin_addr), + addr_str, addr_str_len); + } +#endif + /* getnameinfo return ip v6 address with the scope identifier + * such as : 2a01:e35:8b2b:7330::%4281128194 */ + n = getnameinfo((const struct sockaddr *)&saddr, saddrlen, + addr_str, addr_str_len, + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if(n != 0) { +#ifdef _WIN32 + fprintf(stderr, "getnameinfo() failed : %d\n", n); +#else + fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n)); +#endif + } +#endif + } +#ifdef DEBUG + printf("address miniwget : %s\n", addr_str); +#endif + } + + len = snprintf(buf, sizeof(buf), + "GET %s HTTP/%s\r\n" + "Host: %s:%d\r\n" + "Connection: Close\r\n" + "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + + "\r\n", + path, httpversion, host, port); + if ((unsigned int)len >= sizeof(buf)) + { + closesocket(s); + return NULL; + } + sent = 0; + /* sending the HTTP request */ + while(sent < len) + { + n = send(s, buf+sent, len-sent, 0); + if(n < 0) + { + perror("send"); + closesocket(s); + return NULL; + } + else + { + sent += n; + } + } + content = getHTTPResponse(s, size, status_code); + closesocket(s); + return content; +} + +/* miniwget2() : + * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */ +static void * +miniwget2(const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + unsigned int scope_id, int * status_code) +{ + char * respbuffer; + +#if 1 + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.1", + scope_id, status_code); +#else + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.0", + scope_id, status_code); + if (*size == 0) + { +#ifdef DEBUG + printf("Retrying with HTTP/1.1\n"); +#endif + free(respbuffer); + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.1", + scope_id, status_code); + } +#endif + return respbuffer; +} + + + + +/* parseURL() + * arguments : + * url : source string not modified + * hostname : hostname destination string (size of MAXHOSTNAMELEN+1) + * port : port (destination) + * path : pointer to the path part of the URL + * + * Return values : + * 0 - Failure + * 1 - Success */ +int +parseURL(const char * url, + char * hostname, unsigned short * port, + char * * path, unsigned int * scope_id) +{ + char * p1, *p2, *p3; + if(!url) + return 0; + p1 = strstr(url, "://"); + if(!p1) + return 0; + p1 += 3; + if( (url[0]!='h') || (url[1]!='t') + ||(url[2]!='t') || (url[3]!='p')) + return 0; + memset(hostname, 0, MAXHOSTNAMELEN + 1); + if(*p1 == '[') + { + /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ + char * scope; + scope = strchr(p1, '%'); + p2 = strchr(p1, ']'); + if(p2 && scope && scope < p2 && scope_id) { + /* parse scope */ +#ifdef IF_NAMESIZE + char tmp[IF_NAMESIZE]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= IF_NAMESIZE) + l = IF_NAMESIZE - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = if_nametoindex(tmp); + if(*scope_id == 0) { + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); + } +#else + /* under windows, scope is numerical */ + char tmp[8]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= sizeof(tmp)) + l = sizeof(tmp) - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); +#endif + } + p3 = strchr(p1, '/'); + if(p2 && p3) + { + p2++; + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + if(*p2 == ':') + { + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + else + { + *port = 80; + } + *path = p3; + return 1; + } + } + p2 = strchr(p1, ':'); + p3 = strchr(p1, '/'); + if(!p3) + return 0; + if(!p2 || (p2>p3)) + { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1))); + *port = 80; + } + else + { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + *path = p3; + return 1; +} + +void * +miniwget(const char * url, int * size, + unsigned int scope_id, int * status_code) +{ + unsigned short port; + char * path; + /* protocol://host:port/chemin */ + char hostname[MAXHOSTNAMELEN+1]; + *size = 0; + if(!parseURL(url, hostname, &port, &path, &scope_id)) + return NULL; +#ifdef DEBUG + printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", + hostname, port, path, scope_id); +#endif + return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code); +} + +void * +miniwget_getaddr(const char * url, int * size, + char * addr, int addrlen, unsigned int scope_id, + int * status_code) +{ + unsigned short port; + char * path; + /* protocol://host:port/path */ + char hostname[MAXHOSTNAMELEN+1]; + *size = 0; + if(addr) + addr[0] = '\0'; + if(!parseURL(url, hostname, &port, &path, &scope_id)) + return NULL; +#ifdef DEBUG + printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", + hostname, port, path, scope_id); +#endif + return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code); +} + diff --git a/thirdparty/miniupnpc/miniwget.h b/thirdparty/miniupnpc/miniwget.h new file mode 100644 index 0000000000..f5572c2544 --- /dev/null +++ b/thirdparty/miniupnpc/miniwget.h @@ -0,0 +1,27 @@ +/* $Id: miniwget.h,v 1.12 2016/01/24 17:24:36 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIWGET_H_INCLUDED +#define MINIWGET_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int, int *); + +MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int, int *); + +int parseURL(const char *, char *, unsigned short *, char * *, unsigned int *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/miniupnpc/miniwget_private.h b/thirdparty/miniupnpc/miniwget_private.h new file mode 100644 index 0000000000..e4eaac8085 --- /dev/null +++ b/thirdparty/miniupnpc/miniwget_private.h @@ -0,0 +1,15 @@ +/* $Id: miniwget_private.h,v 1.1 2018/04/06 10:17:58 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIWGET_INTERNAL_H_INCLUDED +#define MINIWGET_INTERNAL_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +void * getHTTPResponse(SOCKET s, int * size, int * status_code); + +#endif diff --git a/thirdparty/miniupnpc/minixml.c b/thirdparty/miniupnpc/minixml.c new file mode 100644 index 0000000000..ed2d3c759c --- /dev/null +++ b/thirdparty/miniupnpc/minixml.c @@ -0,0 +1,231 @@ +/* $Id: minixml.c,v 1.10 2012/03/05 19:42:47 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * minixml.c : the minimum size a xml parser can be ! */ +/* Project : miniupnp + * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + +Copyright (c) 2005-2017, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include <string.h> +#include "minixml.h" + +/* parseatt : used to parse the argument list + * return 0 (false) in case of success and -1 (true) if the end + * of the xmlbuffer is reached. */ +static int parseatt(struct xmlparser * p) +{ + const char * attname; + int attnamelen; + const char * attvalue; + int attvaluelen; + while(p->xml < p->xmlend) + { + if(*p->xml=='/' || *p->xml=='>') + return 0; + if( !IS_WHITE_SPACE(*p->xml) ) + { + char sep; + attname = p->xml; + attnamelen = 0; + while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) + { + attnamelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + while(*(p->xml++) != '=') + { + if(p->xml >= p->xmlend) + return -1; + } + while(IS_WHITE_SPACE(*p->xml)) + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + sep = *p->xml; + if(sep=='\'' || sep=='\"') + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + attvalue = p->xml; + attvaluelen = 0; + while(*p->xml != sep) + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + else + { + attvalue = p->xml; + attvaluelen = 0; + while( !IS_WHITE_SPACE(*p->xml) + && *p->xml != '>' && *p->xml != '/') + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + /*printf("%.*s='%.*s'\n", + attnamelen, attname, attvaluelen, attvalue);*/ + if(p->attfunc) + p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); + } + p->xml++; + } + return -1; +} + +/* parseelt parse the xml stream and + * call the callback functions when needed... */ +static void parseelt(struct xmlparser * p) +{ + int i; + const char * elementname; + while(p->xml < (p->xmlend - 1)) + { + if((p->xml + 4) <= p->xmlend && (0 == memcmp(p->xml, "<!--", 4))) + { + p->xml += 3; + /* ignore comments */ + do + { + p->xml++; + if ((p->xml + 3) >= p->xmlend) + return; + } + while(memcmp(p->xml, "-->", 3) != 0); + p->xml += 3; + } + else if((p->xml)[0]=='<' && (p->xml)[1]!='?') + { + i = 0; elementname = ++p->xml; + while( !IS_WHITE_SPACE(*p->xml) + && (*p->xml!='>') && (*p->xml!='/') + ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + /* to ignore namespace : */ + if(*p->xml==':') + { + i = 0; + elementname = ++p->xml; + } + } + if(i>0) + { + if(p->starteltfunc) + p->starteltfunc(p->data, elementname, i); + if(parseatt(p)) + return; + if(*p->xml!='/') + { + const char * data; + i = 0; data = ++p->xml; + if (p->xml >= p->xmlend) + return; + while( IS_WHITE_SPACE(*p->xml) ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + /* CDATA are at least 9 + 3 characters long : <![CDATA[ ]]> */ + if((p->xmlend >= (p->xml + (9 + 3))) && (memcmp(p->xml, "<![CDATA[", 9) == 0)) + { + /* CDATA handling */ + p->xml += 9; + data = p->xml; + i = 0; + while(memcmp(p->xml, "]]>", 3) != 0) + { + i++; p->xml++; + if ((p->xml + 3) >= p->xmlend) + return; + } + if(i>0 && p->datafunc) + p->datafunc(p->data, data, i); + while(*p->xml!='<') + { + p->xml++; + if (p->xml >= p->xmlend) + return; + } + } + else + { + while(*p->xml!='<') + { + i++; p->xml++; + if ((p->xml + 1) >= p->xmlend) + return; + } + if(i>0 && p->datafunc && *(p->xml + 1) == '/') + p->datafunc(p->data, data, i); + } + } + } + else if(*p->xml == '/') + { + i = 0; elementname = ++p->xml; + if (p->xml >= p->xmlend) + return; + while((*p->xml != '>')) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(p->endeltfunc) + p->endeltfunc(p->data, elementname, i); + p->xml++; + } + } + else + { + p->xml++; + } + } +} + +/* the parser must be initialized before calling this function */ +void parsexml(struct xmlparser * parser) +{ + parser->xml = parser->xmlstart; + parser->xmlend = parser->xmlstart + parser->xmlsize; + parseelt(parser); +} + + diff --git a/thirdparty/miniupnpc/minixml.h b/thirdparty/miniupnpc/minixml.h new file mode 100644 index 0000000000..19e6f513bf --- /dev/null +++ b/thirdparty/miniupnpc/minixml.h @@ -0,0 +1,37 @@ +/* $Id: minixml.h,v 1.6 2006/11/30 11:47:21 nanard Exp $ */ +/* minimal xml parser + * + * Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIXML_H_INCLUDED +#define MINIXML_H_INCLUDED +#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) + +/* if a callback function pointer is set to NULL, + * the function is not called */ +struct xmlparser { + const char *xmlstart; + const char *xmlend; + const char *xml; /* pointer to current character */ + int xmlsize; + void * data; + void (*starteltfunc) (void *, const char *, int); + void (*endeltfunc) (void *, const char *, int); + void (*datafunc) (void *, const char *, int); + void (*attfunc) (void *, const char *, int, const char *, int); +}; + +/* parsexml() + * the xmlparser structure must be initialized before the call + * the following structure members have to be initialized : + * xmlstart, xmlsize, data, *func + * xml is for internal usage, xmlend is computed automatically */ +void parsexml(struct xmlparser *); + +#endif + diff --git a/thirdparty/miniupnpc/minixmlvalid.c b/thirdparty/miniupnpc/minixmlvalid.c new file mode 100644 index 0000000000..dad1488122 --- /dev/null +++ b/thirdparty/miniupnpc/minixmlvalid.c @@ -0,0 +1,163 @@ +/* $Id: minixmlvalid.c,v 1.7 2015/07/15 12:41:15 nanard Exp $ */ +/* MiniUPnP Project + * http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ + * minixmlvalid.c : + * validation program for the minixml parser + * + * (c) 2006-2011 Thomas Bernard */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "minixml.h" + +/* xml event structure */ +struct event { + enum { ELTSTART, ELTEND, ATT, CHARDATA } type; + const char * data; + int len; +}; + +struct eventlist { + int n; + struct event * events; +}; + +/* compare 2 xml event lists + * return 0 if the two lists are equals */ +int evtlistcmp(struct eventlist * a, struct eventlist * b) +{ + int i; + struct event * ae, * be; + if(a->n != b->n) + { + printf("event number not matching : %d != %d\n", a->n, b->n); + /*return 1;*/ + } + for(i=0; i<a->n; i++) + { + ae = a->events + i; + be = b->events + i; + if( (ae->type != be->type) + ||(ae->len != be->len) + ||memcmp(ae->data, be->data, ae->len)) + { + printf("Found a difference : %d '%.*s' != %d '%.*s'\n", + ae->type, ae->len, ae->data, + be->type, be->len, be->data); + return 1; + } + } + return 0; +} + +/* Test data */ +static const char xmldata[] = +"<xmlroot>\n" +" <elt1 att1=\"attvalue1\" att2=\"attvalue2\">" +"character data" +"</elt1> \n \t" +"<elt1b/>" +"<elt1>\n<![CDATA[ <html>stuff !\n ]]> \n</elt1>\n" +"<elt2a> \t<elt2b>chardata1</elt2b><elt2b> chardata2 </elt2b></elt2a>" +"</xmlroot>"; + +static const struct event evtref[] = +{ + {ELTSTART, "xmlroot", 7}, + {ELTSTART, "elt1", 4}, + /* attributes */ + {CHARDATA, "character data", 14}, + {ELTEND, "elt1", 4}, + {ELTSTART, "elt1b", 5}, + {ELTSTART, "elt1", 4}, + {CHARDATA, " <html>stuff !\n ", 16}, + {ELTEND, "elt1", 4}, + {ELTSTART, "elt2a", 5}, + {ELTSTART, "elt2b", 5}, + {CHARDATA, "chardata1", 9}, + {ELTEND, "elt2b", 5}, + {ELTSTART, "elt2b", 5}, + {CHARDATA, " chardata2 ", 11}, + {ELTEND, "elt2b", 5}, + {ELTEND, "elt2a", 5}, + {ELTEND, "xmlroot", 7} +}; + +void startelt(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("startelt : %.*s\n", l, p);*/ + evt->type = ELTSTART; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +void endelt(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("endelt : %.*s\n", l, p);*/ + evt->type = ELTEND; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +void chardata(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("chardata : '%.*s'\n", l, p);*/ + evt->type = CHARDATA; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +int testxmlparser(const char * xml, int size) +{ + int r; + struct eventlist evtlist; + struct eventlist evtlistref; + struct xmlparser parser; + evtlist.n = 0; + evtlist.events = malloc(sizeof(struct event)*100); + if(evtlist.events == NULL) + { + fprintf(stderr, "Memory allocation error.\n"); + return -1; + } + memset(&parser, 0, sizeof(parser)); + parser.xmlstart = xml; + parser.xmlsize = size; + parser.data = &evtlist; + parser.starteltfunc = startelt; + parser.endeltfunc = endelt; + parser.datafunc = chardata; + parsexml(&parser); + printf("%d events\n", evtlist.n); + /* compare */ + evtlistref.n = sizeof(evtref)/sizeof(struct event); + evtlistref.events = (struct event *)evtref; + r = evtlistcmp(&evtlistref, &evtlist); + free(evtlist.events); + return r; +} + +int main(int argc, char * * argv) +{ + int r; + (void)argc; (void)argv; + + r = testxmlparser(xmldata, sizeof(xmldata)-1); + if(r) + printf("minixml validation test failed\n"); + return r; +} + diff --git a/thirdparty/miniupnpc/portlistingparse.c b/thirdparty/miniupnpc/portlistingparse.c new file mode 100644 index 0000000000..55859f2714 --- /dev/null +++ b/thirdparty/miniupnpc/portlistingparse.c @@ -0,0 +1,172 @@ +/* $Id: portlistingparse.c,v 1.9 2015/07/15 12:41:13 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011-2016 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#include <string.h> +#include <stdlib.h> +#ifdef DEBUG +#include <stdio.h> +#endif /* DEBUG */ +#include "portlistingparse.h" +#include "minixml.h" + +/* list of the elements */ +static const struct { + const portMappingElt code; + const char * const str; +} elements[] = { + { PortMappingEntry, "PortMappingEntry"}, + { NewRemoteHost, "NewRemoteHost"}, + { NewExternalPort, "NewExternalPort"}, + { NewProtocol, "NewProtocol"}, + { NewInternalPort, "NewInternalPort"}, + { NewInternalClient, "NewInternalClient"}, + { NewEnabled, "NewEnabled"}, + { NewDescription, "NewDescription"}, + { NewLeaseTime, "NewLeaseTime"}, + { PortMappingEltNone, NULL} +}; + +/* Helper function */ +static UNSIGNED_INTEGER +atoui(const char * p, int l) +{ + UNSIGNED_INTEGER r = 0; + while(l > 0 && *p) + { + if(*p >= '0' && *p <= '9') + r = r*10 + (*p - '0'); + else + break; + p++; + l--; + } + return r; +} + +/* Start element handler */ +static void +startelt(void * d, const char * name, int l) +{ + int i; + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pdata->curelt = PortMappingEltNone; + for(i = 0; elements[i].str; i++) + { + if(strlen(elements[i].str) == (size_t)l && memcmp(name, elements[i].str, l) == 0) + { + pdata->curelt = elements[i].code; + break; + } + } + if(pdata->curelt == PortMappingEntry) + { + struct PortMapping * pm; + pm = calloc(1, sizeof(struct PortMapping)); + if(pm == NULL) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "startelt"); +#endif /* DEBUG */ + return; + } + pm->l_next = pdata->l_head; /* insert in list */ + pdata->l_head = pm; + } +} + +/* End element handler */ +static void +endelt(void * d, const char * name, int l) +{ + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + (void)name; + (void)l; + pdata->curelt = PortMappingEltNone; +} + +/* Data handler */ +static void +data(void * d, const char * data, int l) +{ + struct PortMapping * pm; + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pm = pdata->l_head; + if(!pm) + return; + if(l > 63) + l = 63; + switch(pdata->curelt) + { + case NewRemoteHost: + memcpy(pm->remoteHost, data, l); + pm->remoteHost[l] = '\0'; + break; + case NewExternalPort: + pm->externalPort = (unsigned short)atoui(data, l); + break; + case NewProtocol: + if(l > 3) + l = 3; + memcpy(pm->protocol, data, l); + pm->protocol[l] = '\0'; + break; + case NewInternalPort: + pm->internalPort = (unsigned short)atoui(data, l); + break; + case NewInternalClient: + memcpy(pm->internalClient, data, l); + pm->internalClient[l] = '\0'; + break; + case NewEnabled: + pm->enabled = (unsigned char)atoui(data, l); + break; + case NewDescription: + memcpy(pm->description, data, l); + pm->description[l] = '\0'; + break; + case NewLeaseTime: + pm->leaseTime = atoui(data, l); + break; + default: + break; + } +} + + +/* Parse the PortMappingList XML document for IGD version 2 + */ +void +ParsePortListing(const char * buffer, int bufsize, + struct PortMappingParserData * pdata) +{ + struct xmlparser parser; + + memset(pdata, 0, sizeof(struct PortMappingParserData)); + /* init xmlparser */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = pdata; + parser.starteltfunc = startelt; + parser.endeltfunc = endelt; + parser.datafunc = data; + parser.attfunc = 0; + parsexml(&parser); +} + +void +FreePortListing(struct PortMappingParserData * pdata) +{ + struct PortMapping * pm; + while((pm = pdata->l_head) != NULL) + { + /* remove from list */ + pdata->l_head = pm->l_next; + free(pm); + } +} + diff --git a/thirdparty/miniupnpc/portlistingparse.h b/thirdparty/miniupnpc/portlistingparse.h new file mode 100644 index 0000000000..e3957a3f4c --- /dev/null +++ b/thirdparty/miniupnpc/portlistingparse.h @@ -0,0 +1,65 @@ +/* $Id: portlistingparse.h,v 1.10 2014/11/01 10:37:32 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011-2015 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#ifndef PORTLISTINGPARSE_H_INCLUDED +#define PORTLISTINGPARSE_H_INCLUDED + +#include "miniupnpc_declspec.h" +/* for the definition of UNSIGNED_INTEGER */ +#include "miniupnpctypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* sample of PortMappingEntry : + <p:PortMappingEntry> + <p:NewRemoteHost>202.233.2.1</p:NewRemoteHost> + <p:NewExternalPort>2345</p:NewExternalPort> + <p:NewProtocol>TCP</p:NewProtocol> + <p:NewInternalPort>2345</p:NewInternalPort> + <p:NewInternalClient>192.168.1.137</p:NewInternalClient> + <p:NewEnabled>1</p:NewEnabled> + <p:NewDescription>dooom</p:NewDescription> + <p:NewLeaseTime>345</p:NewLeaseTime> + </p:PortMappingEntry> + */ +typedef enum { PortMappingEltNone, + PortMappingEntry, NewRemoteHost, + NewExternalPort, NewProtocol, + NewInternalPort, NewInternalClient, + NewEnabled, NewDescription, + NewLeaseTime } portMappingElt; + +struct PortMapping { + struct PortMapping * l_next; /* list next element */ + UNSIGNED_INTEGER leaseTime; + unsigned short externalPort; + unsigned short internalPort; + char remoteHost[64]; + char internalClient[64]; + char description[64]; + char protocol[4]; + unsigned char enabled; +}; + +struct PortMappingParserData { + struct PortMapping * l_head; /* list head */ + portMappingElt curelt; +}; + +MINIUPNP_LIBSPEC void +ParsePortListing(const char * buffer, int bufsize, + struct PortMappingParserData * pdata); + +MINIUPNP_LIBSPEC void +FreePortListing(struct PortMappingParserData * pdata); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/miniupnpc/receivedata.c b/thirdparty/miniupnpc/receivedata.c new file mode 100644 index 0000000000..7b9cc5b778 --- /dev/null +++ b/thirdparty/miniupnpc/receivedata.c @@ -0,0 +1,99 @@ +/* $Id: receivedata.c,v 1.7 2015/11/09 21:51:41 nanard Exp $ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2011-2014 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#else /* _WIN32 */ +#include <unistd.h> +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/select.h> +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/socket.h> +#include <netinet/in.h> +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include <poll.h> +#endif /* !defined(__amigaos__) && !defined(__amigaos4__) */ +#include <errno.h> +#define MINIUPNPC_IGNORE_EINTR +#endif /* _WIN32 */ + +#include "receivedata.h" + +int +receivedata(SOCKET socket, + char * data, int length, + int timeout, unsigned int * scope_id) +{ +#ifdef MINIUPNPC_GET_SRC_ADDR + struct sockaddr_storage src_addr; + socklen_t src_addr_len = sizeof(src_addr); +#endif /* MINIUPNPC_GET_SRC_ADDR */ + int n; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* using poll */ + struct pollfd fds[1]; /* for the poll */ +#ifdef MINIUPNPC_IGNORE_EINTR + do { +#endif /* MINIUPNPC_IGNORE_EINTR */ + fds[0].fd = socket; + fds[0].events = POLLIN; + n = poll(fds, 1, timeout); +#ifdef MINIUPNPC_IGNORE_EINTR + } while(n < 0 && errno == EINTR); +#endif /* MINIUPNPC_IGNORE_EINTR */ + if(n < 0) { + PRINT_SOCKET_ERROR("poll"); + return -1; + } else if(n == 0) { + /* timeout */ + return 0; + } +#else /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + /* using select under _WIN32 and amigaos */ + fd_set socketSet; + TIMEVAL timeval; + FD_ZERO(&socketSet); + FD_SET(socket, &socketSet); + timeval.tv_sec = timeout / 1000; + timeval.tv_usec = (timeout % 1000) * 1000; + n = select(FD_SETSIZE, &socketSet, NULL, NULL, &timeval); + if(n < 0) { + PRINT_SOCKET_ERROR("select"); + return -1; + } else if(n == 0) { + return 0; + } +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ +#ifdef MINIUPNPC_GET_SRC_ADDR + memset(&src_addr, 0, sizeof(src_addr)); + n = recvfrom(socket, data, length, 0, + (struct sockaddr *)&src_addr, &src_addr_len); +#else /* MINIUPNPC_GET_SRC_ADDR */ + n = recv(socket, data, length, 0); +#endif /* MINIUPNPC_GET_SRC_ADDR */ + if(n<0) { + PRINT_SOCKET_ERROR("recv"); + } +#ifdef MINIUPNPC_GET_SRC_ADDR + if (src_addr.ss_family == AF_INET6) { + const struct sockaddr_in6 * src_addr6 = (struct sockaddr_in6 *)&src_addr; +#ifdef DEBUG + printf("scope_id=%u\n", src_addr6->sin6_scope_id); +#endif /* DEBUG */ + if(scope_id) + *scope_id = src_addr6->sin6_scope_id; + } +#endif /* MINIUPNPC_GET_SRC_ADDR */ + return n; +} + diff --git a/thirdparty/miniupnpc/receivedata.h b/thirdparty/miniupnpc/receivedata.h new file mode 100644 index 0000000000..c9fdc561f8 --- /dev/null +++ b/thirdparty/miniupnpc/receivedata.h @@ -0,0 +1,21 @@ +/* $Id: receivedata.h,v 1.3 2012/06/23 22:34:47 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2011-2018 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef RECEIVEDATA_H_INCLUDED +#define RECEIVEDATA_H_INCLUDED + +#include "miniupnpc_socketdef.h" + +/* Reads data from the specified socket. + * Returns the number of bytes read if successful, zero if no bytes were + * read or if we timed out. Returns negative if there was an error. */ +int receivedata(SOCKET socket, + char * data, int length, + int timeout, unsigned int * scope_id); + +#endif + diff --git a/thirdparty/miniupnpc/upnpc.c b/thirdparty/miniupnpc/upnpc.c new file mode 100644 index 0000000000..0c65cbe8c0 --- /dev/null +++ b/thirdparty/miniupnpc/upnpc.c @@ -0,0 +1,861 @@ +/* $Id: upnpc.c,v 1.119 2018/03/13 23:34:46 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#ifdef _WIN32 +#include <winsock2.h> +#define snprintf _snprintf +#else +/* for IPPROTO_TCP / IPPROTO_UDP */ +#include <netinet/in.h> +#endif +#include <ctype.h> +#include "miniwget.h" +#include "miniupnpc.h" +#include "upnpcommands.h" +#include "portlistingparse.h" +#include "upnperrors.h" +#include "miniupnpcstrings.h" + +/* protofix() checks if protocol is "UDP" or "TCP" + * returns NULL if not */ +const char * protofix(const char * proto) +{ + static const char proto_tcp[4] = { 'T', 'C', 'P', 0}; + static const char proto_udp[4] = { 'U', 'D', 'P', 0}; + int i, b; + for(i=0, b=1; i<4; i++) + b = b && ( (proto[i] == proto_tcp[i]) + || (proto[i] == (proto_tcp[i] | 32)) ); + if(b) + return proto_tcp; + for(i=0, b=1; i<4; i++) + b = b && ( (proto[i] == proto_udp[i]) + || (proto[i] == (proto_udp[i] | 32)) ); + if(b) + return proto_udp; + return 0; +} + +/* is_int() checks if parameter is an integer or not + * 1 for integer + * 0 for not an integer */ +int is_int(char const* s) +{ + if(s == NULL) + return 0; + while(*s) { + /* #define isdigit(c) ((c) >= '0' && (c) <= '9') */ + if(!isdigit(*s)) + return 0; + s++; + } + return 1; +} + +static void DisplayInfos(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + char externalIPAddress[40]; + char connectionType[64]; + char status[64]; + char lastconnerr[64]; + unsigned int uptime = 0; + unsigned int brUp, brDown; + time_t timenow, timestarted; + int r; + if(UPNP_GetConnectionTypeInfo(urls->controlURL, + data->first.servicetype, + connectionType) != UPNPCOMMAND_SUCCESS) + printf("GetConnectionTypeInfo failed.\n"); + else + printf("Connection Type : %s\n", connectionType); + if(UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, lastconnerr) != UPNPCOMMAND_SUCCESS) + printf("GetStatusInfo failed.\n"); + else + printf("Status : %s, uptime=%us, LastConnectionError : %s\n", + status, uptime, lastconnerr); + if(uptime > 0) { + timenow = time(NULL); + timestarted = timenow - uptime; + printf(" Time started : %s", ctime(×tarted)); + } + if(UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, data->CIF.servicetype, + &brDown, &brUp) != UPNPCOMMAND_SUCCESS) { + printf("GetLinkLayerMaxBitRates failed.\n"); + } else { + printf("MaxBitRateDown : %u bps", brDown); + if(brDown >= 1000000) { + printf(" (%u.%u Mbps)", brDown / 1000000, (brDown / 100000) % 10); + } else if(brDown >= 1000) { + printf(" (%u Kbps)", brDown / 1000); + } + printf(" MaxBitRateUp %u bps", brUp); + if(brUp >= 1000000) { + printf(" (%u.%u Mbps)", brUp / 1000000, (brUp / 100000) % 10); + } else if(brUp >= 1000) { + printf(" (%u Kbps)", brUp / 1000); + } + printf("\n"); + } + r = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); + if(r != UPNPCOMMAND_SUCCESS) { + printf("GetExternalIPAddress failed. (errorcode=%d)\n", r); + } else { + printf("ExternalIPAddress = %s\n", externalIPAddress); + } +} + +static void GetConnectionStatus(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + unsigned int bytessent, bytesreceived, packetsreceived, packetssent; + DisplayInfos(urls, data); + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); + bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); + packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); + packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, data->CIF.servicetype); + printf("Bytes: Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived); + printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); +} + +static void ListRedirections(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + int r; + int i = 0; + char index[6]; + char intClient[40]; + char intPort[6]; + char extPort[6]; + char protocol[4]; + char desc[80]; + char enabled[6]; + char rHost[64]; + char duration[16]; + /*unsigned int num=0; + UPNP_GetPortMappingNumberOfEntries(urls->controlURL, data->servicetype, &num); + printf("PortMappingNumberOfEntries : %u\n", num);*/ + printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n"); + do { + snprintf(index, 6, "%d", i); + rHost[0] = '\0'; enabled[0] = '\0'; + duration[0] = '\0'; desc[0] = '\0'; + extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; + r = UPNP_GetGenericPortMappingEntry(urls->controlURL, + data->first.servicetype, + index, + extPort, intClient, intPort, + protocol, desc, enabled, + rHost, duration); + if(r==0) + /* + printf("%02d - %s %s->%s:%s\tenabled=%s leaseDuration=%s\n" + " desc='%s' rHost='%s'\n", + i, protocol, extPort, intClient, intPort, + enabled, duration, + desc, rHost); + */ + printf("%2d %s %5s->%s:%-5s '%s' '%s' %s\n", + i, protocol, extPort, intClient, intPort, + desc, rHost, duration); + else + printf("GetGenericPortMappingEntry() returned %d (%s)\n", + r, strupnperror(r)); + i++; + } while(r==0); +} + +static void NewListRedirections(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + int r; + int i = 0; + struct PortMappingParserData pdata; + struct PortMapping * pm; + + memset(&pdata, 0, sizeof(struct PortMappingParserData)); + r = UPNP_GetListOfPortMappings(urls->controlURL, + data->first.servicetype, + "0", + "65535", + "TCP", + "1000", + &pdata); + if(r == UPNPCOMMAND_SUCCESS) + { + printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n"); + for(pm = pdata.l_head; pm != NULL; pm = pm->l_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + i++; + } + FreePortListing(&pdata); + } + else + { + printf("GetListOfPortMappings() returned %d (%s)\n", + r, strupnperror(r)); + } + r = UPNP_GetListOfPortMappings(urls->controlURL, + data->first.servicetype, + "0", + "65535", + "UDP", + "1000", + &pdata); + if(r == UPNPCOMMAND_SUCCESS) + { + for(pm = pdata.l_head; pm != NULL; pm = pm->l_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + i++; + } + FreePortListing(&pdata); + } + else + { + printf("GetListOfPortMappings() returned %d (%s)\n", + r, strupnperror(r)); + } +} + +/* Test function + * 1 - get connection type + * 2 - get extenal ip address + * 3 - Add port mapping + * 4 - get this port mapping from the IGD */ +static int SetRedirectAndTest(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * iaddr, + const char * iport, + const char * eport, + const char * proto, + const char * leaseDuration, + const char * description, + int addAny) +{ + char externalIPAddress[40]; + char intClient[40]; + char intPort[6]; + char reservedPort[6]; + char duration[16]; + int r; + + if(!iaddr || !iport || !eport || !proto) + { + fprintf(stderr, "Wrong arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "invalid protocol\n"); + return -1; + } + + r = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetExternalIPAddress failed.\n"); + else + printf("ExternalIPAddress = %s\n", externalIPAddress); + + if (addAny) { + r = UPNP_AddAnyPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, description, + proto, 0, leaseDuration, reservedPort); + if(r==UPNPCOMMAND_SUCCESS) + eport = reservedPort; + else + printf("AddAnyPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + } else { + r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, description, + proto, 0, leaseDuration); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + return -2; + } + } + + r = UPNP_GetSpecificPortMappingEntry(urls->controlURL, + data->first.servicetype, + eport, proto, NULL/*remoteHost*/, + intClient, intPort, NULL/*desc*/, + NULL/*enabled*/, duration); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", + r, strupnperror(r)); + return -2; + } else { + printf("InternalIP:Port = %s:%s\n", intClient, intPort); + printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n", + externalIPAddress, eport, proto, intClient, intPort, duration); + } + return 0; +} + +static int +RemoveRedirect(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * eport, + const char * proto, + const char * remoteHost) +{ + int r; + if(!proto || !eport) + { + fprintf(stderr, "invalid arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "protocol invalid\n"); + return -1; + } + r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, remoteHost); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMapping() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMapping() returned : %d\n", r); + } + return 0; +} + +static int +RemoveRedirectRange(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * ePortStart, char const * ePortEnd, + const char * proto, const char * manage) +{ + int r; + + if (!manage) + manage = "0"; + + if(!proto || !ePortStart || !ePortEnd) + { + fprintf(stderr, "invalid arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "protocol invalid\n"); + return -1; + } + r = UPNP_DeletePortMappingRange(urls->controlURL, data->first.servicetype, ePortStart, ePortEnd, proto, manage); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMappingRange() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMappingRange() returned : %d\n", r); + } + return 0; +} + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +static void GetFirewallStatus(struct UPNPUrls * urls, struct IGDdatas * data) +{ + unsigned int bytessent, bytesreceived, packetsreceived, packetssent; + int firewallEnabled = 0, inboundPinholeAllowed = 0; + + UPNP_GetFirewallStatus(urls->controlURL_6FC, data->IPv6FC.servicetype, &firewallEnabled, &inboundPinholeAllowed); + printf("FirewallEnabled: %d & Inbound Pinhole Allowed: %d\n", firewallEnabled, inboundPinholeAllowed); + printf("GetFirewallStatus:\n Firewall Enabled: %s\n Inbound Pinhole Allowed: %s\n", (firewallEnabled)? "Yes":"No", (inboundPinholeAllowed)? "Yes":"No"); + + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); + bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); + packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); + packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, data->CIF.servicetype); + printf("Bytes: Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived); + printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); +} + +/* Test function + * 1 - Add pinhole + * 2 - Check if pinhole is working from the IGD side */ +static void SetPinholeAndTest(struct UPNPUrls * urls, struct IGDdatas * data, + const char * remoteaddr, const char * eport, + const char * intaddr, const char * iport, + const char * proto, const char * lease_time) +{ + char uniqueID[8]; + /*int isWorking = 0;*/ + int r; + char proto_tmp[8]; + + if(!intaddr || !remoteaddr || !iport || !eport || !proto || !lease_time) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + if(atoi(proto) == 0) + { + const char * protocol; + protocol = protofix(proto); + if(protocol && (strcmp("TCP", protocol) == 0)) + { + snprintf(proto_tmp, sizeof(proto_tmp), "%d", IPPROTO_TCP); + proto = proto_tmp; + } + else if(protocol && (strcmp("UDP", protocol) == 0)) + { + snprintf(proto_tmp, sizeof(proto_tmp), "%d", IPPROTO_UDP); + proto = proto_tmp; + } + else + { + fprintf(stderr, "invalid protocol\n"); + return; + } + } + r = UPNP_AddPinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, lease_time, uniqueID); + if(r!=UPNPCOMMAND_SUCCESS) + printf("AddPinhole([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", + remoteaddr, eport, intaddr, iport, r, strupnperror(r)); + else + { + printf("AddPinhole: ([%s]:%s -> [%s]:%s) / Pinhole ID = %s\n", + remoteaddr, eport, intaddr, iport, uniqueID); + /*r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->servicetype_6FC, uniqueID, &isWorking); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No");*/ + } +} + +/* Test function + * 1 - Check if pinhole is working from the IGD side + * 2 - Update pinhole */ +static void GetPinholeAndUpdate(struct UPNPUrls * urls, struct IGDdatas * data, + const char * uniqueID, const char * lease_time) +{ + int isWorking = 0; + int r; + + if(!uniqueID || !lease_time) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + if(isWorking || r==709) + { + r = UPNP_UpdatePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, lease_time); + printf("UpdatePinhole: Pinhole ID = %s with Lease Time: %s\n", uniqueID, lease_time); + if(r!=UPNPCOMMAND_SUCCESS) + printf("UpdatePinhole: ID (%s) failed with code %d (%s)\n", uniqueID, r, strupnperror(r)); + } +} + +/* Test function + * Get pinhole timeout + */ +static void GetPinholeOutboundTimeout(struct UPNPUrls * urls, struct IGDdatas * data, + const char * remoteaddr, const char * eport, + const char * intaddr, const char * iport, + const char * proto) +{ + int timeout = 0; + int r; + + if(!intaddr || !remoteaddr || !iport || !eport || !proto) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + + r = UPNP_GetOutboundPinholeTimeout(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, &timeout); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetOutboundPinholeTimeout([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", + intaddr, iport, remoteaddr, eport, r, strupnperror(r)); + else + printf("GetOutboundPinholeTimeout: ([%s]:%s -> [%s]:%s) / Timeout = %d\n", intaddr, iport, remoteaddr, eport, timeout); +} + +static void +GetPinholePackets(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r, pinholePackets = 0; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_GetPinholePackets(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &pinholePackets); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetPinholePackets() failed with code %d (%s)\n", r, strupnperror(r)); + else + printf("GetPinholePackets: Pinhole ID = %s / PinholePackets = %d\n", uniqueID, pinholePackets); +} + +static void +CheckPinhole(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r, isWorking = 0; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + else + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); +} + +static void +RemovePinhole(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_DeletePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID); + printf("UPNP_DeletePinhole() returned : %d\n", r); +} + + +/* sample upnp client program */ +int main(int argc, char ** argv) +{ + char command = 0; + char ** commandargv = 0; + int commandargc = 0; + struct UPNPDev * devlist = 0; + char lanaddr[64] = "unset"; /* my ip address on the LAN */ + int i; + const char * rootdescurl = 0; + const char * multicastif = 0; + const char * minissdpdpath = 0; + int localport = UPNP_LOCAL_PORT_ANY; + int retcode = 0; + int error = 0; + int ipv6 = 0; + unsigned char ttl = 2; /* defaulting to 2 */ + const char * description = 0; + +#ifdef _WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + printf("upnpc : miniupnpc library test client, version %s.\n", MINIUPNPC_VERSION_STRING); + printf(" (c) 2005-2018 Thomas Bernard.\n"); + printf("Go to http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/\n" + "for more information.\n"); + /* command line processing */ + for(i=1; i<argc; i++) + { + if(0 == strcmp(argv[i], "--help") || 0 == strcmp(argv[i], "-h")) + { + command = 0; + break; + } + if(argv[i][0] == '-') + { + if(argv[i][1] == 'u') + rootdescurl = argv[++i]; + else if(argv[i][1] == 'm') + { + multicastif = argv[++i]; + minissdpdpath = ""; /* Disable usage of minissdpd */ + } + else if(argv[i][1] == 'z') + { + char junk; + if(sscanf(argv[++i], "%d%c", &localport, &junk)!=1 || + localport<0 || localport>65535 || + (localport >1 && localport < 1024)) + { + fprintf(stderr, "Invalid localport '%s'\n", argv[i]); + localport = UPNP_LOCAL_PORT_ANY; + break; + } + } + else if(argv[i][1] == 'p') + minissdpdpath = argv[++i]; + else if(argv[i][1] == '6') + ipv6 = 1; + else if(argv[i][1] == 'e') + description = argv[++i]; + else if(argv[i][1] == 't') + ttl = (unsigned char)atoi(argv[++i]); + else + { + command = argv[i][1]; + i++; + commandargv = argv + i; + commandargc = argc - i; + break; + } + } + else + { + fprintf(stderr, "option '%s' invalid\n", argv[i]); + } + } + + if(!command + || (command == 'a' && commandargc<4) + || (command == 'd' && argc<2) + || (command == 'r' && argc<2) + || (command == 'A' && commandargc<6) + || (command == 'U' && commandargc<2) + || (command == 'D' && commandargc<1)) + { + fprintf(stderr, "Usage :\t%s [options] -a ip port external_port protocol [duration]\n\t\tAdd port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -d external_port protocol <remote host>\n\t\tDelete port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -s\n\t\tGet Connection status\n", argv[0]); + fprintf(stderr, " \t%s [options] -l\n\t\tList redirections\n", argv[0]); + fprintf(stderr, " \t%s [options] -L\n\t\tList redirections (using GetListOfPortMappings (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -n ip port external_port protocol [duration]\n\t\tAdd (any) port redirection allowing IGD to use alternative external_port (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -N external_port_start external_port_end protocol [manage]\n\t\tDelete range of port redirections (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -r port1 [external_port1] protocol1 [port2 [external_port2] protocol2] [...]\n\t\tAdd all redirections to the current host\n", argv[0]); + fprintf(stderr, " \t%s [options] -A remote_ip remote_port internal_ip internal_port protocol lease_time\n\t\tAdd Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -U uniqueID new_lease_time\n\t\tUpdate Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -C uniqueID\n\t\tCheck if Pinhole is Working (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -K uniqueID\n\t\tGet Number of packets going through the rule (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -D uniqueID\n\t\tDelete Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -S\n\t\tGet Firewall status (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -G remote_ip remote_port internal_ip internal_port protocol\n\t\tGet Outbound Pinhole Timeout (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -P\n\t\tGet Presentation url\n", argv[0]); + fprintf(stderr, "\nprotocol is UDP or TCP\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -e description : set description for port mapping.\n"); + fprintf(stderr, " -6 : use ip v6 instead of ip v4.\n"); + fprintf(stderr, " -u url : bypass discovery process by providing the XML root description url.\n"); + fprintf(stderr, " -m address/interface : provide ip address (ip v4) or interface name (ip v4 or v6) to use for sending SSDP multicast packets.\n"); + fprintf(stderr, " -z localport : SSDP packets local (source) port (1024-65535).\n"); + fprintf(stderr, " -p path : use this path for MiniSSDPd socket.\n"); + fprintf(stderr, " -t ttl : set multicast TTL. Default value is 2.\n"); + return 1; + } + + if( rootdescurl + || (devlist = upnpDiscover(2000, multicastif, minissdpdpath, + localport, ipv6, ttl, &error))) + { + struct UPNPDev * device; + struct UPNPUrls urls; + struct IGDdatas data; + if(devlist) + { + printf("List of UPNP devices found on the network :\n"); + for(device = devlist; device; device = device->pNext) + { + printf(" desc: %s\n st: %s\n\n", + device->descURL, device->st); + } + } + else if(!rootdescurl) + { + printf("upnpDiscover() error code=%d\n", error); + } + i = 1; + if( (rootdescurl && UPNP_GetIGDFromUrl(rootdescurl, &urls, &data, lanaddr, sizeof(lanaddr))) + || (i = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)))) + { + switch(i) { + case 1: + printf("Found valid IGD : %s\n", urls.controlURL); + break; + case 2: + printf("Found a (not connected?) IGD : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + break; + case 3: + printf("UPnP device found. Is it an IGD ? : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + break; + default: + printf("Found device (igd ?) : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + } + printf("Local LAN ip address : %s\n", lanaddr); + #if 0 + printf("getting \"%s\"\n", urls.ipcondescURL); + descXML = miniwget(urls.ipcondescURL, &descXMLsize); + if(descXML) + { + /*fwrite(descXML, 1, descXMLsize, stdout);*/ + free(descXML); descXML = NULL; + } + #endif + + switch(command) + { + case 'l': + DisplayInfos(&urls, &data); + ListRedirections(&urls, &data); + break; + case 'L': + NewListRedirections(&urls, &data); + break; + case 'a': + if (SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4)?commandargv[4]:"0", + description, 0) < 0) + retcode = 2; + break; + case 'd': + if (RemoveRedirect(&urls, &data, commandargv[0], commandargv[1], + commandargc > 2 ? commandargv[2] : NULL) < 0) + retcode = 2; + break; + case 'n': /* aNy */ + if (SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4)?commandargv[4]:"0", + description, 1) < 0) + retcode = 2; + break; + case 'N': + if (commandargc < 3) + fprintf(stderr, "too few arguments\n"); + + if (RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], + commandargc > 3 ? commandargv[3] : NULL) < 0) + retcode = 2; + break; + case 's': + GetConnectionStatus(&urls, &data); + break; + case 'r': + i = 0; + while(i<commandargc) + { + if(!is_int(commandargv[i])) { + /* 1st parameter not an integer : error */ + fprintf(stderr, "command -r : %s is not an port number\n", commandargv[i]); + retcode = 1; + break; + } else if(is_int(commandargv[i+1])){ + /* 2nd parameter is an integer : <port> <external_port> <protocol> */ + if (SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i+1], commandargv[i+2], "0", + description, 0) < 0) + retcode = 2; + i+=3; /* 3 parameters parsed */ + } else { + /* 2nd parameter not an integer : <port> <protocol> */ + if (SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i], commandargv[i+1], "0", + description, 0) < 0) + retcode = 2; + i+=2; /* 2 parameters parsed */ + } + } + break; + case 'A': + SetPinholeAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + commandargv[4], commandargv[5]); + break; + case 'U': + GetPinholeAndUpdate(&urls, &data, + commandargv[0], commandargv[1]); + break; + case 'C': + for(i=0; i<commandargc; i++) + { + CheckPinhole(&urls, &data, commandargv[i]); + } + break; + case 'K': + for(i=0; i<commandargc; i++) + { + GetPinholePackets(&urls, &data, commandargv[i]); + } + break; + case 'D': + for(i=0; i<commandargc; i++) + { + RemovePinhole(&urls, &data, commandargv[i]); + } + break; + case 'S': + GetFirewallStatus(&urls, &data); + break; + case 'G': + GetPinholeOutboundTimeout(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + commandargv[4]); + break; + case 'P': + printf("Presentation URL found:\n"); + printf(" %s\n", data.presentationurl); + break; + default: + fprintf(stderr, "Unknown switch -%c\n", command); + retcode = 1; + } + + FreeUPNPUrls(&urls); + } + else + { + fprintf(stderr, "No valid UPNP Internet Gateway Device found.\n"); + retcode = 1; + } + freeUPNPDevlist(devlist); devlist = 0; + } + else + { + fprintf(stderr, "No IGD UPnP Device found on the network !\n"); + retcode = 1; + } +#ifdef _WIN32 + nResult = WSACleanup(); + if(nResult != NO_ERROR) { + fprintf(stderr, "WSACleanup() failed.\n"); + } +#endif /* _WIN32 */ + return retcode; +} + diff --git a/thirdparty/miniupnpc/upnpcommands.c b/thirdparty/miniupnpc/upnpcommands.c new file mode 100644 index 0000000000..b6a693a93a --- /dev/null +++ b/thirdparty/miniupnpc/upnpcommands.c @@ -0,0 +1,1241 @@ +/* $Id: upnpcommands.c,v 1.49 2018/03/13 23:34:47 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "upnpcommands.h" +#include "miniupnpc.h" +#include "portlistingparse.h" +#include "upnpreplyparse.h" + +static UNSIGNED_INTEGER +my_atoui(const char * s) +{ + return s ? ((UNSIGNED_INTEGER)STRTOUI(s, NULL, 0)) : 0; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalBytesSent", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalBytesSent"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalBytesReceived", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalBytesReceived"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalPacketsSent", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalPacketsSent"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalPacketsReceived", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalPacketsReceived"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* UPNP_GetStatusInfo() call the corresponding UPNP method + * returns the current status and uptime */ +MINIUPNP_LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + char * up; + char * err; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!status && !uptime) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetStatusInfo", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + up = GetValueFromNameValueList(&pdata, "NewUptime"); + p = GetValueFromNameValueList(&pdata, "NewConnectionStatus"); + err = GetValueFromNameValueList(&pdata, "NewLastConnectionError"); + if(p && up) + ret = UPNPCOMMAND_SUCCESS; + + if(status) { + if(p){ + strncpy(status, p, 64 ); + status[63] = '\0'; + }else + status[0]= '\0'; + } + + if(uptime) { + if(up) + sscanf(up,"%u",uptime); + else + *uptime = 0; + } + + if(lastconnerror) { + if(err) { + strncpy(lastconnerror, err, 64 ); + lastconnerror[63] = '\0'; + } else + lastconnerror[0] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetConnectionTypeInfo() call the corresponding UPNP method + * returns the connection type */ +MINIUPNP_LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!connectionType) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetConnectionTypeInfo", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewConnectionType"); + /*p = GetValueFromNameValueList(&pdata, "NewPossibleConnectionTypes");*/ + /* PossibleConnectionTypes will have several values.... */ + if(p) { + strncpy(connectionType, p, 64 ); + connectionType[63] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + connectionType[0] = '\0'; + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetLinkLayerMaxBitRate() call the corresponding UPNP method. + * Returns 2 values: Downloadlink bandwidth and Uplink bandwidth. + * One of the values can be null + * Note : GetLinkLayerMaxBitRates belongs to WANPPPConnection:1 only + * We can use the GetCommonLinkProperties from WANCommonInterfaceConfig:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char * controlURL, + const char * servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + char * down; + char * up; + char * p; + + if(!bitrateDown && !bitrateUp) + return UPNPCOMMAND_INVALID_ARGS; + + /* shouldn't we use GetCommonLinkProperties ? */ + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetCommonLinkProperties", 0, &bufsize))) { + /*"GetLinkLayerMaxBitRates", 0, &bufsize);*/ + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + /*down = GetValueFromNameValueList(&pdata, "NewDownstreamMaxBitRate");*/ + /*up = GetValueFromNameValueList(&pdata, "NewUpstreamMaxBitRate");*/ + down = GetValueFromNameValueList(&pdata, "NewLayer1DownstreamMaxBitRate"); + up = GetValueFromNameValueList(&pdata, "NewLayer1UpstreamMaxBitRate"); + /*GetValueFromNameValueList(&pdata, "NewWANAccessType");*/ + /*GetValueFromNameValueList(&pdata, "NewPhysicalLinkStatus");*/ + if(down && up) + ret = UPNPCOMMAND_SUCCESS; + + if(bitrateDown) { + if(down) + sscanf(down,"%u",bitrateDown); + else + *bitrateDown = 0; + } + + if(bitrateUp) { + if(up) + sscanf(up,"%u",bitrateUp); + else + *bitrateUp = 0; + } + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + */ +MINIUPNP_LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!extIpAdd || !controlURL || !servicetype) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetExternalIPAddress", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + /*printf("external ip = %s\n", GetValueFromNameValueList(&pdata, "NewExternalIPAddress") );*/ + p = GetValueFromNameValueList(&pdata, "NewExternalIPAddress"); + if(p) { + strncpy(extIpAdd, p, 16 ); + extIpAdd[15] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + extIpAdd[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration) +{ + struct UPNParg * AddPortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!inPort || !inClient || !proto || !extPort) + return UPNPCOMMAND_INVALID_ARGS; + + AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + if(AddPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + AddPortMappingArgs[0].elt = "NewRemoteHost"; + AddPortMappingArgs[0].val = remoteHost; + AddPortMappingArgs[1].elt = "NewExternalPort"; + AddPortMappingArgs[1].val = extPort; + AddPortMappingArgs[2].elt = "NewProtocol"; + AddPortMappingArgs[2].val = proto; + AddPortMappingArgs[3].elt = "NewInternalPort"; + AddPortMappingArgs[3].val = inPort; + AddPortMappingArgs[4].elt = "NewInternalClient"; + AddPortMappingArgs[4].val = inClient; + AddPortMappingArgs[5].elt = "NewEnabled"; + AddPortMappingArgs[5].val = "1"; + AddPortMappingArgs[6].elt = "NewPortMappingDescription"; + AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; + AddPortMappingArgs[7].elt = "NewLeaseDuration"; + AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPortMapping", AddPortMappingArgs, + &bufsize); + free(AddPortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + /*buffer[bufsize] = '\0';*/ + /*puts(buffer);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort) +{ + struct UPNParg * AddPortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!inPort || !inClient || !proto || !extPort) + return UPNPCOMMAND_INVALID_ARGS; + + AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + if(AddPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + AddPortMappingArgs[0].elt = "NewRemoteHost"; + AddPortMappingArgs[0].val = remoteHost; + AddPortMappingArgs[1].elt = "NewExternalPort"; + AddPortMappingArgs[1].val = extPort; + AddPortMappingArgs[2].elt = "NewProtocol"; + AddPortMappingArgs[2].val = proto; + AddPortMappingArgs[3].elt = "NewInternalPort"; + AddPortMappingArgs[3].val = inPort; + AddPortMappingArgs[4].elt = "NewInternalClient"; + AddPortMappingArgs[4].val = inClient; + AddPortMappingArgs[5].elt = "NewEnabled"; + AddPortMappingArgs[5].val = "1"; + AddPortMappingArgs[6].elt = "NewPortMappingDescription"; + AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; + AddPortMappingArgs[7].elt = "NewLeaseDuration"; + AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddAnyPortMapping", AddPortMappingArgs, + &bufsize); + free(AddPortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + char *p; + + p = GetValueFromNameValueList(&pdata, "NewReservedPort"); + if(p) { + strncpy(reservedPort, p, 6); + reservedPort[5] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else { + ret = UPNPCOMMAND_INVALID_RESPONSE; + } + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost) +{ + /*struct NameValueParserData pdata;*/ + struct UPNParg * DeletePortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!extPort || !proto) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePortMappingArgs = calloc(4, sizeof(struct UPNParg)); + if(DeletePortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePortMappingArgs[0].elt = "NewRemoteHost"; + DeletePortMappingArgs[0].val = remoteHost; + DeletePortMappingArgs[1].elt = "NewExternalPort"; + DeletePortMappingArgs[1].val = extPort; + DeletePortMappingArgs[2].elt = "NewProtocol"; + DeletePortMappingArgs[2].val = proto; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMapping", + DeletePortMappingArgs, &bufsize); + free(DeletePortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage) +{ + struct UPNParg * DeletePortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!extPortStart || !extPortEnd || !proto || !manage) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePortMappingArgs = calloc(5, sizeof(struct UPNParg)); + if(DeletePortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePortMappingArgs[0].elt = "NewStartPort"; + DeletePortMappingArgs[0].val = extPortStart; + DeletePortMappingArgs[1].elt = "NewEndPort"; + DeletePortMappingArgs[1].val = extPortEnd; + DeletePortMappingArgs[2].elt = "NewProtocol"; + DeletePortMappingArgs[2].val = proto; + DeletePortMappingArgs[3].elt = "NewManage"; + DeletePortMappingArgs[3].val = manage; + + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMappingRange", + DeletePortMappingArgs, &bufsize); + free(DeletePortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPortMappingArgs; + char * buffer; + int bufsize; + char * p; + int r = UPNPCOMMAND_UNKNOWN_ERROR; + if(!index) + return UPNPCOMMAND_INVALID_ARGS; + intClient[0] = '\0'; + intPort[0] = '\0'; + GetPortMappingArgs = calloc(2, sizeof(struct UPNParg)); + if(GetPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetPortMappingArgs[0].elt = "NewPortMappingIndex"; + GetPortMappingArgs[0].val = index; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetGenericPortMappingEntry", + GetPortMappingArgs, &bufsize); + free(GetPortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewRemoteHost"); + if(p && rHost) + { + strncpy(rHost, p, 64); + rHost[63] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewExternalPort"); + if(p && extPort) + { + strncpy(extPort, p, 6); + extPort[5] = '\0'; + r = UPNPCOMMAND_SUCCESS; + } + p = GetValueFromNameValueList(&pdata, "NewProtocol"); + if(p && protocol) + { + strncpy(protocol, p, 4); + protocol[3] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewInternalClient"); + if(p) + { + strncpy(intClient, p, 16); + intClient[15] = '\0'; + r = 0; + } + p = GetValueFromNameValueList(&pdata, "NewInternalPort"); + if(p) + { + strncpy(intPort, p, 6); + intPort[5] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewEnabled"); + if(p && enabled) + { + strncpy(enabled, p, 4); + enabled[3] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription"); + if(p && desc) + { + strncpy(desc, p, 80); + desc[79] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewLeaseDuration"); + if(p && duration) + { + strncpy(duration, p, 16); + duration[15] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + r = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &r); + } + ClearNameValueList(&pdata); + return r; +} + +MINIUPNP_LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char * controlURL, + const char * servicetype, + unsigned int * numEntries) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char* p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetPortMappingNumberOfEntries", 0, + &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } +#ifdef DEBUG + DisplayNameValueList(buffer, bufsize); +#endif + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewPortMappingNumberOfEntries"); + if(numEntries && p) { + *numEntries = 0; + sscanf(p, "%u", numEntries); + ret = UPNPCOMMAND_SUCCESS; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetSpecificPortMappingEntry retrieves an existing port mapping + * the result is returned in the intClient and intPort strings + * please provide 16 and 6 bytes of data */ +MINIUPNP_LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + const char * remoteHost, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPortMappingArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!intPort || !intClient || !extPort || !proto) + return UPNPCOMMAND_INVALID_ARGS; + + GetPortMappingArgs = calloc(4, sizeof(struct UPNParg)); + if(GetPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetPortMappingArgs[0].elt = "NewRemoteHost"; + GetPortMappingArgs[0].val = remoteHost; + GetPortMappingArgs[1].elt = "NewExternalPort"; + GetPortMappingArgs[1].val = extPort; + GetPortMappingArgs[2].elt = "NewProtocol"; + GetPortMappingArgs[2].val = proto; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetSpecificPortMappingEntry", + GetPortMappingArgs, &bufsize); + free(GetPortMappingArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewInternalClient"); + if(p) { + strncpy(intClient, p, 16); + intClient[15] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + intClient[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "NewInternalPort"); + if(p) { + strncpy(intPort, p, 6); + intPort[5] = '\0'; + } else + intPort[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "NewEnabled"); + if(p && enabled) { + strncpy(enabled, p, 4); + enabled[3] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription"); + if(p && desc) { + strncpy(desc, p, 80); + desc[79] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "NewLeaseDuration"); + if(p && leaseDuration) + { + strncpy(leaseDuration, p, 16); + leaseDuration[15] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetListOfPortMappings() + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +MINIUPNP_LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data) +{ + struct NameValueParserData pdata; + struct UPNParg * GetListOfPortMappingsArgs; + const char * p; + char * buffer; + int bufsize; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!startPort || !endPort || !protocol) + return UPNPCOMMAND_INVALID_ARGS; + + GetListOfPortMappingsArgs = calloc(6, sizeof(struct UPNParg)); + if(GetListOfPortMappingsArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetListOfPortMappingsArgs[0].elt = "NewStartPort"; + GetListOfPortMappingsArgs[0].val = startPort; + GetListOfPortMappingsArgs[1].elt = "NewEndPort"; + GetListOfPortMappingsArgs[1].val = endPort; + GetListOfPortMappingsArgs[2].elt = "NewProtocol"; + GetListOfPortMappingsArgs[2].val = protocol; + GetListOfPortMappingsArgs[3].elt = "NewManage"; + GetListOfPortMappingsArgs[3].val = "1"; + GetListOfPortMappingsArgs[4].elt = "NewNumberOfPorts"; + GetListOfPortMappingsArgs[4].val = numberOfPorts?numberOfPorts:"1000"; + + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetListOfPortMappings", + GetListOfPortMappingsArgs, &bufsize); + free(GetListOfPortMappingsArgs); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + /*p = GetValueFromNameValueList(&pdata, "NewPortListing");*/ + /*if(p) { + printf("NewPortListing : %s\n", p); + }*/ + /*printf("NewPortListing(%d chars) : %s\n", + pdata.portListingLength, pdata.portListing);*/ + if(pdata.portListing) + { + /*struct PortMapping * pm; + int i = 0;*/ + ParsePortListing(pdata.portListing, pdata.portListingLength, + data); + ret = UPNPCOMMAND_SUCCESS; + /* + for(pm = data->head.lh_first; pm != NULL; pm = pm->entries.le_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s'\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost); + i++; + } + */ + /*FreePortListing(&data);*/ + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + + /*printf("%.*s", bufsize, buffer);*/ + + return ret; +} + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * fe, *ipa, *p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!firewallEnabled || !inboundPinholeAllowed) + return UPNPCOMMAND_INVALID_ARGS; + + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetFirewallStatus", 0, &bufsize); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + fe = GetValueFromNameValueList(&pdata, "FirewallEnabled"); + ipa = GetValueFromNameValueList(&pdata, "InboundPinholeAllowed"); + if(ipa && fe) + ret = UPNPCOMMAND_SUCCESS; + if(fe) + *firewallEnabled = my_atoui(fe); + /*else + *firewallEnabled = 0;*/ + if(ipa) + *inboundPinholeAllowed = my_atoui(ipa); + /*else + *inboundPinholeAllowed = 0;*/ + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout) +{ + struct UPNParg * GetOutboundPinholeTimeoutArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + char * p; + int ret; + + if(!intPort || !intClient || !proto || !remotePort || !remoteHost) + return UPNPCOMMAND_INVALID_ARGS; + + GetOutboundPinholeTimeoutArgs = calloc(6, sizeof(struct UPNParg)); + if(GetOutboundPinholeTimeoutArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetOutboundPinholeTimeoutArgs[0].elt = "RemoteHost"; + GetOutboundPinholeTimeoutArgs[0].val = remoteHost; + GetOutboundPinholeTimeoutArgs[1].elt = "RemotePort"; + GetOutboundPinholeTimeoutArgs[1].val = remotePort; + GetOutboundPinholeTimeoutArgs[2].elt = "Protocol"; + GetOutboundPinholeTimeoutArgs[2].val = proto; + GetOutboundPinholeTimeoutArgs[3].elt = "InternalPort"; + GetOutboundPinholeTimeoutArgs[3].val = intPort; + GetOutboundPinholeTimeoutArgs[4].elt = "InternalClient"; + GetOutboundPinholeTimeoutArgs[4].val = intClient; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs, &bufsize); + free(GetOutboundPinholeTimeoutArgs); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + p = GetValueFromNameValueList(&pdata, "OutboundPinholeTimeout"); + if(p) + *opTimeout = my_atoui(p); + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID) +{ + struct UPNParg * AddPinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + char * p; + int ret; + + if(!intPort || !intClient || !proto || !remoteHost || !remotePort || !leaseTime) + return UPNPCOMMAND_INVALID_ARGS; + + AddPinholeArgs = calloc(7, sizeof(struct UPNParg)); + if(AddPinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + /* RemoteHost can be wilcarded */ + if(strncmp(remoteHost, "empty", 5)==0) + { + AddPinholeArgs[0].elt = "RemoteHost"; + AddPinholeArgs[0].val = ""; + } + else + { + AddPinholeArgs[0].elt = "RemoteHost"; + AddPinholeArgs[0].val = remoteHost; + } + AddPinholeArgs[1].elt = "RemotePort"; + AddPinholeArgs[1].val = remotePort; + AddPinholeArgs[2].elt = "Protocol"; + AddPinholeArgs[2].val = proto; + AddPinholeArgs[3].elt = "InternalPort"; + AddPinholeArgs[3].val = intPort; + if(strncmp(intClient, "empty", 5)==0) + { + AddPinholeArgs[4].elt = "InternalClient"; + AddPinholeArgs[4].val = ""; + } + else + { + AddPinholeArgs[4].elt = "InternalClient"; + AddPinholeArgs[4].val = intClient; + } + AddPinholeArgs[5].elt = "LeaseTime"; + AddPinholeArgs[5].val = leaseTime; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPinhole", AddPinholeArgs, &bufsize); + free(AddPinholeArgs); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "UniqueID"); + if(p) + { + strncpy(uniqueID, p, 8); + uniqueID[7] = '\0'; + } + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + /*printf("AddPortMapping errorCode = '%s'\n", resVal);*/ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime) +{ + struct UPNParg * UpdatePinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!uniqueID || !leaseTime) + return UPNPCOMMAND_INVALID_ARGS; + + UpdatePinholeArgs = calloc(3, sizeof(struct UPNParg)); + if(UpdatePinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + UpdatePinholeArgs[0].elt = "UniqueID"; + UpdatePinholeArgs[0].val = uniqueID; + UpdatePinholeArgs[1].elt = "NewLeaseTime"; + UpdatePinholeArgs[1].val = leaseTime; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "UpdatePinhole", UpdatePinholeArgs, &bufsize); + free(UpdatePinholeArgs); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID) +{ + /*struct NameValueParserData pdata;*/ + struct UPNParg * DeletePinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePinholeArgs = calloc(2, sizeof(struct UPNParg)); + if(DeletePinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePinholeArgs[0].elt = "UniqueID"; + DeletePinholeArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePinhole", DeletePinholeArgs, &bufsize); + free(DeletePinholeArgs); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking) +{ + struct NameValueParserData pdata; + struct UPNParg * CheckPinholeWorkingArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + CheckPinholeWorkingArgs = calloc(4, sizeof(struct UPNParg)); + if(CheckPinholeWorkingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + CheckPinholeWorkingArgs[0].elt = "UniqueID"; + CheckPinholeWorkingArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "CheckPinholeWorking", CheckPinholeWorkingArgs, &bufsize); + free(CheckPinholeWorkingArgs); + if(!buffer) + { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "IsWorking"); + if(p) + { + *isWorking=my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; + } + else + *isWorking = 0; + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPinholePacketsArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + GetPinholePacketsArgs = calloc(4, sizeof(struct UPNParg)); + if(GetPinholePacketsArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetPinholePacketsArgs[0].elt = "UniqueID"; + GetPinholePacketsArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetPinholePackets", GetPinholePacketsArgs, &bufsize); + free(GetPinholePacketsArgs); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "PinholePackets"); + if(p) + { + *packets=my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + + diff --git a/thirdparty/miniupnpc/upnpcommands.h b/thirdparty/miniupnpc/upnpcommands.h new file mode 100644 index 0000000000..0c6d501666 --- /dev/null +++ b/thirdparty/miniupnpc/upnpcommands.h @@ -0,0 +1,348 @@ +/* $Id: upnpcommands.h,v 1.32 2018/03/13 23:34:47 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2018 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef UPNPCOMMANDS_H_INCLUDED +#define UPNPCOMMANDS_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "miniupnpctypes.h" + +/* MiniUPnPc return codes : */ +#define UPNPCOMMAND_SUCCESS (0) +#define UPNPCOMMAND_UNKNOWN_ERROR (-1) +#define UPNPCOMMAND_INVALID_ARGS (-2) +#define UPNPCOMMAND_HTTP_ERROR (-3) +#define UPNPCOMMAND_INVALID_RESPONSE (-4) +#define UPNPCOMMAND_MEM_ALLOC_ERROR (-5) + +#ifdef __cplusplus +extern "C" { +#endif + +struct PortMappingParserData; + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype); + +/* UPNP_GetStatusInfo() + * status and lastconnerror are 64 byte buffers + * Return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +MINIUPNP_LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror); + +/* UPNP_GetConnectionTypeInfo() + * argument connectionType is a 64 character buffer + * Return Values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +MINIUPNP_LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType); + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * possible UPnP Errors : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. */ +MINIUPNP_LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd); + +/* UPNP_GetLinkLayerMaxBitRates() + * call WANCommonInterfaceConfig:1#GetCommonLinkProperties + * + * return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. */ +MINIUPNP_LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char* controlURL, + const char* servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp); + +/* UPNP_AddPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 718 ConflictInMappingEntry - The port mapping entry specified conflicts + * with a mapping assigned previously to another client + * 724 SamePortValuesRequired - Internal and External port values + * must be the same + * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports + * permanent lease times on port mappings + * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard + * and cannot be a specific IP address or DNS name + * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and + * cannot be a specific port value + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration); + +/* UPNP_AddAnyPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort); + +/* UPNP_DeletePortMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost); + +/* UPNP_DeletePortRangeMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 730 PortMappingNotFound - This error message is returned if no port + * mapping is found in the specified range. + * 733 InconsistentParameters - NewStartPort and NewEndPort values are not consistent. */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage); + +/* UPNP_GetPortMappingNumberOfEntries() + * not supported by all routers */ +MINIUPNP_LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char* controlURL, + const char* servicetype, + unsigned int * num); + +/* UPNP_GetSpecificPortMappingEntry() + * retrieves an existing port mapping + * params : + * in extPort + * in proto + * in remoteHost + * out intClient (16 bytes) + * out intPort (6 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out leaseDuration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * List of possible UPnP errors for _GetSpecificPortMappingEntry : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array. + */ +MINIUPNP_LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + const char * remoteHost, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration); + +/* UPNP_GetGenericPortMappingEntry() + * params : + * in index + * out extPort (6 bytes) + * out intClient (16 bytes) + * out intPort (6 bytes) + * out protocol (4 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out rHost (64 bytes) + * out duration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * Possible UPNP Error codes : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds + */ +MINIUPNP_LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration); + +/* UPNP_GetListOfPortMappings() Available in IGD v2 + * + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +MINIUPNP_LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data); + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed); + +MINIUPNP_LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout); + +MINIUPNP_LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID); + +MINIUPNP_LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime); + +MINIUPNP_LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID); + +MINIUPNP_LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking); + +MINIUPNP_LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/thirdparty/miniupnpc/upnpdev.c b/thirdparty/miniupnpc/upnpdev.c new file mode 100644 index 0000000000..d89a9934c3 --- /dev/null +++ b/thirdparty/miniupnpc/upnpdev.c @@ -0,0 +1,23 @@ +/* $Id: upnpdev.c,v 1.1 2015/08/28 12:14:19 nanard Exp $ */ +/* Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include <stdlib.h> +#include "upnpdev.h" + +/* freeUPNPDevlist() should be used to + * free the chained list returned by upnpDiscover() */ +void freeUPNPDevlist(struct UPNPDev * devlist) +{ + struct UPNPDev * next; + while(devlist) + { + next = devlist->pNext; + free(devlist); + devlist = next; + } +} + diff --git a/thirdparty/miniupnpc/upnpdev.h b/thirdparty/miniupnpc/upnpdev.h new file mode 100644 index 0000000000..f4ae174426 --- /dev/null +++ b/thirdparty/miniupnpc/upnpdev.h @@ -0,0 +1,36 @@ +/* $Id: upnpdev.h,v 1.1 2015/08/28 12:14:19 nanard Exp $ */ +/* Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2018 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#ifndef UPNPDEV_H_INCLUDED +#define UPNPDEV_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct UPNPDev { + struct UPNPDev * pNext; + char * descURL; + char * st; + char * usn; + unsigned int scope_id; + char buffer[3]; +}; + +/* freeUPNPDevlist() + * free list returned by upnpDiscover() */ +MINIUPNP_LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist); + + +#ifdef __cplusplus +} +#endif + + +#endif /* UPNPDEV_H_INCLUDED */ diff --git a/thirdparty/miniupnpc/upnperrors.c b/thirdparty/miniupnpc/upnperrors.c new file mode 100644 index 0000000000..40a2e7857f --- /dev/null +++ b/thirdparty/miniupnpc/upnperrors.c @@ -0,0 +1,107 @@ +/* $Id: upnperrors.c,v 1.5 2011/04/10 11:19:36 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2007 Thomas Bernard + * All Right reserved. + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#include <string.h> +#include "upnperrors.h" +#include "upnpcommands.h" +#include "miniupnpc.h" + +const char * strupnperror(int err) +{ + const char * s = NULL; + switch(err) { + case UPNPCOMMAND_SUCCESS: + s = "Success"; + break; + case UPNPCOMMAND_UNKNOWN_ERROR: + s = "Miniupnpc Unknown Error"; + break; + case UPNPCOMMAND_INVALID_ARGS: + s = "Miniupnpc Invalid Arguments"; + break; + case UPNPCOMMAND_INVALID_RESPONSE: + s = "Miniupnpc Invalid response"; + break; + case UPNPDISCOVER_SOCKET_ERROR: + s = "Miniupnpc Socket error"; + break; + case UPNPDISCOVER_MEMORY_ERROR: + s = "Miniupnpc Memory allocation error"; + break; + case 401: + s = "Invalid Action"; + break; + case 402: + s = "Invalid Args"; + break; + case 501: + s = "Action Failed"; + break; + case 606: + s = "Action not authorized"; + break; + case 701: + s = "PinholeSpaceExhausted"; + break; + case 702: + s = "FirewallDisabled"; + break; + case 703: + s = "InboundPinholeNotAllowed"; + break; + case 704: + s = "NoSuchEntry"; + break; + case 705: + s = "ProtocolNotSupported"; + break; + case 706: + s = "InternalPortWildcardingNotAllowed"; + break; + case 707: + s = "ProtocolWildcardingNotAllowed"; + break; + case 708: + s = "WildcardNotPermittedInSrcIP"; + break; + case 709: + s = "NoPacketSent"; + break; + case 713: + s = "SpecifiedArrayIndexInvalid"; + break; + case 714: + s = "NoSuchEntryInArray"; + break; + case 715: + s = "WildCardNotPermittedInSrcIP"; + break; + case 716: + s = "WildCardNotPermittedInExtPort"; + break; + case 718: + s = "ConflictInMappingEntry"; + break; + case 724: + s = "SamePortValuesRequired"; + break; + case 725: + s = "OnlyPermanentLeasesSupported"; + break; + case 726: + s = "RemoteHostOnlySupportsWildcard"; + break; + case 727: + s = "ExternalPortOnlySupportsWildcard"; + break; + default: + s = "UnknownError"; + break; + } + return s; +} diff --git a/thirdparty/miniupnpc/upnperrors.h b/thirdparty/miniupnpc/upnperrors.h new file mode 100644 index 0000000000..8499d9a1c9 --- /dev/null +++ b/thirdparty/miniupnpc/upnperrors.h @@ -0,0 +1,26 @@ +/* $Id: upnperrors.h,v 1.2 2008/07/02 23:31:15 nanard Exp $ */ +/* (c) 2007-2015 Thomas Bernard + * All rights reserved. + * MiniUPnP Project. + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef UPNPERRORS_H_INCLUDED +#define UPNPERRORS_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* strupnperror() + * Return a string description of the UPnP error code + * or NULL for undefinded errors */ +MINIUPNP_LIBSPEC const char * strupnperror(int err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/miniupnpc/upnpreplyparse.c b/thirdparty/miniupnpc/upnpreplyparse.c new file mode 100644 index 0000000000..68a47c0278 --- /dev/null +++ b/thirdparty/miniupnpc/upnpreplyparse.c @@ -0,0 +1,196 @@ +/* $Id: upnpreplyparse.c,v 1.19 2015/07/15 10:29:11 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2017 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "upnpreplyparse.h" +#include "minixml.h" + +static void +NameValueParserStartElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + data->topelt = 1; + if(l>63) + l = 63; + memcpy(data->curelt, name, l); + data->curelt[l] = '\0'; + data->cdata = NULL; + data->cdatalen = 0; +} + +static void +NameValueParserEndElt(void * d, const char * name, int namelen) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValue * nv; + (void)name; + (void)namelen; + if(!data->topelt) + return; + if(strcmp(data->curelt, "NewPortListing") != 0) + { + int l; + /* standard case. Limited to n chars strings */ + l = data->cdatalen; + nv = malloc(sizeof(struct NameValue)); + if(nv == NULL) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "NameValueParserEndElt"); +#endif /* DEBUG */ + return; + } + if(l>=(int)sizeof(nv->value)) + l = sizeof(nv->value) - 1; + strncpy(nv->name, data->curelt, 64); + nv->name[63] = '\0'; + if(data->cdata != NULL) + { + memcpy(nv->value, data->cdata, l); + nv->value[l] = '\0'; + } + else + { + nv->value[0] = '\0'; + } + nv->l_next = data->l_head; /* insert in list */ + data->l_head = nv; + } + data->cdata = NULL; + data->cdatalen = 0; + data->topelt = 0; +} + +static void +NameValueParserGetData(void * d, const char * datas, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + if(strcmp(data->curelt, "NewPortListing") == 0) + { + /* specific case for NewPortListing which is a XML Document */ + data->portListing = malloc(l + 1); + if(!data->portListing) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "NameValueParserGetData"); +#endif /* DEBUG */ + return; + } + memcpy(data->portListing, datas, l); + data->portListing[l] = '\0'; + data->portListingLength = l; + } + else + { + /* standard case. */ + data->cdata = datas; + data->cdatalen = l; + } +} + +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data) +{ + struct xmlparser parser; + memset(data, 0, sizeof(struct NameValueParserData)); + /* init xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = NameValueParserStartElt; + parser.endeltfunc = NameValueParserEndElt; + parser.datafunc = NameValueParserGetData; + parser.attfunc = 0; + parsexml(&parser); +} + +void +ClearNameValueList(struct NameValueParserData * pdata) +{ + struct NameValue * nv; + if(pdata->portListing) + { + free(pdata->portListing); + pdata->portListing = NULL; + pdata->portListingLength = 0; + } + while((nv = pdata->l_head) != NULL) + { + pdata->l_head = nv->l_next; + free(nv); + } +} + +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + for(nv = pdata->l_head; + (nv != NULL) && (p == NULL); + nv = nv->l_next) + { + if(strcmp(nv->name, Name) == 0) + p = nv->value; + } + return p; +} + +#if 0 +/* useless now that minixml ignores namespaces by itself */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + char * pname; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + pname = strrchr(nv->name, ':'); + if(pname) + pname++; + else + pname = nv->name; + if(strcmp(pname, Name)==0) + p = nv->value; + } + return p; +} +#endif + +/* debug all-in-one function + * do parsing then display to stdout */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize) +{ + struct NameValueParserData pdata; + struct NameValue * nv; + ParseNameValue(buffer, bufsize, &pdata); + for(nv = pdata.l_head; + nv != NULL; + nv = nv->l_next) + { + printf("%s = %s\n", nv->name, nv->value); + } + ClearNameValueList(&pdata); +} +#endif /* DEBUG */ + diff --git a/thirdparty/miniupnpc/upnpreplyparse.h b/thirdparty/miniupnpc/upnpreplyparse.h new file mode 100644 index 0000000000..6badd15b26 --- /dev/null +++ b/thirdparty/miniupnpc/upnpreplyparse.h @@ -0,0 +1,63 @@ +/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2013 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef UPNPREPLYPARSE_H_INCLUDED +#define UPNPREPLYPARSE_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +struct NameValue { + struct NameValue * l_next; + char name[64]; + char value[128]; +}; + +struct NameValueParserData { + struct NameValue * l_head; + char curelt[64]; + char * portListing; + int portListingLength; + int topelt; + const char * cdata; + int cdatalen; +}; + +/* ParseNameValue() */ +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data); + +/* ClearNameValueList() */ +void +ClearNameValueList(struct NameValueParserData * pdata); + +/* GetValueFromNameValueList() */ +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name); + +#if 0 +/* GetValueFromNameValueListIgnoreNS() */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name); +#endif + +/* DisplayNameValueList() */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + |