diff --git a/.clang-format b/.clang-format index 5bf2f83a..ddc90941 100644 --- a/.clang-format +++ b/.clang-format @@ -1,2 +1,2 @@ -BasedOnStyle: LLVM +BasedOnStyle: LLVM IndentWidth: 4 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cff3de0..0559088a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,64 +1,64 @@ -name: CI - -on: [push, pull_request] - -jobs: - build: - strategy: - matrix: - os: - - ubuntu-24.04 - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - with: - submodules: 'true' - - name: Check translations - if: ${{ matrix.os == 'ubuntu-24.04' }} - run: | - for f in po/*.po; do grep -LE '^msgid "translator-credits"' $f ; done - - name: Install Ubuntu Build Dependencies - if: ${{ matrix.os == 'ubuntu-24.04' }} - run: | - sudo apt update - sudo apt install gettext libwxgtk3.2-dev libgtk-3-dev libgcrypt20-dev liblzo2-dev - - name: Install MacOS Build Dependencies - if: ${{ matrix.os == 'macos-latest' }} - run: | - brew uninstall --ignore-dependencies gnutls libgcrypt - brew install wxwidgets - - name: Install Windows Build Dependencies - if: ${{ matrix.os == 'windows-latest' }} - uses: johnwason/vcpkg-action@v6 - id: vcpkg - with: - pkgs: wxwidgets gettext[tools] - triplet: x64-windows - token: ${{ github.token }} - - name: Build - run: | - mkdir build - cd build - cmake ${{ steps.vcpkg.outputs.vcpkg-cmake-config }} .. - cmake --build . - cpack -V -C Debug - - name: Archive Ubuntu Build Artifacts - if: ${{ matrix.os == 'ubuntu-24.04' }} - uses: actions/upload-artifact@v3 - with: - name: MultiVNC Debian Package - path: build/*.deb - - name: Archive MacOS Build Artifacts - if: ${{ matrix.os == 'macos-latest' }} - uses: actions/upload-artifact@v3 - with: - name: MultiVNC MacOS Package - path: build/*.dmg - - name: Archive Windows Build Artifacts - if: ${{ matrix.os == 'windows-latest' }} - uses: actions/upload-artifact@v3 - with: - name: MultiVNC Windows Package - path: build/*.exe +name: CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + os: + - ubuntu-24.04 + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + with: + submodules: 'true' + - name: Check translations + if: ${{ matrix.os == 'ubuntu-24.04' }} + run: | + for f in po/*.po; do grep -LE '^msgid "translator-credits"' $f ; done + - name: Install Ubuntu Build Dependencies + if: ${{ matrix.os == 'ubuntu-24.04' }} + run: | + sudo apt update + sudo apt install gettext libwxgtk3.2-dev libgtk-3-dev libgcrypt20-dev liblzo2-dev + - name: Install MacOS Build Dependencies + if: ${{ matrix.os == 'macos-latest' }} + run: | + brew uninstall --ignore-dependencies gnutls libgcrypt + brew install wxwidgets + - name: Install Windows Build Dependencies + if: ${{ matrix.os == 'windows-latest' }} + uses: johnwason/vcpkg-action@v6 + id: vcpkg + with: + pkgs: wxwidgets gettext[tools] + triplet: x64-windows + token: ${{ github.token }} + - name: Build + run: | + mkdir build + cd build + cmake ${{ steps.vcpkg.outputs.vcpkg-cmake-config }} .. + cmake --build . + cpack -V -C Debug + - name: Archive Ubuntu Build Artifacts + if: ${{ matrix.os == 'ubuntu-24.04' }} + uses: actions/upload-artifact@v3 + with: + name: MultiVNC Debian Package + path: build/*.deb + - name: Archive MacOS Build Artifacts + if: ${{ matrix.os == 'macos-latest' }} + uses: actions/upload-artifact@v3 + with: + name: MultiVNC MacOS Package + path: build/*.dmg + - name: Archive Windows Build Artifacts + if: ${{ matrix.os == 'windows-latest' }} + uses: actions/upload-artifact@v3 + with: + name: MultiVNC Windows Package + path: build/*.exe diff --git a/.gitignore b/.gitignore index b331b5e2..57206505 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,45 @@ -# Backup files -*~ - -# Generated header files -config.h - -# Compiled Object files -*.o -*.obj - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.a -*.lib - -# Executables -*.exe -*.app - -# CMake files -CMakeCache.txt -CMakeFiles -CMakeScripts -Makefile -*.cmake -compile_commands.json -build/* - -# Flatpak artifacts -.flatpak-builder -flatpak/build-dir/ - -# MacOS artifacts -macos/build-dir/ - -# ccls cache -.ccls-cache/ -.cache/ - -# OS files -.DS_Store +# Backup files +*~ + +# Generated header files +config.h + +# Compiled Object files +*.o +*.obj + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.a +*.lib + +# Executables +*.exe +*.app + +# CMake files +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +*.cmake +compile_commands.json +build/* + +# Flatpak artifacts +.flatpak-builder +flatpak/build-dir/ + +# MacOS artifacts +macos/build-dir/ + +# ccls cache +.ccls-cache/ +.cache/ + +# OS files +.DS_Store diff --git a/.gitmodules b/.gitmodules index d84c42bd..cf20df77 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,18 +1,18 @@ -[submodule "src/wxServDisc"] - path = libwxservdisc - url = https://github.com/bk138/wxservdisc.git -[submodule "libvncserver"] - path = libvncserver - url = https://github.com/LibVNC/libvncserver.git -[submodule "libjpeg-turbo"] - path = libjpeg-turbo - url = https://github.com/libjpeg-turbo/libjpeg-turbo.git -[submodule "libressl"] - path = libressl - url = https://github.com/libressl-portable/portable.git -[submodule "libssh2"] - path = libssh2 - url = https://github.com/libssh2/libssh2.git -[submodule "libsshtunnel"] - path = libsshtunnel - url = https://github.com/bk138/libsshtunnel.git +[submodule "src/wxServDisc"] + path = libwxservdisc + url = https://github.com/bk138/wxservdisc.git +[submodule "libvncserver"] + path = libvncserver + url = https://github.com/LibVNC/libvncserver.git +[submodule "libjpeg-turbo"] + path = libjpeg-turbo + url = https://github.com/libjpeg-turbo/libjpeg-turbo.git +[submodule "libressl"] + path = libressl + url = https://github.com/libressl-portable/portable.git +[submodule "libssh2"] + path = libssh2 + url = https://github.com/libssh2/libssh2.git +[submodule "libsshtunnel"] + path = libsshtunnel + url = https://github.com/bk138/libsshtunnel.git diff --git a/AUTHORS b/AUTHORS index 772c5996..e3c18f39 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,3 @@ -Christian Beier -Evgeny Zinoviev -Audrey Dutcher +Christian Beier +Evgeny Zinoviev +Audrey Dutcher diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a056a1b..ccbe01a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,125 +1,125 @@ -# MultiVNC 0.9.0 - "Hello New Year" - 2024-12-29 -* Improved bookmark editing/deleting by adding a context menu. -* Added UTF-8 cuttext handling. -* Improved log window by making it wider. -* Fixed a bug where MultiVNC on Apple Silicon would not be able to connect - to TLS-enabled servers. - -# MultiVNC 0.8.0 - "Hello New World" - 2024-12-06 -* Added a scale-to-fit view mode which is now the default. - Previous 1-to-1 view mode can still be toggled. -* Improved fullscreen mode to show less controls and more of the remote view. -* Improved toolbar icons to use scalable resources that look good for any - display resolution as well as light and dark display mode. -* Added MacOS packaging. - -# MultiVNC 0.7.0 - "A step in between" - 2023-11-05 -* Added Swedish translation thanks to Åke Engelbrektson. -* Added more tooltips to more UI elements. -* Added keyboard shortcut for making a new connection. -* Added secret store use for credentials without user name. -* Fixed error dialog sometimes being not shown. -* Fixed drawing on MacOS and Wayland. The flatpak now uses Wayland. -* Fixed hang when connecting to unreachable servers. - -# MultiVNC 0.6.0 - "Back from the dead" - 2023-05-17 -* Added record/replay of user input. You can now record a - VNC session and replay this 'macro' later. -* Added support for entering credentials on login and saving them - in bookmarks. -* Added translations into German and Spanish. -* Added flatpak packaging. -* Added easy emailing of bug reports. -* Added optional OpenSSL support (instead of GnuTLS). -* Added ability to listen on IPv6 addresses. -* Added and fixed Linux, MacOS and Windows continous integration. -* Fixed the portable edition for Windows: it is now truly - portable as it saves its preferences into a file under Windows - now, and is not using the registry. - -# MultiVNC 0.5 - "Bag o' stuff" - 2011-05-07 -* MulticastVNC now supports the Ultra encoding and features - a freely sizeable receive buffer in addition to the - OS-dependent socket receive buffer. -* The new MulticastVNC flow control adapts the server's - transmission rate to the capabilities of the network - and clients. -* MultiVNC is now able to connect to Apple Remote Desktop - servers. -* It is now possible to select the desired VNC encoding. -* Encrypted connections now work on Windows as well. -* Added a keyboard grab button that allows to enter arbitrary - key combinations like Ctrl-Alt-Del into MultiVNC without - being interpreted by the local OS. Works on UNIX by now. -* This time really fixed the bug with the viewer becoming - unresponsive under high multicast loads. -* Fixed not being able to enter IPv6 addresses. - -# MultiVNC 0.4.1 - "Fix me up" - 2010-11-09 -* Changed default MulticastVNC receive buffer size to 5120 KB - to play it safe. -* Shared windows are now view-only on Linux clients. Having - the local cursor warped here and there is quite distracting. -* Window sharing on Windows has no more trayicon and - distracting setup dialog. -* Window sharing should be more stable now. -* Link latency measuring is way more exact now. -* Fixed crash on startup when toolbar was disabled. -* Fixed Windows build. MultiVNC 0.4 was unusable. - -# MultiVNC 0.4 - "One for the road" - 2010-10-27 -* MultiVNC can now connect to IPv6 servers! -* MulticastVNC now incorporates a NACK mechanism which makes - multicasting a lot more reliable: Lost packets are - retransmitted if the server supports it. -* MultiVNC now supports QoS: data sent to a VNC server can be - marked for expedited forwarding! -* Fixed bug where changing edge connector settings would - crash MultiVNC. - -# MultiVNC 0.3 - "Collab me!" - 2010-07-05 -* Implemented window sharing: You can now beam one of your - windows to the remote side. Works on UNIX and Windows. -* Implemented Edge Connector: You can now tell MultiVNC that - one of your screen edges should be the border to the remote - display. When you move the pointer over this edge, MultiVNC - will take over your mouse and keyboard and send your input - to the remote side. When the pointer is moved back towards - the opposite edge on the remote screen, you're back on your - local desktop. -* Implemented FastRequest feature: You can now set an interval - at which MultiVNC continously asks the server for updates - instead of just asking after each received server message. - This should improve framerate on high latency links. -* Fixed a bug where a fast VNC server would make MultiVNC - GUI unresponsive on a slow client. -* Fixed bug where saving log or screenshot would terminate - the connection or even crash on some machines. -* Fixed bug where setting up connection would take very long - due to unnecessary DNS lookups. - - -# MultiVNC 0.2 - "The real thing" - 2010-02-15 -* MulticastVNC incorporated! You can now connect to a - MulticastVNC enabled server, e.g. based on libvncserver. -* Listen mode (Reverse VNC) implemented. Via tabs it is - possible to listen for and serve multiple incoming - connections. -* Bookmarks are working now. -* Clipboard data is now properly encoded. -* Fix bug with canvas centering. -* Fix bug where disconnecting from a fast server would - terminate MultiVNC. -* Fix really nasty bug where listening on windows would - take 100% CPU. - - -# MultiVNC 0.1 - "In the beginning" - 2009-10-12 -* a usable tight-enabled vncviewer with the following special - features: -* supports server framebuffer resize -* several connections with one viewer using tabs -* simple, loggable statistics -* discovery of VNC servers advertising themselves via ZeroConf -* runs under UNIX and Windows - +# MultiVNC 0.9.0 - "Hello New Year" - 2024-12-29 +* Improved bookmark editing/deleting by adding a context menu. +* Added UTF-8 cuttext handling. +* Improved log window by making it wider. +* Fixed a bug where MultiVNC on Apple Silicon would not be able to connect + to TLS-enabled servers. + +# MultiVNC 0.8.0 - "Hello New World" - 2024-12-06 +* Added a scale-to-fit view mode which is now the default. + Previous 1-to-1 view mode can still be toggled. +* Improved fullscreen mode to show less controls and more of the remote view. +* Improved toolbar icons to use scalable resources that look good for any + display resolution as well as light and dark display mode. +* Added MacOS packaging. + +# MultiVNC 0.7.0 - "A step in between" - 2023-11-05 +* Added Swedish translation thanks to Åke Engelbrektson. +* Added more tooltips to more UI elements. +* Added keyboard shortcut for making a new connection. +* Added secret store use for credentials without user name. +* Fixed error dialog sometimes being not shown. +* Fixed drawing on MacOS and Wayland. The flatpak now uses Wayland. +* Fixed hang when connecting to unreachable servers. + +# MultiVNC 0.6.0 - "Back from the dead" - 2023-05-17 +* Added record/replay of user input. You can now record a + VNC session and replay this 'macro' later. +* Added support for entering credentials on login and saving them + in bookmarks. +* Added translations into German and Spanish. +* Added flatpak packaging. +* Added easy emailing of bug reports. +* Added optional OpenSSL support (instead of GnuTLS). +* Added ability to listen on IPv6 addresses. +* Added and fixed Linux, MacOS and Windows continous integration. +* Fixed the portable edition for Windows: it is now truly + portable as it saves its preferences into a file under Windows + now, and is not using the registry. + +# MultiVNC 0.5 - "Bag o' stuff" - 2011-05-07 +* MulticastVNC now supports the Ultra encoding and features + a freely sizeable receive buffer in addition to the + OS-dependent socket receive buffer. +* The new MulticastVNC flow control adapts the server's + transmission rate to the capabilities of the network + and clients. +* MultiVNC is now able to connect to Apple Remote Desktop + servers. +* It is now possible to select the desired VNC encoding. +* Encrypted connections now work on Windows as well. +* Added a keyboard grab button that allows to enter arbitrary + key combinations like Ctrl-Alt-Del into MultiVNC without + being interpreted by the local OS. Works on UNIX by now. +* This time really fixed the bug with the viewer becoming + unresponsive under high multicast loads. +* Fixed not being able to enter IPv6 addresses. + +# MultiVNC 0.4.1 - "Fix me up" - 2010-11-09 +* Changed default MulticastVNC receive buffer size to 5120 KB + to play it safe. +* Shared windows are now view-only on Linux clients. Having + the local cursor warped here and there is quite distracting. +* Window sharing on Windows has no more trayicon and + distracting setup dialog. +* Window sharing should be more stable now. +* Link latency measuring is way more exact now. +* Fixed crash on startup when toolbar was disabled. +* Fixed Windows build. MultiVNC 0.4 was unusable. + +# MultiVNC 0.4 - "One for the road" - 2010-10-27 +* MultiVNC can now connect to IPv6 servers! +* MulticastVNC now incorporates a NACK mechanism which makes + multicasting a lot more reliable: Lost packets are + retransmitted if the server supports it. +* MultiVNC now supports QoS: data sent to a VNC server can be + marked for expedited forwarding! +* Fixed bug where changing edge connector settings would + crash MultiVNC. + +# MultiVNC 0.3 - "Collab me!" - 2010-07-05 +* Implemented window sharing: You can now beam one of your + windows to the remote side. Works on UNIX and Windows. +* Implemented Edge Connector: You can now tell MultiVNC that + one of your screen edges should be the border to the remote + display. When you move the pointer over this edge, MultiVNC + will take over your mouse and keyboard and send your input + to the remote side. When the pointer is moved back towards + the opposite edge on the remote screen, you're back on your + local desktop. +* Implemented FastRequest feature: You can now set an interval + at which MultiVNC continously asks the server for updates + instead of just asking after each received server message. + This should improve framerate on high latency links. +* Fixed a bug where a fast VNC server would make MultiVNC + GUI unresponsive on a slow client. +* Fixed bug where saving log or screenshot would terminate + the connection or even crash on some machines. +* Fixed bug where setting up connection would take very long + due to unnecessary DNS lookups. + + +# MultiVNC 0.2 - "The real thing" - 2010-02-15 +* MulticastVNC incorporated! You can now connect to a + MulticastVNC enabled server, e.g. based on libvncserver. +* Listen mode (Reverse VNC) implemented. Via tabs it is + possible to listen for and serve multiple incoming + connections. +* Bookmarks are working now. +* Clipboard data is now properly encoded. +* Fix bug with canvas centering. +* Fix bug where disconnecting from a fast server would + terminate MultiVNC. +* Fix really nasty bug where listening on windows would + take 100% CPU. + + +# MultiVNC 0.1 - "In the beginning" - 2009-10-12 +* a usable tight-enabled vncviewer with the following special + features: +* supports server framebuffer resize +* several connections with one viewer using tabs +* simple, loggable statistics +* discovery of VNC servers advertising themselves via ZeroConf +* runs under UNIX and Windows + diff --git a/CMakeLists.txt b/CMakeLists.txt index 25604db6..709dcf99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,243 +1,243 @@ -cmake_minimum_required(VERSION 3.13) - -project(multivnc VERSION 0.9.0) -set (CMAKE_CXX_STANDARD 11) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") -# some LSP servers expect compile_commands.json in the project root -add_custom_target( - multivnc-copy-compile-commands ALL - ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_BINARY_DIR}/compile_commands.json - ${CMAKE_CURRENT_SOURCE_DIR} - ) -endif(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") - -configure_file(config.h.cmake_in config.h) - - -# -# dependencies -# - -option(BUILD_SHARED_LIBS "Build shared libraries" OFF) - -# wxservdisc -message("-----wxServDisc-----") -set(WXSERVDISC_INSTALL OFF CACHE BOOL "Set to OFF to not include wxservdisc artifacts in install") -add_subdirectory(libwxservdisc/src) - -# libvncclient -message("-----LibVNCClient-----") -set(LIBVNCSERVER_INSTALL OFF CACHE BOOL "Set to OFF to not include libvncserver artifacts in install") -set(WITH_EXAMPLES OFF CACHE BOOL "Set to OFF to not build libvncserver examples") -add_subdirectory(libvncserver) - - -# -# source proper -# -message("-----MultiVNC-----") -add_subdirectory(src) - - -# -# l18n -# - -set(LOCALE_DOMAIN ${CMAKE_PROJECT_NAME}) - -find_package(Gettext) - -GETTEXT_PROCESS_PO_FILES(de ALL PO_FILES po/de.po) -GETTEXT_PROCESS_PO_FILES(es ALL PO_FILES po/es.po) -GETTEXT_PROCESS_PO_FILES(sv ALL PO_FILES po/sv.po) - - -# -# install directives -# - -if(UNIX AND NOT APPLE) - include(GNUInstallDirs) - install(TARGETS multivnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - install(FILES src/net.christianbeier.MultiVNC.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) - install(FILES flatpak/net.christianbeier.MultiVNC.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) - install(FILES README.md AUTHORS CHANGELOG.md DESTINATION ${CMAKE_INSTALL_DOCDIR}) - install(FILES src/gui/res/multivnc.xpm DESTINATION ${CMAKE_INSTALL_DATADIR}/pixmaps) - install(FILES src/gui/res/net.christianbeier.MultiVNC.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) - install(DIRECTORY src/gui/res/dark DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) - install(DIRECTORY src/gui/res/light DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) - install(FILES src/gui/res/about.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) - install(FILES src/gui/res/unicast.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) - install(FILES src/gui/res/multicast.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/de.gmo DESTINATION ${CMAKE_INSTALL_DATADIR}/locale/de/LC_MESSAGES/ RENAME ${LOCALE_DOMAIN}.mo) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/es.gmo DESTINATION ${CMAKE_INSTALL_DATADIR}/locale/es/LC_MESSAGES/ RENAME ${LOCALE_DOMAIN}.mo) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sv.gmo DESTINATION ${CMAKE_INSTALL_DATADIR}/locale/sv/LC_MESSAGES/ RENAME ${LOCALE_DOMAIN}.mo) - endif(UNIX AND NOT APPLE) - -if(APPLE) - set(APPS ${CMAKE_CURRENT_BINARY_DIR}/src/MultiVNC.app) # paths to executables - set(DIRS "") # directories to search for prerequisites - INSTALL(CODE " -set(BU_CHMOD_BUNDLE_ITEMS ON) # as per https://cmake.org/Bug/view.php?id=13833 -include(BundleUtilities) -fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") - " COMPONENT Runtime) - INSTALL(TARGETS MultiVNC BUNDLE DESTINATION . COMPONENT Runtime) - INSTALL(FILES README.md DESTINATION . RENAME Readme.txt) - install(DIRECTORY src/gui/res/dark DESTINATION MultiVNC.app/Contents/Resources) - install(DIRECTORY src/gui/res/light DESTINATION MultiVNC.app/Contents/Resources) - install(FILES src/gui/res/about.svg DESTINATION MultiVNC.app/Contents/Resources) - install(FILES src/gui/res/unicast.svg DESTINATION MultiVNC.app/Contents/Resources) - install(FILES src/gui/res/multicast.svg DESTINATION MultiVNC.app/Contents/Resources) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/de.gmo DESTINATION MultiVNC.app/Contents/Resources/de.lproj RENAME ${LOCALE_DOMAIN}.mo) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/es.gmo DESTINATION MultiVNC.app/Contents/Resources/es.lproj RENAME ${LOCALE_DOMAIN}.mo) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sv.gmo DESTINATION MultiVNC.app/Contents/Resources/sv.lproj RENAME ${LOCALE_DOMAIN}.mo) - install(FILES po/InfoPlist.strings.de DESTINATION MultiVNC.app/Contents/Resources/de.lproj RENAME InfoPlist.strings) - install(FILES po/InfoPlist.strings.es DESTINATION MultiVNC.app/Contents/Resources/es.lproj RENAME InfoPlist.strings) - install(FILES po/InfoPlist.strings.sv DESTINATION MultiVNC.app/Contents/Resources/sv.lproj RENAME InfoPlist.strings) -endif(APPLE) - -if(WIN32) - install(TARGETS multivnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - install(FILES README.md AUTHORS CHANGELOG.md DESTINATION .) -endif(WIN32) - - -# -# packaging directives -# - -set(CPACK_STRIP_FILES true) -set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) -set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) -set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) -set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/COPYING) - -if(UNIX AND NOT APPLE) - set(CPACK_GENERATOR "DEB") - set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Christian Beier ") - set(CPACK_DEBIAN_PACKAGE_SECTION "net") - set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Multicast-enabled VNC viewer - MultiVNC is a cross-platform Multicast-enabled VNC client using wxWidgets - and libvncclient. It runs on Unix, Mac OS X and Windows.") - set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) - set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) -endif(UNIX AND NOT APPLE) - -if(APPLE) - set(CPACK_GENERATOR "DragNDrop") - set(CPACK_PACKAGE_FILE_NAME "MultiVNC-${PROJECT_VERSION}") - set(CPACK_INSTALL_PREFIX .) # for some reason defaults to /usr/local/ -endif(APPLE) - - -include(CPack) - - - - - -#original Makefile.am contents follow: - -### Process this file with automake to produce Makefile.in -# -#ACLOCAL_AMFLAGS = -I m4 -# -#SUBDIRS = src -# -#multivncdocdir = ${datadir}/doc/multivnc -#multivncdoc_DATA = \ -# README\ -# AUTHORS\ -# ChangeLog\ -# NEWS -# -#Applicationsdir = $(datadir)/applications/ -#Applications_DATA = src/multivnc.desktop -# -#pixmapdir = $(datadir)/pixmaps -#pixmap_DATA = src/gui/res/multivnc.xpm -# -# -#EXTRA_DIST = $(multivncdoc_DATA) \ -# $(Applications_DATA) \ -# $(pixmap_DATA)\ -# multivnc.nsi\ -# contrib/x11vnc contrib/windowshare.exe \ -# contrib/VNCHooks.dll contrib/README-contrib.txt\ -# contrib/tightvnc_win32_1.3.10.patch\ -# debian/changelog debian/compat debian/control debian/copyright\ -# debian/dirs debian/docs debian/menu debian/rules debian/watch -# -# -#if DARWIN -#bundle_contents = MultiVNC.app/Contents -#appbundle: src/multivnc -# mkdir -p $(bundle_contents)/MacOS -# mkdir -p $(bundle_contents)/Resources -# echo "APPL????" > $(bundle_contents)/PkgInfo -# $(INSTALL_PROGRAM) $< $(bundle_contents)/MacOS/ -# cp contrib/x11vnc $(bundle_contents)/MacOS/ -# cp src/icon.icns $(bundle_contents)/Resources -# cp README $(bundle_contents)/Resources -# cp NEWS $(bundle_contents)/Resources -# cp COPYING $(bundle_contents)/Resources -# cp AUTHORS $(bundle_contents)/Resources -# echo \ -# "\ -# \ -# \ -# CFBundleDevelopmentRegion\ -# English\ -# CFBundleExecutable\ -# @PACKAGE_NAME@\ -# CFBundleIconFile\ -# icon.icns\ -# CFBundleGetInfoString\ -# MultiVNC @PACKAGE_VERSION@, © Christian Beier (dontmind@freeshell.org) 2009 \ -# CFBundleName \ -# MultiVNC \ -# CFBundlePackageType \ -# APPL \ -# CFBundleShortVersionString \ -# @PACKAGE_VERSION@ \ -# CFBundleVersion \ -# @PACKAGE_VERSION@ \ -# \ -# \ -# " > $(bundle_contents)/Info.plist -#endif -# -# -#if MINGW -#nsis_installer: all multivnc.nsi README NEWS COPYING -# cat README | unix2dos > README.TXT -# cat NEWS | unix2dos > NEWS.TXT -# cat TODO | unix2dos > TODO.TXT -# cat COPYING | unix2dos > COPYING.TXT -# cp src/.libs/multivnc.exe src/multivnc.exe -# $(STRIP) src/multivnc.exe -# makensis multivnc.nsi -# -#zip_package: all README NEWS COPYING -# mkdir -p @PACKAGE_NAME@-@PACKAGE_VERSION@ -# cat README | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/README.TXT -# cat NEWS | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/NEWS.TXT -# cat TODO | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/TODO.TXT -# cat COPYING | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/COPYING.TXT -# cp src/.libs/multivnc.exe @PACKAGE_NAME@-@PACKAGE_VERSION@ -# $(STRIP) @PACKAGE_NAME@-@PACKAGE_VERSION@/multivnc.exe -# cp src/mingwm10.dll @PACKAGE_NAME@-@PACKAGE_VERSION@ -# cp src/libgpg-error-0.dll @PACKAGE_NAME@-@PACKAGE_VERSION@ -# cp contrib/windowshare.exe @PACKAGE_NAME@-@PACKAGE_VERSION@ -# cp contrib/VNCHooks.dll @PACKAGE_NAME@-@PACKAGE_VERSION@ -# cp contrib/README-contrib.txt @PACKAGE_NAME@-@PACKAGE_VERSION@ -# zip @PACKAGE_NAME@-@PACKAGE_VERSION@-win32-portable.zip -r @PACKAGE_NAME@-@PACKAGE_VERSION@ -# rm -rf @PACKAGE_NAME@-@PACKAGE_VERSION@ -#endif -# -# +cmake_minimum_required(VERSION 3.13) + +project(multivnc VERSION 0.9.0) +set (CMAKE_CXX_STANDARD 11) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") +# some LSP servers expect compile_commands.json in the project root +add_custom_target( + multivnc-copy-compile-commands ALL + ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_BINARY_DIR}/compile_commands.json + ${CMAKE_CURRENT_SOURCE_DIR} + ) +endif(CMAKE_GENERATOR MATCHES "Unix Makefiles|Ninja") + +configure_file(config.h.cmake_in config.h) + + +# +# dependencies +# + +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +# wxservdisc +message("-----wxServDisc-----") +set(WXSERVDISC_INSTALL OFF CACHE BOOL "Set to OFF to not include wxservdisc artifacts in install") +add_subdirectory(libwxservdisc/src) + +# libvncclient +message("-----LibVNCClient-----") +set(LIBVNCSERVER_INSTALL OFF CACHE BOOL "Set to OFF to not include libvncserver artifacts in install") +set(WITH_EXAMPLES OFF CACHE BOOL "Set to OFF to not build libvncserver examples") +add_subdirectory(libvncserver) + + +# +# source proper +# +message("-----MultiVNC-----") +add_subdirectory(src) + + +# +# l18n +# + +set(LOCALE_DOMAIN ${CMAKE_PROJECT_NAME}) + +find_package(Gettext REQUIRED) + +GETTEXT_PROCESS_PO_FILES(de ALL PO_FILES po/de.po) +GETTEXT_PROCESS_PO_FILES(es ALL PO_FILES po/es.po) +GETTEXT_PROCESS_PO_FILES(sv ALL PO_FILES po/sv.po) + + +# +# install directives +# + +if(UNIX AND NOT APPLE) + include(GNUInstallDirs) + install(TARGETS multivnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES src/net.christianbeier.MultiVNC.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) + install(FILES flatpak/net.christianbeier.MultiVNC.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) + install(FILES README.md AUTHORS CHANGELOG.md DESTINATION ${CMAKE_INSTALL_DOCDIR}) + install(FILES src/gui/res/multivnc.xpm DESTINATION ${CMAKE_INSTALL_DATADIR}/pixmaps) + install(FILES src/gui/res/net.christianbeier.MultiVNC.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) + install(DIRECTORY src/gui/res/dark DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) + install(DIRECTORY src/gui/res/light DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) + install(FILES src/gui/res/about.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) + install(FILES src/gui/res/unicast.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) + install(FILES src/gui/res/multicast.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/multivnc) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/de.gmo DESTINATION ${CMAKE_INSTALL_DATADIR}/locale/de/LC_MESSAGES/ RENAME ${LOCALE_DOMAIN}.mo) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/es.gmo DESTINATION ${CMAKE_INSTALL_DATADIR}/locale/es/LC_MESSAGES/ RENAME ${LOCALE_DOMAIN}.mo) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sv.gmo DESTINATION ${CMAKE_INSTALL_DATADIR}/locale/sv/LC_MESSAGES/ RENAME ${LOCALE_DOMAIN}.mo) + endif(UNIX AND NOT APPLE) + +if(APPLE) + set(APPS ${CMAKE_CURRENT_BINARY_DIR}/src/MultiVNC.app) # paths to executables + set(DIRS "") # directories to search for prerequisites + INSTALL(CODE " +set(BU_CHMOD_BUNDLE_ITEMS ON) # as per https://cmake.org/Bug/view.php?id=13833 +include(BundleUtilities) +fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") + " COMPONENT Runtime) + INSTALL(TARGETS MultiVNC BUNDLE DESTINATION . COMPONENT Runtime) + INSTALL(FILES README.md DESTINATION . RENAME Readme.txt) + install(DIRECTORY src/gui/res/dark DESTINATION MultiVNC.app/Contents/Resources) + install(DIRECTORY src/gui/res/light DESTINATION MultiVNC.app/Contents/Resources) + install(FILES src/gui/res/about.svg DESTINATION MultiVNC.app/Contents/Resources) + install(FILES src/gui/res/unicast.svg DESTINATION MultiVNC.app/Contents/Resources) + install(FILES src/gui/res/multicast.svg DESTINATION MultiVNC.app/Contents/Resources) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/de.gmo DESTINATION MultiVNC.app/Contents/Resources/de.lproj RENAME ${LOCALE_DOMAIN}.mo) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/es.gmo DESTINATION MultiVNC.app/Contents/Resources/es.lproj RENAME ${LOCALE_DOMAIN}.mo) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sv.gmo DESTINATION MultiVNC.app/Contents/Resources/sv.lproj RENAME ${LOCALE_DOMAIN}.mo) + install(FILES po/InfoPlist.strings.de DESTINATION MultiVNC.app/Contents/Resources/de.lproj RENAME InfoPlist.strings) + install(FILES po/InfoPlist.strings.es DESTINATION MultiVNC.app/Contents/Resources/es.lproj RENAME InfoPlist.strings) + install(FILES po/InfoPlist.strings.sv DESTINATION MultiVNC.app/Contents/Resources/sv.lproj RENAME InfoPlist.strings) +endif(APPLE) + +if(WIN32) + install(TARGETS multivnc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES README.md AUTHORS CHANGELOG.md DESTINATION .) +endif(WIN32) + + +# +# packaging directives +# + +set(CPACK_STRIP_FILES true) +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/COPYING) + +if(UNIX AND NOT APPLE) + set(CPACK_GENERATOR "DEB") + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Christian Beier ") + set(CPACK_DEBIAN_PACKAGE_SECTION "net") + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Multicast-enabled VNC viewer + MultiVNC is a cross-platform Multicast-enabled VNC client using wxWidgets + and libvncclient. It runs on Unix, Mac OS X and Windows.") + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) +endif(UNIX AND NOT APPLE) + +if(APPLE) + set(CPACK_GENERATOR "DragNDrop") + set(CPACK_PACKAGE_FILE_NAME "MultiVNC-${PROJECT_VERSION}") + set(CPACK_INSTALL_PREFIX .) # for some reason defaults to /usr/local/ +endif(APPLE) + + +include(CPack) + + + + + +#original Makefile.am contents follow: + +### Process this file with automake to produce Makefile.in +# +#ACLOCAL_AMFLAGS = -I m4 +# +#SUBDIRS = src +# +#multivncdocdir = ${datadir}/doc/multivnc +#multivncdoc_DATA = \ +# README\ +# AUTHORS\ +# ChangeLog\ +# NEWS +# +#Applicationsdir = $(datadir)/applications/ +#Applications_DATA = src/multivnc.desktop +# +#pixmapdir = $(datadir)/pixmaps +#pixmap_DATA = src/gui/res/multivnc.xpm +# +# +#EXTRA_DIST = $(multivncdoc_DATA) \ +# $(Applications_DATA) \ +# $(pixmap_DATA)\ +# multivnc.nsi\ +# contrib/x11vnc contrib/windowshare.exe \ +# contrib/VNCHooks.dll contrib/README-contrib.txt\ +# contrib/tightvnc_win32_1.3.10.patch\ +# debian/changelog debian/compat debian/control debian/copyright\ +# debian/dirs debian/docs debian/menu debian/rules debian/watch +# +# +#if DARWIN +#bundle_contents = MultiVNC.app/Contents +#appbundle: src/multivnc +# mkdir -p $(bundle_contents)/MacOS +# mkdir -p $(bundle_contents)/Resources +# echo "APPL????" > $(bundle_contents)/PkgInfo +# $(INSTALL_PROGRAM) $< $(bundle_contents)/MacOS/ +# cp contrib/x11vnc $(bundle_contents)/MacOS/ +# cp src/icon.icns $(bundle_contents)/Resources +# cp README $(bundle_contents)/Resources +# cp NEWS $(bundle_contents)/Resources +# cp COPYING $(bundle_contents)/Resources +# cp AUTHORS $(bundle_contents)/Resources +# echo \ +# "\ +# \ +# \ +# CFBundleDevelopmentRegion\ +# English\ +# CFBundleExecutable\ +# @PACKAGE_NAME@\ +# CFBundleIconFile\ +# icon.icns\ +# CFBundleGetInfoString\ +# MultiVNC @PACKAGE_VERSION@, © Christian Beier (dontmind@freeshell.org) 2009 \ +# CFBundleName \ +# MultiVNC \ +# CFBundlePackageType \ +# APPL \ +# CFBundleShortVersionString \ +# @PACKAGE_VERSION@ \ +# CFBundleVersion \ +# @PACKAGE_VERSION@ \ +# \ +# \ +# " > $(bundle_contents)/Info.plist +#endif +# +# +#if MINGW +#nsis_installer: all multivnc.nsi README NEWS COPYING +# cat README | unix2dos > README.TXT +# cat NEWS | unix2dos > NEWS.TXT +# cat TODO | unix2dos > TODO.TXT +# cat COPYING | unix2dos > COPYING.TXT +# cp src/.libs/multivnc.exe src/multivnc.exe +# $(STRIP) src/multivnc.exe +# makensis multivnc.nsi +# +#zip_package: all README NEWS COPYING +# mkdir -p @PACKAGE_NAME@-@PACKAGE_VERSION@ +# cat README | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/README.TXT +# cat NEWS | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/NEWS.TXT +# cat TODO | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/TODO.TXT +# cat COPYING | unix2dos > @PACKAGE_NAME@-@PACKAGE_VERSION@/COPYING.TXT +# cp src/.libs/multivnc.exe @PACKAGE_NAME@-@PACKAGE_VERSION@ +# $(STRIP) @PACKAGE_NAME@-@PACKAGE_VERSION@/multivnc.exe +# cp src/mingwm10.dll @PACKAGE_NAME@-@PACKAGE_VERSION@ +# cp src/libgpg-error-0.dll @PACKAGE_NAME@-@PACKAGE_VERSION@ +# cp contrib/windowshare.exe @PACKAGE_NAME@-@PACKAGE_VERSION@ +# cp contrib/VNCHooks.dll @PACKAGE_NAME@-@PACKAGE_VERSION@ +# cp contrib/README-contrib.txt @PACKAGE_NAME@-@PACKAGE_VERSION@ +# zip @PACKAGE_NAME@-@PACKAGE_VERSION@-win32-portable.zip -r @PACKAGE_NAME@-@PACKAGE_VERSION@ +# rm -rf @PACKAGE_NAME@-@PACKAGE_VERSION@ +#endif +# +# diff --git a/README.md b/README.md index ebaaad14..3a86596a 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,109 @@ - -# MultiVNC - -[![CI](https://github.com/bk138/multivnc/actions/workflows/ci.yml/badge.svg)](https://github.com/bk138/multivnc/actions/workflows/ci.yml) -[![Help making this possible](https://img.shields.io/badge/liberapay-donate-yellow.png)](https://liberapay.com/bk138/donate) -[![Become a patron](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/bk138) -[![Donate](https://img.shields.io/badge/paypal-donate-yellow.png)](https://www.paypal.com/donate/?hosted_button_id=HKRTWKNKBKPKN) -[![Gitter](https://badges.gitter.im/multivnc/community.svg)](https://gitter.im/multivnc/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -![MultiVNC logo](src/gui/res/net.christianbeier.MultiVNC.svg) - -MultiVNC is a cross-platform Multicast-enabled VNC viewer based on -[LibVNCClient](https://github.com/LibVNC/libvncserver). The desktop version -uses [wxWidgets](https://www.wxwidgets.org/) and runs on Unix, Mac OS X and -Windows. There also is an [Android version](/android/). - -The roadmap for future developments regarding the project can be found -[here](https://github.com/bk138/multivnc/projects). - -## Features - -* Support for most VNC encodings including Tight. -* TLS support, i.e. AnonTLS and VeNCrypt. -* Discovery of VNC servers advertising themselves via ZeroConf. -* Bookmarking of connections. -* Supports server framebuffer resize. -* Experimental support for MulticastVNC. - -### Android-only Features - -* Support for SSH-Tunnelling with password- and privkey-based authentication. -* UltraVNC Repeater support. -* Import and export of saved connections. -* Virtual mouse button controls with haptic feedback. -* Two-finger swipe gesture recognition. -* A super fast touchpad mode for local use. -* Hardware-accelerated OpenGL drawing and zooming. -* Copy&paste to and from Android. - -### Desktop-only Features - -* Several connections with one viewer using tabs. -* Listen mode (Reverse VNC). Via tabs it's possible to listen - for and serve multiple incoming connections. -* Record and replay of user input macros. -* Under X11, seamless control of the remote side by moving pointer over the - (default upper) screen edge. Borrows heavily from x2vnc by - Fredrik Hübinette , which in turn was based on - ideas from x2x and code from vncviewer. -* Simple, loggable statistics. - -## How to get it - -### MultiVNC for Android -[Get it on F-Droid](https://f-droid.org/packages/com.coboltforge.dontmind.multivnc/) -[Get it on Google Play](https://play.google.com/store/apps/details?id=com.coboltforge.dontmind.multivnc) - -### MultiVNC for Desktop - -[](https://flathub.org/apps/details/net.christianbeier.MultiVNC) -[](https://apps.apple.com/us/app/multivnc/id6738012997) - -To get bleeding-edge packages built from the master development branch, navigate to -[the list of CI runs](https://github.com/bk138/multivnc/actions/workflows/ci.yml), -select the last successful one and download the wanted artifact. - -## How to build - -### MultiVNC for Android - -See the [Android version's README](android/README.md). - -### MultiVNC for Desktop - -The prerequisites: - -* the usual c-compiler with headers and stuff -* wxWidgets dev package version >= 3.0 -* zlib dev package -* libjpeg dev package - -After cloning the repo, do - -``` - git submodule init - git submodule update -``` - -To build: - -``` - mkdir build - cd build - cmake .. - cmake --build . - cpack -``` - -Depending on which OS you are on, you end up with a .deb, .dmg or .exe you can install. - -## MulticastVNC notes - -You can get a modified libvncserver/libvncclient at -https://github.com/LibVNC/libvncserver/tree/multicastvnc - -this is the same library that MultiVNC uses internally. + +# MultiVNC + +[![CI](https://github.com/bk138/multivnc/actions/workflows/ci.yml/badge.svg)](https://github.com/bk138/multivnc/actions/workflows/ci.yml) +[![Help making this possible](https://img.shields.io/badge/liberapay-donate-yellow.png)](https://liberapay.com/bk138/donate) +[![Become a patron](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://www.patreon.com/bk138) +[![Donate](https://img.shields.io/badge/paypal-donate-yellow.png)](https://www.paypal.com/donate/?hosted_button_id=HKRTWKNKBKPKN) +[![Gitter](https://badges.gitter.im/multivnc/community.svg)](https://gitter.im/multivnc/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +![MultiVNC logo](src/gui/res/net.christianbeier.MultiVNC.svg) + +MultiVNC is a cross-platform Multicast-enabled VNC viewer based on +[LibVNCClient](https://github.com/LibVNC/libvncserver). The desktop version +uses [wxWidgets](https://www.wxwidgets.org/) and runs on Unix, Mac OS X and +Windows. There also is an [Android version](/android/). + +The roadmap for future developments regarding the project can be found +[here](https://github.com/bk138/multivnc/projects). + +## Features + +* Support for most VNC encodings including Tight. +* TLS support, i.e. AnonTLS and VeNCrypt. +* Discovery of VNC servers advertising themselves via ZeroConf. +* Bookmarking of connections. +* Supports server framebuffer resize. +* Experimental support for MulticastVNC. + +### Android-only Features + +* Support for SSH-Tunnelling with password- and privkey-based authentication. +* UltraVNC Repeater support. +* Import and export of saved connections. +* Virtual mouse button controls with haptic feedback. +* Two-finger swipe gesture recognition. +* A super fast touchpad mode for local use. +* Hardware-accelerated OpenGL drawing and zooming. +* Copy&paste to and from Android. + +### Desktop-only Features + +* Several connections with one viewer using tabs. +* Listen mode (Reverse VNC). Via tabs it's possible to listen + for and serve multiple incoming connections. +* Record and replay of user input macros. +* Under X11, seamless control of the remote side by moving pointer over the + (default upper) screen edge. Borrows heavily from x2vnc by + Fredrik Hübinette , which in turn was based on + ideas from x2x and code from vncviewer. +* Simple, loggable statistics. + +## How to get it + +### MultiVNC for Android +[Get it on F-Droid](https://f-droid.org/packages/com.coboltforge.dontmind.multivnc/) +[Get it on Google Play](https://play.google.com/store/apps/details?id=com.coboltforge.dontmind.multivnc) + +### MultiVNC for Desktop + +[](https://flathub.org/apps/details/net.christianbeier.MultiVNC) +[](https://apps.apple.com/us/app/multivnc/id6738012997) + +To get bleeding-edge packages built from the master development branch, navigate to +[the list of CI runs](https://github.com/bk138/multivnc/actions/workflows/ci.yml), +select the last successful one and download the wanted artifact. + +## How to build + +### MultiVNC for Android + +See the [Android version's README](android/README.md). + +### MultiVNC for Desktop + +The prerequisites: + +* the usual c-compiler with headers and stuff +* wxWidgets dev package version >= 3.0 +* zlib dev package +* libjpeg dev package + +After cloning the repo, do + +``` + git submodule init + git submodule update +``` + +To build: + +``` + mkdir build + cd build + cmake .. + cmake --build . + cpack +``` + +Depending on which OS you are on, you end up with a .deb, .dmg or .exe you can install. + +## MulticastVNC notes + +You can get a modified libvncserver/libvncclient at +https://github.com/LibVNC/libvncserver/tree/multicastvnc - +this is the same library that MultiVNC uses internally. diff --git a/android/.gitignore b/android/.gitignore index 21599a71..0759d60c 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,19 +1,19 @@ -# Generated files -bin/ -gen/ -build/ -.cxx/ -release/ - -# Gradle -.gradle/ - -# Local configuration file (sdk path, etc) -local.properties - -# Android Studio -*.iml -.idea/* - -# Whitelist -!.idea/encodings.xml +# Generated files +bin/ +gen/ +build/ +.cxx/ +release/ + +# Gradle +.gradle/ + +# Local configuration file (sdk path, etc) +local.properties + +# Android Studio +*.iml +.idea/* + +# Whitelist +!.idea/encodings.xml diff --git a/android/ChangeLog.md b/android/ChangeLog.md index 039ed402..2eff334a 100644 --- a/android/ChangeLog.md +++ b/android/ChangeLog.md @@ -1,497 +1,497 @@ -# Version 2.1.x: - -## 2.1.8 - -### 🛠 Fixes - -* Fix regression that caused failures when connecting VNC servers using encryption. -* Fix race condition on start when init UI would be tried to show despite activity finishing. - -## 2.1.7 - -### 🛠 Fixes - -* Fix another reported crash in screen drawing module. - -## 2.1.6 - -### 🛠 Fixes - -* Fix reported crash in screen drawing module. - -## 2.1.5 - -### 🛠 Fixes - -* Adapted to API level 34 as required by Google. -* Fixed rare crash in service discovery module. - -## 2.1.4 - -### 🛠 Fixes - -* Fixed one rare crash in native code. -* Refactored VncCanvasActivity coupling with other components. -* Fixed server discovery Toast messages wrongly showing when main menu activity was in background. -* Fixed notification permission handling on Android 13+. -* Fixed crash in foreground service showing active connections. - -## 2.1.3 - -### 🛠 Fixes - -* Fixed framebuffer update requests being sent although the client being in touchpad mode. -* Updated Android library dependencies. -* Updated libjpeg-turbo to latest ESR release. -* Updated the internal service discovery to use Android-provided machinery, removed dependency on - jmDNS. -* Refactored coupling of VNC connection handling with UI. - -## 2.1.2 - -### 🛠 Fixes - -* Fixed a _server_ crash when connecting to UltraVNC that has MSLogonII enabled. -* Fixed a crash that happened when setting up an SSH tunnel failed for some reason. - -## 2.1.1 - -### 🛠 Fixes - -* Fixed bug on Android 7 devices where "VNC connection failed! Only the original thread that created - a view hierarchy can touch its views." would appear on connection initialisation. - -## 2.1.0 - -This feature release brings a first MVP of SSH-Tunneling, a dark mode, the ability to set -preferred VNC encodings and quality/compress levels and also some bug fixes. - -### ⚡ Features - -* Added support for night mode aka a dark theme, thanks to Gaurav Ujjwal. -* Added Portuguese and Chinese(Taiwan) translations. -* Improved soft-keyboard access by making keyboard and zoom controls always visible. -* Improved support for physical mice: middle mouse button support, improved right button support. -* Added functionality to set preferred VNC encodings, compress and quality levels, thanks to - Masato Nagasawa. -* Added a first MVP of SSH-Tunneling: works with password and priv-key. - -### 🛠 Fixes - -* Fixed a possible crash when trying to (wrongfully) import very big database exports. -* Fixed possible crash when showing connection info. -* Fixed help dialog showing up on 2nd and later connections although the user denied. -* Fixed the haptic feedback of some on-screen actions not obeying the system settings. - - -# Version 2.0.x: - -## 2.0.10 - -### 🛠 Fixes - -* Fix ExtendedDesktopSize handling for older UltraVNC servers. - -## 2.0.9 - -### 🛠 Fixes - -* Actually use the new call introduced in v2.0.8. - -## 2.0.8 - -### 🛠 Fixes - -* Fixed app crashes on Android >= 11 caused by [Android 11 behavior changes](https://developer.android.com/about/versions/11/behavior-changes-all#fdsan). - -## 2.0.7 - -### 🛠 Fixes - -* Fixed keyboard not being toggleable from menu on Android 12. -* Fixed right mouse button clicks from Bluetooth mice being intertwined with flaky left mouse button - clicks. -* Fixed bookmarks having empty names when saving a connection that was not coming from - Zeroconf/Bonjour. - -## 2.0.6 - -### 🛠 Fixes - -* Fixed connecting to servers running on Fedora 35. -* Fixed white artifacts showing up on Android 12 when scrolling the remote desktop view. -* Fixed the home screen widget so that it now displays all kinds of connections, not only the ones - with password. - -## 2.0.5 - -### 🛠 Fixes - -- Fixed uppercase letters and symbols not working with some VNC servers thanks to Gaurav Ujjwal. - -## 2.0.4 - -### 🛠 Fixes - -- Fixed JSON Import/Export in Release builds thanks to Gaurav Ujjwal. -- Fixed Import not showing any files with certain file pickers thanks to Gaurav Ujjwal. -- Fixed crash when copying non-UTF8 characters on the server side, also thanks to Gaurav Ujjwal. - -## 2.0.3 - -### 🛠 Fixes - -- Fixed the VNCConnService crash in a different way since the cause was actually a different one. (#172) - -## 2.0.2 - -### 🛠 Fixes - -- Fixed Triple-T changelog to simply point to online ChangeLog.md. -- Fixed a race crash in VNCConnService, now in a more robust way. - -## 2.0.1 - -### 🛠 Fixes - -- Fixed possible crash on connecting to servers that immediately sent a NewFBSize. -- Fixed a race crash in VNCConnService. - -## 2.0.0 - -Version 2.0.0 is the culmination of the 1.9.x series and marks the completion of -exchanging the previous Java-based RFB engine with a fully native one based on -[LibVNCClient](https://github.com/LibVNC/libvncserver). Here's why: - -It's more feature-rich: the native backend now supports Apple Remote Desktop -servers (i.e. all Macs), UltraVNC's MSLogon security type (Microsoft Windows) -and Vino's AnonTLS authentication (GNOME's remote desktop server). - -It's faster: with LibVNCClient, MultiVNC can now finally make use of Tight VNC -encoding, a lossy JPEG encoding which drastically reduces needed throughput capacity. -Also, the now-native JPEG decoding can make use of [libjpeg-turbo](https://www.libjpeg-turbo.org/), -a JPEG image codec that uses SIMD instructions to accelerate JPEG decompression. - -Big thanks go out to [Gaurav Ujjwal](https://github.com/gujjwal00) who contributed -a lot of very good UI improvements as well as under-the-hood changes. You might want -to check out his excellent [AVNC VNC client for Android](https://github.com/gujjwal00/avnc). -Thanks also to Alexandr Kondratev, Sergiy Stupar, Suso Comesaña, ferrumcccp, -nagasawa and Frischid for contributing code and translations. - -Besides these big changes compared to versions 1.8.x, 2.0.0 brings the following -changes on top of 1.9.11: - -### ⚡ Features - -- Added handling of remote framebuffer resizes. - -### 🛠 Fixes - -- Fixed crash on entering wrong password -- Fixed last key-combo not being remembered (#132) -- Fixed import/export on Android 11. - -# Version 1.9.x: - -## 1.9.11 (2021-02-24) - -- Fixed Triple-T metadata. - -## 1.9.10 (2021-02-20) - -- Fixed connection being terminated when the app was in the background for a longer time. -- Fixed usage over SSH tunnels by not making connections to localhost always use Raw encoding. -- Fixed sending of key combos with modifier keys. -- Fixed jerky cursor movement in high zoom levels. - -## 1.9.9 (2021-02-12) - -- Added support for handling Samsung SPen button thanks to Frischid. -- Updated internal database handling to modern API thanks to Gaurav Ujjwal. - -## 1.9.8 (2021-02-02) - -- Made connections to localhost simply use Raw encoding. -- Added screen content resizing on software keyboard opening so keyboard does not obscure screen - contents thanks to Gaurav Ujjwal. -- Added SIMD support for faster Tight decoding thanks to Gaurav Ujjwal. -- Added Triple-T metadata for F-Droid thanks to Gaurav Ujjwal. -- Added Catalan and Galego translation thanks to Suso Comesaña. -- Improved Japanese translation thanks to Masato Nagasawa. -- Improved Spanish translation thanks to Suso Comesaña. -- Fixed D-Pad focus handling and navigation in the main menu. -- Fixed connection failure when entered server address contained whitespace. -- Fixed crash when importing legacy bookmarks. - -## 1.9.7 (2020-10-30) - -- Updated Chinese translation. -- Added Spanish and Japanese translations. - -## 1.9.6 (2020-10-10) - -- Added Ukrainian translation thanks to Sergiy Stupar. -- Removed legacy Java RFB engine, all things VNC are now native. 🎉 -- Fixed crash in connection info toast. -- Added functionality to auto-open soft keyboard for credentials dialog. -- Changed Import/Export to use native format, keeping the possibility for legacy XML import. - -## 1.9.5 (2020-09-29) - -- Made native connections the default, removed chooser dialog. -- Added links to GitHub issues and ChangeLog in About. -- Removed donation links in About for Google Play version. -- Improved russian translation thanks to Alexandr Kondratev. -- Added Chinese (machine) translation. - -## 1.9.4 (2020-09-18) - -- Reworked UltraVNC-repeater connection functionality to be more user-friendly. -- Fixed right mouse/touchpad button handling for more devices. - -## 1.9.3 (2020-08-30) - -- Fixed an ANR that occurred when a native connection setup timed out. -- Added colour mode selection for native connections. -- Added functionality to keep screen on when remote screen is shown. - -## 1.9.2 (2020-07-17) - -- Fixed native connections not working with IPv6. - -## 1.9.1 (2020-07-17) - -- Added functionality to not ask for frambuffer updates in native-connection touchpad mode. -- Worked around crash in Zeroconf scanner on Android 9. - -## 1.9.0 (2020-07-13) - -- The first MultiVNC for Android to sport a native backend implementation that supports TightVNC - and TurboVNC, Apple Remote Desktop and Vino's AnonTLS authentication. In the 1.9.x builds this - will start as an opt-in feature. 1.9.x builds will be available via the Google Play Beta program - or F-Droid only. -- Improved scaling so the whole remote screen can be seen at once in portrait orientation. -- Improved the credentials dialog to provide more hints on what is being entered in landscape mode. - -# Version 1.8.x: - -## 1.8.12 (2020-07-16) - -- Added dialog notifying about beta test program. -- Worked around crash in Zeroconf scanner on Android 9. - -## 1.8.11 (2020-07-02) - -- The whole screen scaling user experience is now way smoother thanks to Gaurav Ujjwal. -- The online help text was updated. -- MultiVNC now requires Android 5 in order to make better use of vector drawables. - -## 1.8.10 (2020-06-07) - -- Added support for external mouse's scroll wheels. -- MultiVNC now completely uses vector images. - -## 1.8.9 (2020-03-01) - -- VNC activity is now rotatable thanks to work done by Gaurav Ujjwal. -- Added ability to make key combos with 'Super' key. -- Turned more icons into vector icons. -- Fixed some possible crashes. - -## 1.8.8 (2019-12-31) - -- Now hides navigation bar per default, resulting in more usable screen space. -- Fixed hamburger menu not opening on some Samsung devices. - -## 1.8.7 (2019-12-18) - -- Fixed possible crash in import/export activity. - -## 1.8.6 (2019-11-25) - -- Added ability to connect to MacOS servers. -- Fixed bookmarks sometimes not having a name. - -## 1.8.5 (2019-11-17) - -- Handle right-mouse-button-clicks from USB-OTG mice the right way. - -## 1.8.4 (2019-10-27) - -- Fix erronous auto-bookmarking of connections started via vnc:// scheme. -- Fix some keyboards not accepting input. -- Fix resending of sent special key combo. - -## 1.8.3 (2019-10-17) - -- Main menu layout improved on small tablet devices. -- Fixed two possible crashes. - -## 1.8.2 (2019-09-29) - -- Some UI fixes for Android Kitkat. - -## 1.8.1 (2019-09-16) - -- More adaptations for tablets. -- Fixed two possible crashes. - -## 1.8.0 (2019-09-14) - -- Updated UI to use Material Design. -- Made remote desktop view use more screen real estate. -- Changed default color mode to TrueColor. -- Fixed possible crash on app start. - -# Version 1.7.x: - -## 1.7.10 (2019-09-04) - -- Fixed a possible race condition and subsequent crash on app start. - -## 1.7.9 (2019-07-31) - -- Fixed a possible crash. -- Added Russian translation. - -## 1.7.8 (2019-07-06) - -- Added Italian translation. - -## 1.7.7 (2019-06-30) - -- Fixed possible crash on ChromeBox. - -## 1.7.6 (2019-04-07) - -- Inform user when there are no bookmarks to create shortcuts with. -- Fixed possible crash when importing settings. - -## 1.7.5 (2019-01-15) - -- Fixed possible crash when closing the app. - -## 1.7.4 (2019-01-13) - -- Fixed possible crash in cursor handling. - -## 1.7.3 (2019-01-12) - -- Some updates to the server discovery module. -- Fixed minor issues in german translation. - -## 1.7.2 (2018-12-07) - -- Fixed another possible crash. -- Added german translation. - -## 1.7.1 (2018-12-02) - -- Fix crash on connection initialisation. - -## 1.7.0 (2018-11-18) - -- Updated the UI theming to use the default look on the respective Android version. -- Made the import/export UI more usable. -- Updated the launcher icon to be more hi-res. -- Removed the ad system. - -# Version 1.6.x: - -## 1.6.4 (2014-01-07) - -- Fix immediate crash upon connecting on some devices. - -## 1.6.3 (2013-06-27) - -- Fix hidden menu items on some devices. -- Fix Zeroconf/Bonjour/mDNS VNC server discovery on Android 4.2 devices. - -## 1.6.2 (2013-06-06) - -- Hide Actionbar when there is a hardware menu button. -- Removed broken hungarian translation. - -## 1.6.1 (2013-03-11) - -- Fixed ANR that could sometimes occur when connection broke. -- Fixed crash in discovered servers list. - -## 1.6.0 (2013-02-03) - -- Added support for hardware keyboards and mice. Kudos to Kevin Pansky. -- Added copy&paste function to and from Android clipboard. -- Virtual mouse button click-and-drag now also works on Honeycomb and newer devices. -- A bit more screen real estate on ICS and newer devices by hiding the status bar. - -# Version 1.5.x: - -## 1.5.2 (2012-12-26) - -- Hopefully fix crash on dialog dismissal. - -## 1.5.1 (2012-12-18) - -- Prevent ActionBar from stealing keyboard focus. - -## 1.5.0 (2012-12-14) - -- Full IPv6 support on Honeycomb and newer! -- Added help screen. -- Improved touchpad mode. -- Fixed 'Key Combo' button being focused when soft keyboard was activated. -- Fixed virtual mouse button toggling. -- Fixed bookmarked connections. -- Fixed ActionBar taking focus on soft keyboard toggle. -- Fixed a lot of maybe-crash situations. -- Removed broken Flattr button. - -# Version 1.4.x: - -## 1.4.10 (2012-08-23) - -- Added button to be able to rate the app. - -## 1.4.9 (2012-08-13) - -- Fix crash introduced by the 1.4.8 fix. - -## 1.4.8 (2012-08-12) - -- Fix an ANR error when pausing the app. - -## 1.4.7 (2012-07-15) - -- Fix hang after password entry. Should also work with bookmarks now. - -## 1.4.6 (2012-07-14) - -- Add menu item to toggle pointer highlighting. - -## 1.4.5 (2012-06-26) - -- Fix the 1.4.4 fix. - -## 1.4.4 (2012-06-23) - -- Fix crash on startup on Honeycomb or newer devices. - -## 1.4.3 (2012-06-18) - -- Hopefully fixed keyboard disappearing bug. - -## 1.4.2 (2012-06-15) - -- Fixed crash on mouse button press. - -## 1.4.1 (2012-06-14) - -- Fixed app not starting on Honeycomb devices. - -## 1.4.0 (2012-06-12) - -- ICS and Tablet Support! -- Added password prompt. Shown when no password was given but one is required. -- Greatly improved key handling. Most special symbols work now. -- Fixed a nasty app restart bug on Honeycomb and newer devices. -- Fixed some possible crashes. +# Version 2.1.x: + +## 2.1.8 + +### 🛠 Fixes + +* Fix regression that caused failures when connecting VNC servers using encryption. +* Fix race condition on start when init UI would be tried to show despite activity finishing. + +## 2.1.7 + +### 🛠 Fixes + +* Fix another reported crash in screen drawing module. + +## 2.1.6 + +### 🛠 Fixes + +* Fix reported crash in screen drawing module. + +## 2.1.5 + +### 🛠 Fixes + +* Adapted to API level 34 as required by Google. +* Fixed rare crash in service discovery module. + +## 2.1.4 + +### 🛠 Fixes + +* Fixed one rare crash in native code. +* Refactored VncCanvasActivity coupling with other components. +* Fixed server discovery Toast messages wrongly showing when main menu activity was in background. +* Fixed notification permission handling on Android 13+. +* Fixed crash in foreground service showing active connections. + +## 2.1.3 + +### 🛠 Fixes + +* Fixed framebuffer update requests being sent although the client being in touchpad mode. +* Updated Android library dependencies. +* Updated libjpeg-turbo to latest ESR release. +* Updated the internal service discovery to use Android-provided machinery, removed dependency on + jmDNS. +* Refactored coupling of VNC connection handling with UI. + +## 2.1.2 + +### 🛠 Fixes + +* Fixed a _server_ crash when connecting to UltraVNC that has MSLogonII enabled. +* Fixed a crash that happened when setting up an SSH tunnel failed for some reason. + +## 2.1.1 + +### 🛠 Fixes + +* Fixed bug on Android 7 devices where "VNC connection failed! Only the original thread that created + a view hierarchy can touch its views." would appear on connection initialisation. + +## 2.1.0 + +This feature release brings a first MVP of SSH-Tunneling, a dark mode, the ability to set +preferred VNC encodings and quality/compress levels and also some bug fixes. + +### ⚡ Features + +* Added support for night mode aka a dark theme, thanks to Gaurav Ujjwal. +* Added Portuguese and Chinese(Taiwan) translations. +* Improved soft-keyboard access by making keyboard and zoom controls always visible. +* Improved support for physical mice: middle mouse button support, improved right button support. +* Added functionality to set preferred VNC encodings, compress and quality levels, thanks to + Masato Nagasawa. +* Added a first MVP of SSH-Tunneling: works with password and priv-key. + +### 🛠 Fixes + +* Fixed a possible crash when trying to (wrongfully) import very big database exports. +* Fixed possible crash when showing connection info. +* Fixed help dialog showing up on 2nd and later connections although the user denied. +* Fixed the haptic feedback of some on-screen actions not obeying the system settings. + + +# Version 2.0.x: + +## 2.0.10 + +### 🛠 Fixes + +* Fix ExtendedDesktopSize handling for older UltraVNC servers. + +## 2.0.9 + +### 🛠 Fixes + +* Actually use the new call introduced in v2.0.8. + +## 2.0.8 + +### 🛠 Fixes + +* Fixed app crashes on Android >= 11 caused by [Android 11 behavior changes](https://developer.android.com/about/versions/11/behavior-changes-all#fdsan). + +## 2.0.7 + +### 🛠 Fixes + +* Fixed keyboard not being toggleable from menu on Android 12. +* Fixed right mouse button clicks from Bluetooth mice being intertwined with flaky left mouse button + clicks. +* Fixed bookmarks having empty names when saving a connection that was not coming from + Zeroconf/Bonjour. + +## 2.0.6 + +### 🛠 Fixes + +* Fixed connecting to servers running on Fedora 35. +* Fixed white artifacts showing up on Android 12 when scrolling the remote desktop view. +* Fixed the home screen widget so that it now displays all kinds of connections, not only the ones + with password. + +## 2.0.5 + +### 🛠 Fixes + +- Fixed uppercase letters and symbols not working with some VNC servers thanks to Gaurav Ujjwal. + +## 2.0.4 + +### 🛠 Fixes + +- Fixed JSON Import/Export in Release builds thanks to Gaurav Ujjwal. +- Fixed Import not showing any files with certain file pickers thanks to Gaurav Ujjwal. +- Fixed crash when copying non-UTF8 characters on the server side, also thanks to Gaurav Ujjwal. + +## 2.0.3 + +### 🛠 Fixes + +- Fixed the VNCConnService crash in a different way since the cause was actually a different one. (#172) + +## 2.0.2 + +### 🛠 Fixes + +- Fixed Triple-T changelog to simply point to online ChangeLog.md. +- Fixed a race crash in VNCConnService, now in a more robust way. + +## 2.0.1 + +### 🛠 Fixes + +- Fixed possible crash on connecting to servers that immediately sent a NewFBSize. +- Fixed a race crash in VNCConnService. + +## 2.0.0 + +Version 2.0.0 is the culmination of the 1.9.x series and marks the completion of +exchanging the previous Java-based RFB engine with a fully native one based on +[LibVNCClient](https://github.com/LibVNC/libvncserver). Here's why: + +It's more feature-rich: the native backend now supports Apple Remote Desktop +servers (i.e. all Macs), UltraVNC's MSLogon security type (Microsoft Windows) +and Vino's AnonTLS authentication (GNOME's remote desktop server). + +It's faster: with LibVNCClient, MultiVNC can now finally make use of Tight VNC +encoding, a lossy JPEG encoding which drastically reduces needed throughput capacity. +Also, the now-native JPEG decoding can make use of [libjpeg-turbo](https://www.libjpeg-turbo.org/), +a JPEG image codec that uses SIMD instructions to accelerate JPEG decompression. + +Big thanks go out to [Gaurav Ujjwal](https://github.com/gujjwal00) who contributed +a lot of very good UI improvements as well as under-the-hood changes. You might want +to check out his excellent [AVNC VNC client for Android](https://github.com/gujjwal00/avnc). +Thanks also to Alexandr Kondratev, Sergiy Stupar, Suso Comesaña, ferrumcccp, +nagasawa and Frischid for contributing code and translations. + +Besides these big changes compared to versions 1.8.x, 2.0.0 brings the following +changes on top of 1.9.11: + +### ⚡ Features + +- Added handling of remote framebuffer resizes. + +### 🛠 Fixes + +- Fixed crash on entering wrong password +- Fixed last key-combo not being remembered (#132) +- Fixed import/export on Android 11. + +# Version 1.9.x: + +## 1.9.11 (2021-02-24) + +- Fixed Triple-T metadata. + +## 1.9.10 (2021-02-20) + +- Fixed connection being terminated when the app was in the background for a longer time. +- Fixed usage over SSH tunnels by not making connections to localhost always use Raw encoding. +- Fixed sending of key combos with modifier keys. +- Fixed jerky cursor movement in high zoom levels. + +## 1.9.9 (2021-02-12) + +- Added support for handling Samsung SPen button thanks to Frischid. +- Updated internal database handling to modern API thanks to Gaurav Ujjwal. + +## 1.9.8 (2021-02-02) + +- Made connections to localhost simply use Raw encoding. +- Added screen content resizing on software keyboard opening so keyboard does not obscure screen + contents thanks to Gaurav Ujjwal. +- Added SIMD support for faster Tight decoding thanks to Gaurav Ujjwal. +- Added Triple-T metadata for F-Droid thanks to Gaurav Ujjwal. +- Added Catalan and Galego translation thanks to Suso Comesaña. +- Improved Japanese translation thanks to Masato Nagasawa. +- Improved Spanish translation thanks to Suso Comesaña. +- Fixed D-Pad focus handling and navigation in the main menu. +- Fixed connection failure when entered server address contained whitespace. +- Fixed crash when importing legacy bookmarks. + +## 1.9.7 (2020-10-30) + +- Updated Chinese translation. +- Added Spanish and Japanese translations. + +## 1.9.6 (2020-10-10) + +- Added Ukrainian translation thanks to Sergiy Stupar. +- Removed legacy Java RFB engine, all things VNC are now native. 🎉 +- Fixed crash in connection info toast. +- Added functionality to auto-open soft keyboard for credentials dialog. +- Changed Import/Export to use native format, keeping the possibility for legacy XML import. + +## 1.9.5 (2020-09-29) + +- Made native connections the default, removed chooser dialog. +- Added links to GitHub issues and ChangeLog in About. +- Removed donation links in About for Google Play version. +- Improved russian translation thanks to Alexandr Kondratev. +- Added Chinese (machine) translation. + +## 1.9.4 (2020-09-18) + +- Reworked UltraVNC-repeater connection functionality to be more user-friendly. +- Fixed right mouse/touchpad button handling for more devices. + +## 1.9.3 (2020-08-30) + +- Fixed an ANR that occurred when a native connection setup timed out. +- Added colour mode selection for native connections. +- Added functionality to keep screen on when remote screen is shown. + +## 1.9.2 (2020-07-17) + +- Fixed native connections not working with IPv6. + +## 1.9.1 (2020-07-17) + +- Added functionality to not ask for frambuffer updates in native-connection touchpad mode. +- Worked around crash in Zeroconf scanner on Android 9. + +## 1.9.0 (2020-07-13) + +- The first MultiVNC for Android to sport a native backend implementation that supports TightVNC + and TurboVNC, Apple Remote Desktop and Vino's AnonTLS authentication. In the 1.9.x builds this + will start as an opt-in feature. 1.9.x builds will be available via the Google Play Beta program + or F-Droid only. +- Improved scaling so the whole remote screen can be seen at once in portrait orientation. +- Improved the credentials dialog to provide more hints on what is being entered in landscape mode. + +# Version 1.8.x: + +## 1.8.12 (2020-07-16) + +- Added dialog notifying about beta test program. +- Worked around crash in Zeroconf scanner on Android 9. + +## 1.8.11 (2020-07-02) + +- The whole screen scaling user experience is now way smoother thanks to Gaurav Ujjwal. +- The online help text was updated. +- MultiVNC now requires Android 5 in order to make better use of vector drawables. + +## 1.8.10 (2020-06-07) + +- Added support for external mouse's scroll wheels. +- MultiVNC now completely uses vector images. + +## 1.8.9 (2020-03-01) + +- VNC activity is now rotatable thanks to work done by Gaurav Ujjwal. +- Added ability to make key combos with 'Super' key. +- Turned more icons into vector icons. +- Fixed some possible crashes. + +## 1.8.8 (2019-12-31) + +- Now hides navigation bar per default, resulting in more usable screen space. +- Fixed hamburger menu not opening on some Samsung devices. + +## 1.8.7 (2019-12-18) + +- Fixed possible crash in import/export activity. + +## 1.8.6 (2019-11-25) + +- Added ability to connect to MacOS servers. +- Fixed bookmarks sometimes not having a name. + +## 1.8.5 (2019-11-17) + +- Handle right-mouse-button-clicks from USB-OTG mice the right way. + +## 1.8.4 (2019-10-27) + +- Fix erronous auto-bookmarking of connections started via vnc:// scheme. +- Fix some keyboards not accepting input. +- Fix resending of sent special key combo. + +## 1.8.3 (2019-10-17) + +- Main menu layout improved on small tablet devices. +- Fixed two possible crashes. + +## 1.8.2 (2019-09-29) + +- Some UI fixes for Android Kitkat. + +## 1.8.1 (2019-09-16) + +- More adaptations for tablets. +- Fixed two possible crashes. + +## 1.8.0 (2019-09-14) + +- Updated UI to use Material Design. +- Made remote desktop view use more screen real estate. +- Changed default color mode to TrueColor. +- Fixed possible crash on app start. + +# Version 1.7.x: + +## 1.7.10 (2019-09-04) + +- Fixed a possible race condition and subsequent crash on app start. + +## 1.7.9 (2019-07-31) + +- Fixed a possible crash. +- Added Russian translation. + +## 1.7.8 (2019-07-06) + +- Added Italian translation. + +## 1.7.7 (2019-06-30) + +- Fixed possible crash on ChromeBox. + +## 1.7.6 (2019-04-07) + +- Inform user when there are no bookmarks to create shortcuts with. +- Fixed possible crash when importing settings. + +## 1.7.5 (2019-01-15) + +- Fixed possible crash when closing the app. + +## 1.7.4 (2019-01-13) + +- Fixed possible crash in cursor handling. + +## 1.7.3 (2019-01-12) + +- Some updates to the server discovery module. +- Fixed minor issues in german translation. + +## 1.7.2 (2018-12-07) + +- Fixed another possible crash. +- Added german translation. + +## 1.7.1 (2018-12-02) + +- Fix crash on connection initialisation. + +## 1.7.0 (2018-11-18) + +- Updated the UI theming to use the default look on the respective Android version. +- Made the import/export UI more usable. +- Updated the launcher icon to be more hi-res. +- Removed the ad system. + +# Version 1.6.x: + +## 1.6.4 (2014-01-07) + +- Fix immediate crash upon connecting on some devices. + +## 1.6.3 (2013-06-27) + +- Fix hidden menu items on some devices. +- Fix Zeroconf/Bonjour/mDNS VNC server discovery on Android 4.2 devices. + +## 1.6.2 (2013-06-06) + +- Hide Actionbar when there is a hardware menu button. +- Removed broken hungarian translation. + +## 1.6.1 (2013-03-11) + +- Fixed ANR that could sometimes occur when connection broke. +- Fixed crash in discovered servers list. + +## 1.6.0 (2013-02-03) + +- Added support for hardware keyboards and mice. Kudos to Kevin Pansky. +- Added copy&paste function to and from Android clipboard. +- Virtual mouse button click-and-drag now also works on Honeycomb and newer devices. +- A bit more screen real estate on ICS and newer devices by hiding the status bar. + +# Version 1.5.x: + +## 1.5.2 (2012-12-26) + +- Hopefully fix crash on dialog dismissal. + +## 1.5.1 (2012-12-18) + +- Prevent ActionBar from stealing keyboard focus. + +## 1.5.0 (2012-12-14) + +- Full IPv6 support on Honeycomb and newer! +- Added help screen. +- Improved touchpad mode. +- Fixed 'Key Combo' button being focused when soft keyboard was activated. +- Fixed virtual mouse button toggling. +- Fixed bookmarked connections. +- Fixed ActionBar taking focus on soft keyboard toggle. +- Fixed a lot of maybe-crash situations. +- Removed broken Flattr button. + +# Version 1.4.x: + +## 1.4.10 (2012-08-23) + +- Added button to be able to rate the app. + +## 1.4.9 (2012-08-13) + +- Fix crash introduced by the 1.4.8 fix. + +## 1.4.8 (2012-08-12) + +- Fix an ANR error when pausing the app. + +## 1.4.7 (2012-07-15) + +- Fix hang after password entry. Should also work with bookmarks now. + +## 1.4.6 (2012-07-14) + +- Add menu item to toggle pointer highlighting. + +## 1.4.5 (2012-06-26) + +- Fix the 1.4.4 fix. + +## 1.4.4 (2012-06-23) + +- Fix crash on startup on Honeycomb or newer devices. + +## 1.4.3 (2012-06-18) + +- Hopefully fixed keyboard disappearing bug. + +## 1.4.2 (2012-06-15) + +- Fixed crash on mouse button press. + +## 1.4.1 (2012-06-14) + +- Fixed app not starting on Honeycomb devices. + +## 1.4.0 (2012-06-12) + +- ICS and Tablet Support! +- Added password prompt. Shown when no password was given but one is required. +- Greatly improved key handling. Most special symbols work now. +- Fixed a nasty app restart bug on Honeycomb and newer devices. +- Fixed some possible crashes. diff --git a/android/README.md b/android/README.md index e2e33e6c..f88e3038 100644 --- a/android/README.md +++ b/android/README.md @@ -1,53 +1,53 @@ -# AndroidMultiVNC - -This README mostly contains technical information about MultiVNC for Android. - -## Building the Android Version - -To prepare the source tree, do the following in the root of the repository: - -* `git submodule update --init` -* `./prepareLibreSSL.sh` - -After that setup step, simply fire up Android Studio and build the app from -the `android` directory. - -## Technical Notes - -### Supported URL Schemes - -* `vnc://host:port` - -### I/O Architecture - -* MainMenuActivity - * launches VncCanvasActivity - -* VncCanvasActivity - * contains VncCanvas, which is a GLSurfaceView - * contains UI buttons, ZoomControls - * contains PointerInputHandler - * creates a new VNCConn, connects it with the VncCanvas and sets it up from a ConnectionBean - -* VNCConn - * encapsulates the native rfbClient, allows querying info from it as well as triggering actions - * facilitates threaded input/output - -* InputHandler - * gets scaling from VncCanvas - * sends pointer events (from touch, gestures, mouse, stylus) to VncCanvas - -* VncCanvas - * receives touch/motion events from system, pipes them to InputHandler - * receives pointer events from InputHandler, pipes them to VNCConn - * receives key events from VncCanvasActivity, pipes them to VNCConn - * draws a VNCConn's rfbClients's framebuffer - * OpenGL setup and drawing is done in managed code - * framebuffer data is bound to texture by native code - * contains some UI as well (Toast and Dialog) - -* Framebuffer Abstractions - * none, framebuffer completely resides in native part - - - +# AndroidMultiVNC + +This README mostly contains technical information about MultiVNC for Android. + +## Building the Android Version + +To prepare the source tree, do the following in the root of the repository: + +* `git submodule update --init` +* `./prepareLibreSSL.sh` + +After that setup step, simply fire up Android Studio and build the app from +the `android` directory. + +## Technical Notes + +### Supported URL Schemes + +* `vnc://host:port` + +### I/O Architecture + +* MainMenuActivity + * launches VncCanvasActivity + +* VncCanvasActivity + * contains VncCanvas, which is a GLSurfaceView + * contains UI buttons, ZoomControls + * contains PointerInputHandler + * creates a new VNCConn, connects it with the VncCanvas and sets it up from a ConnectionBean + +* VNCConn + * encapsulates the native rfbClient, allows querying info from it as well as triggering actions + * facilitates threaded input/output + +* InputHandler + * gets scaling from VncCanvas + * sends pointer events (from touch, gestures, mouse, stylus) to VncCanvas + +* VncCanvas + * receives touch/motion events from system, pipes them to InputHandler + * receives pointer events from InputHandler, pipes them to VNCConn + * receives key events from VncCanvasActivity, pipes them to VNCConn + * draws a VNCConn's rfbClients's framebuffer + * OpenGL setup and drawing is done in managed code + * framebuffer data is bound to texture by native code + * contains some UI as well (Toast and Dialog) + +* Framebuffer Abstractions + * none, framebuffer completely resides in native part + + + diff --git a/android/app/build.gradle b/android/app/build.gradle index 389220da..b94b2cd7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,58 +1,58 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlinx-serialization' - -android { - compileSdkVersion 34 - - defaultConfig { - applicationId "com.coboltforge.dontmind.multivnc" - minSdkVersion 21 - targetSdkVersion 34 - versionCode 101 - versionName "2.1.8" - externalNativeBuild { - cmake { - // specify explicit target list to exclude examples, tests, utils. etc from used libraries - targets "ssl", "crypto", "libssh2", "sshtunnel", "turbojpeg-static", "vncclient", "vncconn" , "vnccanvas" - } - } - } - - buildTypes { - release { - minifyEnabled true - shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' - } - } - compileOptions { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' - } - kotlinOptions { - jvmTarget = '1.8' - } - externalNativeBuild { - cmake { - version '3.22.1' - path 'src/main/cpp/CMakeLists.txt' - } - } - namespace 'com.coboltforge.dontmind.multivnc' -} - -dependencies { - def room_version = "2.6.1" - - implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation "androidx.lifecycle:lifecycle-common-java8:2.8.6" - implementation "androidx.room:room-runtime:$room_version" - annotationProcessor "androidx.room:room-compiler:$room_version" - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" -} +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlinx-serialization' + +android { + compileSdkVersion 34 + + defaultConfig { + applicationId "com.coboltforge.dontmind.multivnc" + minSdkVersion 21 + targetSdkVersion 34 + versionCode 101 + versionName "2.1.8" + externalNativeBuild { + cmake { + // specify explicit target list to exclude examples, tests, utils. etc from used libraries + targets "ssl", "crypto", "libssh2", "sshtunnel", "turbojpeg-static", "vncclient", "vncconn" , "vnccanvas" + } + } + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } + kotlinOptions { + jvmTarget = '1.8' + } + externalNativeBuild { + cmake { + version '3.22.1' + path 'src/main/cpp/CMakeLists.txt' + } + } + namespace 'com.coboltforge.dontmind.multivnc' +} + +dependencies { + def room_version = "2.6.1" + + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation "androidx.lifecycle:lifecycle-common-java8:2.8.6" + implementation "androidx.room:room-runtime:$room_version" + annotationProcessor "androidx.room:room-compiler:$room_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 48ea190b..b2eace71 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,79 +1,79 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/assets/help.html b/android/app/src/main/assets/help.html index b76e3d60..62a4ee42 100644 --- a/android/app/src/main/assets/help.html +++ b/android/app/src/main/assets/help.html @@ -1,46 +1,46 @@ - - - - -

Using MultiVNC

-Welcome to the MultiVNC help page! This will give a short introduction on the viewer's basic operation. - -

Basic Control

-After connecting, you start out in View mode, where you can actually see the remote desktop. Your pointer will be highlighted by a -purple circle - you can turn that feature off via the menu. -

-At the bottom of the screen will be three virtual mouse buttons. These can -also be turned off, but you will want to use them, as they enable you to drag things around on the remote desktop. When virtual mouse buttons -are disabled, a single tap anywhere on the screen will emulate a left mouse button click. A double tap will send a right mouse button click -to the remote side. -

-The keyboard can be toggled on and off via the menu or the keyboard button at the bottom of the screen. -

-Copy & Paste to and from the Android clipboard is done automatically, MultiVNC keeps the remote's clipboard in sync with the local one. -Just use the usual means of clipboard manipulation on the remote (Ctrl-C & Ctrl-V on Windows/OS X, select and middle mouse button on Unix). -

-Scrolling up/down can be done using the hardware volume keys of your device. - -

Gestures

-

Scaling

-Zooming in and out of the screen is hardware accelerated via OpenGL and can be initiated in two ways: First is simply with a two-finger -pinch gesture. On low-end devices that do not support multitouch, zooming can also be done via the zoom controls at the bottom of the screen. -

Swipe Arrow Keys

-By swiping left, right, up or down with two fingers, you can send a left, right, up or down arrow key to the remote side. Handy for controlling presentation -slides or the like. - - - -

Touchpad Mode

-This is a really fast remote control mode specific to MultiVNC where no screen contents are transmitted at all. Intended for situations -where you do not need to see the remote screen, e. g. for remote controlling your laptop connected to a projector from your phone during a -presentation. Can be enabled via the menu. - - - -

Bookmarking

-If you want to bookmark the current connection, simply tap the bookmark save entry. A bookmark will be created for later access. - - - - + + + + +

Using MultiVNC

+Welcome to the MultiVNC help page! This will give a short introduction on the viewer's basic operation. + +

Basic Control

+After connecting, you start out in View mode, where you can actually see the remote desktop. Your pointer will be highlighted by a +purple circle - you can turn that feature off via the menu. +

+At the bottom of the screen will be three virtual mouse buttons. These can +also be turned off, but you will want to use them, as they enable you to drag things around on the remote desktop. When virtual mouse buttons +are disabled, a single tap anywhere on the screen will emulate a left mouse button click. A double tap will send a right mouse button click +to the remote side. +

+The keyboard can be toggled on and off via the menu or the keyboard button at the bottom of the screen. +

+Copy & Paste to and from the Android clipboard is done automatically, MultiVNC keeps the remote's clipboard in sync with the local one. +Just use the usual means of clipboard manipulation on the remote (Ctrl-C & Ctrl-V on Windows/OS X, select and middle mouse button on Unix). +

+Scrolling up/down can be done using the hardware volume keys of your device. + +

Gestures

+

Scaling

+Zooming in and out of the screen is hardware accelerated via OpenGL and can be initiated in two ways: First is simply with a two-finger +pinch gesture. On low-end devices that do not support multitouch, zooming can also be done via the zoom controls at the bottom of the screen. +

Swipe Arrow Keys

+By swiping left, right, up or down with two fingers, you can send a left, right, up or down arrow key to the remote side. Handy for controlling presentation +slides or the like. + + + +

Touchpad Mode

+This is a really fast remote control mode specific to MultiVNC where no screen contents are transmitted at all. Intended for situations +where you do not need to see the remote screen, e. g. for remote controlling your laptop connected to a projector from your phone during a +presentation. Can be enabled via the menu. + + + +

Bookmarking

+If you want to bookmark the current connection, simply tap the bookmark save entry. A bookmark will be created for later access. + + + + diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 21ebd60a..cc5bf588 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -1,95 +1,95 @@ - -cmake_minimum_required(VERSION 3.4.1) - -project (MultiVNC C ASM) # defaults to C, the ASM is needed for LibreSSL - -set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared Libs" FORCE) - -# Requred to enable SIMD support on ARM -if (CMAKE_ANDROID_ARCH STREQUAL "arm64") - set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=aarch64-linux-android${ANDROID_NATIVE_API_LEVEL}") -elseif (CMAKE_ANDROID_ARCH STREQUAL "arm") - set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=arm-linux-androideabi${ANDROID_NATIVE_API_LEVEL}") -endif () - -# build libJPEG -message("-----libjpeg-turbo-----") -set(libjpeg_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libjpeg-turbo) -set(libjpeg_build_DIR ${CMAKE_BINARY_DIR}/libjpeg) -# adds a source subdir with specifying a build output dir -add_subdirectory(${libjpeg_src_DIR} ${libjpeg_build_DIR}) -# set these variables so FindJPEG can find the library -set(JPEG_LIBRARY ${libjpeg_build_DIR}/libturbojpeg.a CACHE FILEPATH "") -set(JPEG_INCLUDE_DIR ${libjpeg_src_DIR} CACHE PATH "") -# set include directories so dependent code can find the headers -include_directories( - ${libjpeg_src_DIR} - ${libjpeg_build_DIR} -) - -# build libressl -message("-----LibreSSL-----") -set(libssl_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libressl) -set(libssl_build_DIR ${CMAKE_BINARY_DIR}/libssl) -# adds a source subdir with specifying a build output dir -add_subdirectory(${libssl_src_DIR} ${libssl_build_DIR}) -# set these variables so Findssl can find the library -set(OPENSSL_SSL_LIBRARY ${libssl_build_DIR}/ssl/libssl.a CACHE FILEPATH "") -set(OPENSSL_CRYPTO_LIBRARY ${libssl_build_DIR}/crypto/libcrypto.a CACHE FILEPATH "") -set(OPENSSL_INCLUDE_DIR ${libssl_src_DIR}/include CACHE PATH "") -# set include directories so dependent code can find the headers -include_directories( - ${CMAKE_BINARY_DIR}/include # generates headers in there - ${libssl_src_DIR}/include -) - -# build libssh2 -message("-----libssh2-----") -SET(BUILD_TESTING OFF CACHE BOOL "Build libssh2 test suite") -set(libssh2_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libssh2) -set(libssh2_build_DIR ${CMAKE_BINARY_DIR}/libssh2) -# adds a source subdir with specifying a build output dir -add_subdirectory(${libssh2_src_DIR} ${libssh2_build_DIR}) -# set these variables so FindLibSSH2 can find the library -set(LIBSSH2_LIBRARY ${libssh2_build_DIR}/src/libssh2.a CACHE FILEPATH "") -set(LIBSSH2_INCLUDE_DIR ${libssh2_src_DIR}/include CACHE PATH "") -# set include directories so dependent code can find the headers -include_directories( - ${libssh2_src_DIR}/include -) - -# build libsshtunnel -message("-----libsshtunnel-----") -set(libsshtunnel_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libsshtunnel) -set(libsshtunnel_build_DIR ${CMAKE_BINARY_DIR}/libsshtunnel) -# adds a source subdir with specifying a build output dir -add_subdirectory(${libsshtunnel_src_DIR} ${libsshtunnel_build_DIR}) -# set include directories so dependent code can find the headers -include_directories( - ${libsshtunnel_src_DIR}/include -) - -# build LibVNCClient -message("-----LibVNCClient-----") -set(libvnc_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libvncserver) -set(libvnc_build_DIR ${CMAKE_BINARY_DIR}/libvnc) -# adds a source subdir with specifying a build output dir -add_subdirectory(${libvnc_src_DIR} ${libvnc_build_DIR}) -# need to add the build dir to include dirs as well because of generated rfbconfig.h -include_directories( - ${libvnc_src_DIR}/include - ${libvnc_build_DIR}/include -) - -# build VNCConn -add_library(vncconn SHARED vncconn.c) -target_link_libraries(vncconn - log - sshtunnel - vncclient) - -# build VNCCanvas -add_library(vnccanvas SHARED vnccanvas.c) -target_link_libraries(vnccanvas - log - GLESv1_CM) + +cmake_minimum_required(VERSION 3.4.1) + +project (MultiVNC C ASM) # defaults to C, the ASM is needed for LibreSSL + +set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared Libs" FORCE) + +# Requred to enable SIMD support on ARM +if (CMAKE_ANDROID_ARCH STREQUAL "arm64") + set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=aarch64-linux-android${ANDROID_NATIVE_API_LEVEL}") +elseif (CMAKE_ANDROID_ARCH STREQUAL "arm") + set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=arm-linux-androideabi${ANDROID_NATIVE_API_LEVEL}") +endif () + +# build libJPEG +message("-----libjpeg-turbo-----") +set(libjpeg_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libjpeg-turbo) +set(libjpeg_build_DIR ${CMAKE_BINARY_DIR}/libjpeg) +# adds a source subdir with specifying a build output dir +add_subdirectory(${libjpeg_src_DIR} ${libjpeg_build_DIR}) +# set these variables so FindJPEG can find the library +set(JPEG_LIBRARY ${libjpeg_build_DIR}/libturbojpeg.a CACHE FILEPATH "") +set(JPEG_INCLUDE_DIR ${libjpeg_src_DIR} CACHE PATH "") +# set include directories so dependent code can find the headers +include_directories( + ${libjpeg_src_DIR} + ${libjpeg_build_DIR} +) + +# build libressl +message("-----LibreSSL-----") +set(libssl_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libressl) +set(libssl_build_DIR ${CMAKE_BINARY_DIR}/libssl) +# adds a source subdir with specifying a build output dir +add_subdirectory(${libssl_src_DIR} ${libssl_build_DIR}) +# set these variables so Findssl can find the library +set(OPENSSL_SSL_LIBRARY ${libssl_build_DIR}/ssl/libssl.a CACHE FILEPATH "") +set(OPENSSL_CRYPTO_LIBRARY ${libssl_build_DIR}/crypto/libcrypto.a CACHE FILEPATH "") +set(OPENSSL_INCLUDE_DIR ${libssl_src_DIR}/include CACHE PATH "") +# set include directories so dependent code can find the headers +include_directories( + ${CMAKE_BINARY_DIR}/include # generates headers in there + ${libssl_src_DIR}/include +) + +# build libssh2 +message("-----libssh2-----") +SET(BUILD_TESTING OFF CACHE BOOL "Build libssh2 test suite") +set(libssh2_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libssh2) +set(libssh2_build_DIR ${CMAKE_BINARY_DIR}/libssh2) +# adds a source subdir with specifying a build output dir +add_subdirectory(${libssh2_src_DIR} ${libssh2_build_DIR}) +# set these variables so FindLibSSH2 can find the library +set(LIBSSH2_LIBRARY ${libssh2_build_DIR}/src/libssh2.a CACHE FILEPATH "") +set(LIBSSH2_INCLUDE_DIR ${libssh2_src_DIR}/include CACHE PATH "") +# set include directories so dependent code can find the headers +include_directories( + ${libssh2_src_DIR}/include +) + +# build libsshtunnel +message("-----libsshtunnel-----") +set(libsshtunnel_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libsshtunnel) +set(libsshtunnel_build_DIR ${CMAKE_BINARY_DIR}/libsshtunnel) +# adds a source subdir with specifying a build output dir +add_subdirectory(${libsshtunnel_src_DIR} ${libsshtunnel_build_DIR}) +# set include directories so dependent code can find the headers +include_directories( + ${libsshtunnel_src_DIR}/include +) + +# build LibVNCClient +message("-----LibVNCClient-----") +set(libvnc_src_DIR ${CMAKE_SOURCE_DIR}/../../../../../libvncserver) +set(libvnc_build_DIR ${CMAKE_BINARY_DIR}/libvnc) +# adds a source subdir with specifying a build output dir +add_subdirectory(${libvnc_src_DIR} ${libvnc_build_DIR}) +# need to add the build dir to include dirs as well because of generated rfbconfig.h +include_directories( + ${libvnc_src_DIR}/include + ${libvnc_build_DIR}/include +) + +# build VNCConn +add_library(vncconn SHARED vncconn.c) +target_link_libraries(vncconn + log + sshtunnel + vncclient) + +# build VNCCanvas +add_library(vnccanvas SHARED vnccanvas.c) +target_link_libraries(vnccanvas + log + GLESv1_CM) diff --git a/android/app/src/main/cpp/vncconn.c b/android/app/src/main/cpp/vncconn.c index bd2b6037..4de6cb4f 100644 --- a/android/app/src/main/cpp/vncconn.c +++ b/android/app/src/main/cpp/vncconn.c @@ -1,683 +1,683 @@ -/* - vncconn.c: VNCConn.java native backend. - This file is part of MultiVNC, a Multicast-enabled cross-platform - VNC viewer. - Copyright (C) 2009 - 2019 Christian Beier - MultiVNC is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - MultiVNC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -#include -#include -#include -#include -#include -#include - -#define TAG "VNCConn-native" - -/* id for the managed VNCConn */ -#define VNCCONN_OBJ_ID (void*)1 -#define VNCCONN_ENV_ID (void*)2 -#define VNCCONN_SSH_ID (void*)3 - - -/* - * Modeled after rfbDefaultClientLog: - * - with Android log functions - * - without time stamping as the Android logging does this already - * There's no per-connection log since we cannot find out which client - * called the logger function :-( - */ -static void logcat_info(const char *format, ...) -{ - va_list args; - - if(!rfbEnableClientLogging) - return; - - va_start(args, format); - __android_log_vprint(ANDROID_LOG_INFO, TAG, format, args); - va_end(args); -} - -static void logcat_err(const char *format, ...) -{ - va_list args; - - if(!rfbEnableClientLogging) - return; - - va_start(args, format); - __android_log_vprint(ANDROID_LOG_ERROR, TAG, format, args); - va_end(args); -} - -static void log_obj_tostring(JNIEnv *env, jobject obj, int prio, const char *format, ...) { - - if(!env) - return; - - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); - jstring jStr = (*env)->CallObjectMethod(env, obj, mid); - const char *cStr = (*env)->GetStringUTFChars(env, jStr, NULL); - if(!cStr) - return; - - va_list args; - - /* prefix format string with result of toString() */ - const size_t format_buf_len = 1024; - char *format_buf[format_buf_len]; - snprintf((char*)format_buf, format_buf_len, "%s: %s", cStr, format); - - va_start(args, format); - __android_log_vprint(prio, TAG, (char*)format_buf, args); - va_end(args); - - (*env)->ReleaseStringUTFChars(env, jStr, cStr); -} - - -/* - * The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). - * JNI_OnLoad must return the JNI version needed by the native library. - * We use this to wire up LibVNCClient logging to logcat. - */ -JNIEXPORT jint JNI_OnLoad(JavaVM __unused * vm, void __unused * reserved) { - - __android_log_print(ANDROID_LOG_DEBUG, TAG, "libvncconn loading\n"); - - rfbClientLog = logcat_info; - rfbClientErr = logcat_err; - - // this is not thread-safe so run here once - if(ssh_tunnel_init()) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "libsshtunnel initialization failed\n"); - } - - return JNI_VERSION_1_6; -} - - -/** - * Get the managed VNCConn's rfbClient. - */ -static rfbClient* getRfbClient(JNIEnv *env, jobject conn) { - - if(!env) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "getRfbClient failed due to env NULL"); - return NULL; - } - - rfbClient* cl = NULL; - jclass cls = (*env)->GetObjectClass(env, conn); - jfieldID fid = (*env)->GetFieldID(env, cls, "rfbClient", "J"); - if (fid == 0) - return NULL; - - cl = (rfbClient*)(long)(*env)->GetLongField(env, conn, fid); - - return cl; -} - -/** - * Set the managed VNCConn's rfbClient. - */ -static jboolean setRfbClient(JNIEnv *env, jobject conn, rfbClient* cl) { - - if(!env) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "setRfbClient failed due to env NULL"); - return JNI_FALSE; - } - - jclass cls = (*env)->GetObjectClass(env, conn); - jfieldID fid = (*env)->GetFieldID(env, cls, "rfbClient", "J"); - if (fid == 0) - return JNI_FALSE; - - (*env)->SetLongField(env, conn, fid, (long)cl); - - return JNI_TRUE; -} - - -static void onFramebufferUpdateFinished(rfbClient* client) -{ - if(!client) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onFramebufferUpdateFinished failed due to client NULL"); - return; - } - - jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); - JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); - - if(!env || !obj) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onFramebufferUpdateFinished failed due to env or obj NULL"); - return; - } - - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "onFramebufferUpdateFinished", "()V"); - (*env)->CallVoidMethod(env, obj, mid); -} - -static void onGotCutText(rfbClient *client, const char *text, int len) -{ - if(!client) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGotCutText failed due to client NULL"); - return; - } - - jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); - JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); - - if(!env || !obj) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGotCutText failed due to env or obj NULL"); - return; - } - - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "onGotCutText", "([B)V"); - jbyteArray jBytes = (*env)->NewByteArray(env, len); - (*env)->SetByteArrayRegion(env, jBytes, 0, len, (jbyte *) text); - (*env)->CallVoidMethod(env, obj, mid, jBytes); -} - -static char *onGetPassword(rfbClient *client) -{ - if(!client) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetPassword failed due to client NULL"); - return NULL; - } - - jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); - JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); - - if(!env || !obj) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetPassword failed due to env or obj NULL"); - return NULL; - } - - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "onGetPassword", "()Ljava/lang/String;"); - jstring passwd = (*env)->CallObjectMethod(env, obj, mid); - - const char *cPasswd = (*env)->GetStringUTFChars(env, passwd, NULL); - if(!cPasswd) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetPassword failed due to cPasswd NULL"); - return NULL; - } - char *cPasswdCopy = strdup(cPasswd); // this is free()'d by LibVNCClient in HandleVncAuth() - (*env)->ReleaseStringUTFChars(env, passwd, cPasswd); - - return cPasswdCopy; -} - -static rfbCredential *onGetCredential(rfbClient *client, int credentialType) -{ - if(!client) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to client NULL"); - return NULL; - } - - if (credentialType != rfbCredentialTypeUser) { - //Only user credentials (i.e. username & password) are currently supported - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to unsupported credential type %d requested", credentialType); - return NULL; - } - - jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); - JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); - - if(!env || !obj) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to env or obj NULL"); - return NULL; - } - - // Retrieve credentials - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "onGetUserCredential", "()Lcom/coboltforge/dontmind/multivnc/VNCConn$UserCredential;"); - jobject jCredential = (*env)->CallObjectMethod(env, obj, mid); - - // Extract username & password - jclass jCredentialCls = (*env)->GetObjectClass(env, jCredential); - jfieldID usernameField = (*env)->GetFieldID(env, jCredentialCls, "username", "Ljava/lang/String;"); - jstring jUsername = (*env)->GetObjectField(env, jCredential, usernameField); - - jfieldID passwordField = (*env)->GetFieldID(env, jCredentialCls, "password", "Ljava/lang/String;"); - jstring jPassword = (*env)->GetObjectField(env, jCredential, passwordField); - - // Create native rfbCredential, this is free()'d by FreeUserCredential() in LibVNCClient - rfbCredential *credential = malloc(sizeof(rfbCredential)); - if(!credential) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to credential NULL"); - return NULL; - } - - const char *cUsername = (*env)->GetStringUTFChars(env, jUsername, NULL); - if(!cUsername) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to cUsername NULL"); - return NULL; - } - credential->userCredential.username = strdup(cUsername); - (*env)->ReleaseStringUTFChars(env, jUsername, cUsername); - - const char *cPassword = (*env)->GetStringUTFChars(env, jPassword, NULL); - if(!cPassword) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to cPassword NULL"); - return NULL; - } - credential->userCredential.password = strdup(cPassword); - (*env)->ReleaseStringUTFChars(env, jPassword, cPassword); - - return credential; -} - -MallocFrameBufferProc defaultMallocFramebuffer; -/** - * This basically is just a thin wrapper around the libraries built-in framebuffer resizing that - * conveys width/height changes up to the managed VNCConn. - * @param client - * @return - */ -static rfbBool onNewFBSize(rfbClient *client) -{ - if(!client) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onNewFBSize failed due to client NULL"); - return FALSE; - } - - jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); - JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); - - if(!env || !obj) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onNewFBSize failed due to env or obj NULL"); - return FALSE; - } - - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "onNewFramebufferSize", "(II)V"); - (*env)->CallVoidMethod(env, obj, mid, client->width, client->height); - - return defaultMallocFramebuffer(client); -} - -static void onSshError(void *client, ssh_tunnel_error_t error_code, const char *error_message) { - __android_log_print(ANDROID_LOG_ERROR, TAG,"libsshtunnel error: %d - %s", error_code, error_message); -} - -/** - Decide whether or not the SSH tunnel setup should continue - based on the current host and its fingerprint. - Business logic is up to the implementer in a real app, i.e. - compare keys, ask user etc... - @return -1 if tunnel setup should be aborted - 0 if tunnel setup should continue - */ -static int onSshFingerprintCheck(void *client, - const char *fingerprint, - int fingerprint_len, - const char *host) -{ - if(!client) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onSshFingerprintCheck failed due to client NULL"); - return -1; - } - - jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); - JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); - - if(!env || !obj) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "onSshFingerprintCheck failed due to env or obj NULL"); - return -1; - } - - jbyteArray jFingerprint = (*env)->NewByteArray(env, (jsize)fingerprint_len); - (*env)->SetByteArrayRegion(env, jFingerprint, 0, (jsize)fingerprint_len, (jbyte *) fingerprint); - - jclass cls = (*env)->GetObjectClass(env, obj); - jmethodID mid = (*env)->GetMethodID(env, cls, "onSshFingerprintCheck", "(Ljava/lang/String;[B)I"); - return (*env)->CallIntMethod(env, obj, mid, (*env)->NewStringUTF(env, host), jFingerprint); -} - - -/** - * Allocates and sets up the VNCConn's rfbClient. - * @param env - * @param obj - * @param bytesPerPixel - * @return - */ -static jboolean setupClient(JNIEnv *env, jobject obj, jint bytesPerPixel) { - - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "setupClient()"); - - if(getRfbClient(env, obj)) { /* already set up */ - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "setupClient() already done"); - return JNI_FALSE; - } - - rfbClient *cl = NULL; - - /* - * We only allow 24 and 15 bit colour, as the GL canvas is not able to digest any other format - * _directly_, without converting the whole client framebuffer byte-per-byte. - */ - switch(bytesPerPixel) { - case 2: - // 15-bit colour - cl = rfbGetClient(5, 3, 2); - // the GL canvas is using GL_UNSIGNED_SHORT_5_5_5_1 for 15-bit colour depth - cl->format.redShift = 11; - cl->format.greenShift = 6; - cl->format.blueShift = 1; - break; - case 4: - // 24-bit colour, occupying 4 bytes - cl = rfbGetClient(8, 3, 4); - break; - default: - break; - } - - if(!cl) { - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "setupClient() failed due to client NULL"); - return JNI_FALSE; - } - - // set callbacks - cl->FinishedFrameBufferUpdate = onFramebufferUpdateFinished; - cl->GotXCutText = onGotCutText; - cl->GetPassword = onGetPassword; - cl->GetCredential = onGetCredential; - defaultMallocFramebuffer = cl->MallocFrameBuffer; // save default one - cl->MallocFrameBuffer = onNewFBSize; // set new one - - // set flags - cl->canHandleNewFBSize = TRUE; - - /* - * Save pointers to the managed VNCConn and env in the rfbClient for use in the onXYZ callbacks. - * In addition to rfbProcessServerMessage(), we have to do this are as some callbacks (namely - * related to authentication) are called before rfbProcessServerMessage(). - */ - rfbClientSetClientData(cl, VNCCONN_OBJ_ID, obj); - rfbClientSetClientData(cl, VNCCONN_ENV_ID, env); - - setRfbClient(env, obj, cl); - - return JNI_TRUE; -} - -JNIEXPORT void JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbShutdown(JNIEnv *env, jobject obj) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) { - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbShutdown() closing connection"); - rfbCloseSocket(cl->sock); - - ssh_tunnel_close(rfbClientGetClientData(cl, VNCCONN_SSH_ID)); - - if(cl->frameBuffer) { - free(cl->frameBuffer); - cl->frameBuffer = 0; - } - - rfbClientCleanup(cl); - // rfbClientCleanup does not zero the pointer - setRfbClient(env, obj, 0); - } -} - - -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbInit(JNIEnv *env, jobject obj, jstring host, jint port, jint repeaterId, jint bytesPerPixel, - jstring encodingsString, - jboolean enableCompress, - jboolean enableJPEG, - jint compressLevel, - jint qualityLevel, - jstring ssh_host, - jstring ssh_user, - jstring ssh_password, - jbyteArray ssh_priv_key, - jstring ssh_priv_key_password) { - - if(!getRfbClient(env, obj)) - setupClient(env, obj, bytesPerPixel); - - rfbClient *cl = getRfbClient(env, obj); - - if(!cl) { - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() failed due to client NULL"); - return JNI_FALSE; - } - - cl->programName = "VNCConn"; - - /* - * Get all C representations from managec code. The checks for NULL are needed - * as the Get*() methods fail on a null reference. - */ - const char *cHost = host ? (*env)->GetStringUTFChars(env, host, NULL) : NULL; - const char *cEncodingsString = encodingsString ? (*env)->GetStringUTFChars(env, encodingsString, NULL) : NULL; - const char *cSshHost = ssh_host ? (*env)->GetStringUTFChars(env, ssh_host, NULL) : NULL; - const char *cSshUser = ssh_user ? (*env)->GetStringUTFChars(env, ssh_user, NULL) : NULL; - const char *cSshPassword = ssh_password ? (*env)->GetStringUTFChars(env, ssh_password, NULL) : NULL; - jbyte *cSshPrivKey = ssh_priv_key ? (*env)->GetByteArrayElements(env, ssh_priv_key, NULL) : NULL; - jsize cSshPrivKeyLen = ssh_priv_key ? (*env)->GetArrayLength(env, ssh_priv_key) : 0; - const char *cSshPrivKeyPassword = ssh_priv_key_password ? (*env)->GetStringUTFChars(env, ssh_priv_key_password, NULL) : NULL; - - // see whether we are ssh-tunneling or not - int is_ssh_connection = cSshHost != NULL; - ssh_tunnel_t *tunnel = NULL; - - if(is_ssh_connection) { - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbInit() setting up SSH-tunneled connection"); - // ssh-tunneling, check whether it's password- or key-based - if(cSshPassword) { - // password-based - tunnel = ssh_tunnel_open_with_password(cSshHost, 22, cSshUser, cSshPassword, cHost, port, cl, onSshFingerprintCheck, onSshError); - } else { - // key-based - tunnel = ssh_tunnel_open_with_privkey(cSshHost, 22, cSshUser, (char*)cSshPrivKey, cSshPrivKeyLen, cSshPrivKeyPassword, cHost, port, cl, onSshFingerprintCheck, onSshError); - } - - cl->serverHost = strdup("127.0.0.1"); - if(tunnel) // might be NULL if ssh setup failed - cl->serverPort = ssh_tunnel_get_port(tunnel); - rfbClientSetClientData(cl, VNCCONN_SSH_ID, tunnel); - } else { - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbInit() setting up direct connection"); - // direct connection - if(cHost) // strdup(NULL) is UB - cl->serverHost = strdup(cHost); - - cl->serverPort = port; - // Support short-form (:0, :1) - if(cl->serverPort < 100) - cl->serverPort += 5900; - } - - if(cEncodingsString) // strdup(NULL) is UB - cl->appData.encodingsString = strdup(cEncodingsString); - - cl->appData.compressLevel = compressLevel; - - cl->appData.enableJPEG = enableJPEG; - cl->appData.qualityLevel = qualityLevel; - - - // release all handles to managed strings again - if (cHost) - (*env)->ReleaseStringUTFChars(env, host, cHost); - if(cEncodingsString) - (*env)->ReleaseStringUTFChars(env, encodingsString, cEncodingsString); - if (cSshHost) - (*env)->ReleaseStringUTFChars(env, ssh_host, cSshHost); - if(cSshUser) - (*env)->ReleaseStringUTFChars(env, ssh_user, cSshUser); - if(cSshPassword) - (*env)->ReleaseStringUTFChars(env, ssh_password, cSshPassword); - if (cSshPrivKey) - (*env)->ReleaseByteArrayElements(env, ssh_priv_key, cSshPrivKey, JNI_ABORT); - if (cSshPrivKeyPassword) - (*env)->ReleaseStringUTFChars(env, ssh_priv_key_password, cSshPrivKeyPassword); - - // for both direct and ssh-tunneled connection, set repeater - if(repeaterId >= 0) { - cl->destHost = strdup("ID"); - cl->destPort = repeaterId; - } - - - // check if ssh connection was wanted and succeeded - if(is_ssh_connection && !rfbClientGetClientData(cl, VNCCONN_SSH_ID)) { - // ssh connection failed, bail out now - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() SSH tunnel failed."); - Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbShutdown(env, obj); - return JNI_FALSE; - } - - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbInit() about to connect to '%s', port %d, repeaterId %d\n", cl->serverHost, cl->serverPort, cl->destPort); - - if(!rfbInitClient(cl, 0, NULL)) { - setRfbClient(env, obj, 0); // rfbInitClient() calls rfbClientCleanup() on failure, but this does not zero the ptr - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() connection failed. Cleanup by library."); - // There might be the case that the SSH tunnel got setup alright, but connecting to the VNC server failed. - // In this case we have to dispose of the tunnel explicitly here. - // We cannot use rfbClientGetClientData(cl, VNCCONN_SSH_ID) here as there was already a rfbClientCleanup() - ssh_tunnel_close(tunnel); - return JNI_FALSE; - } - - // if there was an error in alloc_framebuffer(), catch that here - if(!cl->frameBuffer) { - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() failed due to framebuffer NULL"); - Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbShutdown(env, obj); - return JNI_FALSE; - } - - return JNI_TRUE; -} - - -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbProcessServerMessage(JNIEnv *env, jobject obj) { - rfbClient *cl = getRfbClient(env, obj); - - if(!cl) { - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbProcessServerMessage() failed due to client NULL"); - return JNI_FALSE; - } - - /* - * Save pointers to the managed VNCConn and env in the rfbClient for use in the onXYZ callbacks. - * We do this each time here and not in setupClient() because the managed objects get moved - * around by the VM. - */ - rfbClientSetClientData(cl, VNCCONN_OBJ_ID, obj); - rfbClientSetClientData(cl, VNCCONN_ENV_ID, env); - - /* request update and handle response */ - if(!rfbProcessServerMessage(cl, 500)) { - if(errno == EINTR) - return JNI_TRUE; - - log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbProcessServerMessage() failed"); - return JNI_FALSE; - } - return JNI_TRUE; -} - -#pragma clang diagnostic push -#pragma ide diagnostic ignored "hicpp-signed-bitwise" -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSetFramebufferUpdatesEnabled(JNIEnv *env, jobject obj, jboolean enable) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) { - log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbSetFramebufferUpdatesEnabled() %d", enable); - if(enable) { - // set to managed-by-lib again - rfbClientSetUpdateRect(cl, NULL); - // request full update - SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, FALSE); - } else { - // set to no-updates-wanted - rfbRectangle noRect = {0,0,0,0,}; - rfbClientSetUpdateRect(cl, &noRect); - } - return JNI_TRUE; - } - else - return JNI_FALSE; -} -#pragma clang diagnostic pop - -JNIEXPORT jstring JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbGetDesktopName(JNIEnv *env, jobject obj) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) - return (*env)->NewStringUTF(env, cl->desktopName); - else - return NULL; -} - -JNIEXPORT jint JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbGetFramebufferWidth(JNIEnv *env, jobject obj) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) - return cl->width; - else - return 0; -} - -JNIEXPORT jint JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbGetFramebufferHeight(JNIEnv *env, jobject obj) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) - return cl->height; - else - return 0; -} - -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSendKeyEvent(JNIEnv *env, jobject obj, jlong keysym, jboolean down) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) - return (jboolean) SendKeyEvent(cl, (uint32_t) keysym, down); - else - return JNI_FALSE; -} - -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSendPointerEvent(JNIEnv *env, jobject obj, jint x, jint y, jint buttonMask) { - rfbClient *cl = getRfbClient(env, obj); - if(cl) - return (jboolean) SendPointerEvent(cl, x, y, buttonMask); - else - return JNI_FALSE; -} - -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSendClientCutText(JNIEnv *env, jobject obj, jbyteArray bytes) { - rfbClient *cl = getRfbClient(env, obj); - if (cl) { - jbyte *cText = (*env)->GetByteArrayElements(env, bytes, NULL); - int cTextLen = (*env)->GetArrayLength(env, bytes); - jboolean status = SendClientCutText(cl, (char *) cText, cTextLen); - (*env)->ReleaseByteArrayElements(env, bytes, cText, JNI_ABORT); - return status; - } - else - return JNI_FALSE; -} - -JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbIsEncrypted(JNIEnv *env, jobject obj) { - rfbClient *cl = getRfbClient(env, obj); - if (cl) - return cl->tlsSession != NULL || rfbClientGetClientData(cl, VNCCONN_SSH_ID) != NULL; - else - return JNI_FALSE; +/* + vncconn.c: VNCConn.java native backend. + This file is part of MultiVNC, a Multicast-enabled cross-platform + VNC viewer. + Copyright (C) 2009 - 2019 Christian Beier + MultiVNC is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + MultiVNC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#define TAG "VNCConn-native" + +/* id for the managed VNCConn */ +#define VNCCONN_OBJ_ID (void*)1 +#define VNCCONN_ENV_ID (void*)2 +#define VNCCONN_SSH_ID (void*)3 + + +/* + * Modeled after rfbDefaultClientLog: + * - with Android log functions + * - without time stamping as the Android logging does this already + * There's no per-connection log since we cannot find out which client + * called the logger function :-( + */ +static void logcat_info(const char *format, ...) +{ + va_list args; + + if(!rfbEnableClientLogging) + return; + + va_start(args, format); + __android_log_vprint(ANDROID_LOG_INFO, TAG, format, args); + va_end(args); +} + +static void logcat_err(const char *format, ...) +{ + va_list args; + + if(!rfbEnableClientLogging) + return; + + va_start(args, format); + __android_log_vprint(ANDROID_LOG_ERROR, TAG, format, args); + va_end(args); +} + +static void log_obj_tostring(JNIEnv *env, jobject obj, int prio, const char *format, ...) { + + if(!env) + return; + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); + jstring jStr = (*env)->CallObjectMethod(env, obj, mid); + const char *cStr = (*env)->GetStringUTFChars(env, jStr, NULL); + if(!cStr) + return; + + va_list args; + + /* prefix format string with result of toString() */ + const size_t format_buf_len = 1024; + char *format_buf[format_buf_len]; + snprintf((char*)format_buf, format_buf_len, "%s: %s", cStr, format); + + va_start(args, format); + __android_log_vprint(prio, TAG, (char*)format_buf, args); + va_end(args); + + (*env)->ReleaseStringUTFChars(env, jStr, cStr); +} + + +/* + * The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). + * JNI_OnLoad must return the JNI version needed by the native library. + * We use this to wire up LibVNCClient logging to logcat. + */ +JNIEXPORT jint JNI_OnLoad(JavaVM __unused * vm, void __unused * reserved) { + + __android_log_print(ANDROID_LOG_DEBUG, TAG, "libvncconn loading\n"); + + rfbClientLog = logcat_info; + rfbClientErr = logcat_err; + + // this is not thread-safe so run here once + if(ssh_tunnel_init()) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "libsshtunnel initialization failed\n"); + } + + return JNI_VERSION_1_6; +} + + +/** + * Get the managed VNCConn's rfbClient. + */ +static rfbClient* getRfbClient(JNIEnv *env, jobject conn) { + + if(!env) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "getRfbClient failed due to env NULL"); + return NULL; + } + + rfbClient* cl = NULL; + jclass cls = (*env)->GetObjectClass(env, conn); + jfieldID fid = (*env)->GetFieldID(env, cls, "rfbClient", "J"); + if (fid == 0) + return NULL; + + cl = (rfbClient*)(long)(*env)->GetLongField(env, conn, fid); + + return cl; +} + +/** + * Set the managed VNCConn's rfbClient. + */ +static jboolean setRfbClient(JNIEnv *env, jobject conn, rfbClient* cl) { + + if(!env) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "setRfbClient failed due to env NULL"); + return JNI_FALSE; + } + + jclass cls = (*env)->GetObjectClass(env, conn); + jfieldID fid = (*env)->GetFieldID(env, cls, "rfbClient", "J"); + if (fid == 0) + return JNI_FALSE; + + (*env)->SetLongField(env, conn, fid, (long)cl); + + return JNI_TRUE; +} + + +static void onFramebufferUpdateFinished(rfbClient* client) +{ + if(!client) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onFramebufferUpdateFinished failed due to client NULL"); + return; + } + + jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); + JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); + + if(!env || !obj) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onFramebufferUpdateFinished failed due to env or obj NULL"); + return; + } + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "onFramebufferUpdateFinished", "()V"); + (*env)->CallVoidMethod(env, obj, mid); +} + +static void onGotCutText(rfbClient *client, const char *text, int len) +{ + if(!client) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGotCutText failed due to client NULL"); + return; + } + + jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); + JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); + + if(!env || !obj) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGotCutText failed due to env or obj NULL"); + return; + } + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "onGotCutText", "([B)V"); + jbyteArray jBytes = (*env)->NewByteArray(env, len); + (*env)->SetByteArrayRegion(env, jBytes, 0, len, (jbyte *) text); + (*env)->CallVoidMethod(env, obj, mid, jBytes); +} + +static char *onGetPassword(rfbClient *client) +{ + if(!client) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetPassword failed due to client NULL"); + return NULL; + } + + jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); + JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); + + if(!env || !obj) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetPassword failed due to env or obj NULL"); + return NULL; + } + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "onGetPassword", "()Ljava/lang/String;"); + jstring passwd = (*env)->CallObjectMethod(env, obj, mid); + + const char *cPasswd = (*env)->GetStringUTFChars(env, passwd, NULL); + if(!cPasswd) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetPassword failed due to cPasswd NULL"); + return NULL; + } + char *cPasswdCopy = strdup(cPasswd); // this is free()'d by LibVNCClient in HandleVncAuth() + (*env)->ReleaseStringUTFChars(env, passwd, cPasswd); + + return cPasswdCopy; +} + +static rfbCredential *onGetCredential(rfbClient *client, int credentialType) +{ + if(!client) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to client NULL"); + return NULL; + } + + if (credentialType != rfbCredentialTypeUser) { + //Only user credentials (i.e. username & password) are currently supported + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to unsupported credential type %d requested", credentialType); + return NULL; + } + + jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); + JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); + + if(!env || !obj) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to env or obj NULL"); + return NULL; + } + + // Retrieve credentials + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "onGetUserCredential", "()Lcom/coboltforge/dontmind/multivnc/VNCConn$UserCredential;"); + jobject jCredential = (*env)->CallObjectMethod(env, obj, mid); + + // Extract username & password + jclass jCredentialCls = (*env)->GetObjectClass(env, jCredential); + jfieldID usernameField = (*env)->GetFieldID(env, jCredentialCls, "username", "Ljava/lang/String;"); + jstring jUsername = (*env)->GetObjectField(env, jCredential, usernameField); + + jfieldID passwordField = (*env)->GetFieldID(env, jCredentialCls, "password", "Ljava/lang/String;"); + jstring jPassword = (*env)->GetObjectField(env, jCredential, passwordField); + + // Create native rfbCredential, this is free()'d by FreeUserCredential() in LibVNCClient + rfbCredential *credential = malloc(sizeof(rfbCredential)); + if(!credential) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to credential NULL"); + return NULL; + } + + const char *cUsername = (*env)->GetStringUTFChars(env, jUsername, NULL); + if(!cUsername) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to cUsername NULL"); + return NULL; + } + credential->userCredential.username = strdup(cUsername); + (*env)->ReleaseStringUTFChars(env, jUsername, cUsername); + + const char *cPassword = (*env)->GetStringUTFChars(env, jPassword, NULL); + if(!cPassword) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onGetCredential failed due to cPassword NULL"); + return NULL; + } + credential->userCredential.password = strdup(cPassword); + (*env)->ReleaseStringUTFChars(env, jPassword, cPassword); + + return credential; +} + +MallocFrameBufferProc defaultMallocFramebuffer; +/** + * This basically is just a thin wrapper around the libraries built-in framebuffer resizing that + * conveys width/height changes up to the managed VNCConn. + * @param client + * @return + */ +static rfbBool onNewFBSize(rfbClient *client) +{ + if(!client) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onNewFBSize failed due to client NULL"); + return FALSE; + } + + jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); + JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); + + if(!env || !obj) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onNewFBSize failed due to env or obj NULL"); + return FALSE; + } + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "onNewFramebufferSize", "(II)V"); + (*env)->CallVoidMethod(env, obj, mid, client->width, client->height); + + return defaultMallocFramebuffer(client); +} + +static void onSshError(void *client, ssh_tunnel_error_t error_code, const char *error_message) { + __android_log_print(ANDROID_LOG_ERROR, TAG,"libsshtunnel error: %d - %s", error_code, error_message); +} + +/** + Decide whether or not the SSH tunnel setup should continue + based on the current host and its fingerprint. + Business logic is up to the implementer in a real app, i.e. + compare keys, ask user etc... + @return -1 if tunnel setup should be aborted + 0 if tunnel setup should continue + */ +static int onSshFingerprintCheck(void *client, + const char *fingerprint, + int fingerprint_len, + const char *host) +{ + if(!client) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onSshFingerprintCheck failed due to client NULL"); + return -1; + } + + jobject obj = rfbClientGetClientData(client, VNCCONN_OBJ_ID); + JNIEnv *env = rfbClientGetClientData(client, VNCCONN_ENV_ID); + + if(!env || !obj) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "onSshFingerprintCheck failed due to env or obj NULL"); + return -1; + } + + jbyteArray jFingerprint = (*env)->NewByteArray(env, (jsize)fingerprint_len); + (*env)->SetByteArrayRegion(env, jFingerprint, 0, (jsize)fingerprint_len, (jbyte *) fingerprint); + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID mid = (*env)->GetMethodID(env, cls, "onSshFingerprintCheck", "(Ljava/lang/String;[B)I"); + return (*env)->CallIntMethod(env, obj, mid, (*env)->NewStringUTF(env, host), jFingerprint); +} + + +/** + * Allocates and sets up the VNCConn's rfbClient. + * @param env + * @param obj + * @param bytesPerPixel + * @return + */ +static jboolean setupClient(JNIEnv *env, jobject obj, jint bytesPerPixel) { + + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "setupClient()"); + + if(getRfbClient(env, obj)) { /* already set up */ + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "setupClient() already done"); + return JNI_FALSE; + } + + rfbClient *cl = NULL; + + /* + * We only allow 24 and 15 bit colour, as the GL canvas is not able to digest any other format + * _directly_, without converting the whole client framebuffer byte-per-byte. + */ + switch(bytesPerPixel) { + case 2: + // 15-bit colour + cl = rfbGetClient(5, 3, 2); + // the GL canvas is using GL_UNSIGNED_SHORT_5_5_5_1 for 15-bit colour depth + cl->format.redShift = 11; + cl->format.greenShift = 6; + cl->format.blueShift = 1; + break; + case 4: + // 24-bit colour, occupying 4 bytes + cl = rfbGetClient(8, 3, 4); + break; + default: + break; + } + + if(!cl) { + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "setupClient() failed due to client NULL"); + return JNI_FALSE; + } + + // set callbacks + cl->FinishedFrameBufferUpdate = onFramebufferUpdateFinished; + cl->GotXCutText = onGotCutText; + cl->GetPassword = onGetPassword; + cl->GetCredential = onGetCredential; + defaultMallocFramebuffer = cl->MallocFrameBuffer; // save default one + cl->MallocFrameBuffer = onNewFBSize; // set new one + + // set flags + cl->canHandleNewFBSize = TRUE; + + /* + * Save pointers to the managed VNCConn and env in the rfbClient for use in the onXYZ callbacks. + * In addition to rfbProcessServerMessage(), we have to do this are as some callbacks (namely + * related to authentication) are called before rfbProcessServerMessage(). + */ + rfbClientSetClientData(cl, VNCCONN_OBJ_ID, obj); + rfbClientSetClientData(cl, VNCCONN_ENV_ID, env); + + setRfbClient(env, obj, cl); + + return JNI_TRUE; +} + +JNIEXPORT void JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbShutdown(JNIEnv *env, jobject obj) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) { + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbShutdown() closing connection"); + rfbCloseSocket(cl->sock); + + ssh_tunnel_close(rfbClientGetClientData(cl, VNCCONN_SSH_ID)); + + if(cl->frameBuffer) { + free(cl->frameBuffer); + cl->frameBuffer = 0; + } + + rfbClientCleanup(cl); + // rfbClientCleanup does not zero the pointer + setRfbClient(env, obj, 0); + } +} + + +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbInit(JNIEnv *env, jobject obj, jstring host, jint port, jint repeaterId, jint bytesPerPixel, + jstring encodingsString, + jboolean enableCompress, + jboolean enableJPEG, + jint compressLevel, + jint qualityLevel, + jstring ssh_host, + jstring ssh_user, + jstring ssh_password, + jbyteArray ssh_priv_key, + jstring ssh_priv_key_password) { + + if(!getRfbClient(env, obj)) + setupClient(env, obj, bytesPerPixel); + + rfbClient *cl = getRfbClient(env, obj); + + if(!cl) { + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() failed due to client NULL"); + return JNI_FALSE; + } + + cl->programName = "VNCConn"; + + /* + * Get all C representations from managec code. The checks for NULL are needed + * as the Get*() methods fail on a null reference. + */ + const char *cHost = host ? (*env)->GetStringUTFChars(env, host, NULL) : NULL; + const char *cEncodingsString = encodingsString ? (*env)->GetStringUTFChars(env, encodingsString, NULL) : NULL; + const char *cSshHost = ssh_host ? (*env)->GetStringUTFChars(env, ssh_host, NULL) : NULL; + const char *cSshUser = ssh_user ? (*env)->GetStringUTFChars(env, ssh_user, NULL) : NULL; + const char *cSshPassword = ssh_password ? (*env)->GetStringUTFChars(env, ssh_password, NULL) : NULL; + jbyte *cSshPrivKey = ssh_priv_key ? (*env)->GetByteArrayElements(env, ssh_priv_key, NULL) : NULL; + jsize cSshPrivKeyLen = ssh_priv_key ? (*env)->GetArrayLength(env, ssh_priv_key) : 0; + const char *cSshPrivKeyPassword = ssh_priv_key_password ? (*env)->GetStringUTFChars(env, ssh_priv_key_password, NULL) : NULL; + + // see whether we are ssh-tunneling or not + int is_ssh_connection = cSshHost != NULL; + ssh_tunnel_t *tunnel = NULL; + + if(is_ssh_connection) { + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbInit() setting up SSH-tunneled connection"); + // ssh-tunneling, check whether it's password- or key-based + if(cSshPassword) { + // password-based + tunnel = ssh_tunnel_open_with_password(cSshHost, 22, cSshUser, cSshPassword, cHost, port, cl, onSshFingerprintCheck, onSshError); + } else { + // key-based + tunnel = ssh_tunnel_open_with_privkey(cSshHost, 22, cSshUser, (char*)cSshPrivKey, cSshPrivKeyLen, cSshPrivKeyPassword, cHost, port, cl, onSshFingerprintCheck, onSshError); + } + + cl->serverHost = strdup("127.0.0.1"); + if(tunnel) // might be NULL if ssh setup failed + cl->serverPort = ssh_tunnel_get_port(tunnel); + rfbClientSetClientData(cl, VNCCONN_SSH_ID, tunnel); + } else { + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbInit() setting up direct connection"); + // direct connection + if(cHost) // strdup(NULL) is UB + cl->serverHost = strdup(cHost); + + cl->serverPort = port; + // Support short-form (:0, :1) + if(cl->serverPort < 100) + cl->serverPort += 5900; + } + + if(cEncodingsString) // strdup(NULL) is UB + cl->appData.encodingsString = strdup(cEncodingsString); + + cl->appData.compressLevel = compressLevel; + + cl->appData.enableJPEG = enableJPEG; + cl->appData.qualityLevel = qualityLevel; + + + // release all handles to managed strings again + if (cHost) + (*env)->ReleaseStringUTFChars(env, host, cHost); + if(cEncodingsString) + (*env)->ReleaseStringUTFChars(env, encodingsString, cEncodingsString); + if (cSshHost) + (*env)->ReleaseStringUTFChars(env, ssh_host, cSshHost); + if(cSshUser) + (*env)->ReleaseStringUTFChars(env, ssh_user, cSshUser); + if(cSshPassword) + (*env)->ReleaseStringUTFChars(env, ssh_password, cSshPassword); + if (cSshPrivKey) + (*env)->ReleaseByteArrayElements(env, ssh_priv_key, cSshPrivKey, JNI_ABORT); + if (cSshPrivKeyPassword) + (*env)->ReleaseStringUTFChars(env, ssh_priv_key_password, cSshPrivKeyPassword); + + // for both direct and ssh-tunneled connection, set repeater + if(repeaterId >= 0) { + cl->destHost = strdup("ID"); + cl->destPort = repeaterId; + } + + + // check if ssh connection was wanted and succeeded + if(is_ssh_connection && !rfbClientGetClientData(cl, VNCCONN_SSH_ID)) { + // ssh connection failed, bail out now + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() SSH tunnel failed."); + Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbShutdown(env, obj); + return JNI_FALSE; + } + + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbInit() about to connect to '%s', port %d, repeaterId %d\n", cl->serverHost, cl->serverPort, cl->destPort); + + if(!rfbInitClient(cl, 0, NULL)) { + setRfbClient(env, obj, 0); // rfbInitClient() calls rfbClientCleanup() on failure, but this does not zero the ptr + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() connection failed. Cleanup by library."); + // There might be the case that the SSH tunnel got setup alright, but connecting to the VNC server failed. + // In this case we have to dispose of the tunnel explicitly here. + // We cannot use rfbClientGetClientData(cl, VNCCONN_SSH_ID) here as there was already a rfbClientCleanup() + ssh_tunnel_close(tunnel); + return JNI_FALSE; + } + + // if there was an error in alloc_framebuffer(), catch that here + if(!cl->frameBuffer) { + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbInit() failed due to framebuffer NULL"); + Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbShutdown(env, obj); + return JNI_FALSE; + } + + return JNI_TRUE; +} + + +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbProcessServerMessage(JNIEnv *env, jobject obj) { + rfbClient *cl = getRfbClient(env, obj); + + if(!cl) { + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbProcessServerMessage() failed due to client NULL"); + return JNI_FALSE; + } + + /* + * Save pointers to the managed VNCConn and env in the rfbClient for use in the onXYZ callbacks. + * We do this each time here and not in setupClient() because the managed objects get moved + * around by the VM. + */ + rfbClientSetClientData(cl, VNCCONN_OBJ_ID, obj); + rfbClientSetClientData(cl, VNCCONN_ENV_ID, env); + + /* request update and handle response */ + if(!rfbProcessServerMessage(cl, 500)) { + if(errno == EINTR) + return JNI_TRUE; + + log_obj_tostring(env, obj, ANDROID_LOG_ERROR, "rfbProcessServerMessage() failed"); + return JNI_FALSE; + } + return JNI_TRUE; +} + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "hicpp-signed-bitwise" +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSetFramebufferUpdatesEnabled(JNIEnv *env, jobject obj, jboolean enable) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) { + log_obj_tostring(env, obj, ANDROID_LOG_INFO, "rfbSetFramebufferUpdatesEnabled() %d", enable); + if(enable) { + // set to managed-by-lib again + rfbClientSetUpdateRect(cl, NULL); + // request full update + SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, FALSE); + } else { + // set to no-updates-wanted + rfbRectangle noRect = {0,0,0,0,}; + rfbClientSetUpdateRect(cl, &noRect); + } + return JNI_TRUE; + } + else + return JNI_FALSE; +} +#pragma clang diagnostic pop + +JNIEXPORT jstring JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbGetDesktopName(JNIEnv *env, jobject obj) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) + return (*env)->NewStringUTF(env, cl->desktopName); + else + return NULL; +} + +JNIEXPORT jint JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbGetFramebufferWidth(JNIEnv *env, jobject obj) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) + return cl->width; + else + return 0; +} + +JNIEXPORT jint JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbGetFramebufferHeight(JNIEnv *env, jobject obj) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) + return cl->height; + else + return 0; +} + +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSendKeyEvent(JNIEnv *env, jobject obj, jlong keysym, jboolean down) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) + return (jboolean) SendKeyEvent(cl, (uint32_t) keysym, down); + else + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSendPointerEvent(JNIEnv *env, jobject obj, jint x, jint y, jint buttonMask) { + rfbClient *cl = getRfbClient(env, obj); + if(cl) + return (jboolean) SendPointerEvent(cl, x, y, buttonMask); + else + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbSendClientCutText(JNIEnv *env, jobject obj, jbyteArray bytes) { + rfbClient *cl = getRfbClient(env, obj); + if (cl) { + jbyte *cText = (*env)->GetByteArrayElements(env, bytes, NULL); + int cTextLen = (*env)->GetArrayLength(env, bytes); + jboolean status = SendClientCutText(cl, (char *) cText, cTextLen); + (*env)->ReleaseByteArrayElements(env, bytes, cText, JNI_ABORT); + return status; + } + else + return JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL Java_com_coboltforge_dontmind_multivnc_VNCConn_rfbIsEncrypted(JNIEnv *env, jobject obj) { + rfbClient *cl = getRfbClient(env, obj); + if (cl) + return cl->tlsSession != NULL || rfbClientGetClientData(cl, VNCCONN_SSH_ID) != NULL; + else + return JNI_FALSE; } \ No newline at end of file diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/MDNSService.java b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/MDNSService.java index 02ea14cc..1f654d9e 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/MDNSService.java +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/MDNSService.java @@ -1,339 +1,339 @@ -package com.coboltforge.dontmind.multivnc; - -/* - * @author Christian Beier - * mDNS Service Discovery Service. - * Copyright © 2011-2023 Christian Beier - */ - -import java.util.Hashtable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; - -import android.annotation.SuppressLint; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.net.nsd.NsdManager; -import android.net.nsd.NsdServiceInfo; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.coboltforge.dontmind.multivnc.db.ConnectionBean; - -public class MDNSService extends Service { - - private final String TAG = "MDNSService"; - private final IBinder mBinder = new LocalBinder(); - - private NsdManager nsdManager; - // we use a separate worker thread to do the resolve OPs one after another, see - // https://stackoverflow.com/a/68424163/361413 - private final MDNSWorkerThread workerThread = new MDNSWorkerThread(); - - private OnEventListener callback; - - public interface OnEventListener { - void onMDNSstartupCompleted(boolean wasSuccessful); - void onMDNSnotify(final String conn_name, final ConnectionBean conn, final Hashtable connectionTable); - } - - /** - * Class for clients to access. Because we know this service always runs in - * the same process as its clients, we don't need to deal with IPC. - */ - public class LocalBinder extends Binder { - public MDNSService getService() { - return MDNSService.this; - } - } - - - - - @Override - public IBinder onBind(Intent intent) { - // handleIntent(intent); - return mBinder; - } - - - - @Override - public void onCreate() { - //code to execute when the service is first created - Log.d(TAG, "mDNS service onCreate()!"); - - nsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE); - workerThread.start(); - } - - - @Override - public void onDestroy() { - //code to execute when the service is shutting down - Log.d(TAG, "mDNS service onDestroy()!"); - - // this will end the worker thread - workerThread.interrupt(); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startid) { - //code to execute when the service is starting up - Log.d(TAG, "mDNS service onStartCommand()!"); - - if(intent == null) - Log.d(TAG, "Restart!"); - - return START_STICKY; - } - - // NB: this is called from the worker thread!! - public void registerCallback(OnEventListener c) { - callback = c; - } - - // force a callback call for every conn in connections_discovered - public void dump() { - try { - Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_DUMP).sendToTarget(); - } - catch(NullPointerException e) { - //unused - } - } - - public void restart() { - Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_STOP).sendToTarget(); - Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_START).sendToTarget(); - } - - - private class MDNSWorkerThread extends Thread - { - private android.net.wifi.WifiManager.MulticastLock multicastLock; - private static final String mdnstype = "_rfb._tcp"; - private NsdManager.DiscoveryListener discoveryListener; - private NsdManager.ResolveListener resolveListener; - // work around the fact that only one resolve OP can be active at one time - // https://stackoverflow.com/a/68424163/361413 - private final Semaphore resolveSemaphore = new Semaphore(1); - // accessed from both worker thread and nsdManager thread - private final ConcurrentHashMap connections_discovered = new ConcurrentHashMap<>(); - private Handler handler; - - final static int MESSAGE_START = 0; - final static int MESSAGE_STOP = 1; - final static int MESSAGE_DUMP = 2; - final static int MESSAGE_RESOLVE = 3; - - - // this just runs a message loop and acts according to messages - @SuppressLint("HandlerLeak") // no handler leak as looper runs on worker thread - public void run() { - - resolveListener = new NsdManager.ResolveListener() { - @Override - public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { - resolveSemaphore.release(); - - Log.e(TAG, "Resolve for " + serviceInfo + " failed with code " + errorCode); - } - - @Override - public void onServiceResolved(NsdServiceInfo serviceInfo) { - resolveSemaphore.release(); - - ConnectionBean c = new ConnectionBean(); - c.id = 0; // new! - c.nickname = serviceInfo.getServiceName(); - c.address = serviceInfo.getHost().toString().replace('/', ' ').trim(); - c.port = serviceInfo.getPort(); - c.useLocalCursor = true; // always enable - - connections_discovered.put(serviceInfo.getServiceName(), c); - - Log.d(TAG, "Resolve succeeded for server :" + serviceInfo.getServiceName() - + ", now " + connections_discovered.size()); - - mDNSnotify(serviceInfo.getServiceName(), c); - } - }; - - Looper.prepare(); - - handler = new Handler() { - - public void handleMessage(@NonNull Message msg) { - // if interrupted, bail out at once - if(isInterrupted()) - { - Log.d(TAG, "INTERRUPTED, bailing out!"); - mDNSstop(); - getLooper().quit(); - return; - } - // otherwise, process our message queue - - switch (msg.what) { - case MDNSWorkerThread.MESSAGE_START: - mDNSstart(); - break; - case MDNSWorkerThread.MESSAGE_STOP: - mDNSstop(); - break; - case MDNSWorkerThread.MESSAGE_DUMP: - for(ConnectionBean c : connections_discovered.values()) { - mDNSnotify(c.nickname, c); - } - break; - case MESSAGE_RESOLVE: - try { - resolveSemaphore.acquire(); - nsdManager.resolveService((NsdServiceInfo) msg.obj, resolveListener); - } catch (InterruptedException e) { - Log.d(TAG, "INTERRUPTED, bailing out!"); - mDNSstop(); - getLooper().quit(); - return; - } - break; - } - } - - }; - - // start on our own - mDNSstart(); - - Looper.loop(); - } - - - private void mDNSstart() - { - Log.d(TAG, "starting MDNS"); - - if(discoveryListener != null) { - Log.d(TAG, "MDNS already running, doing nothing"); - try{ - callback.onMDNSstartupCompleted(false); - } - catch(NullPointerException e) { - Log.d(TAG, "callback is NULL, not notified about startup"); - } - } - else { - // This is needed on older Android platforms as the NsdManager does not have it - // built in before Android 14. - android.net.wifi.WifiManager wifi = (android.net.wifi.WifiManager) getApplicationContext().getSystemService(android.content.Context.WIFI_SERVICE); - assert wifi != null; - multicastLock = wifi.createMulticastLock(TAG); - multicastLock.setReferenceCounted(true); - multicastLock.acquire(); - - - discoveryListener = new NsdManager.DiscoveryListener() { - @Override - public void onDiscoveryStarted(String regType) { - Log.d(TAG, "Discovery started for " + regType); - try{ - callback.onMDNSstartupCompleted(true); - } - catch(NullPointerException e) { - Log.d(TAG, "callback is NULL, not notified about startup"); - } - } - - @Override - public void onServiceFound(NsdServiceInfo service) { - // A service was found! Do something with it. - Log.d(TAG, "Discovery found: " + service); - Message m = Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_RESOLVE); - m.obj = service; - m.sendToTarget(); - } - - @Override - public void onServiceLost(NsdServiceInfo service) { - connections_discovered.remove(service.getServiceName()); - - Log.d(TAG, "Discovery lost: " + service.getServiceName() - + ", now " + connections_discovered.size()); - - mDNSnotify(service.getServiceName(), null); - } - - @Override - public void onDiscoveryStopped(String serviceType) { - Log.d(TAG, "Discovery stopped: " + serviceType); - } - - @Override - public void onStartDiscoveryFailed(String serviceType, int errorCode) { - Log.e(TAG, "Discovery start failed with error code: " + errorCode); - try{ - callback.onMDNSstartupCompleted(false); - } - catch(NullPointerException e) { - Log.d(TAG, "callback is NULL, not notified about startup"); - } - } - - @Override - public void onStopDiscoveryFailed(String serviceType, int errorCode) { - Log.e(TAG, "Discovery stop failed with error code: " + errorCode); - } - }; - - nsdManager.discoverServices(mdnstype, NsdManager.PROTOCOL_DNS_SD, discoveryListener); - } - } - - private void mDNSstop() - { - Log.d(TAG, "stopping MDNS"); - if (discoveryListener != null) { - try { - nsdManager.stopServiceDiscovery(discoveryListener); - } catch (Exception ignored) { - } - discoveryListener = null; - } - - if(multicastLock != null) { - multicastLock.release(); - multicastLock = null; - } - - // notify our callback about our internal state, i.e. the removals - for(ConnectionBean c: connections_discovered.values()) - mDNSnotify(c.nickname, null); - // and clear internal state - connections_discovered.clear(); - - Log.d(TAG, "stopping MDNS done"); - } - - // do the GUI stuff in Runnable posted to main thread handler - private void mDNSnotify(final String conn_name, final ConnectionBean conn) { - if(callback!=null) - callback.onMDNSnotify(conn_name, conn, new Hashtable<>(connections_discovered)); - else - Log.d(TAG, "callback is NULL, not notifying"); - - } - - - } - - -} +package com.coboltforge.dontmind.multivnc; + +/* + * @author Christian Beier + * mDNS Service Discovery Service. + * Copyright © 2011-2023 Christian Beier + */ + +import java.util.Hashtable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; + +import android.annotation.SuppressLint; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.net.nsd.NsdManager; +import android.net.nsd.NsdServiceInfo; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.coboltforge.dontmind.multivnc.db.ConnectionBean; + +public class MDNSService extends Service { + + private final String TAG = "MDNSService"; + private final IBinder mBinder = new LocalBinder(); + + private NsdManager nsdManager; + // we use a separate worker thread to do the resolve OPs one after another, see + // https://stackoverflow.com/a/68424163/361413 + private final MDNSWorkerThread workerThread = new MDNSWorkerThread(); + + private OnEventListener callback; + + public interface OnEventListener { + void onMDNSstartupCompleted(boolean wasSuccessful); + void onMDNSnotify(final String conn_name, final ConnectionBean conn, final Hashtable connectionTable); + } + + /** + * Class for clients to access. Because we know this service always runs in + * the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + public MDNSService getService() { + return MDNSService.this; + } + } + + + + + @Override + public IBinder onBind(Intent intent) { + // handleIntent(intent); + return mBinder; + } + + + + @Override + public void onCreate() { + //code to execute when the service is first created + Log.d(TAG, "mDNS service onCreate()!"); + + nsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE); + workerThread.start(); + } + + + @Override + public void onDestroy() { + //code to execute when the service is shutting down + Log.d(TAG, "mDNS service onDestroy()!"); + + // this will end the worker thread + workerThread.interrupt(); + } + + + @Override + public int onStartCommand(Intent intent, int flags, int startid) { + //code to execute when the service is starting up + Log.d(TAG, "mDNS service onStartCommand()!"); + + if(intent == null) + Log.d(TAG, "Restart!"); + + return START_STICKY; + } + + // NB: this is called from the worker thread!! + public void registerCallback(OnEventListener c) { + callback = c; + } + + // force a callback call for every conn in connections_discovered + public void dump() { + try { + Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_DUMP).sendToTarget(); + } + catch(NullPointerException e) { + //unused + } + } + + public void restart() { + Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_STOP).sendToTarget(); + Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_START).sendToTarget(); + } + + + private class MDNSWorkerThread extends Thread + { + private android.net.wifi.WifiManager.MulticastLock multicastLock; + private static final String mdnstype = "_rfb._tcp"; + private NsdManager.DiscoveryListener discoveryListener; + private NsdManager.ResolveListener resolveListener; + // work around the fact that only one resolve OP can be active at one time + // https://stackoverflow.com/a/68424163/361413 + private final Semaphore resolveSemaphore = new Semaphore(1); + // accessed from both worker thread and nsdManager thread + private final ConcurrentHashMap connections_discovered = new ConcurrentHashMap<>(); + private Handler handler; + + final static int MESSAGE_START = 0; + final static int MESSAGE_STOP = 1; + final static int MESSAGE_DUMP = 2; + final static int MESSAGE_RESOLVE = 3; + + + // this just runs a message loop and acts according to messages + @SuppressLint("HandlerLeak") // no handler leak as looper runs on worker thread + public void run() { + + resolveListener = new NsdManager.ResolveListener() { + @Override + public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { + resolveSemaphore.release(); + + Log.e(TAG, "Resolve for " + serviceInfo + " failed with code " + errorCode); + } + + @Override + public void onServiceResolved(NsdServiceInfo serviceInfo) { + resolveSemaphore.release(); + + ConnectionBean c = new ConnectionBean(); + c.id = 0; // new! + c.nickname = serviceInfo.getServiceName(); + c.address = serviceInfo.getHost().toString().replace('/', ' ').trim(); + c.port = serviceInfo.getPort(); + c.useLocalCursor = true; // always enable + + connections_discovered.put(serviceInfo.getServiceName(), c); + + Log.d(TAG, "Resolve succeeded for server :" + serviceInfo.getServiceName() + + ", now " + connections_discovered.size()); + + mDNSnotify(serviceInfo.getServiceName(), c); + } + }; + + Looper.prepare(); + + handler = new Handler() { + + public void handleMessage(@NonNull Message msg) { + // if interrupted, bail out at once + if(isInterrupted()) + { + Log.d(TAG, "INTERRUPTED, bailing out!"); + mDNSstop(); + getLooper().quit(); + return; + } + // otherwise, process our message queue + + switch (msg.what) { + case MDNSWorkerThread.MESSAGE_START: + mDNSstart(); + break; + case MDNSWorkerThread.MESSAGE_STOP: + mDNSstop(); + break; + case MDNSWorkerThread.MESSAGE_DUMP: + for(ConnectionBean c : connections_discovered.values()) { + mDNSnotify(c.nickname, c); + } + break; + case MESSAGE_RESOLVE: + try { + resolveSemaphore.acquire(); + nsdManager.resolveService((NsdServiceInfo) msg.obj, resolveListener); + } catch (InterruptedException e) { + Log.d(TAG, "INTERRUPTED, bailing out!"); + mDNSstop(); + getLooper().quit(); + return; + } + break; + } + } + + }; + + // start on our own + mDNSstart(); + + Looper.loop(); + } + + + private void mDNSstart() + { + Log.d(TAG, "starting MDNS"); + + if(discoveryListener != null) { + Log.d(TAG, "MDNS already running, doing nothing"); + try{ + callback.onMDNSstartupCompleted(false); + } + catch(NullPointerException e) { + Log.d(TAG, "callback is NULL, not notified about startup"); + } + } + else { + // This is needed on older Android platforms as the NsdManager does not have it + // built in before Android 14. + android.net.wifi.WifiManager wifi = (android.net.wifi.WifiManager) getApplicationContext().getSystemService(android.content.Context.WIFI_SERVICE); + assert wifi != null; + multicastLock = wifi.createMulticastLock(TAG); + multicastLock.setReferenceCounted(true); + multicastLock.acquire(); + + + discoveryListener = new NsdManager.DiscoveryListener() { + @Override + public void onDiscoveryStarted(String regType) { + Log.d(TAG, "Discovery started for " + regType); + try{ + callback.onMDNSstartupCompleted(true); + } + catch(NullPointerException e) { + Log.d(TAG, "callback is NULL, not notified about startup"); + } + } + + @Override + public void onServiceFound(NsdServiceInfo service) { + // A service was found! Do something with it. + Log.d(TAG, "Discovery found: " + service); + Message m = Message.obtain(workerThread.handler, MDNSWorkerThread.MESSAGE_RESOLVE); + m.obj = service; + m.sendToTarget(); + } + + @Override + public void onServiceLost(NsdServiceInfo service) { + connections_discovered.remove(service.getServiceName()); + + Log.d(TAG, "Discovery lost: " + service.getServiceName() + + ", now " + connections_discovered.size()); + + mDNSnotify(service.getServiceName(), null); + } + + @Override + public void onDiscoveryStopped(String serviceType) { + Log.d(TAG, "Discovery stopped: " + serviceType); + } + + @Override + public void onStartDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "Discovery start failed with error code: " + errorCode); + try{ + callback.onMDNSstartupCompleted(false); + } + catch(NullPointerException e) { + Log.d(TAG, "callback is NULL, not notified about startup"); + } + } + + @Override + public void onStopDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "Discovery stop failed with error code: " + errorCode); + } + }; + + nsdManager.discoverServices(mdnstype, NsdManager.PROTOCOL_DNS_SD, discoveryListener); + } + } + + private void mDNSstop() + { + Log.d(TAG, "stopping MDNS"); + if (discoveryListener != null) { + try { + nsdManager.stopServiceDiscovery(discoveryListener); + } catch (Exception ignored) { + } + discoveryListener = null; + } + + if(multicastLock != null) { + multicastLock.release(); + multicastLock = null; + } + + // notify our callback about our internal state, i.e. the removals + for(ConnectionBean c: connections_discovered.values()) + mDNSnotify(c.nickname, null); + // and clear internal state + connections_discovered.clear(); + + Log.d(TAG, "stopping MDNS done"); + } + + // do the GUI stuff in Runnable posted to main thread handler + private void mDNSnotify(final String conn_name, final ConnectionBean conn) { + if(callback!=null) + callback.onMDNSnotify(conn_name, conn, new Hashtable<>(connections_discovered)); + else + Log.d(TAG, "callback is NULL, not notifying"); + + } + + + } + + +} diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/VNCConn.java b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/VNCConn.java index 6ddcc627..5833a1e5 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/VNCConn.java +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/VNCConn.java @@ -1,793 +1,793 @@ -/** - * - * This represents an *active* VNC connection (as opposed to ConnectionBean, which is more like a bookmark.). - * Copyright (C) 2012-2023 Christian Beier - */ - - -package com.coboltforge.dontmind.multivnc; - -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import android.util.Log; -import android.view.KeyEvent; -import androidx.annotation.Keep; - -import com.coboltforge.dontmind.multivnc.db.ConnectionBean; - - -public class VNCConn { - - - public interface OnInitListener { - /** - * Fired on connection init. - * @param error Null on successful connection init (i.e. connected), non-null on failure. - */ - void onInit(Throwable error); - } - - public interface OnDisconnectListener { - /** - * Fired on disconnect, either orderly or with error - * @param err Null on orderly disconnect, non-null on failure. - */ - void onDisconnected(Throwable err); - } - - public interface OnFramebufferEventListener { - void onFramebufferUpdateFinished(); - void onNewFramebufferSize(int w, int h); - } - - public interface OnAuthEventListener { - void onRequestCredsFromUser(final ConnectionBean conn, boolean isUserNameNeeded); - void onRequestSshFingerprintCheck(String host, byte[] fingerprint, final AtomicBoolean doContinue); - } - - private final OnFramebufferEventListener onFramebufferEventCallback; - private final OnAuthEventListener onAuthEventCallback; - - static { - System.loadLibrary("vncconn"); - } - - private final static String TAG = "VNCConn"; - - private ServerToClientThread inputThread; - private ClientToServerThread outputThread; - - // the native rfbClient - @Keep - public long rfbClient; - private ConnectionBean connSettings; - private COLORMODEL pendingColorModel = COLORMODEL.C24bit; - - // Runtime control flags - private boolean maintainConnection = true; - private boolean framebufferUpdatesEnabled = true; - - private final Lock bitmapDataPixelsLock = new ReentrantLock(); - - // message queue for communicating with the output worker thread - private final ConcurrentLinkedQueue outputEventQueue = new ConcurrentLinkedQueue<>(); - - private COLORMODEL colorModel; - - private String serverCutText; - - // Useful shortcuts for modifier masks. - public final static int CTRL_MASK = KeyEvent.META_SYM_ON; - public final static int SHIFT_MASK = KeyEvent.META_SHIFT_ON; - public final static int META_MASK = 0; - public final static int ALT_MASK = KeyEvent.META_ALT_ON; - public final static int SUPER_MASK = KeyEvent.META_FUNCTION_ON; // mhm rather sym_on? - - public static final int MOUSE_BUTTON_NONE = 0; - public static final int MOUSE_BUTTON_LEFT = 1; - public static final int MOUSE_BUTTON_MIDDLE = 2; - public static final int MOUSE_BUTTON_RIGHT = 4; - public static final int MOUSE_BUTTON_SCROLL_UP = 8; - public static final int MOUSE_BUTTON_SCROLL_DOWN = 16; - - - - private class OutputEvent { - - public OutputEvent(int x, int y, int modifiers, int pointerMask) { - pointer = new PointerEvent(); - pointer.x = x; - pointer.y = y; - pointer.modifiers = modifiers; - pointer.mask = pointerMask; - } - - public OutputEvent(int keyCode, int metaState, boolean down) { - key = new KeyboardEvent(); - key.keyCode = keyCode; - key.metaState = metaState; - key.down = down; - } - - public OutputEvent(boolean incremental) { - ffur = new FullFramebufferUpdateRequest(); - ffur.incremental = incremental; - } - - public OutputEvent(int x, int y, int w, int h, boolean incremental) { - fur = new FramebufferUpdateRequest(); - fur.x = x; - fur.y = y; - fur.w = w; - fur.h = h; - fur.incremental = incremental; - } - - public OutputEvent(String text) { - cuttext = new ClientCutText(); - cuttext.text = text; - } - - private class PointerEvent { - int x; - int y; - int mask; - int modifiers; - } - - private class KeyboardEvent { - int keyCode; - int metaState; - boolean down; - } - - private class FullFramebufferUpdateRequest { - boolean incremental; - } - - private class FramebufferUpdateRequest { - int x,y,w,h; - boolean incremental; - } - - private class ClientCutText { - String text; - } - - public FullFramebufferUpdateRequest ffur; - public FramebufferUpdateRequest fur; - public PointerEvent pointer; - public KeyboardEvent key; - public ClientCutText cuttext; - } - - - private class ServerToClientThread extends Thread { - - private final OnInitListener initCallback; - private final OnDisconnectListener disconnectCallback; - - public ServerToClientThread(OnInitListener initCallback, OnDisconnectListener disconnectCallback) { - this.initCallback = initCallback; - this.disconnectCallback = disconnectCallback; - } - - - public void run() { - - if(Utils.DEBUG()) Log.d(TAG, "ServerToClientThread started!"); - - Throwable maybeError = null; - - try { - - /* - * if IPv6 address, add scope id - */ - try { - InetAddress address = InetAddress.getByName(connSettings.address); - - Inet6Address in6 = Inet6Address.getByAddress( - address.getHostName(), - address.getAddress(), - Utils.getActiveNetworkInterface()); - - connSettings.address = in6.getHostAddress(); - Log.i(TAG, "Using IPv6"); - - } catch (UnknownHostException unused) { - Log.i(TAG, "Using IPv4"); - } catch (NullPointerException ne) { - Log.e(TAG, ne.toString()); - } - - - int repeaterId = (connSettings.useRepeater && connSettings.repeaterId != null && connSettings.repeaterId.length() > 0) ? Integer.parseInt(connSettings.repeaterId) : -1; - lockFramebuffer(); - - final COMPRESSMODEL compressModel = COMPRESSMODEL.valueOf(connSettings.compressModel); - final QUALITYMODEL qualityModel = QUALITYMODEL.valueOf(connSettings.qualityModel); - final boolean enableCompress = COMPRESSMODEL.None != compressModel; - final boolean enableJPEG = QUALITYMODEL.None != qualityModel; - if (!rfbInit(connSettings.address, connSettings.port, repeaterId, pendingColorModel.bpp(), - connSettings.encodingsString, - enableCompress, - enableJPEG, - COMPRESSMODEL.valueOf(connSettings.compressModel).toParameter(), - QUALITYMODEL.valueOf(connSettings.qualityModel).toParameter(), - connSettings.sshHost, - connSettings.sshUsername, - connSettings.sshPassword, - connSettings.sshPrivkey, - connSettings.sshPrivkeyPassword - )) { - unlockFramebuffer(); - throw new Exception(); //TODO add some error reoprting here, e.g. if auth fail or not - } - colorModel = pendingColorModel; - unlockFramebuffer(); - - // update connection's nickname with desktop name if unset - if(connSettings.nickname == null || connSettings.nickname.isEmpty()) - connSettings.nickname = getDesktopName(); - - // start output thread here - outputThread = new ClientToServerThread(); - outputThread.start(); - - initCallback.onInit(null); - - // main loop - while (maintainConnection) { - if (!rfbProcessServerMessage()) { - throw new Exception(); - } - } - } catch (Throwable e) { - if (maintainConnection) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - - // save for callback - maybeError = e; - } - } - - // we might get here when maintainConnection is set to false or when an exception was thrown - lockFramebuffer(); // make sure the native texture drawing is not accessing something invalid - rfbShutdown(); - unlockFramebuffer(); - - if(outputThread == null) { - // if outputThread is still unset, we were in init - initCallback.onInit(maybeError); - } else { - disconnectCallback.onDisconnected(maybeError); - } - - if(Utils.DEBUG()) Log.d(TAG, "ServerToClientThread done!"); - } - - } - - - - - private class ClientToServerThread extends Thread { - - public void run() { - - if(Utils.DEBUG()) { - Log.d(TAG, "ClientToServerThread started!"); - } - - // - // main output loop - // - while (maintainConnection) { - - // check input queue - OutputEvent ev; - while( (ev = outputEventQueue.poll()) != null ) { - if(ev.pointer != null) - sendPointerEvent(ev.pointer); - if(ev.key != null) - sendKeyEvent(ev.key); - if(ev.ffur != null) - try { - // bitmapData.writeFullUpdateRequest(ev.ffur.incremental); - } catch (Exception e) { - e.printStackTrace(); - } - if(ev.fur != null) - try { - // rfb.writeFramebufferUpdateRequest(ev.fur.x, ev.fur.y, ev.fur.w, ev.fur.h, ev.fur.incremental); - } catch (Exception e) { - e.printStackTrace(); - } - if(ev.cuttext != null) - sendCutText(ev.cuttext.text); - } - - // at this point, queue is empty, wait for input instead of hogging CPU - synchronized (outputEventQueue) { - try { - outputEventQueue.wait(); - } catch (InterruptedException e) { - // go on - } - } - - } - - if(Utils.DEBUG()) Log.d(TAG, "ClientToServerThread done!"); - - } - - - private boolean sendPointerEvent(OutputEvent.PointerEvent pe) { - return rfbSendPointerEvent(pe.x, pe.y, pe.mask); - } - - - private boolean sendKeyEvent(OutputEvent.KeyboardEvent evt) { - if (rfbClient != 0) { - - try { - - if((evt.metaState & VNCConn.SHIFT_MASK) != 0) { - if(Utils.DEBUG()) Log.d(TAG, "sending key Shift" + (evt.down?" down":" up")); - rfbSendKeyEvent(0xffe1, evt.down); - } - if((evt.metaState & VNCConn.CTRL_MASK) != 0) { - if(Utils.DEBUG()) Log.d(TAG, "sending key Ctrl" + (evt.down?" down":" up")); - rfbSendKeyEvent(0xffe3, evt.down); - } - if((evt.metaState & VNCConn.ALT_MASK) != 0) { - if(Utils.DEBUG()) Log.d(TAG, "sending key Alt" + (evt.down?" down":" up")); - rfbSendKeyEvent(0xffe9, evt.down); - } - if((evt.metaState & VNCConn.SUPER_MASK) != 0) { - if(Utils.DEBUG()) Log.d(TAG, "sending key Super" + (evt.down?" down":" up")); - rfbSendKeyEvent(0xffeb, evt.down); - } - if((evt.metaState & VNCConn.META_MASK) != 0) { - if(Utils.DEBUG()) Log.d(TAG, "sending key Meta" + (evt.down?" down":" up")); - rfbSendKeyEvent(0xffe7, evt.down); - } - - if(Utils.DEBUG()) Log.d(TAG, "sending key " + evt.keyCode + (evt.down?" down":" up")); - rfbSendKeyEvent(evt.keyCode, evt.down); - - return true; - } catch (Exception e) { - return false; - } - } - return false; - } - - - private boolean sendCutText(String text) { - if (rfbClient != 0) { - if (Utils.DEBUG()) Log.d(TAG, "sending cuttext " + text); - return rfbSendClientCutText(StandardCharsets.ISO_8859_1.encode(text).array()); - } - return false; - } - - - } - - - private native boolean rfbInit(String host, int port, int repeaterId, int bytesPerPixel, - String encodingsString, boolean hasCompress, boolean enableJPEG, int compressLevel, int qualityLevel, - String ssh_host, - String ssh_user, - String ssh_password, - byte[] ssh_priv_key, - String ssh_priv_key_password); - - private native void rfbShutdown(); - private native boolean rfbProcessServerMessage(); - private native boolean rfbSetFramebufferUpdatesEnabled(boolean enable); - private native String rfbGetDesktopName(); - private native int rfbGetFramebufferWidth(); - private native int rfbGetFramebufferHeight(); - private native boolean rfbSendKeyEvent(long keysym, boolean down); - private native boolean rfbSendPointerEvent(int x, int y, int buttonMask); - private native boolean rfbSendClientCutText(byte[] bytes); - private native boolean rfbIsEncrypted(); - - - public VNCConn(OnFramebufferEventListener framebufferCallback, OnAuthEventListener authCallback) { - onFramebufferEventCallback = framebufferCallback; - onAuthEventCallback = authCallback; - if(Utils.DEBUG()) Log.d(TAG, this + " constructed!"); - } - - protected void finalize() { - if(Utils.DEBUG()) Log.d(TAG, this + " finalized!"); - } - - - /** - * Initialise a VNC connection - * @param bean Connection settings - * @param initCallback Callback that's called after init has succeeded or failed - * @param disconnectCallback Callback that's called when an established connection disconnects - */ - public void init(ConnectionBean bean, OnInitListener initCallback, OnDisconnectListener disconnectCallback) { - - Log.d(TAG, "initializing"); - - maintainConnection = true; - - connSettings = bean; - this.pendingColorModel = COLORMODEL.valueOf(bean.colorModel); - - inputThread = new ServerToClientThread(initCallback, disconnectCallback); - inputThread.start(); - } - - - public void shutdown() { - - Log.d(TAG, "shutting down"); - - maintainConnection = false; - - try { - // the input thread stops by itself, but the putput thread not - outputThread.interrupt(); - } - catch(Exception e) { - } - - connSettings = null; - - System.gc(); - } - - - public boolean sendCutText(String text) { - - if(rfbClient != 0) { // only queue if already connected - OutputEvent e = new OutputEvent(text); - outputEventQueue.add(e); - synchronized (outputEventQueue) { - outputEventQueue.notify(); - } - - return true; - } - else - return false; - } - - - public boolean sendPointerEvent(int x, int y, int modifiers, int pointerMask) { - - - if(rfbClient != 0) { // only queue if already connected - - // safety: trim coordinates - x = trimX(x); - y = trimY(y); - - OutputEvent e = new OutputEvent(x, y, modifiers, pointerMask); - outputEventQueue.add(e); - synchronized (outputEventQueue) { - outputEventQueue.notify(); - } - - return true; - } - else - return false; - } - - /** - * queue key event for sending - * @param keyCode - * @param evt - * @param sendDirectly send key event directly without doing local->rfbKeySym translation - * @return - */ - public boolean sendKeyEvent(int keyCode, KeyEvent evt, boolean sendDirectly) { - - if(rfbClient != 0) { // only queue if already connected - - if(Utils.DEBUG()) Log.d(TAG, "queueing key evt " + evt.toString() + " code 0x" + Integer.toHexString(keyCode)); - - // check if special char - if(evt.getAction() == KeyEvent.ACTION_MULTIPLE && evt.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { - OutputEvent down = new OutputEvent( - evt.getCharacters().codePointAt(0), - evt.getMetaState(), - true); - outputEventQueue.add(down); - - OutputEvent up = new OutputEvent( - evt.getCharacters().codePointAt(0), - evt.getMetaState(), - false); - outputEventQueue.add(up); - - synchronized (outputEventQueue) { - outputEventQueue.notify(); - } - - } - // 'normal' key, i.e. either up or down - else { - - int metaState = evt.getMetaState(); - - // only do translation for events that were *not* synthesized, - // i.e. coming from a metakeybean which already is translated - if(!sendDirectly) { - switch(keyCode) { - case KeyEvent.KEYCODE_BACK : keyCode = 0xff1b; break; - case KeyEvent.KEYCODE_DPAD_LEFT: keyCode = 0xff51; break; - case KeyEvent.KEYCODE_DPAD_UP: keyCode = 0xff52; break; - case KeyEvent.KEYCODE_DPAD_RIGHT: keyCode = 0xff53; break; - case KeyEvent.KEYCODE_DPAD_DOWN: keyCode = 0xff54; break; - case KeyEvent.KEYCODE_DEL: keyCode = 0xff08; break; - case KeyEvent.KEYCODE_ENTER: keyCode = 0xff0d; break; - case KeyEvent.KEYCODE_DPAD_CENTER: keyCode = 0xff0d; break; - case KeyEvent.KEYCODE_TAB: keyCode = 0xff09; break; - case KeyEvent.KEYCODE_ESCAPE: keyCode = 0xff1b; break; - case KeyEvent.KEYCODE_ALT_LEFT: keyCode = 0xffe9; break; - case KeyEvent.KEYCODE_ALT_RIGHT: keyCode = 0xffea; break; - case KeyEvent.KEYCODE_F1: keyCode = 0xffbe; break; - case KeyEvent.KEYCODE_F2: keyCode = 0xffbf; break; - case KeyEvent.KEYCODE_F3: keyCode = 0xffc0; break; - case KeyEvent.KEYCODE_F4: keyCode = 0xffc1; break; - case KeyEvent.KEYCODE_F5: keyCode = 0xffc2; break; - case KeyEvent.KEYCODE_F6: keyCode = 0xffc3; break; - case KeyEvent.KEYCODE_F7: keyCode = 0xffc4; break; - case KeyEvent.KEYCODE_F8: keyCode = 0xffc5; break; - case KeyEvent.KEYCODE_F9: keyCode = 0xffc6; break; - case KeyEvent.KEYCODE_F10: keyCode = 0xffc7; break; - case KeyEvent.KEYCODE_F11: keyCode = 0xffc8; break; - case KeyEvent.KEYCODE_F12: keyCode = 0xffc9; break; - case KeyEvent.KEYCODE_INSERT: keyCode = 0xff63; break; - case KeyEvent.KEYCODE_FORWARD_DEL: keyCode = 0xffff; break; - case KeyEvent.KEYCODE_MOVE_HOME: keyCode = 0xff50; break; - case KeyEvent.KEYCODE_MOVE_END: keyCode = 0xff57; break; - case KeyEvent.KEYCODE_PAGE_UP: keyCode = 0xff55; break; - case KeyEvent.KEYCODE_PAGE_DOWN: keyCode = 0xff56; break; - case KeyEvent.KEYCODE_CTRL_LEFT: keyCode = 0xffe3; break; - case KeyEvent.KEYCODE_CTRL_RIGHT: keyCode = 0xffe4; break; - case KeyEvent.KEYCODE_SHIFT_LEFT: keyCode = 0xffe1; break; - case KeyEvent.KEYCODE_SHIFT_RIGHT: keyCode = 0xffe2; break; - default: - // do keycode -> UTF-8 keysym conversion - KeyEvent tmp = new KeyEvent( - 0, - 0, - 0, - keyCode, - 0, - metaState); - - keyCode = tmp.getUnicodeChar(); - - // Ctrl-C for example needs this... - if (keyCode == 0) { - metaState &= ~KeyEvent.META_CTRL_MASK; - metaState &= ~KeyEvent.META_ALT_MASK; - keyCode = tmp.getUnicodeChar(metaState); - } - - metaState = 0; - break; - - } - } - - OutputEvent e = new OutputEvent( - keyCode, - metaState, - evt.getAction() == KeyEvent.ACTION_DOWN); - outputEventQueue.add(e); - synchronized (outputEventQueue) { - outputEventQueue.notify(); - } - - } - - return true; - } - else - return false; - } - - - - public boolean toggleFramebufferUpdates() - { - framebufferUpdatesEnabled = !framebufferUpdatesEnabled; - rfbSetFramebufferUpdatesEnabled(framebufferUpdatesEnabled); - return framebufferUpdatesEnabled; - } - - - void sendFramebufferUpdateRequest(int x, int y, int w, int h, boolean incremental) { - if(framebufferUpdatesEnabled) - { - try { - OutputEvent e = new OutputEvent(x, y, w, h, incremental); - outputEventQueue.add(e); - synchronized (outputEventQueue) { - outputEventQueue.notify(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - - public void setColorModel(COLORMODEL cm) { - // Only update if color model changes - if (colorModel == null || !colorModel.equals(cm)) - pendingColorModel = cm; - } - - - - public final COLORMODEL getColorModel() { - return colorModel; - } - - - public String getEncoding() { - return ""; //TODO: wire up to native - } - - - public final String getDesktopName() { - return rfbGetDesktopName(); - } - - public final int getFramebufferWidth() { - return rfbGetFramebufferWidth(); - } - - public final int getFramebufferHeight() { - return rfbGetFramebufferHeight(); - } - - public int trimX(int x) { - if (x<0) return 0; - else if (x>=getFramebufferWidth()) return getFramebufferWidth()-1; - return x; - } - - public int trimY(int y) { - if (y<0) return 0; - else if (y>=getFramebufferHeight()) return getFramebufferHeight()-1; - return y; - } - - public String getCutText() { - return serverCutText; - } - - public boolean isEncrypted() { return rfbIsEncrypted(); } - - public void lockFramebuffer() { - bitmapDataPixelsLock.lock(); - } - - public void unlockFramebuffer() { - bitmapDataPixelsLock.unlock(); - } - - - public final ConnectionBean getConnSettings() { - return connSettings; - } - - - // called from native via worker thread context - @Keep - private void onFramebufferUpdateFinished() { - if(onFramebufferEventCallback != null) - onFramebufferEventCallback.onFramebufferUpdateFinished(); - } - - // called from native via worker thread context - @Keep - private void onGotCutText(byte[] bytes) { - serverCutText = StandardCharsets.ISO_8859_1.decode(ByteBuffer.wrap(bytes)).toString(); - Log.d(TAG, "got server cuttext: " + serverCutText); - } - - // called from native via worker thread context - @Keep - private String onGetPassword() { - if (onAuthEventCallback != null && (connSettings.password == null || connSettings.password.length() == 0)) { - onAuthEventCallback.onRequestCredsFromUser(connSettings, false); // this cares for running on the main thread - synchronized (VNCConn.this) { - try { - VNCConn.this.wait(); // wait for user input to finish - } catch (InterruptedException e) { - //unused - } - } - } - return connSettings.password; - } - - /** - * This class is used for returning user credentials from onGetCredential() to native - */ - @Keep - private static class UserCredential { - public String username; - public String password; - } - - // called from native via worker thread context - @Keep - private UserCredential onGetUserCredential() { - - if (onAuthEventCallback != null && - (connSettings.userName == null || connSettings.userName.isEmpty() - || connSettings.password == null || connSettings.password.isEmpty())) { - onAuthEventCallback.onRequestCredsFromUser(connSettings, connSettings.userName == null || connSettings.userName.isEmpty()); - synchronized (VNCConn.this) { - try { - VNCConn.this.wait(); // wait for user input to finish - } catch (InterruptedException e) { - //unused - } - } - } - - UserCredential creds = new UserCredential(); - creds.username = connSettings.userName; - creds.password = connSettings.password; - return creds; - } - - // called from native via worker thread context - @Keep - private void onNewFramebufferSize(int w, int h) { - Log.d(TAG, "new framebuffer size " + w + " x " + h); - if(onFramebufferEventCallback != null) - onFramebufferEventCallback.onNewFramebufferSize(w, h); - } - - // called from native via worker thread context - @Keep - private int onSshFingerprintCheck(String host, byte[] fingerprint) { - if(onAuthEventCallback != null) { - AtomicBoolean doContinue = new AtomicBoolean(); - onAuthEventCallback.onRequestSshFingerprintCheck(host, fingerprint, doContinue); - synchronized (VNCConn.this) { - try { - VNCConn.this.wait(); // wait for user input to finish - } catch (InterruptedException e) { - //unused - } - } - // evaluate result - if(doContinue.get()) { - return 0; - } - } - return -1; - } -} - +/** + * + * This represents an *active* VNC connection (as opposed to ConnectionBean, which is more like a bookmark.). + * Copyright (C) 2012-2023 Christian Beier + */ + + +package com.coboltforge.dontmind.multivnc; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import android.util.Log; +import android.view.KeyEvent; +import androidx.annotation.Keep; + +import com.coboltforge.dontmind.multivnc.db.ConnectionBean; + + +public class VNCConn { + + + public interface OnInitListener { + /** + * Fired on connection init. + * @param error Null on successful connection init (i.e. connected), non-null on failure. + */ + void onInit(Throwable error); + } + + public interface OnDisconnectListener { + /** + * Fired on disconnect, either orderly or with error + * @param err Null on orderly disconnect, non-null on failure. + */ + void onDisconnected(Throwable err); + } + + public interface OnFramebufferEventListener { + void onFramebufferUpdateFinished(); + void onNewFramebufferSize(int w, int h); + } + + public interface OnAuthEventListener { + void onRequestCredsFromUser(final ConnectionBean conn, boolean isUserNameNeeded); + void onRequestSshFingerprintCheck(String host, byte[] fingerprint, final AtomicBoolean doContinue); + } + + private final OnFramebufferEventListener onFramebufferEventCallback; + private final OnAuthEventListener onAuthEventCallback; + + static { + System.loadLibrary("vncconn"); + } + + private final static String TAG = "VNCConn"; + + private ServerToClientThread inputThread; + private ClientToServerThread outputThread; + + // the native rfbClient + @Keep + public long rfbClient; + private ConnectionBean connSettings; + private COLORMODEL pendingColorModel = COLORMODEL.C24bit; + + // Runtime control flags + private boolean maintainConnection = true; + private boolean framebufferUpdatesEnabled = true; + + private final Lock bitmapDataPixelsLock = new ReentrantLock(); + + // message queue for communicating with the output worker thread + private final ConcurrentLinkedQueue outputEventQueue = new ConcurrentLinkedQueue<>(); + + private COLORMODEL colorModel; + + private String serverCutText; + + // Useful shortcuts for modifier masks. + public final static int CTRL_MASK = KeyEvent.META_SYM_ON; + public final static int SHIFT_MASK = KeyEvent.META_SHIFT_ON; + public final static int META_MASK = 0; + public final static int ALT_MASK = KeyEvent.META_ALT_ON; + public final static int SUPER_MASK = KeyEvent.META_FUNCTION_ON; // mhm rather sym_on? + + public static final int MOUSE_BUTTON_NONE = 0; + public static final int MOUSE_BUTTON_LEFT = 1; + public static final int MOUSE_BUTTON_MIDDLE = 2; + public static final int MOUSE_BUTTON_RIGHT = 4; + public static final int MOUSE_BUTTON_SCROLL_UP = 8; + public static final int MOUSE_BUTTON_SCROLL_DOWN = 16; + + + + private class OutputEvent { + + public OutputEvent(int x, int y, int modifiers, int pointerMask) { + pointer = new PointerEvent(); + pointer.x = x; + pointer.y = y; + pointer.modifiers = modifiers; + pointer.mask = pointerMask; + } + + public OutputEvent(int keyCode, int metaState, boolean down) { + key = new KeyboardEvent(); + key.keyCode = keyCode; + key.metaState = metaState; + key.down = down; + } + + public OutputEvent(boolean incremental) { + ffur = new FullFramebufferUpdateRequest(); + ffur.incremental = incremental; + } + + public OutputEvent(int x, int y, int w, int h, boolean incremental) { + fur = new FramebufferUpdateRequest(); + fur.x = x; + fur.y = y; + fur.w = w; + fur.h = h; + fur.incremental = incremental; + } + + public OutputEvent(String text) { + cuttext = new ClientCutText(); + cuttext.text = text; + } + + private class PointerEvent { + int x; + int y; + int mask; + int modifiers; + } + + private class KeyboardEvent { + int keyCode; + int metaState; + boolean down; + } + + private class FullFramebufferUpdateRequest { + boolean incremental; + } + + private class FramebufferUpdateRequest { + int x,y,w,h; + boolean incremental; + } + + private class ClientCutText { + String text; + } + + public FullFramebufferUpdateRequest ffur; + public FramebufferUpdateRequest fur; + public PointerEvent pointer; + public KeyboardEvent key; + public ClientCutText cuttext; + } + + + private class ServerToClientThread extends Thread { + + private final OnInitListener initCallback; + private final OnDisconnectListener disconnectCallback; + + public ServerToClientThread(OnInitListener initCallback, OnDisconnectListener disconnectCallback) { + this.initCallback = initCallback; + this.disconnectCallback = disconnectCallback; + } + + + public void run() { + + if(Utils.DEBUG()) Log.d(TAG, "ServerToClientThread started!"); + + Throwable maybeError = null; + + try { + + /* + * if IPv6 address, add scope id + */ + try { + InetAddress address = InetAddress.getByName(connSettings.address); + + Inet6Address in6 = Inet6Address.getByAddress( + address.getHostName(), + address.getAddress(), + Utils.getActiveNetworkInterface()); + + connSettings.address = in6.getHostAddress(); + Log.i(TAG, "Using IPv6"); + + } catch (UnknownHostException unused) { + Log.i(TAG, "Using IPv4"); + } catch (NullPointerException ne) { + Log.e(TAG, ne.toString()); + } + + + int repeaterId = (connSettings.useRepeater && connSettings.repeaterId != null && connSettings.repeaterId.length() > 0) ? Integer.parseInt(connSettings.repeaterId) : -1; + lockFramebuffer(); + + final COMPRESSMODEL compressModel = COMPRESSMODEL.valueOf(connSettings.compressModel); + final QUALITYMODEL qualityModel = QUALITYMODEL.valueOf(connSettings.qualityModel); + final boolean enableCompress = COMPRESSMODEL.None != compressModel; + final boolean enableJPEG = QUALITYMODEL.None != qualityModel; + if (!rfbInit(connSettings.address, connSettings.port, repeaterId, pendingColorModel.bpp(), + connSettings.encodingsString, + enableCompress, + enableJPEG, + COMPRESSMODEL.valueOf(connSettings.compressModel).toParameter(), + QUALITYMODEL.valueOf(connSettings.qualityModel).toParameter(), + connSettings.sshHost, + connSettings.sshUsername, + connSettings.sshPassword, + connSettings.sshPrivkey, + connSettings.sshPrivkeyPassword + )) { + unlockFramebuffer(); + throw new Exception(); //TODO add some error reoprting here, e.g. if auth fail or not + } + colorModel = pendingColorModel; + unlockFramebuffer(); + + // update connection's nickname with desktop name if unset + if(connSettings.nickname == null || connSettings.nickname.isEmpty()) + connSettings.nickname = getDesktopName(); + + // start output thread here + outputThread = new ClientToServerThread(); + outputThread.start(); + + initCallback.onInit(null); + + // main loop + while (maintainConnection) { + if (!rfbProcessServerMessage()) { + throw new Exception(); + } + } + } catch (Throwable e) { + if (maintainConnection) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + + // save for callback + maybeError = e; + } + } + + // we might get here when maintainConnection is set to false or when an exception was thrown + lockFramebuffer(); // make sure the native texture drawing is not accessing something invalid + rfbShutdown(); + unlockFramebuffer(); + + if(outputThread == null) { + // if outputThread is still unset, we were in init + initCallback.onInit(maybeError); + } else { + disconnectCallback.onDisconnected(maybeError); + } + + if(Utils.DEBUG()) Log.d(TAG, "ServerToClientThread done!"); + } + + } + + + + + private class ClientToServerThread extends Thread { + + public void run() { + + if(Utils.DEBUG()) { + Log.d(TAG, "ClientToServerThread started!"); + } + + // + // main output loop + // + while (maintainConnection) { + + // check input queue + OutputEvent ev; + while( (ev = outputEventQueue.poll()) != null ) { + if(ev.pointer != null) + sendPointerEvent(ev.pointer); + if(ev.key != null) + sendKeyEvent(ev.key); + if(ev.ffur != null) + try { + // bitmapData.writeFullUpdateRequest(ev.ffur.incremental); + } catch (Exception e) { + e.printStackTrace(); + } + if(ev.fur != null) + try { + // rfb.writeFramebufferUpdateRequest(ev.fur.x, ev.fur.y, ev.fur.w, ev.fur.h, ev.fur.incremental); + } catch (Exception e) { + e.printStackTrace(); + } + if(ev.cuttext != null) + sendCutText(ev.cuttext.text); + } + + // at this point, queue is empty, wait for input instead of hogging CPU + synchronized (outputEventQueue) { + try { + outputEventQueue.wait(); + } catch (InterruptedException e) { + // go on + } + } + + } + + if(Utils.DEBUG()) Log.d(TAG, "ClientToServerThread done!"); + + } + + + private boolean sendPointerEvent(OutputEvent.PointerEvent pe) { + return rfbSendPointerEvent(pe.x, pe.y, pe.mask); + } + + + private boolean sendKeyEvent(OutputEvent.KeyboardEvent evt) { + if (rfbClient != 0) { + + try { + + if((evt.metaState & VNCConn.SHIFT_MASK) != 0) { + if(Utils.DEBUG()) Log.d(TAG, "sending key Shift" + (evt.down?" down":" up")); + rfbSendKeyEvent(0xffe1, evt.down); + } + if((evt.metaState & VNCConn.CTRL_MASK) != 0) { + if(Utils.DEBUG()) Log.d(TAG, "sending key Ctrl" + (evt.down?" down":" up")); + rfbSendKeyEvent(0xffe3, evt.down); + } + if((evt.metaState & VNCConn.ALT_MASK) != 0) { + if(Utils.DEBUG()) Log.d(TAG, "sending key Alt" + (evt.down?" down":" up")); + rfbSendKeyEvent(0xffe9, evt.down); + } + if((evt.metaState & VNCConn.SUPER_MASK) != 0) { + if(Utils.DEBUG()) Log.d(TAG, "sending key Super" + (evt.down?" down":" up")); + rfbSendKeyEvent(0xffeb, evt.down); + } + if((evt.metaState & VNCConn.META_MASK) != 0) { + if(Utils.DEBUG()) Log.d(TAG, "sending key Meta" + (evt.down?" down":" up")); + rfbSendKeyEvent(0xffe7, evt.down); + } + + if(Utils.DEBUG()) Log.d(TAG, "sending key " + evt.keyCode + (evt.down?" down":" up")); + rfbSendKeyEvent(evt.keyCode, evt.down); + + return true; + } catch (Exception e) { + return false; + } + } + return false; + } + + + private boolean sendCutText(String text) { + if (rfbClient != 0) { + if (Utils.DEBUG()) Log.d(TAG, "sending cuttext " + text); + return rfbSendClientCutText(StandardCharsets.ISO_8859_1.encode(text).array()); + } + return false; + } + + + } + + + private native boolean rfbInit(String host, int port, int repeaterId, int bytesPerPixel, + String encodingsString, boolean hasCompress, boolean enableJPEG, int compressLevel, int qualityLevel, + String ssh_host, + String ssh_user, + String ssh_password, + byte[] ssh_priv_key, + String ssh_priv_key_password); + + private native void rfbShutdown(); + private native boolean rfbProcessServerMessage(); + private native boolean rfbSetFramebufferUpdatesEnabled(boolean enable); + private native String rfbGetDesktopName(); + private native int rfbGetFramebufferWidth(); + private native int rfbGetFramebufferHeight(); + private native boolean rfbSendKeyEvent(long keysym, boolean down); + private native boolean rfbSendPointerEvent(int x, int y, int buttonMask); + private native boolean rfbSendClientCutText(byte[] bytes); + private native boolean rfbIsEncrypted(); + + + public VNCConn(OnFramebufferEventListener framebufferCallback, OnAuthEventListener authCallback) { + onFramebufferEventCallback = framebufferCallback; + onAuthEventCallback = authCallback; + if(Utils.DEBUG()) Log.d(TAG, this + " constructed!"); + } + + protected void finalize() { + if(Utils.DEBUG()) Log.d(TAG, this + " finalized!"); + } + + + /** + * Initialise a VNC connection + * @param bean Connection settings + * @param initCallback Callback that's called after init has succeeded or failed + * @param disconnectCallback Callback that's called when an established connection disconnects + */ + public void init(ConnectionBean bean, OnInitListener initCallback, OnDisconnectListener disconnectCallback) { + + Log.d(TAG, "initializing"); + + maintainConnection = true; + + connSettings = bean; + this.pendingColorModel = COLORMODEL.valueOf(bean.colorModel); + + inputThread = new ServerToClientThread(initCallback, disconnectCallback); + inputThread.start(); + } + + + public void shutdown() { + + Log.d(TAG, "shutting down"); + + maintainConnection = false; + + try { + // the input thread stops by itself, but the putput thread not + outputThread.interrupt(); + } + catch(Exception e) { + } + + connSettings = null; + + System.gc(); + } + + + public boolean sendCutText(String text) { + + if(rfbClient != 0) { // only queue if already connected + OutputEvent e = new OutputEvent(text); + outputEventQueue.add(e); + synchronized (outputEventQueue) { + outputEventQueue.notify(); + } + + return true; + } + else + return false; + } + + + public boolean sendPointerEvent(int x, int y, int modifiers, int pointerMask) { + + + if(rfbClient != 0) { // only queue if already connected + + // safety: trim coordinates + x = trimX(x); + y = trimY(y); + + OutputEvent e = new OutputEvent(x, y, modifiers, pointerMask); + outputEventQueue.add(e); + synchronized (outputEventQueue) { + outputEventQueue.notify(); + } + + return true; + } + else + return false; + } + + /** + * queue key event for sending + * @param keyCode + * @param evt + * @param sendDirectly send key event directly without doing local->rfbKeySym translation + * @return + */ + public boolean sendKeyEvent(int keyCode, KeyEvent evt, boolean sendDirectly) { + + if(rfbClient != 0) { // only queue if already connected + + if(Utils.DEBUG()) Log.d(TAG, "queueing key evt " + evt.toString() + " code 0x" + Integer.toHexString(keyCode)); + + // check if special char + if(evt.getAction() == KeyEvent.ACTION_MULTIPLE && evt.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { + OutputEvent down = new OutputEvent( + evt.getCharacters().codePointAt(0), + evt.getMetaState(), + true); + outputEventQueue.add(down); + + OutputEvent up = new OutputEvent( + evt.getCharacters().codePointAt(0), + evt.getMetaState(), + false); + outputEventQueue.add(up); + + synchronized (outputEventQueue) { + outputEventQueue.notify(); + } + + } + // 'normal' key, i.e. either up or down + else { + + int metaState = evt.getMetaState(); + + // only do translation for events that were *not* synthesized, + // i.e. coming from a metakeybean which already is translated + if(!sendDirectly) { + switch(keyCode) { + case KeyEvent.KEYCODE_BACK : keyCode = 0xff1b; break; + case KeyEvent.KEYCODE_DPAD_LEFT: keyCode = 0xff51; break; + case KeyEvent.KEYCODE_DPAD_UP: keyCode = 0xff52; break; + case KeyEvent.KEYCODE_DPAD_RIGHT: keyCode = 0xff53; break; + case KeyEvent.KEYCODE_DPAD_DOWN: keyCode = 0xff54; break; + case KeyEvent.KEYCODE_DEL: keyCode = 0xff08; break; + case KeyEvent.KEYCODE_ENTER: keyCode = 0xff0d; break; + case KeyEvent.KEYCODE_DPAD_CENTER: keyCode = 0xff0d; break; + case KeyEvent.KEYCODE_TAB: keyCode = 0xff09; break; + case KeyEvent.KEYCODE_ESCAPE: keyCode = 0xff1b; break; + case KeyEvent.KEYCODE_ALT_LEFT: keyCode = 0xffe9; break; + case KeyEvent.KEYCODE_ALT_RIGHT: keyCode = 0xffea; break; + case KeyEvent.KEYCODE_F1: keyCode = 0xffbe; break; + case KeyEvent.KEYCODE_F2: keyCode = 0xffbf; break; + case KeyEvent.KEYCODE_F3: keyCode = 0xffc0; break; + case KeyEvent.KEYCODE_F4: keyCode = 0xffc1; break; + case KeyEvent.KEYCODE_F5: keyCode = 0xffc2; break; + case KeyEvent.KEYCODE_F6: keyCode = 0xffc3; break; + case KeyEvent.KEYCODE_F7: keyCode = 0xffc4; break; + case KeyEvent.KEYCODE_F8: keyCode = 0xffc5; break; + case KeyEvent.KEYCODE_F9: keyCode = 0xffc6; break; + case KeyEvent.KEYCODE_F10: keyCode = 0xffc7; break; + case KeyEvent.KEYCODE_F11: keyCode = 0xffc8; break; + case KeyEvent.KEYCODE_F12: keyCode = 0xffc9; break; + case KeyEvent.KEYCODE_INSERT: keyCode = 0xff63; break; + case KeyEvent.KEYCODE_FORWARD_DEL: keyCode = 0xffff; break; + case KeyEvent.KEYCODE_MOVE_HOME: keyCode = 0xff50; break; + case KeyEvent.KEYCODE_MOVE_END: keyCode = 0xff57; break; + case KeyEvent.KEYCODE_PAGE_UP: keyCode = 0xff55; break; + case KeyEvent.KEYCODE_PAGE_DOWN: keyCode = 0xff56; break; + case KeyEvent.KEYCODE_CTRL_LEFT: keyCode = 0xffe3; break; + case KeyEvent.KEYCODE_CTRL_RIGHT: keyCode = 0xffe4; break; + case KeyEvent.KEYCODE_SHIFT_LEFT: keyCode = 0xffe1; break; + case KeyEvent.KEYCODE_SHIFT_RIGHT: keyCode = 0xffe2; break; + default: + // do keycode -> UTF-8 keysym conversion + KeyEvent tmp = new KeyEvent( + 0, + 0, + 0, + keyCode, + 0, + metaState); + + keyCode = tmp.getUnicodeChar(); + + // Ctrl-C for example needs this... + if (keyCode == 0) { + metaState &= ~KeyEvent.META_CTRL_MASK; + metaState &= ~KeyEvent.META_ALT_MASK; + keyCode = tmp.getUnicodeChar(metaState); + } + + metaState = 0; + break; + + } + } + + OutputEvent e = new OutputEvent( + keyCode, + metaState, + evt.getAction() == KeyEvent.ACTION_DOWN); + outputEventQueue.add(e); + synchronized (outputEventQueue) { + outputEventQueue.notify(); + } + + } + + return true; + } + else + return false; + } + + + + public boolean toggleFramebufferUpdates() + { + framebufferUpdatesEnabled = !framebufferUpdatesEnabled; + rfbSetFramebufferUpdatesEnabled(framebufferUpdatesEnabled); + return framebufferUpdatesEnabled; + } + + + void sendFramebufferUpdateRequest(int x, int y, int w, int h, boolean incremental) { + if(framebufferUpdatesEnabled) + { + try { + OutputEvent e = new OutputEvent(x, y, w, h, incremental); + outputEventQueue.add(e); + synchronized (outputEventQueue) { + outputEventQueue.notify(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + public void setColorModel(COLORMODEL cm) { + // Only update if color model changes + if (colorModel == null || !colorModel.equals(cm)) + pendingColorModel = cm; + } + + + + public final COLORMODEL getColorModel() { + return colorModel; + } + + + public String getEncoding() { + return ""; //TODO: wire up to native + } + + + public final String getDesktopName() { + return rfbGetDesktopName(); + } + + public final int getFramebufferWidth() { + return rfbGetFramebufferWidth(); + } + + public final int getFramebufferHeight() { + return rfbGetFramebufferHeight(); + } + + public int trimX(int x) { + if (x<0) return 0; + else if (x>=getFramebufferWidth()) return getFramebufferWidth()-1; + return x; + } + + public int trimY(int y) { + if (y<0) return 0; + else if (y>=getFramebufferHeight()) return getFramebufferHeight()-1; + return y; + } + + public String getCutText() { + return serverCutText; + } + + public boolean isEncrypted() { return rfbIsEncrypted(); } + + public void lockFramebuffer() { + bitmapDataPixelsLock.lock(); + } + + public void unlockFramebuffer() { + bitmapDataPixelsLock.unlock(); + } + + + public final ConnectionBean getConnSettings() { + return connSettings; + } + + + // called from native via worker thread context + @Keep + private void onFramebufferUpdateFinished() { + if(onFramebufferEventCallback != null) + onFramebufferEventCallback.onFramebufferUpdateFinished(); + } + + // called from native via worker thread context + @Keep + private void onGotCutText(byte[] bytes) { + serverCutText = StandardCharsets.ISO_8859_1.decode(ByteBuffer.wrap(bytes)).toString(); + Log.d(TAG, "got server cuttext: " + serverCutText); + } + + // called from native via worker thread context + @Keep + private String onGetPassword() { + if (onAuthEventCallback != null && (connSettings.password == null || connSettings.password.length() == 0)) { + onAuthEventCallback.onRequestCredsFromUser(connSettings, false); // this cares for running on the main thread + synchronized (VNCConn.this) { + try { + VNCConn.this.wait(); // wait for user input to finish + } catch (InterruptedException e) { + //unused + } + } + } + return connSettings.password; + } + + /** + * This class is used for returning user credentials from onGetCredential() to native + */ + @Keep + private static class UserCredential { + public String username; + public String password; + } + + // called from native via worker thread context + @Keep + private UserCredential onGetUserCredential() { + + if (onAuthEventCallback != null && + (connSettings.userName == null || connSettings.userName.isEmpty() + || connSettings.password == null || connSettings.password.isEmpty())) { + onAuthEventCallback.onRequestCredsFromUser(connSettings, connSettings.userName == null || connSettings.userName.isEmpty()); + synchronized (VNCConn.this) { + try { + VNCConn.this.wait(); // wait for user input to finish + } catch (InterruptedException e) { + //unused + } + } + } + + UserCredential creds = new UserCredential(); + creds.username = connSettings.userName; + creds.password = connSettings.password; + return creds; + } + + // called from native via worker thread context + @Keep + private void onNewFramebufferSize(int w, int h) { + Log.d(TAG, "new framebuffer size " + w + " x " + h); + if(onFramebufferEventCallback != null) + onFramebufferEventCallback.onNewFramebufferSize(w, h); + } + + // called from native via worker thread context + @Keep + private int onSshFingerprintCheck(String host, byte[] fingerprint) { + if(onAuthEventCallback != null) { + AtomicBoolean doContinue = new AtomicBoolean(); + onAuthEventCallback.onRequestSshFingerprintCheck(host, fingerprint, doContinue); + synchronized (VNCConn.this) { + try { + VNCConn.this.wait(); // wait for user input to finish + } catch (InterruptedException e) { + //unused + } + } + // evaluate result + if(doContinue.get()) { + return 0; + } + } + return -1; + } +} + diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/MainMenuActivity.java b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/MainMenuActivity.java index aefabf6c..9b32f73f 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/MainMenuActivity.java +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/MainMenuActivity.java @@ -1,645 +1,645 @@ -/* - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -// -// MainMenuActivity is the Activity for setting VNC server IP and port. -// - -package com.coboltforge.dontmind.multivnc.ui; - -import android.app.AlertDialog; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.util.Log; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.OnLifecycleEvent; -import androidx.lifecycle.ProcessLifecycleOwner; - -import com.coboltforge.dontmind.multivnc.Constants; -import com.coboltforge.dontmind.multivnc.MDNSService; -import com.coboltforge.dontmind.multivnc.R; -import com.coboltforge.dontmind.multivnc.Utils; -import com.coboltforge.dontmind.multivnc.db.ConnectionBean; -import com.coboltforge.dontmind.multivnc.db.VncDatabase; -import com.google.android.material.textfield.TextInputLayout; - -import java.util.Collections; -import java.util.Hashtable; -import java.util.List; - - -public class MainMenuActivity extends AppCompatActivity implements MDNSService.OnEventListener, LifecycleObserver { - - private static final String TAG = "MainMenuActivity"; - - private LinearLayout serverlist; - private LinearLayout bookmarkslist; - - private VncDatabase database; - - // service discovery stuff - private MDNSService boundMDNSService; - private ServiceConnection connection_MDNSService; - private android.os.Handler handler = new android.os.Handler(); - - - public class MDNSServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, IBinder service) { - // This is called when the connection with the service has been - // established, giving us the service object we can use to - // interact with the service. Because we have bound to a explicit - // service that we know is running in our own process, we can - // cast its IBinder to a concrete class and directly access it. - boundMDNSService = ((MDNSService.LocalBinder) service).getService(); - - // register our callback to be notified about changes - boundMDNSService.registerCallback(MainMenuActivity.this); - - // and force a dump of discovered connections - boundMDNSService.dump(); - - Log.d(TAG, "connected to MDNSService " + boundMDNSService); - } - - public void onServiceDisconnected(ComponentName className) { - - Log.d(TAG, "disconnected from MDNSService " + boundMDNSService); - - // This is called when the connection with the service has been - // unexpectedly disconnected -- that is, its process crashed. - // Because it is running in our same process, we should never - // see this happen. - boundMDNSService = null; - } - }; - - - - @Override - public void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - setContentView(R.layout.mainmenu_activity); - - // start the MDNS service - startMDNSService(); - // and (re-)bind to MDNS service - bindToMDNSService(new Intent(this, MDNSService.class)); - - serverlist = (LinearLayout) findViewById(R.id.discovered_servers_list); - bookmarkslist = (LinearLayout) findViewById(R.id.bookmarks_list); - - Button goButton = (Button) findViewById(R.id.buttonGO); - goButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ConnectionEditFragment editor = (ConnectionEditFragment) getSupportFragmentManager().findFragmentById(R.id.connectionEditFragment); - ConnectionBean conn = editor != null ? editor.getConnection() : null; - if(conn == null) - return; - Log.d(TAG, "Starting NEW connection " + conn.toString()); - Intent intent = new Intent(MainMenuActivity.this, VncCanvasActivity.class); - intent.putExtra(Constants.CONNECTION,conn); - startActivity(intent); - } - }); - - Button saveBookmarkButton = (Button) findViewById(R.id.buttonSaveBookmark); - saveBookmarkButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ConnectionEditFragment editor = (ConnectionEditFragment) getSupportFragmentManager().findFragmentById(R.id.connectionEditFragment); - final ConnectionBean conn = editor != null ? editor.getConnection() : null; - if(conn == null) - return; - - final String defaultName = conn.address + ":" + conn.port; - - final EditText input = new EditText(MainMenuActivity.this); - input.setHint(defaultName); - TextInputLayout inputLayout = new TextInputLayout(MainMenuActivity.this); - inputLayout.setPadding( - (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), - 0, - (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), - 0 - ); - inputLayout.addView(input); - - new AlertDialog.Builder(MainMenuActivity.this) - .setMessage(getString(R.string.enterbookmarkname)) - .setView(inputLayout) - .setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - String name = input.getText().toString(); - if(name.length() == 0) - name = defaultName; - conn.nickname = name; - saveBookmark(conn); - updateBookmarkView(); - } - }).setNegativeButton(getString(android.R.string.cancel), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - // Do nothing. - } - }).show(); - - } - }); - - database = VncDatabase.getInstance(this); - - - final SharedPreferences settings = getSharedPreferences(Constants.PREFSNAME, MODE_PRIVATE); - - /* - * show support dialog on third (and maybe later) runs - */ - if(settings.getInt(Constants.PREFS_KEY_MAIN_MENU_STARTS, 0) > 2) - { - if(settings.getBoolean(Constants.PREFS_KEY_SUPPORTDLG, true) && savedInstanceState == null) - { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(getString(R.string.support_dialog_title)); - dialog.setIcon(getResources().getDrawable(R.drawable.ic_launcher)); - dialog.setMessage(R.string.support_dialog_text); - - dialog.setPositiveButton(getString(R.string.support_dialog_yes), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startActivity(new Intent(MainMenuActivity.this, AboutActivity.class)); - try{ - dialog.dismiss(); - } - catch(Exception e) { - } - } - }); - dialog.setNeutralButton(getString(R.string.support_dialog_no), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try{ - dialog.dismiss(); - } - catch(Exception e) { - } - } - }); - dialog.setNegativeButton(getString(R.string.support_dialog_neveragain), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences settings = getSharedPreferences(Constants.PREFSNAME, MODE_PRIVATE); - SharedPreferences.Editor ed = settings.edit(); - ed.putBoolean(Constants.PREFS_KEY_SUPPORTDLG, false); - ed.commit(); - - try{ - dialog.dismiss(); - } - catch(Exception e) { - } - } - }); - - dialog.show(); - } - } - SharedPreferences.Editor ed = settings.edit(); - ed.putInt(Constants.PREFS_KEY_MAIN_MENU_STARTS, settings.getInt(Constants.PREFS_KEY_MAIN_MENU_STARTS, 0)+1); - ed.apply(); - - - /* - * show changelog if version changed - */ - try { - //current version - PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - int versionCode = packageInfo.versionCode; - - int lastVersionCode = settings.getInt("lastVersionCode", 0); - - if(lastVersionCode < versionCode) { - SharedPreferences.Editor editor = settings.edit(); - editor.putInt("lastVersionCode", versionCode); - editor.commit(); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.changelog_dialog_title)); - builder.setIcon(getResources().getDrawable(R.drawable.ic_launcher)); - builder.setMessage(Html.fromHtml(getString(R.string.changelog_dialog_text))); - builder.setPositiveButton(getString(android.R.string.ok), new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try{ - dialog.dismiss(); - } - catch(Exception e) { - } - } - }); - - AlertDialog dialog = builder.create(); - dialog.show(); - TextView msgTxt = (TextView) dialog.findViewById(android.R.id.message); - msgTxt.setMovementMethod(LinkMovementMethod.getInstance()); - } - } catch (NameNotFoundException e) { - Log.w(TAG, "Unable to get version code. Will not show changelog", e); - } - - - } - - protected void onDestroy() { - - Log.d(TAG, "onDestroy()"); - - super.onDestroy(); - - unbindFromMDNSService(); - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - stopService(new Intent(Intent.ACTION_VIEW, null, this, MDNSService.class)); - } - - /* (non-Javadoc) - * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) - */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.mainmenuactivitymenu,menu); - - menu.findItem(R.id.itemMDNSRestart).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.findItem(R.id.itemImportExport).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.findItem(R.id.itemOpenDoc).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - - return true; - } - - - /* (non-Javadoc) - * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) - { - case R.id.itemMDNSRestart : - serverlist.removeAllViews(); - findViewById(R.id.discovered_servers_waitwheel).setVisibility(View.VISIBLE); - - try { - boundMDNSService.restart(); - } - catch(NullPointerException e) { - } - break; - case R.id.itemImportExport : - new ImportExportDialog().show(getSupportFragmentManager(), "importexport"); - break; - case R.id.itemOpenDoc : - Intent intent = new Intent (this, AboutActivity.class); - this.startActivity(intent); - break; - } - return true; - } - - - - - protected void onStart() { - super.onStart(); - updateBookmarkView(); - } - - - void updateBookmarkView() { - List bookmarked_connections = database.getConnectionDao().getAll(); - - Collections.sort(bookmarked_connections); - - Log.d(TAG, "updateBookMarkView()"); - -// int connectionIndex=0; -// if ( bookmarked_connections.size()>1) -// { -// MostRecentBean mostRecent = getMostRecent(database.getReadableDatabase()); -// if (mostRecent != null) -// { -// for ( int i=1; i= Build.VERSION_CODES.O) { - if (ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { - Intent serviceIntent = new Intent(Intent.ACTION_VIEW, null, this, MDNSService.class); - try { - this.startService(serviceIntent); - } - catch(IllegalStateException e) { - // Can still happen on Android 9 due to https://issuetracker.google.com/issues/113122354 - // At least, don't crash - user's can restart the scanner manually anyway. - } - } else { - ProcessLifecycleOwner.get().getLifecycle().addObserver(this); - } - } else { - Intent serviceIntent = new Intent(Intent.ACTION_VIEW, null, this, MDNSService.class); - this.startService(serviceIntent); - } - } - - @OnLifecycleEvent(Lifecycle.Event.ON_START) - void onEnterForeground() { - startMDNSService(); - ProcessLifecycleOwner.get().getLifecycle().removeObserver(this); - } - - void bindToMDNSService(Intent serviceIntent) { - unbindFromMDNSService(); - connection_MDNSService = new MDNSServiceConnection(); - if (!this.bindService(serviceIntent, connection_MDNSService, Context.BIND_AUTO_CREATE)) - Toast.makeText(this, "Could not bind to MDNSService!", Toast.LENGTH_LONG).show(); - } - - void unbindFromMDNSService() - { - if(connection_MDNSService != null) - { - this.unbindService(connection_MDNSService); - connection_MDNSService = null; - try { - boundMDNSService.registerCallback(null); - boundMDNSService = null; - } catch (NullPointerException e) { - // was already null - } - } - } - - @Override - public void onMDNSnotify(final String conn_name, final ConnectionBean conn, final Hashtable connections_discovered ) { - handler.postDelayed(new Runnable() { - public void run() { - - String msg; - if(conn != null) - msg = getString(R.string.server_found) + ": " + conn_name; - else - msg = getString(R.string.server_removed) + ": " + conn_name; - - // only show Toasts if activity is in foreground - if(getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) - Toast.makeText(getApplicationContext(), msg , Toast.LENGTH_SHORT).show(); - - // update - LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - /* - * we don't care whether this is a conn add or removal (conn!=null or conn==null), - * we always rebuild the whole view for the sake of code simplicity - */ - serverlist.removeAllViews(); - // refill - synchronized (connections_discovered) { - for(final ConnectionBean c : connections_discovered.values()) { - // the list item - View v = vi.inflate(R.layout.discovered_servers_list_item, null); - - // name part of list item - TextView name = (TextView) v.findViewById(R.id.discovered_server_name); - name.setText(c.nickname); - name.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Log.d(TAG, "Starting discovered connection " + c.toString()); - Intent intent = new Intent(MainMenuActivity.this, VncCanvasActivity.class); - intent.putExtra(Constants.CONNECTION , c); - startActivity(intent); - } - }); - - // button part of list item - ImageButton button = (ImageButton) v.findViewById(R.id.discovered_server_save_button); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - AlertDialog.Builder builder = new AlertDialog.Builder(MainMenuActivity.this); - builder.setMessage(getString(R.string.bookmark_save_question)) - .setCancelable(false) - .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Log.d(TAG, "Bookmarking connection " + c.id); - // save bookmark - saveBookmark(c); - // set as 'new' again. makes this ConnectionBean saveable again - c.id = 0; - // and update view - updateBookmarkView(); - } - }) - .setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - AlertDialog saveornot = builder.create(); - saveornot.show(); - - } - }); - - serverlist.addView(v); - } - } - - - - } - }, 1); - - } - - @Override - public void onMDNSstartupCompleted(boolean wasSuccessful) { - runOnUiThread(new Runnable() { - @Override - public void run() { - try { - findViewById(R.id.discovered_servers_waitwheel).setVisibility(View.GONE); - } - catch(Exception e) { - //unused - } - } - }); - } -} +/* + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// +// MainMenuActivity is the Activity for setting VNC server IP and port. +// + +package com.coboltforge.dontmind.multivnc.ui; + +import android.app.AlertDialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.util.Log; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; +import androidx.lifecycle.ProcessLifecycleOwner; + +import com.coboltforge.dontmind.multivnc.Constants; +import com.coboltforge.dontmind.multivnc.MDNSService; +import com.coboltforge.dontmind.multivnc.R; +import com.coboltforge.dontmind.multivnc.Utils; +import com.coboltforge.dontmind.multivnc.db.ConnectionBean; +import com.coboltforge.dontmind.multivnc.db.VncDatabase; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; + + +public class MainMenuActivity extends AppCompatActivity implements MDNSService.OnEventListener, LifecycleObserver { + + private static final String TAG = "MainMenuActivity"; + + private LinearLayout serverlist; + private LinearLayout bookmarkslist; + + private VncDatabase database; + + // service discovery stuff + private MDNSService boundMDNSService; + private ServiceConnection connection_MDNSService; + private android.os.Handler handler = new android.os.Handler(); + + + public class MDNSServiceConnection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. Because we have bound to a explicit + // service that we know is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. + boundMDNSService = ((MDNSService.LocalBinder) service).getService(); + + // register our callback to be notified about changes + boundMDNSService.registerCallback(MainMenuActivity.this); + + // and force a dump of discovered connections + boundMDNSService.dump(); + + Log.d(TAG, "connected to MDNSService " + boundMDNSService); + } + + public void onServiceDisconnected(ComponentName className) { + + Log.d(TAG, "disconnected from MDNSService " + boundMDNSService); + + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + // Because it is running in our same process, we should never + // see this happen. + boundMDNSService = null; + } + }; + + + + @Override + public void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + setContentView(R.layout.mainmenu_activity); + + // start the MDNS service + startMDNSService(); + // and (re-)bind to MDNS service + bindToMDNSService(new Intent(this, MDNSService.class)); + + serverlist = (LinearLayout) findViewById(R.id.discovered_servers_list); + bookmarkslist = (LinearLayout) findViewById(R.id.bookmarks_list); + + Button goButton = (Button) findViewById(R.id.buttonGO); + goButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ConnectionEditFragment editor = (ConnectionEditFragment) getSupportFragmentManager().findFragmentById(R.id.connectionEditFragment); + ConnectionBean conn = editor != null ? editor.getConnection() : null; + if(conn == null) + return; + Log.d(TAG, "Starting NEW connection " + conn.toString()); + Intent intent = new Intent(MainMenuActivity.this, VncCanvasActivity.class); + intent.putExtra(Constants.CONNECTION,conn); + startActivity(intent); + } + }); + + Button saveBookmarkButton = (Button) findViewById(R.id.buttonSaveBookmark); + saveBookmarkButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ConnectionEditFragment editor = (ConnectionEditFragment) getSupportFragmentManager().findFragmentById(R.id.connectionEditFragment); + final ConnectionBean conn = editor != null ? editor.getConnection() : null; + if(conn == null) + return; + + final String defaultName = conn.address + ":" + conn.port; + + final EditText input = new EditText(MainMenuActivity.this); + input.setHint(defaultName); + TextInputLayout inputLayout = new TextInputLayout(MainMenuActivity.this); + inputLayout.setPadding( + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), + 0, + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), + 0 + ); + inputLayout.addView(input); + + new AlertDialog.Builder(MainMenuActivity.this) + .setMessage(getString(R.string.enterbookmarkname)) + .setView(inputLayout) + .setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String name = input.getText().toString(); + if(name.length() == 0) + name = defaultName; + conn.nickname = name; + saveBookmark(conn); + updateBookmarkView(); + } + }).setNegativeButton(getString(android.R.string.cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Do nothing. + } + }).show(); + + } + }); + + database = VncDatabase.getInstance(this); + + + final SharedPreferences settings = getSharedPreferences(Constants.PREFSNAME, MODE_PRIVATE); + + /* + * show support dialog on third (and maybe later) runs + */ + if(settings.getInt(Constants.PREFS_KEY_MAIN_MENU_STARTS, 0) > 2) + { + if(settings.getBoolean(Constants.PREFS_KEY_SUPPORTDLG, true) && savedInstanceState == null) + { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(getString(R.string.support_dialog_title)); + dialog.setIcon(getResources().getDrawable(R.drawable.ic_launcher)); + dialog.setMessage(R.string.support_dialog_text); + + dialog.setPositiveButton(getString(R.string.support_dialog_yes), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(MainMenuActivity.this, AboutActivity.class)); + try{ + dialog.dismiss(); + } + catch(Exception e) { + } + } + }); + dialog.setNeutralButton(getString(R.string.support_dialog_no), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try{ + dialog.dismiss(); + } + catch(Exception e) { + } + } + }); + dialog.setNegativeButton(getString(R.string.support_dialog_neveragain), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + SharedPreferences settings = getSharedPreferences(Constants.PREFSNAME, MODE_PRIVATE); + SharedPreferences.Editor ed = settings.edit(); + ed.putBoolean(Constants.PREFS_KEY_SUPPORTDLG, false); + ed.commit(); + + try{ + dialog.dismiss(); + } + catch(Exception e) { + } + } + }); + + dialog.show(); + } + } + SharedPreferences.Editor ed = settings.edit(); + ed.putInt(Constants.PREFS_KEY_MAIN_MENU_STARTS, settings.getInt(Constants.PREFS_KEY_MAIN_MENU_STARTS, 0)+1); + ed.apply(); + + + /* + * show changelog if version changed + */ + try { + //current version + PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + int versionCode = packageInfo.versionCode; + + int lastVersionCode = settings.getInt("lastVersionCode", 0); + + if(lastVersionCode < versionCode) { + SharedPreferences.Editor editor = settings.edit(); + editor.putInt("lastVersionCode", versionCode); + editor.commit(); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.changelog_dialog_title)); + builder.setIcon(getResources().getDrawable(R.drawable.ic_launcher)); + builder.setMessage(Html.fromHtml(getString(R.string.changelog_dialog_text))); + builder.setPositiveButton(getString(android.R.string.ok), new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try{ + dialog.dismiss(); + } + catch(Exception e) { + } + } + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + TextView msgTxt = (TextView) dialog.findViewById(android.R.id.message); + msgTxt.setMovementMethod(LinkMovementMethod.getInstance()); + } + } catch (NameNotFoundException e) { + Log.w(TAG, "Unable to get version code. Will not show changelog", e); + } + + + } + + protected void onDestroy() { + + Log.d(TAG, "onDestroy()"); + + super.onDestroy(); + + unbindFromMDNSService(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + stopService(new Intent(Intent.ACTION_VIEW, null, this, MDNSService.class)); + } + + /* (non-Javadoc) + * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.mainmenuactivitymenu,menu); + + menu.findItem(R.id.itemMDNSRestart).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.findItem(R.id.itemImportExport).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.findItem(R.id.itemOpenDoc).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + + return true; + } + + + /* (non-Javadoc) + * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) + { + case R.id.itemMDNSRestart : + serverlist.removeAllViews(); + findViewById(R.id.discovered_servers_waitwheel).setVisibility(View.VISIBLE); + + try { + boundMDNSService.restart(); + } + catch(NullPointerException e) { + } + break; + case R.id.itemImportExport : + new ImportExportDialog().show(getSupportFragmentManager(), "importexport"); + break; + case R.id.itemOpenDoc : + Intent intent = new Intent (this, AboutActivity.class); + this.startActivity(intent); + break; + } + return true; + } + + + + + protected void onStart() { + super.onStart(); + updateBookmarkView(); + } + + + void updateBookmarkView() { + List bookmarked_connections = database.getConnectionDao().getAll(); + + Collections.sort(bookmarked_connections); + + Log.d(TAG, "updateBookMarkView()"); + +// int connectionIndex=0; +// if ( bookmarked_connections.size()>1) +// { +// MostRecentBean mostRecent = getMostRecent(database.getReadableDatabase()); +// if (mostRecent != null) +// { +// for ( int i=1; i= Build.VERSION_CODES.O) { + if (ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { + Intent serviceIntent = new Intent(Intent.ACTION_VIEW, null, this, MDNSService.class); + try { + this.startService(serviceIntent); + } + catch(IllegalStateException e) { + // Can still happen on Android 9 due to https://issuetracker.google.com/issues/113122354 + // At least, don't crash - user's can restart the scanner manually anyway. + } + } else { + ProcessLifecycleOwner.get().getLifecycle().addObserver(this); + } + } else { + Intent serviceIntent = new Intent(Intent.ACTION_VIEW, null, this, MDNSService.class); + this.startService(serviceIntent); + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + void onEnterForeground() { + startMDNSService(); + ProcessLifecycleOwner.get().getLifecycle().removeObserver(this); + } + + void bindToMDNSService(Intent serviceIntent) { + unbindFromMDNSService(); + connection_MDNSService = new MDNSServiceConnection(); + if (!this.bindService(serviceIntent, connection_MDNSService, Context.BIND_AUTO_CREATE)) + Toast.makeText(this, "Could not bind to MDNSService!", Toast.LENGTH_LONG).show(); + } + + void unbindFromMDNSService() + { + if(connection_MDNSService != null) + { + this.unbindService(connection_MDNSService); + connection_MDNSService = null; + try { + boundMDNSService.registerCallback(null); + boundMDNSService = null; + } catch (NullPointerException e) { + // was already null + } + } + } + + @Override + public void onMDNSnotify(final String conn_name, final ConnectionBean conn, final Hashtable connections_discovered ) { + handler.postDelayed(new Runnable() { + public void run() { + + String msg; + if(conn != null) + msg = getString(R.string.server_found) + ": " + conn_name; + else + msg = getString(R.string.server_removed) + ": " + conn_name; + + // only show Toasts if activity is in foreground + if(getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) + Toast.makeText(getApplicationContext(), msg , Toast.LENGTH_SHORT).show(); + + // update + LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + /* + * we don't care whether this is a conn add or removal (conn!=null or conn==null), + * we always rebuild the whole view for the sake of code simplicity + */ + serverlist.removeAllViews(); + // refill + synchronized (connections_discovered) { + for(final ConnectionBean c : connections_discovered.values()) { + // the list item + View v = vi.inflate(R.layout.discovered_servers_list_item, null); + + // name part of list item + TextView name = (TextView) v.findViewById(R.id.discovered_server_name); + name.setText(c.nickname); + name.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.d(TAG, "Starting discovered connection " + c.toString()); + Intent intent = new Intent(MainMenuActivity.this, VncCanvasActivity.class); + intent.putExtra(Constants.CONNECTION , c); + startActivity(intent); + } + }); + + // button part of list item + ImageButton button = (ImageButton) v.findViewById(R.id.discovered_server_save_button); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + AlertDialog.Builder builder = new AlertDialog.Builder(MainMenuActivity.this); + builder.setMessage(getString(R.string.bookmark_save_question)) + .setCancelable(false) + .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Log.d(TAG, "Bookmarking connection " + c.id); + // save bookmark + saveBookmark(c); + // set as 'new' again. makes this ConnectionBean saveable again + c.id = 0; + // and update view + updateBookmarkView(); + } + }) + .setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog saveornot = builder.create(); + saveornot.show(); + + } + }); + + serverlist.addView(v); + } + } + + + + } + }, 1); + + } + + @Override + public void onMDNSstartupCompleted(boolean wasSuccessful) { + runOnUiThread(new Runnable() { + @Override + public void run() { + try { + findViewById(R.id.discovered_servers_waitwheel).setVisibility(View.GONE); + } + catch(Exception e) { + //unused + } + } + }); + } +} diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/PointerInputHandler.java b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/PointerInputHandler.java index 4d0f05ea..b99f98d8 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/PointerInputHandler.java +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/PointerInputHandler.java @@ -1,562 +1,562 @@ -package com.coboltforge.dontmind.multivnc.ui; - -import android.os.Build; -import android.util.Log; -import android.view.GestureDetector; -import android.view.HapticFeedbackConstants; -import android.view.InputDevice; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.SoundEffectConstants; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import com.coboltforge.dontmind.multivnc.Utils; -import com.coboltforge.dontmind.multivnc.VNCConn; -import com.coboltforge.dontmind.multivnc.db.MetaKeyBean; - -/** - * Handles pointer input from: - * * touchscreen - * * stylus - * * physical mouse - * and uses this for: - * * scaling - * * two-finger fling - * * pointer movement - * * click - * * drag - * detection and handover of events to VncCanvasActivity and VncCanvas. - */ -@SuppressWarnings("FieldCanBeLocal") -public class PointerInputHandler extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener { - - private static final String TAG = "PointerInputHandler"; - - private final VncCanvas vncCanvas; - private final ViewGroup mousebuttons; - private final Toast notificationToast; - protected GestureDetector gestures; - protected ScaleGestureDetector scaleGestures; - - float xInitialFocus; - float yInitialFocus; - boolean inScaling; - - /* - * Samsung S Pen support - */ - private final int SAMSUNG_SPEN_ACTION_MOVE = 213; - private final int SAMSUNG_SPEN_ACTION_UP = 212; - private final int SAMSUNG_SPEN_ACTION_DOWN = 211; - private final String SAMSUNG_MANUFACTURER_NAME = "samsung"; - - - /** - * In drag mode (entered with long press) you process mouse events - * without sending them through the gesture detector - */ - private boolean dragMode; - float dragX, dragY; - private boolean dragModeButtonDown = false; - private boolean dragModeButton2insteadof1 = false; - - /* - * two-finger fling gesture stuff - */ - private long twoFingerFlingStart = -1; - private VelocityTracker twoFingerFlingVelocityTracker; - private boolean twoFingerFlingDetected = false; - private final int TWO_FINGER_FLING_UNITS = 1000; - private final float TWO_FINGER_FLING_THRESHOLD = 1000; - - PointerInputHandler(VncCanvas vncCanvas, ViewGroup mouseButtonView, Toast notificationToast) { - this.vncCanvas = vncCanvas; - mousebuttons = mouseButtonView; - this.notificationToast = notificationToast; - gestures= new GestureDetector(vncCanvas.getContext(), this, null, false); // this is a SDK 8+ feature and apparently needed if targetsdk is set - gestures.setOnDoubleTapListener(this); - scaleGestures = new ScaleGestureDetector(vncCanvas.getContext(), this); - scaleGestures.setQuickScaleEnabled(false); - - Log.d(TAG, "MightyInputHandler " + this + " created!"); - } - - - public void init() { - twoFingerFlingVelocityTracker = VelocityTracker.obtain(); - Log.d(TAG, "MightyInputHandler " + this + " init!"); - } - - public void shutdown() { - try { - twoFingerFlingVelocityTracker.recycle(); - twoFingerFlingVelocityTracker = null; - } - catch (NullPointerException ignored) { - } - Log.d(TAG, "MightyInputHandler " + this + " shutdown!"); - } - - - protected boolean isTouchEvent(MotionEvent event) { - return event.getSource() == InputDevice.SOURCE_TOUCHSCREEN || - event.getSource() == InputDevice.SOURCE_TOUCHPAD; - } - - - @Override - public boolean onScale(ScaleGestureDetector detector) { - boolean consumed = true; - float fx = detector.getFocusX(); - float fy = detector.getFocusY(); - - if (Math.abs(1.0 - detector.getScaleFactor()) < 0.01) - consumed = false; - - //`inScaling` is used to disable multi-finger scroll/fling gestures while scaling. - //But instead of setting it in `onScaleBegin()`, we do it here after some checks. - //This is a work around for some devices which triggers scaling very early which - //disables fling gestures. - if (!inScaling) { - double xfs = fx - xInitialFocus; - double yfs = fy - yInitialFocus; - double fs = Math.sqrt(xfs * xfs + yfs * yfs); - if (fs * 2 < Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan())) { - inScaling = true; - } - } - - if (consumed && inScaling) { - if (vncCanvas != null && vncCanvas.scaling != null) - vncCanvas.scaling.adjust(detector.getScaleFactor(), fx, fy); - } - - return consumed; - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - xInitialFocus = detector.getFocusX(); - yInitialFocus = detector.getFocusY(); - inScaling = false; - //Log.i(TAG,"scale begin ("+xInitialFocus+","+yInitialFocus+")"); - return true; - } - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - //Log.i(TAG,"scale end"); - inScaling = false; - } - - /** - * scale down delta when it is small. This will allow finer control - * when user is making a small movement on touch screen. - * Scale up delta when delta is big. This allows fast mouse movement when - * user is flinging. - */ - public static float fineCtrlScale(float delta) { - float sign = (delta>0) ? 1 : -1; - delta = Math.abs(delta); - if (delta>=1 && delta <=3) { - delta = 1; - }else if (delta <= 10) { - delta *= 0.34; - } else if (delta <= 30 ) { - delta *= delta/30; - } else if (delta <= 90) { - delta *= (delta/30); - } else { - delta *= 3.0; - } - return sign * delta; - } - - private void twoFingerFlingNotification(String str) - { - // bzzt if user enabled it in System Settings - vncCanvas.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - - // beep if user enabled it in System Settings - vncCanvas.playSoundEffect(SoundEffectConstants.CLICK); - - notificationToast.setText(str); - notificationToast.show(); - } - - private void twoFingerFlingAction(Character d) - { - switch(d) { - case '←': - vncCanvas.sendMetaKey(MetaKeyBean.keyArrowLeft); - break; - case '→': - vncCanvas.sendMetaKey(MetaKeyBean.keyArrowRight); - break; - case '↑': - vncCanvas.sendMetaKey(MetaKeyBean.keyArrowUp); - break; - case '↓': - vncCanvas.sendMetaKey(MetaKeyBean.keyArrowDown); - break; - } - } - - @Override - public void onLongPress(MotionEvent e) { - - if (!isTouchEvent(e)) - return; - - if(Utils.DEBUG()) Log.d(TAG, "Input: long press"); - - dragMode = true; - dragX = e.getX(); - dragY = e.getY(); - - // only interpret as button down if virtual mouse buttons are disabled - if(mousebuttons.getVisibility() != View.VISIBLE) { - dragModeButtonDown = true; - vncCanvas.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - - if (!isTouchEvent(e1)) - return false; - - if (!isTouchEvent(e2)) - return false; - - if (e2.getPointerCount() > 1) - { - if(Utils.DEBUG()) Log.d(TAG, "Input: scroll multitouch"); - - if (inScaling) - return false; - - // pan on 3 fingers and more - if(e2.getPointerCount() > 2) { - return vncCanvas.pan((int) distanceX, (int) distanceY); - } - - /* - * here comes the stuff that acts for two fingers - */ - - // gesture start - if(twoFingerFlingStart != e1.getEventTime()) // it's a new scroll sequence - { - twoFingerFlingStart = e1.getEventTime(); - twoFingerFlingDetected = false; - twoFingerFlingVelocityTracker.clear(); - if(Utils.DEBUG()) Log.d(TAG, "new twoFingerFling detection started"); - } - - // gesture end - if(!twoFingerFlingDetected) // not yet detected in this sequence (we only want ONE event per sequence!) - { - // update our velocity tracker - twoFingerFlingVelocityTracker.addMovement(e2); - twoFingerFlingVelocityTracker.computeCurrentVelocity(TWO_FINGER_FLING_UNITS); - - float velocityX = twoFingerFlingVelocityTracker.getXVelocity(); - float velocityY = twoFingerFlingVelocityTracker.getYVelocity(); - - // check for left/right flings - if(Math.abs(velocityX) > TWO_FINGER_FLING_THRESHOLD - && Math.abs(velocityX) > Math.abs(2*velocityY)) { - if(velocityX < 0) { - if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling LEFT detected"); - twoFingerFlingNotification("←"); - twoFingerFlingAction('←'); - } - else { - if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling RIGHT detected"); - twoFingerFlingNotification("→"); - twoFingerFlingAction('→'); - } - - twoFingerFlingDetected = true; - } - - // check for left/right flings - if(Math.abs(velocityY) > TWO_FINGER_FLING_THRESHOLD - && Math.abs(velocityY) > Math.abs(2*velocityX)) { - if(velocityY < 0) { - if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling UP detected"); - twoFingerFlingNotification("↑"); - twoFingerFlingAction('↑'); - } - else { - if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling DOWN detected"); - twoFingerFlingNotification("↓"); - twoFingerFlingAction('↓'); - } - - twoFingerFlingDetected = true; - } - - } - - return twoFingerFlingDetected; - } - else - { - // compute the relative movement offset on the remote screen. - float deltaX = -distanceX; - float deltaY = -distanceY; - deltaX = fineCtrlScale(deltaX); - deltaY = fineCtrlScale(deltaY); - - // compute the absolution new mouse pos on the remote site. - float newRemoteX = vncCanvas.mouseX + deltaX; - float newRemoteY = vncCanvas.mouseY + deltaY; - - if(Utils.DEBUG()) Log.d(TAG, "Input: scroll single touch from " - + vncCanvas.mouseX + "," + vncCanvas.mouseY - + " to " + (int)newRemoteX + "," + (int)newRemoteY); - -// if (dragMode) { -// -// Log.d(TAG, "dragmode in scroll!!!!"); -// -// if (e2.getAction() == MotionEvent.ACTION_UP) -// dragMode = false; -// dragX = e2.getX(); -// dragY = e2.getY(); -// e2.setLocation(newRemoteX, newRemoteY); -// return vncCanvas.processPointerEvent(e2, true); -// } - - e2.setLocation(newRemoteX, newRemoteY); - vncCanvas.processPointerEvent(e2, false); - } - return false; - } - - - public boolean onTouchEvent(MotionEvent e) { - if (!isTouchEvent(e)) { // physical input device - - if(Utils.DEBUG()) Log.d(TAG, "Input: touch not screen nor pad: x:" + e.getX() + " y:" + e.getY() + " action:" + e.getAction()); - - e = vncCanvas.changeTouchCoordinatesToFullFrame(e); - - // modify MotionEvent to support Samsung S Pen Event and activate rightButton accordingly - // if Samsung S Pen is not present or reports false, check for right mouse button - boolean isSPen = spenActionConvert(e); - boolean isRightButton = e.isButtonPressed(MotionEvent.BUTTON_SECONDARY); - - if (isSPen || Build.VERSION.SDK_INT < 23) - vncCanvas.processPointerEvent(e, true, isSPen || isRightButton); - else if (e.getActionMasked() == MotionEvent.ACTION_MOVE) - vncCanvas.processMouseEvent(0, true, (int) e.getX(), (int) e.getY()); - - return true; - } - - if (dragMode) { - - if(Utils.DEBUG()) Log.d(TAG, "Input: touch dragMode"); - - // compute the relative movement offset on the remote screen. - float deltaX = (e.getX() - dragX); - float deltaY = (e.getY() - dragY); - dragX = e.getX(); - dragY = e.getY(); - deltaX = fineCtrlScale(deltaX); - deltaY = fineCtrlScale(deltaY); - - // compute the absolution new mouse pos on the remote site. - float newRemoteX = vncCanvas.mouseX + deltaX; - float newRemoteY = vncCanvas.mouseY + deltaY; - - - if (e.getAction() == MotionEvent.ACTION_UP) - { - if(Utils.DEBUG()) Log.d(TAG, "Input: touch dragMode, finger up"); - - dragMode = false; - - dragModeButtonDown = false; - dragModeButton2insteadof1 = false; - - remoteMouseStayPut(e); - vncCanvas.processPointerEvent(e, false); - scaleGestures.onTouchEvent(e); - return gestures.onTouchEvent(e); // important! otherwise the gesture detector gets confused! - } - e.setLocation(newRemoteX, newRemoteY); - - boolean status; - if(dragModeButtonDown) { - if(!dragModeButton2insteadof1) // button 1 down - status = vncCanvas.processPointerEvent(e, true, false); - else // button2 down - status = vncCanvas.processPointerEvent(e, true, true); - } - else { // dragging without any button down - status = vncCanvas.processPointerEvent(e, false); - } - - return status; - - } - - - if(Utils.DEBUG()) - Log.d(TAG, "Input: touch normal: x:" + e.getX() + " y:" + e.getY() + " action:" + e.getAction()); - - scaleGestures.onTouchEvent(e); - return gestures.onTouchEvent(e); - } - - /** - * Modify MotionEvent so that Samsung S Pen Actions are handled as normal Action; returns true when it's a Samsung S Pen Action - */ - private boolean spenActionConvert(MotionEvent e) { - if((e.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) && - Build.MANUFACTURER.equals(SAMSUNG_MANUFACTURER_NAME)){ - - switch(e.getAction()){ - case SAMSUNG_SPEN_ACTION_DOWN: - e.setAction(MotionEvent.ACTION_DOWN); - return true; - case SAMSUNG_SPEN_ACTION_UP: - e.setAction(MotionEvent.ACTION_UP); - return true; - case SAMSUNG_SPEN_ACTION_MOVE: - e.setAction(MotionEvent.ACTION_MOVE); - return true; - default: - return false; - } - } - return false; - } - - public boolean onGenericMotionEvent(MotionEvent e) { - if (Utils.DEBUG()) - Log.d(TAG, "Input: generic motion: " + e); - - if (isTouchEvent(e)) - return false; - - vncCanvas.changeTouchCoordinatesToFullFrame(e); - int x = (int) e.getX(); - int y = (int) e.getY(); - - switch (e.getAction()) { - case MotionEvent.ACTION_HOVER_MOVE: - vncCanvas.processMouseEvent(0, false, x, y); - break; - - case MotionEvent.ACTION_SCROLL: - if (e.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) { - vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_DOWN, true, x, y); - vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_DOWN, false, x, y); - } else { - vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_UP, true, x, y); - vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_UP, false, x, y); - } - break; - } - - if (Build.VERSION.SDK_INT >= 23) { - int button = androidButtonToVncButton(e.getActionButton()); - switch (e.getAction()) { - case MotionEvent.ACTION_BUTTON_PRESS: - vncCanvas.processMouseEvent(button, true, x, y); - break; - - case MotionEvent.ACTION_BUTTON_RELEASE: - vncCanvas.processMouseEvent(button, false, x, y); - break; - } - } - - return true; - } - - private int androidButtonToVncButton(int androidButton) { - switch (androidButton) { - case MotionEvent.BUTTON_PRIMARY: - return VNCConn.MOUSE_BUTTON_LEFT; - case MotionEvent.BUTTON_SECONDARY: - return VNCConn.MOUSE_BUTTON_RIGHT; - case MotionEvent.BUTTON_TERTIARY: - return VNCConn.MOUSE_BUTTON_MIDDLE; - default: - return 0; - } - } - - /** - * Modify the event so that it does not move the mouse on the - * remote server. - */ - private void remoteMouseStayPut(MotionEvent e) { - e.setLocation(vncCanvas.mouseX, vncCanvas.mouseY); - - } - - /** - * Do a left mouse down and up click on remote without moving the mouse. - */ - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - - if (!isTouchEvent(e)) - return false; - - // disable if virtual mouse buttons are in use - if(mousebuttons.getVisibility()== View.VISIBLE) - return false; - - if(Utils.DEBUG()) Log.d(TAG, "Input: single tap"); - - boolean multiTouch = e.getPointerCount() > 1; - remoteMouseStayPut(e); - - vncCanvas.processPointerEvent(e, true, multiTouch|| vncCanvas.cameraButtonDown); - e.setAction(MotionEvent.ACTION_UP); - return vncCanvas.processPointerEvent(e, false, multiTouch|| vncCanvas.cameraButtonDown); - } - - /** - * Do right mouse down and up click on remote without moving the mouse. - */ - @Override - public boolean onDoubleTap(MotionEvent e) { - - if (!isTouchEvent(e)) - return false; - - // disable if virtual mouse buttons are in use - if(mousebuttons.getVisibility()== View.VISIBLE) - return false; - - if(Utils.DEBUG()) Log.d(TAG, "Input: double tap"); - - dragModeButtonDown = true; - dragModeButton2insteadof1 = true; - - remoteMouseStayPut(e); - vncCanvas.processPointerEvent(e, true, true); - e.setAction(MotionEvent.ACTION_UP); - return vncCanvas.processPointerEvent(e, false, true); - } - - - @Override - public boolean onDown(MotionEvent e) { - return isTouchEvent(e); - } -} +package com.coboltforge.dontmind.multivnc.ui; + +import android.os.Build; +import android.util.Log; +import android.view.GestureDetector; +import android.view.HapticFeedbackConstants; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.SoundEffectConstants; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.coboltforge.dontmind.multivnc.Utils; +import com.coboltforge.dontmind.multivnc.VNCConn; +import com.coboltforge.dontmind.multivnc.db.MetaKeyBean; + +/** + * Handles pointer input from: + * * touchscreen + * * stylus + * * physical mouse + * and uses this for: + * * scaling + * * two-finger fling + * * pointer movement + * * click + * * drag + * detection and handover of events to VncCanvasActivity and VncCanvas. + */ +@SuppressWarnings("FieldCanBeLocal") +public class PointerInputHandler extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener { + + private static final String TAG = "PointerInputHandler"; + + private final VncCanvas vncCanvas; + private final ViewGroup mousebuttons; + private final Toast notificationToast; + protected GestureDetector gestures; + protected ScaleGestureDetector scaleGestures; + + float xInitialFocus; + float yInitialFocus; + boolean inScaling; + + /* + * Samsung S Pen support + */ + private final int SAMSUNG_SPEN_ACTION_MOVE = 213; + private final int SAMSUNG_SPEN_ACTION_UP = 212; + private final int SAMSUNG_SPEN_ACTION_DOWN = 211; + private final String SAMSUNG_MANUFACTURER_NAME = "samsung"; + + + /** + * In drag mode (entered with long press) you process mouse events + * without sending them through the gesture detector + */ + private boolean dragMode; + float dragX, dragY; + private boolean dragModeButtonDown = false; + private boolean dragModeButton2insteadof1 = false; + + /* + * two-finger fling gesture stuff + */ + private long twoFingerFlingStart = -1; + private VelocityTracker twoFingerFlingVelocityTracker; + private boolean twoFingerFlingDetected = false; + private final int TWO_FINGER_FLING_UNITS = 1000; + private final float TWO_FINGER_FLING_THRESHOLD = 1000; + + PointerInputHandler(VncCanvas vncCanvas, ViewGroup mouseButtonView, Toast notificationToast) { + this.vncCanvas = vncCanvas; + mousebuttons = mouseButtonView; + this.notificationToast = notificationToast; + gestures= new GestureDetector(vncCanvas.getContext(), this, null, false); // this is a SDK 8+ feature and apparently needed if targetsdk is set + gestures.setOnDoubleTapListener(this); + scaleGestures = new ScaleGestureDetector(vncCanvas.getContext(), this); + scaleGestures.setQuickScaleEnabled(false); + + Log.d(TAG, "MightyInputHandler " + this + " created!"); + } + + + public void init() { + twoFingerFlingVelocityTracker = VelocityTracker.obtain(); + Log.d(TAG, "MightyInputHandler " + this + " init!"); + } + + public void shutdown() { + try { + twoFingerFlingVelocityTracker.recycle(); + twoFingerFlingVelocityTracker = null; + } + catch (NullPointerException ignored) { + } + Log.d(TAG, "MightyInputHandler " + this + " shutdown!"); + } + + + protected boolean isTouchEvent(MotionEvent event) { + return event.getSource() == InputDevice.SOURCE_TOUCHSCREEN || + event.getSource() == InputDevice.SOURCE_TOUCHPAD; + } + + + @Override + public boolean onScale(ScaleGestureDetector detector) { + boolean consumed = true; + float fx = detector.getFocusX(); + float fy = detector.getFocusY(); + + if (Math.abs(1.0 - detector.getScaleFactor()) < 0.01) + consumed = false; + + //`inScaling` is used to disable multi-finger scroll/fling gestures while scaling. + //But instead of setting it in `onScaleBegin()`, we do it here after some checks. + //This is a work around for some devices which triggers scaling very early which + //disables fling gestures. + if (!inScaling) { + double xfs = fx - xInitialFocus; + double yfs = fy - yInitialFocus; + double fs = Math.sqrt(xfs * xfs + yfs * yfs); + if (fs * 2 < Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan())) { + inScaling = true; + } + } + + if (consumed && inScaling) { + if (vncCanvas != null && vncCanvas.scaling != null) + vncCanvas.scaling.adjust(detector.getScaleFactor(), fx, fy); + } + + return consumed; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + xInitialFocus = detector.getFocusX(); + yInitialFocus = detector.getFocusY(); + inScaling = false; + //Log.i(TAG,"scale begin ("+xInitialFocus+","+yInitialFocus+")"); + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + //Log.i(TAG,"scale end"); + inScaling = false; + } + + /** + * scale down delta when it is small. This will allow finer control + * when user is making a small movement on touch screen. + * Scale up delta when delta is big. This allows fast mouse movement when + * user is flinging. + */ + public static float fineCtrlScale(float delta) { + float sign = (delta>0) ? 1 : -1; + delta = Math.abs(delta); + if (delta>=1 && delta <=3) { + delta = 1; + }else if (delta <= 10) { + delta *= 0.34; + } else if (delta <= 30 ) { + delta *= delta/30; + } else if (delta <= 90) { + delta *= (delta/30); + } else { + delta *= 3.0; + } + return sign * delta; + } + + private void twoFingerFlingNotification(String str) + { + // bzzt if user enabled it in System Settings + vncCanvas.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + + // beep if user enabled it in System Settings + vncCanvas.playSoundEffect(SoundEffectConstants.CLICK); + + notificationToast.setText(str); + notificationToast.show(); + } + + private void twoFingerFlingAction(Character d) + { + switch(d) { + case '←': + vncCanvas.sendMetaKey(MetaKeyBean.keyArrowLeft); + break; + case '→': + vncCanvas.sendMetaKey(MetaKeyBean.keyArrowRight); + break; + case '↑': + vncCanvas.sendMetaKey(MetaKeyBean.keyArrowUp); + break; + case '↓': + vncCanvas.sendMetaKey(MetaKeyBean.keyArrowDown); + break; + } + } + + @Override + public void onLongPress(MotionEvent e) { + + if (!isTouchEvent(e)) + return; + + if(Utils.DEBUG()) Log.d(TAG, "Input: long press"); + + dragMode = true; + dragX = e.getX(); + dragY = e.getY(); + + // only interpret as button down if virtual mouse buttons are disabled + if(mousebuttons.getVisibility() != View.VISIBLE) { + dragModeButtonDown = true; + vncCanvas.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + + if (!isTouchEvent(e1)) + return false; + + if (!isTouchEvent(e2)) + return false; + + if (e2.getPointerCount() > 1) + { + if(Utils.DEBUG()) Log.d(TAG, "Input: scroll multitouch"); + + if (inScaling) + return false; + + // pan on 3 fingers and more + if(e2.getPointerCount() > 2) { + return vncCanvas.pan((int) distanceX, (int) distanceY); + } + + /* + * here comes the stuff that acts for two fingers + */ + + // gesture start + if(twoFingerFlingStart != e1.getEventTime()) // it's a new scroll sequence + { + twoFingerFlingStart = e1.getEventTime(); + twoFingerFlingDetected = false; + twoFingerFlingVelocityTracker.clear(); + if(Utils.DEBUG()) Log.d(TAG, "new twoFingerFling detection started"); + } + + // gesture end + if(!twoFingerFlingDetected) // not yet detected in this sequence (we only want ONE event per sequence!) + { + // update our velocity tracker + twoFingerFlingVelocityTracker.addMovement(e2); + twoFingerFlingVelocityTracker.computeCurrentVelocity(TWO_FINGER_FLING_UNITS); + + float velocityX = twoFingerFlingVelocityTracker.getXVelocity(); + float velocityY = twoFingerFlingVelocityTracker.getYVelocity(); + + // check for left/right flings + if(Math.abs(velocityX) > TWO_FINGER_FLING_THRESHOLD + && Math.abs(velocityX) > Math.abs(2*velocityY)) { + if(velocityX < 0) { + if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling LEFT detected"); + twoFingerFlingNotification("←"); + twoFingerFlingAction('←'); + } + else { + if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling RIGHT detected"); + twoFingerFlingNotification("→"); + twoFingerFlingAction('→'); + } + + twoFingerFlingDetected = true; + } + + // check for left/right flings + if(Math.abs(velocityY) > TWO_FINGER_FLING_THRESHOLD + && Math.abs(velocityY) > Math.abs(2*velocityX)) { + if(velocityY < 0) { + if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling UP detected"); + twoFingerFlingNotification("↑"); + twoFingerFlingAction('↑'); + } + else { + if(Utils.DEBUG()) Log.d(TAG, "twoFingerFling DOWN detected"); + twoFingerFlingNotification("↓"); + twoFingerFlingAction('↓'); + } + + twoFingerFlingDetected = true; + } + + } + + return twoFingerFlingDetected; + } + else + { + // compute the relative movement offset on the remote screen. + float deltaX = -distanceX; + float deltaY = -distanceY; + deltaX = fineCtrlScale(deltaX); + deltaY = fineCtrlScale(deltaY); + + // compute the absolution new mouse pos on the remote site. + float newRemoteX = vncCanvas.mouseX + deltaX; + float newRemoteY = vncCanvas.mouseY + deltaY; + + if(Utils.DEBUG()) Log.d(TAG, "Input: scroll single touch from " + + vncCanvas.mouseX + "," + vncCanvas.mouseY + + " to " + (int)newRemoteX + "," + (int)newRemoteY); + +// if (dragMode) { +// +// Log.d(TAG, "dragmode in scroll!!!!"); +// +// if (e2.getAction() == MotionEvent.ACTION_UP) +// dragMode = false; +// dragX = e2.getX(); +// dragY = e2.getY(); +// e2.setLocation(newRemoteX, newRemoteY); +// return vncCanvas.processPointerEvent(e2, true); +// } + + e2.setLocation(newRemoteX, newRemoteY); + vncCanvas.processPointerEvent(e2, false); + } + return false; + } + + + public boolean onTouchEvent(MotionEvent e) { + if (!isTouchEvent(e)) { // physical input device + + if(Utils.DEBUG()) Log.d(TAG, "Input: touch not screen nor pad: x:" + e.getX() + " y:" + e.getY() + " action:" + e.getAction()); + + e = vncCanvas.changeTouchCoordinatesToFullFrame(e); + + // modify MotionEvent to support Samsung S Pen Event and activate rightButton accordingly + // if Samsung S Pen is not present or reports false, check for right mouse button + boolean isSPen = spenActionConvert(e); + boolean isRightButton = e.isButtonPressed(MotionEvent.BUTTON_SECONDARY); + + if (isSPen || Build.VERSION.SDK_INT < 23) + vncCanvas.processPointerEvent(e, true, isSPen || isRightButton); + else if (e.getActionMasked() == MotionEvent.ACTION_MOVE) + vncCanvas.processMouseEvent(0, true, (int) e.getX(), (int) e.getY()); + + return true; + } + + if (dragMode) { + + if(Utils.DEBUG()) Log.d(TAG, "Input: touch dragMode"); + + // compute the relative movement offset on the remote screen. + float deltaX = (e.getX() - dragX); + float deltaY = (e.getY() - dragY); + dragX = e.getX(); + dragY = e.getY(); + deltaX = fineCtrlScale(deltaX); + deltaY = fineCtrlScale(deltaY); + + // compute the absolution new mouse pos on the remote site. + float newRemoteX = vncCanvas.mouseX + deltaX; + float newRemoteY = vncCanvas.mouseY + deltaY; + + + if (e.getAction() == MotionEvent.ACTION_UP) + { + if(Utils.DEBUG()) Log.d(TAG, "Input: touch dragMode, finger up"); + + dragMode = false; + + dragModeButtonDown = false; + dragModeButton2insteadof1 = false; + + remoteMouseStayPut(e); + vncCanvas.processPointerEvent(e, false); + scaleGestures.onTouchEvent(e); + return gestures.onTouchEvent(e); // important! otherwise the gesture detector gets confused! + } + e.setLocation(newRemoteX, newRemoteY); + + boolean status; + if(dragModeButtonDown) { + if(!dragModeButton2insteadof1) // button 1 down + status = vncCanvas.processPointerEvent(e, true, false); + else // button2 down + status = vncCanvas.processPointerEvent(e, true, true); + } + else { // dragging without any button down + status = vncCanvas.processPointerEvent(e, false); + } + + return status; + + } + + + if(Utils.DEBUG()) + Log.d(TAG, "Input: touch normal: x:" + e.getX() + " y:" + e.getY() + " action:" + e.getAction()); + + scaleGestures.onTouchEvent(e); + return gestures.onTouchEvent(e); + } + + /** + * Modify MotionEvent so that Samsung S Pen Actions are handled as normal Action; returns true when it's a Samsung S Pen Action + */ + private boolean spenActionConvert(MotionEvent e) { + if((e.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) && + Build.MANUFACTURER.equals(SAMSUNG_MANUFACTURER_NAME)){ + + switch(e.getAction()){ + case SAMSUNG_SPEN_ACTION_DOWN: + e.setAction(MotionEvent.ACTION_DOWN); + return true; + case SAMSUNG_SPEN_ACTION_UP: + e.setAction(MotionEvent.ACTION_UP); + return true; + case SAMSUNG_SPEN_ACTION_MOVE: + e.setAction(MotionEvent.ACTION_MOVE); + return true; + default: + return false; + } + } + return false; + } + + public boolean onGenericMotionEvent(MotionEvent e) { + if (Utils.DEBUG()) + Log.d(TAG, "Input: generic motion: " + e); + + if (isTouchEvent(e)) + return false; + + vncCanvas.changeTouchCoordinatesToFullFrame(e); + int x = (int) e.getX(); + int y = (int) e.getY(); + + switch (e.getAction()) { + case MotionEvent.ACTION_HOVER_MOVE: + vncCanvas.processMouseEvent(0, false, x, y); + break; + + case MotionEvent.ACTION_SCROLL: + if (e.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) { + vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_DOWN, true, x, y); + vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_DOWN, false, x, y); + } else { + vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_UP, true, x, y); + vncCanvas.processMouseEvent(VNCConn.MOUSE_BUTTON_SCROLL_UP, false, x, y); + } + break; + } + + if (Build.VERSION.SDK_INT >= 23) { + int button = androidButtonToVncButton(e.getActionButton()); + switch (e.getAction()) { + case MotionEvent.ACTION_BUTTON_PRESS: + vncCanvas.processMouseEvent(button, true, x, y); + break; + + case MotionEvent.ACTION_BUTTON_RELEASE: + vncCanvas.processMouseEvent(button, false, x, y); + break; + } + } + + return true; + } + + private int androidButtonToVncButton(int androidButton) { + switch (androidButton) { + case MotionEvent.BUTTON_PRIMARY: + return VNCConn.MOUSE_BUTTON_LEFT; + case MotionEvent.BUTTON_SECONDARY: + return VNCConn.MOUSE_BUTTON_RIGHT; + case MotionEvent.BUTTON_TERTIARY: + return VNCConn.MOUSE_BUTTON_MIDDLE; + default: + return 0; + } + } + + /** + * Modify the event so that it does not move the mouse on the + * remote server. + */ + private void remoteMouseStayPut(MotionEvent e) { + e.setLocation(vncCanvas.mouseX, vncCanvas.mouseY); + + } + + /** + * Do a left mouse down and up click on remote without moving the mouse. + */ + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + if (!isTouchEvent(e)) + return false; + + // disable if virtual mouse buttons are in use + if(mousebuttons.getVisibility()== View.VISIBLE) + return false; + + if(Utils.DEBUG()) Log.d(TAG, "Input: single tap"); + + boolean multiTouch = e.getPointerCount() > 1; + remoteMouseStayPut(e); + + vncCanvas.processPointerEvent(e, true, multiTouch|| vncCanvas.cameraButtonDown); + e.setAction(MotionEvent.ACTION_UP); + return vncCanvas.processPointerEvent(e, false, multiTouch|| vncCanvas.cameraButtonDown); + } + + /** + * Do right mouse down and up click on remote without moving the mouse. + */ + @Override + public boolean onDoubleTap(MotionEvent e) { + + if (!isTouchEvent(e)) + return false; + + // disable if virtual mouse buttons are in use + if(mousebuttons.getVisibility()== View.VISIBLE) + return false; + + if(Utils.DEBUG()) Log.d(TAG, "Input: double tap"); + + dragModeButtonDown = true; + dragModeButton2insteadof1 = true; + + remoteMouseStayPut(e); + vncCanvas.processPointerEvent(e, true, true); + e.setAction(MotionEvent.ACTION_UP); + return vncCanvas.processPointerEvent(e, false, true); + } + + + @Override + public boolean onDown(MotionEvent e) { + return isTouchEvent(e); + } +} diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VNCConnService.kt b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VNCConnService.kt index 3654890d..a5198ca4 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VNCConnService.kt +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VNCConnService.kt @@ -1,139 +1,139 @@ -package com.coboltforge.dontmind.multivnc.ui - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Handler -import android.os.IBinder -import android.os.Looper -import android.util.Log -import androidx.core.app.NotificationCompat -import com.coboltforge.dontmind.multivnc.R -import com.coboltforge.dontmind.multivnc.VNCConn - -/** - * Foreground service that runs whenever there is an active VNCConn. - */ -class VNCConnService : Service() { - - companion object { - private const val TAG = "VNCConnService" - private const val NOTIFICATION_ID = 11 - private val mConnectionList = ArrayList() - - /* - These static methods hide away the peculiarities of Service starting/stopping from the - caller. A possible improvement here is to move the service starting or rather binding to - our own Application subclass so that the service exists throughout app lifetime. We could - then (de)register without a Context arg and instead of starting/stopping the whole service - would call startForeground()/stopForeground(). - */ - @JvmStatic - fun register(ctx: Context, conn: VNCConn) { - Handler(Looper.getMainLooper()).post { - mConnectionList.add(conn) - // tell service to update - val intent = Intent(ctx, VNCConnService::class.java) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - ctx.startForegroundService(intent) - else - ctx.startService(intent) - } - } - - @JvmStatic - fun deregister(ctx: Context, conn: VNCConn) { - Handler(Looper.getMainLooper()).post { - if(mConnectionList.isNotEmpty()) { // calling startForegroundService() without Service.startForeground() crashes by OS intention - mConnectionList.remove(conn) - // tell service to update - val intent = Intent(ctx, VNCConnService::class.java) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - ctx.startForegroundService(intent) - else - ctx.startService(intent) - } - } - } - } - - - override fun onBind(intent: Intent?): IBinder? = null - - - override fun onCreate() { - Log.d(TAG, "onCreate") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - /* - Create notification channel, needed for for Oreo and newer - */ - val serviceChannel = NotificationChannel( - packageName, - "MultiVNC Connection Service Channel", - NotificationManager.IMPORTANCE_LOW - ).apply { setSound(null, null) } - - val manager = getSystemService(NotificationManager::class.java) - manager.createNotificationChannel(serviceChannel) - } - - // call startForeground() ASAP, with empty message - startForeground(NOTIFICATION_ID, getNotification("")) - } - - - override fun onDestroy() { - Log.d(TAG, "onDestroy") - } - - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - // the connection list was updated in the (de)register methods, update UI here only. - // stop if connection list got empty from a deregister() call or there is a connection but - // its connSettings are null. This happens when: - // * VNCConnService registered in VNCConn.ServerToClientThread - // * register() runnable is dispatched on Main thread - // * VNCConn.shutdown() is called for some reason - // * register() runnable now runs but is actually late to the party and finds a VNCConn emptied by shutdown() - if (mConnectionList.isEmpty() || mConnectionList[0].connSettings == null) { - stopSelf() - } else { - // assemble notification text - var hosts = "" - if (mConnectionList.size == 1) { - hosts = mConnectionList[0].connSettings.nickname ?: "" - hosts += "(" + (if (mConnectionList[0].isEncrypted) getString(R.string.encrypted) else getString(R.string.unencrypted)) + ")" - } else { - for (conn in mConnectionList) { - hosts += getString(R.string.host_and, conn.connSettings.nickname + "(" + (if (mConnectionList[0].isEncrypted) getString(R.string.encrypted) else getString(R.string.unencrypted)) + ")") - } - } - Log.d(TAG, "onStartCommand: notifying with " + getString(R.string.connected_to, hosts)) - (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(NOTIFICATION_ID, getNotification(getString(R.string.connected_to, hosts))) - } - // stay until explicitly stopped - return START_STICKY - } - - private fun getNotification(text: String): Notification { - val notificationIntent = Intent(this, VncCanvasActivity::class.java) - val pendingIntent = PendingIntent.getActivity(this, 0, - notificationIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) - val notificationBuilder = NotificationCompat.Builder(this, packageName) - .setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(getString(R.string.app_name)) - .setContentText(text) - .setContentIntent(pendingIntent) - .setOnlyAlertOnce(true) - if (Build.VERSION.SDK_INT >= 31) { - notificationBuilder.foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE - } - return notificationBuilder.build() - } - +package com.coboltforge.dontmind.multivnc.ui + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.util.Log +import androidx.core.app.NotificationCompat +import com.coboltforge.dontmind.multivnc.R +import com.coboltforge.dontmind.multivnc.VNCConn + +/** + * Foreground service that runs whenever there is an active VNCConn. + */ +class VNCConnService : Service() { + + companion object { + private const val TAG = "VNCConnService" + private const val NOTIFICATION_ID = 11 + private val mConnectionList = ArrayList() + + /* + These static methods hide away the peculiarities of Service starting/stopping from the + caller. A possible improvement here is to move the service starting or rather binding to + our own Application subclass so that the service exists throughout app lifetime. We could + then (de)register without a Context arg and instead of starting/stopping the whole service + would call startForeground()/stopForeground(). + */ + @JvmStatic + fun register(ctx: Context, conn: VNCConn) { + Handler(Looper.getMainLooper()).post { + mConnectionList.add(conn) + // tell service to update + val intent = Intent(ctx, VNCConnService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + ctx.startForegroundService(intent) + else + ctx.startService(intent) + } + } + + @JvmStatic + fun deregister(ctx: Context, conn: VNCConn) { + Handler(Looper.getMainLooper()).post { + if(mConnectionList.isNotEmpty()) { // calling startForegroundService() without Service.startForeground() crashes by OS intention + mConnectionList.remove(conn) + // tell service to update + val intent = Intent(ctx, VNCConnService::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + ctx.startForegroundService(intent) + else + ctx.startService(intent) + } + } + } + } + + + override fun onBind(intent: Intent?): IBinder? = null + + + override fun onCreate() { + Log.d(TAG, "onCreate") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + /* + Create notification channel, needed for for Oreo and newer + */ + val serviceChannel = NotificationChannel( + packageName, + "MultiVNC Connection Service Channel", + NotificationManager.IMPORTANCE_LOW + ).apply { setSound(null, null) } + + val manager = getSystemService(NotificationManager::class.java) + manager.createNotificationChannel(serviceChannel) + } + + // call startForeground() ASAP, with empty message + startForeground(NOTIFICATION_ID, getNotification("")) + } + + + override fun onDestroy() { + Log.d(TAG, "onDestroy") + } + + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // the connection list was updated in the (de)register methods, update UI here only. + // stop if connection list got empty from a deregister() call or there is a connection but + // its connSettings are null. This happens when: + // * VNCConnService registered in VNCConn.ServerToClientThread + // * register() runnable is dispatched on Main thread + // * VNCConn.shutdown() is called for some reason + // * register() runnable now runs but is actually late to the party and finds a VNCConn emptied by shutdown() + if (mConnectionList.isEmpty() || mConnectionList[0].connSettings == null) { + stopSelf() + } else { + // assemble notification text + var hosts = "" + if (mConnectionList.size == 1) { + hosts = mConnectionList[0].connSettings.nickname ?: "" + hosts += "(" + (if (mConnectionList[0].isEncrypted) getString(R.string.encrypted) else getString(R.string.unencrypted)) + ")" + } else { + for (conn in mConnectionList) { + hosts += getString(R.string.host_and, conn.connSettings.nickname + "(" + (if (mConnectionList[0].isEncrypted) getString(R.string.encrypted) else getString(R.string.unencrypted)) + ")") + } + } + Log.d(TAG, "onStartCommand: notifying with " + getString(R.string.connected_to, hosts)) + (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(NOTIFICATION_ID, getNotification(getString(R.string.connected_to, hosts))) + } + // stay until explicitly stopped + return START_STICKY + } + + private fun getNotification(text: String): Notification { + val notificationIntent = Intent(this, VncCanvasActivity::class.java) + val pendingIntent = PendingIntent.getActivity(this, 0, + notificationIntent, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) + val notificationBuilder = NotificationCompat.Builder(this, packageName) + .setSmallIcon(R.drawable.ic_launcher) + .setContentTitle(getString(R.string.app_name)) + .setContentText(text) + .setContentIntent(pendingIntent) + .setOnlyAlertOnce(true) + if (Build.VERSION.SDK_INT >= 31) { + notificationBuilder.foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE + } + return notificationBuilder.build() + } + } \ No newline at end of file diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvas.java b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvas.java index 2ceab2a5..b397d619 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvas.java +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvas.java @@ -1,979 +1,979 @@ -// -// Copyright (C) 2011 Christian Beier -// Copyright (C) 2010 Michael A. MacDonald -// Copyright (C) 2004 Horizon Wimba. All Rights Reserved. -// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved. -// Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved. -// Copyright (C) 2000 Tridia Corporation. All Rights Reserved. -// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. -// -// This is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This software is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this software; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, -// USA. -// - -// -// VncCanvas is a subclass of android.view.GLSurfaceView which draws a VNC -// desktop on it. -// - -package com.coboltforge.dontmind.multivnc.ui; - -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; -import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.opengl.GLSurfaceView; -import android.os.Handler; -import android.text.Html; -import android.util.AttributeSet; -import android.util.Base64; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.widget.EditText; -import android.widget.Toast; - -import com.coboltforge.dontmind.multivnc.R; -import com.coboltforge.dontmind.multivnc.Utils; -import com.coboltforge.dontmind.multivnc.VNCConn; -import com.coboltforge.dontmind.multivnc.db.ConnectionBean; -import com.coboltforge.dontmind.multivnc.db.MetaKeyBean; -import com.coboltforge.dontmind.multivnc.db.SshKnownHost; -import com.coboltforge.dontmind.multivnc.db.VncDatabase; - - -public class VncCanvas extends GLSurfaceView implements VNCConn.OnFramebufferEventListener, VNCConn.OnAuthEventListener { - static { - System.loadLibrary("vnccanvas"); - } - - private final static String TAG = "VncCanvas"; - - ZoomScaling scaling; - - - // Runtime control flags - private boolean repaintsEnabled = true; - - /** - * Use camera button as meta key for right mouse button - */ - boolean cameraButtonDown = false; - - private Dialog firstFrameWaitDialog; - - // VNC protocol connection - public VNCConn vncConn; - - public Handler handler = new Handler(); - - private PointerInputHandler inputHandler; - - private VNCGLRenderer glRenderer; - - //whether to do pointer highlighting - boolean doPointerHighLight = true; - - /** - * Current state of "mouse" buttons - * Alt meta means use second mouse button - * 0 = none - * 1 = default button - * 2 = second button - * 4 = third button - */ - private int pointerMask = VNCConn.MOUSE_BUTTON_NONE; - private int overridePointerMask = VNCConn.MOUSE_BUTTON_NONE; // this takes precedence over pointerMask if set to >= 0 - - private MouseScrollRunnable scrollRunnable; - - - // framebuffer coordinates of mouse pointer, Available to activity - public int mouseX, mouseY; - - /** - * Position of the top left portion of the visible part of the screen, in - * full-frame coordinates - */ - int absoluteXPosition = 0, absoluteYPosition = 0; - - - /* - native drawing functions - */ - private static native void on_surface_created(); - private static native void on_surface_changed(int width, int height); - private static native void on_draw_frame(); - private static native void prepareTexture(long rfbClient); - - - private class VNCGLRenderer implements GLSurfaceView.Renderer { - - int[] textureIDs = new int[1]; // Array for 1 texture-ID - private int[] mTexCrop = new int[4]; - GLShape circle; - - - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - - if(Utils.DEBUG()) Log.d(TAG, "onSurfaceCreated()"); - - circle = new GLShape(GLShape.CIRCLE); - - // Set color's clear-value to black - gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - - /* - * By default, OpenGL enables features that improve quality but reduce - * performance. One might want to tweak that especially on software - * renderer. - */ - gl.glDisable(GL10.GL_DITHER); // Disable dithering for better performance - gl.glDisable(GL10.GL_LIGHTING); - gl.glDisable(GL10.GL_DEPTH_TEST); - - /* - * alpha blending has to be enabled manually! - */ - gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); - - /* - * setup texture stuff - */ - // Enable 2d textures - gl.glEnable(GL10.GL_TEXTURE_2D); - // Generate texture-ID array - gl.glDeleteTextures(1, textureIDs, 0); - gl.glGenTextures(1, textureIDs, 0); - // this is a 2D texture - gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]); - // Set up texture filters --> nice smoothing - gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); - gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); - } - - // Call back after onSurfaceCreated() or whenever the window's size changes - @Override - public void onSurfaceChanged(GL10 gl, int width, int height) { - - if(Utils.DEBUG()) Log.d(TAG, "onSurfaceChanged()"); - - // Set the viewport (display area) to cover the entire window - gl.glViewport(0, 0, width, height); - - // Setup orthographic projection - gl.glMatrixMode(GL10.GL_PROJECTION); // Select projection matrix - gl.glLoadIdentity(); // Reset projection matrix - gl.glOrthox(0, width, height, 0, 0, 100); - - - gl.glMatrixMode(GL10.GL_MODELVIEW); // Select model-view matrix - gl.glLoadIdentity(); // Reset - } - - @Override - public void onDrawFrame(GL10 gl) { - - // TODO optimize: texSUBimage ? - // pbuffer: http://blog.shayanjaved.com/2011/05/13/android-opengl-es-2-0-render-to-texture/ - - try{ - gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); - - if(vncConn.getFramebufferWidth() >0 && vncConn.getFramebufferHeight() > 0) { - vncConn.lockFramebuffer(); - prepareTexture(vncConn.rfbClient); - vncConn.unlockFramebuffer(); - } - - /* - * The crop rectangle is given as Ucr, Vcr, Wcr, Hcr. - * That is, "left"/"bottom"/width/height, although you can - * also have negative width and height to flip the image. - * - * This is the part of the framebuffer we show on-screen. - * - * If absolute[XY]Position are negative that means the framebuffer - * is smaller than our viewer window. - * - */ - mTexCrop[0] = absoluteXPosition >= 0 ? absoluteXPosition : 0; // don't let this be <0 - mTexCrop[1] = absoluteYPosition >= 0 ? (int)(absoluteYPosition + VncCanvas.this.getHeight() / getScale()) : vncConn.getFramebufferHeight(); - mTexCrop[2] = (int) (VncCanvas.this.getWidth() < vncConn.getFramebufferWidth()*getScale() ? VncCanvas.this.getWidth() / getScale() : vncConn.getFramebufferWidth()); - mTexCrop[3] = (int) -(VncCanvas.this.getHeight() < vncConn.getFramebufferHeight()*getScale() ? VncCanvas.this.getHeight() / getScale() : vncConn.getFramebufferHeight()); - - if(Utils.DEBUG()) Log.d(TAG, "cropRect: u:" + mTexCrop[0] + " v:" + mTexCrop[1] + " w:" + mTexCrop[2] + " h:" + mTexCrop[3]); - - ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, mTexCrop, 0); - - /* - * Very fast, but very basic transforming: only transpose, flip and scale. - * Uses the GL_OES_draw_texture extension to draw sprites on the screen without - * any sort of projection or vertex buffers involved. - * - * See http://www.khronos.org/registry/gles/extensions/OES/OES_draw_texture.txt - * - * All parameters in GL screen coordinates! - */ - int x = (int) (VncCanvas.this.getWidth() < vncConn.getFramebufferWidth()*getScale() ? 0 : VncCanvas.this.getWidth()/2 - (vncConn.getFramebufferWidth()*getScale())/2); - int y = (int) (VncCanvas.this.getHeight() < vncConn.getFramebufferHeight()*getScale() ? 0 : VncCanvas.this.getHeight()/2 - (vncConn.getFramebufferHeight()*getScale())/2); - int w = (int) (VncCanvas.this.getWidth() < vncConn.getFramebufferWidth()*getScale() ? VncCanvas.this.getWidth(): vncConn.getFramebufferWidth()*getScale()); - int h =(int) (VncCanvas.this.getHeight() < vncConn.getFramebufferHeight()*getScale() ? VncCanvas.this.getHeight(): vncConn.getFramebufferHeight()*getScale()); - gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // opaque! - ((GL11Ext) gl).glDrawTexfOES(x, y, 0, w, h); - - if(Utils.DEBUG()) Log.d(TAG, "drawing to screen: x " + x + " y " + y + " w " + w + " h " + h); - - - /* - * do pointer highlight overlay - */ - if(doPointerHighLight) { - gl.glEnable(GL10.GL_BLEND); - int mouseXonScreen = (int)(getScale()*(mouseX-absoluteXPosition)); - int mouseYonScreen = (int)(getScale()*(mouseY-absoluteYPosition)); - - gl.glLoadIdentity(); // Reset model-view matrix - gl.glTranslatex( mouseXonScreen, mouseYonScreen, 0); - gl.glColor4f(1.0f, 0.2f, 1.0f, 0.1f); - - // simulate some anti-aliasing by drawing the shape 3x - gl.glScalef(0.001f, 0.001f, 0.0f); - circle.draw(gl); - - gl.glScalef(0.99f, 0.99f, 0.0f); - circle.draw(gl); - - gl.glScalef(0.99f, 0.99f, 0.0f); - circle.draw(gl); - - gl.glDisable(GL10.GL_BLEND); - } - - } - catch(NullPointerException e) { - } - - } - - } - - - /** - * Constructor used by the inflation apparatus - * @param context - */ - public VncCanvas(final Context context, AttributeSet attrs) - { - super(context, attrs); - scrollRunnable = new MouseScrollRunnable(); - - setFocusable(true); - - glRenderer = new VNCGLRenderer(); - setRenderer(glRenderer); - // only render upon request - setRenderMode(RENDERMODE_WHEN_DIRTY); - - int oldprio = android.os.Process.getThreadPriority(android.os.Process.myTid()); - // GIVE US MAGIC POWER, O GREAT FAIR SCHEDULER! - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY); - Log.d(TAG, "Changed prio from " + oldprio + " to " + android.os.Process.getThreadPriority(android.os.Process.myTid())); - } - - /** - * Create a view showing a VNC connection - */ - void initializeVncCanvas(Dialog progressDialog, PointerInputHandler inputHandler, VNCConn conn) { - firstFrameWaitDialog = progressDialog; - this.inputHandler = inputHandler; - vncConn = conn; - } - - /** - * Warp the mouse to x, y in the RFB coordinates - * @param x - * @param y - */ - public void warpMouse(int x, int y) - { - mouseX=vncConn.trimX(x); - mouseY=vncConn.trimY(y); - reDraw(); // update local pointer position - panToMouse(); - vncConn.sendPointerEvent(x, y, 0, VNCConn.MOUSE_BUTTON_NONE); - } - - - private void mouseFollowPan() - { - try { - if (vncConn.getConnSettings().followPan) - { - int scrollx = absoluteXPosition; - int scrolly = absoluteYPosition; - int width = getVisibleWidth(); - int height = getVisibleHeight(); - //Log.i(TAG,"scrollx " + scrollx + " scrolly " + scrolly + " mouseX " + mouseX +" Y " + mouseY + " w " + width + " h " + height); - if (mouseX < scrollx || mouseX >= scrollx + width || mouseY < scrolly || mouseY >= scrolly + height) - { - //Log.i(TAG,"warp to " + scrollx+width/2 + "," + scrolly + height/2); - warpMouse(scrollx + width/2, scrolly + height / 2); - } - }} - catch(NullPointerException e) { - } - } - - - /** - * Apply scroll offset and scaling to convert touch-space coordinates to the corresponding - * point on the full frame. - * @param e MotionEvent with the original, touch space coordinates. This event is altered in place. - * @return e -- The same event passed in, with the coordinates mapped - */ - MotionEvent changeTouchCoordinatesToFullFrame(MotionEvent e) - { - //Log.v(TAG, String.format("tap at %f,%f", e.getX(), e.getY())); - float scale = getScale(); - - // Adjust coordinates for Android notification bar. - e.offsetLocation(0, -1f * getTop()); - - e.setLocation(absoluteXPosition + e.getX() / scale, absoluteYPosition + e.getY() / scale); - - return e; - } - - public void onDestroy() { - Log.v(TAG, "Cleaning up resources"); - try { - vncConn.shutdown(); - } - catch(NullPointerException e) { - } - vncConn = null; - } - - @Override - public void onPause() { - /* - * this is to avoid a deadlock between GUI thread and GLThread: - * - * the GUI thread would call onPause on the GLThread which would never return since - * the GL thread's GLThreadManager object is waiting on the GLThread. - */ - try { - vncConn.unlockFramebuffer(); - } - catch(IllegalMonitorStateException e) { - // thrown when mutex was not locked - } - catch(NullPointerException e) { - // thrown if we fatal out at the very beginning - } - - super.onPause(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - // Synchronize absoluteXPosition & absoluteYPosition with new size. - // We use pan() with delta 0 to trigger their re-calculation. - // - // oldw & oldh will be 0 if we are just now added to activity. - if (oldw != 0 || oldh != 0) { - pan(0, 0); - } - } - - - /* - * f(x,s) is a function that returns the coordinate in screen/scroll space corresponding - * to the coordinate x in full-frame space with scaling s. - * - * This function returns the difference between f(x,s1) and f(x,s2) - * - * f(x,s) = (x - i/2) * s + ((i - w)/2)) * s - * = s (x - i/2 + i/2 + w/2) - * = s (x + w/2) - * - * - * f(x,s) = (x - ((i - w)/2)) * s - * @param oldscaling - * @param scaling - * @param imageDim - * @param windowDim - * @param offset - * @return - */ - - - - /** - * Make sure mouse is visible on displayable part of screen - */ - public void panToMouse() - { - try { - if (!vncConn.getConnSettings().followMouse) - return; - } catch (NullPointerException e) { - return; - } - - int x = mouseX; - int y = mouseY; - int w = getVisibleWidth(); - int h = getVisibleHeight(); - int iw = vncConn.getFramebufferWidth(); - int ih = vncConn.getFramebufferHeight(); - - int newX = absoluteXPosition; - int newY = absoluteYPosition; - - if (x - newX >= w - 5) - { - newX = x - w + 5; - if (newX + w > iw) - newX = iw - w; - } - else if (x < newX + 5) - { - newX = x - 5; - if (newX < 0) - newX = 0; - } - if ( newX != absoluteXPosition ) { - absoluteXPosition = newX; - } - if (y - newY >= h - 5) - { - newY = y - h + 5; - if (newY + h > ih) - newY = ih - h; - } - else if (y < newY + 5) - { - newY = y - 5; - if (newY < 0) - newY = 0; - } - if ( newY != absoluteYPosition ) { - absoluteYPosition = newY; - } - } - - /** - * Pan by a number of pixels (relative pan) - * @param dX - * @param dY - * @return True if the pan changed the view (did not move view out of bounds); false otherwise - */ - public boolean pan(int dX, int dY) { - - try { - double scale = getScale(); - - double sX = (double) dX / scale; - double sY = (double) dY / scale; - - if (absoluteXPosition + sX < 0) - // dX = diff to 0 - sX = -absoluteXPosition; - if (absoluteYPosition + sY < 0) - sY = -absoluteYPosition; - - // Prevent panning right or below desktop image - if (absoluteXPosition + getVisibleWidth() + sX > vncConn.getFramebufferWidth()) - sX = vncConn.getFramebufferWidth() - getVisibleWidth() - absoluteXPosition; - if (absoluteYPosition + getVisibleHeight() + sY > vncConn.getFramebufferHeight()) - sY = vncConn.getFramebufferHeight() - getVisibleHeight() - absoluteYPosition; - - absoluteXPosition += sX; - absoluteYPosition += sY; - - // whene the frame buffer is smaller than the view, - // it is is centered! - if (vncConn.getFramebufferWidth() < getVisibleWidth()) - absoluteXPosition /= 2; - if (vncConn.getFramebufferHeight() < getVisibleHeight()) - absoluteYPosition /= 2; - - - if (sX != 0.0 || sY != 0.0) { - return true; - } - } catch (Exception ignored) { - } - return false; - } - - /* (non-Javadoc) - * @see android.view.View#onScrollChanged(int, int, int, int) - */ - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - try { - mouseFollowPan(); - } - catch(NullPointerException e) { - } - } - - - - @Override - public boolean onTouchEvent(MotionEvent event) { - return inputHandler.onTouchEvent(event); - } - - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - return inputHandler.onGenericMotionEvent(event); - } - - public void reDraw() { - - if (repaintsEnabled && vncConn.rfbClient != 0) { - // request a redraw from GL thread - requestRender(); - } - } - - public void disableRepaints() { - repaintsEnabled = false; - } - - public void enableRepaints() { - repaintsEnabled = true; - } - - public void setPointerHighlight(boolean enable) { - doPointerHighLight = enable; - } - - public final boolean getPointerHighlight() { - return doPointerHighLight; - } - - - - public void showConnectionInfo() { - try { - String msg = vncConn.getDesktopName(); - msg += "(" + (vncConn.isEncrypted() ? getContext().getString(R.string.encrypted) : getContext().getString(R.string.unencrypted)) + ")"; - int idx = vncConn.getDesktopName().indexOf("("); - if (idx > -1) { - // Breakup actual desktop name from IP addresses for improved - // readability - String dn = vncConn.getDesktopName().substring(0, idx).trim(); - String ip = vncConn.getDesktopName().substring(idx).trim(); - msg = dn + "\n" + ip; - } - msg += "\n" + vncConn.getFramebufferWidth() + "x" + vncConn.getFramebufferHeight(); - String enc = vncConn.getEncoding(); - // Encoding might not be set when we display this message - if (enc != null && !enc.equals("")) - msg += ", " + vncConn.getEncoding() + " encoding, " + vncConn.getColorModel().toString(); - else - msg += ", " + vncConn.getColorModel().toString(); - Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show(); - } - catch(Exception ignored) { - } - } - - - /** Set which mouse buttons are down. - * @param pointerMask - */ - public void setOverridePointerMask(int pointerMask) { - overridePointerMask = pointerMask; - } - - - /** - * Convert a motion event to a format suitable for sending over the wire - * @param evt motion event; x and y must already have been converted from screen coordinates - * to remote frame buffer coordinates. cameraButton flag is interpreted as second mouse - * button - * @param downEvent True if "mouse button" (touch or trackball button) is down when this happens - * @return true if event was actually sent - */ - public boolean processPointerEvent(MotionEvent evt,boolean downEvent) - { - return processPointerEvent(evt,downEvent,cameraButtonDown); - } - - /** - * Convert a motion event to a format suitable for sending over the wire - * @param evt motion event; x and y must already have been converted from screen coordinates - * to remote frame buffer coordinates. - * @param mouseIsDown True if "mouse button" (touch or trackball button) is down when this happens - * @param useRightButton If true, event is interpreted as happening with right mouse button - * @return true if event was actually sent - */ - public boolean processPointerEvent(MotionEvent evt,boolean mouseIsDown,boolean useRightButton) { - try { - int action = evt.getAction(); - if (action == MotionEvent.ACTION_DOWN || (mouseIsDown && action == MotionEvent.ACTION_MOVE)) { - if (useRightButton) { - if(action == MotionEvent.ACTION_MOVE) - if(Utils.DEBUG()) Log.d(TAG, "Input: moving, right mouse button down"); - else - if(Utils.DEBUG()) Log.d(TAG, "Input: right mouse button down"); - - pointerMask = VNCConn.MOUSE_BUTTON_RIGHT; - } else { - if(action == MotionEvent.ACTION_MOVE) - if(Utils.DEBUG()) Log.d(TAG, "Input: moving, left mouse button down"); - else - if(Utils.DEBUG()) Log.d(TAG, "Input: left mouse button down"); - - pointerMask = VNCConn.MOUSE_BUTTON_LEFT; - } - } else if (action == MotionEvent.ACTION_UP) { - if(Utils.DEBUG()) Log.d(TAG, "Input: all mouse buttons up"); - pointerMask = 0; - } - mouseX = vncConn.trimX((int)evt.getX()); - mouseY = vncConn.trimY((int)evt.getY()); - reDraw(); // update local pointer position - panToMouse(); - if(overridePointerMask > 0) - return vncConn.sendPointerEvent((int)evt.getX(),(int)evt.getY(), evt.getMetaState(), overridePointerMask); - else - return vncConn.sendPointerEvent((int)evt.getX(),(int)evt.getY(), evt.getMetaState(), pointerMask); - - } - catch(NullPointerException e) { - return false; - } - } - - /** - * Process given mouse event. - * - * @param button Which button is pressed/released (0 for no button) - * @param isDown True if button is down - * @param x,y Event position in remote frame buffer coordinates. - */ - public void processMouseEvent(int button, boolean isDown, int x, int y) { - try { - if (isDown) - pointerMask |= button; - else - pointerMask &= ~button; - - mouseX = vncConn.trimX(x); - mouseY = vncConn.trimY(y); - reDraw(); // update local pointer position - panToMouse(); - if (overridePointerMask > 0) - vncConn.sendPointerEvent(x, y, 0, overridePointerMask); - else - vncConn.sendPointerEvent(x, y, 0, pointerMask); - - } catch (NullPointerException ignored) { - } - } - - - - /** - * Moves the scroll while the volume key is held down - * @author Michael A. MacDonald - */ - class MouseScrollRunnable implements Runnable - { - int delay = 100; - - int scrollButton = 0; - - /* (non-Javadoc) - * @see java.lang.Runnable#run() - */ - @Override - public void run() { - vncConn.sendPointerEvent(mouseX, mouseY, 0, scrollButton); - vncConn.sendPointerEvent(mouseX, mouseY, 0, 0); - - handler.postDelayed(this, delay); - } - } - - public boolean processLocalKeyEvent(int keyCode, KeyEvent evt) { - - // prevent actionbar from stealing focus on key down - requestFocus(); - - if (keyCode == KeyEvent.KEYCODE_MENU) - // Ignore menu key - return true; - if (keyCode == KeyEvent.KEYCODE_CAMERA) - { - cameraButtonDown = (evt.getAction() != KeyEvent.ACTION_UP); - } - else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) - { - int mouseChange = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? VNCConn.MOUSE_BUTTON_SCROLL_DOWN : VNCConn.MOUSE_BUTTON_SCROLL_UP; - if (evt.getAction() == KeyEvent.ACTION_DOWN) - { - // If not auto-repeat - if (scrollRunnable.scrollButton != mouseChange) - { - pointerMask |= mouseChange; - scrollRunnable.scrollButton = mouseChange; - handler.postDelayed(scrollRunnable,200); - } - } - else - { - handler.removeCallbacks(scrollRunnable); - scrollRunnable.scrollButton = 0; - pointerMask &= ~mouseChange; - } - - vncConn.sendPointerEvent(mouseX, mouseY, evt.getMetaState(), pointerMask); - - return true; - } - - return vncConn.sendKeyEvent(keyCode, evt, false); - } - - void sendMetaKey(MetaKeyBean meta) - { - Log.d(TAG, "sendMetaKey " + (meta != null ? meta.getKeyDesc() : "none")); - - if(meta == null) - return; - - if (meta.isMouseClick) - { - vncConn.sendPointerEvent(mouseX, mouseY, meta.metaFlags, meta.mouseButtons); - vncConn.sendPointerEvent(mouseX, mouseY, meta.metaFlags, 0); - } - else { - // KeyEvent(downTime, eventTime, action, code, repeat, metaState) - KeyEvent downEvent = new KeyEvent( - System.currentTimeMillis(), - System.currentTimeMillis(), - KeyEvent.ACTION_DOWN, - meta.keySym, - 0, - meta.metaFlags); - vncConn.sendKeyEvent(downEvent.getKeyCode(), downEvent, true); - - // and up again - KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); - vncConn.sendKeyEvent(upEvent.getKeyCode(), upEvent, true); - - } - } - - float getScale() - { - if (scaling == null) - return 1; - return scaling.getScale(); - } - - /** - * - * @return The smallest scale supported by the implementation; the scale at which - * the bitmap would be smaller than the screen - */ - float getMinimumScale() - { - double scale = 0.75; - int displayWidth = getWidth(); - int displayHeight = getHeight(); - for (; scale >= 0; scale -= 0.25) - { - try { - if (scale * vncConn.getFramebufferWidth() < displayWidth && scale * vncConn.getFramebufferHeight() < displayHeight) - break; - } catch (NullPointerException ignored) { - break; - } - } - return (float)(scale + 0.25); - } - - public int getVisibleWidth() { - return (int)((double)getWidth() / getScale() + 0.5); - } - - public int getVisibleHeight() { - return (int)((double)getHeight() / getScale() + 0.5); - } - - @Override - public void onFramebufferUpdateFinished() { - reDraw(); - - // Hide progress dialog - if (firstFrameWaitDialog.isShowing()) - handler.post(() -> { - try { - firstFrameWaitDialog.dismiss(); - } catch (Exception e){ - //unused - } - }); - } - - @Override - public void onNewFramebufferSize(int w, int h) { - // this triggers an update on what the canvas thinks about cursor position. - // without this, the pointer highlight is off by some value after framebuffer size change - handler.post(() -> { - try { - pan(0, 0); - } catch (Exception ignored) { - } - }); - } - - @Override - public void onRequestCredsFromUser(final ConnectionBean c, boolean isUserNameNeeded) { - // this method is probably called from the vnc thread - post(new Runnable() { - @Override - public void run() { - - LayoutInflater layoutinflater = LayoutInflater.from(getContext()); - View credentialsDialog = layoutinflater.inflate(R.layout.credentials_dialog, null); - if(!isUserNameNeeded) - credentialsDialog.findViewById(R.id.username_row).setVisibility(GONE); - - AlertDialog dialog = new AlertDialog.Builder(getContext()) - .setTitle(getContext().getString(R.string.credentials_needed_title)) - .setView(credentialsDialog) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - if(isUserNameNeeded) - c.userName = ((EditText)credentialsDialog.findViewById(R.id.userName)).getText().toString(); - c.password = ((EditText)credentialsDialog.findViewById(R.id.password)).getText().toString(); - synchronized (vncConn) { - vncConn.notify(); - } - } - }).create(); - - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - dialog.show(); - } - }); - - } - - @Override - public void onRequestSshFingerprintCheck(String host, byte[] fingerprint, AtomicBoolean doContinue) { - // this method is probably called from the vnc thread - post(() -> { - // look for host, if not found create entry and return ok - SshKnownHost knownHost = VncDatabase.getInstance(getContext()).getSshKnownHostDao().get(host); - if(knownHost == null) { - AlertDialog dialog = new AlertDialog.Builder(getContext()) - .setTitle(R.string.ssh_key_new_title) - // always using SHA256 in native part, ok to hardcode this here - .setMessage(Html.fromHtml(getContext().getString(R.string.ssh_key_new_message, - "SHA256:" + Base64.encodeToString(fingerprint,Base64.NO_PADDING|Base64.NO_WRAP)))) - .setCancelable(false) - .setPositiveButton(R.string.ssh_key_new_continue, (dialog12, whichButton) -> { - VncDatabase.getInstance(getContext()).getSshKnownHostDao().insert(new SshKnownHost(0, host, fingerprint)); - doContinue.set(true); - synchronized (vncConn) { - vncConn.notify(); - } - }) - .setNegativeButton(R.string.ssh_key_new_abort, (dialog1, whichButton) -> { - doContinue.set(false); - synchronized (vncConn) { - vncConn.notify(); - } - }) - .create(); - - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - dialog.show(); - return; - } - - // host found, check if fingerprint matches - if(Arrays.equals(knownHost.fingerprint, fingerprint)) { - doContinue.set(true); - synchronized (vncConn) { - vncConn.notify(); - } - } else { - // not matching, ask user! - AlertDialog dialog = new AlertDialog.Builder(getContext()) - .setTitle(R.string.ssh_key_mismatch_title) - // always using SHA256 in native part, ok to hardcode this here - .setMessage(Html.fromHtml(getContext().getString(R.string.ssh_key_mismatch_message, - "SHA256:" + Base64.encodeToString(fingerprint,Base64.NO_PADDING|Base64.NO_WRAP)))) - .setCancelable(false) - .setPositiveButton(R.string.ssh_key_mismatch_continue, (dialog12, whichButton) -> { - SshKnownHost updatedHost = new SshKnownHost(knownHost.id, host, fingerprint); - VncDatabase.getInstance(getContext()).getSshKnownHostDao().update(updatedHost); - doContinue.set(true); - synchronized (vncConn) { - vncConn.notify(); - } - }) - .setNegativeButton(R.string.ssh_key_mismatch_abort, (dialog1, whichButton) -> { - doContinue.set(false); - synchronized (vncConn) { - vncConn.notify(); - } - }) - .create(); - - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - dialog.show(); - } - }); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - Log.d(TAG, "onCreateInputConnection"); // only called when focusableInTouchMode==true - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; // need to set this for some keyboards in landscape mode - return super.onCreateInputConnection(outAttrs); - } -} +// +// Copyright (C) 2011 Christian Beier +// Copyright (C) 2010 Michael A. MacDonald +// Copyright (C) 2004 Horizon Wimba. All Rights Reserved. +// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved. +// Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved. +// Copyright (C) 2000 Tridia Corporation. All Rights Reserved. +// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. +// +// This is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this software; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, +// USA. +// + +// +// VncCanvas is a subclass of android.view.GLSurfaceView which draws a VNC +// desktop on it. +// + +package com.coboltforge.dontmind.multivnc.ui; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11Ext; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.opengl.GLSurfaceView; +import android.os.Handler; +import android.text.Html; +import android.util.AttributeSet; +import android.util.Base64; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.EditText; +import android.widget.Toast; + +import com.coboltforge.dontmind.multivnc.R; +import com.coboltforge.dontmind.multivnc.Utils; +import com.coboltforge.dontmind.multivnc.VNCConn; +import com.coboltforge.dontmind.multivnc.db.ConnectionBean; +import com.coboltforge.dontmind.multivnc.db.MetaKeyBean; +import com.coboltforge.dontmind.multivnc.db.SshKnownHost; +import com.coboltforge.dontmind.multivnc.db.VncDatabase; + + +public class VncCanvas extends GLSurfaceView implements VNCConn.OnFramebufferEventListener, VNCConn.OnAuthEventListener { + static { + System.loadLibrary("vnccanvas"); + } + + private final static String TAG = "VncCanvas"; + + ZoomScaling scaling; + + + // Runtime control flags + private boolean repaintsEnabled = true; + + /** + * Use camera button as meta key for right mouse button + */ + boolean cameraButtonDown = false; + + private Dialog firstFrameWaitDialog; + + // VNC protocol connection + public VNCConn vncConn; + + public Handler handler = new Handler(); + + private PointerInputHandler inputHandler; + + private VNCGLRenderer glRenderer; + + //whether to do pointer highlighting + boolean doPointerHighLight = true; + + /** + * Current state of "mouse" buttons + * Alt meta means use second mouse button + * 0 = none + * 1 = default button + * 2 = second button + * 4 = third button + */ + private int pointerMask = VNCConn.MOUSE_BUTTON_NONE; + private int overridePointerMask = VNCConn.MOUSE_BUTTON_NONE; // this takes precedence over pointerMask if set to >= 0 + + private MouseScrollRunnable scrollRunnable; + + + // framebuffer coordinates of mouse pointer, Available to activity + public int mouseX, mouseY; + + /** + * Position of the top left portion of the visible part of the screen, in + * full-frame coordinates + */ + int absoluteXPosition = 0, absoluteYPosition = 0; + + + /* + native drawing functions + */ + private static native void on_surface_created(); + private static native void on_surface_changed(int width, int height); + private static native void on_draw_frame(); + private static native void prepareTexture(long rfbClient); + + + private class VNCGLRenderer implements GLSurfaceView.Renderer { + + int[] textureIDs = new int[1]; // Array for 1 texture-ID + private int[] mTexCrop = new int[4]; + GLShape circle; + + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + + if(Utils.DEBUG()) Log.d(TAG, "onSurfaceCreated()"); + + circle = new GLShape(GLShape.CIRCLE); + + // Set color's clear-value to black + gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + /* + * By default, OpenGL enables features that improve quality but reduce + * performance. One might want to tweak that especially on software + * renderer. + */ + gl.glDisable(GL10.GL_DITHER); // Disable dithering for better performance + gl.glDisable(GL10.GL_LIGHTING); + gl.glDisable(GL10.GL_DEPTH_TEST); + + /* + * alpha blending has to be enabled manually! + */ + gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); + + /* + * setup texture stuff + */ + // Enable 2d textures + gl.glEnable(GL10.GL_TEXTURE_2D); + // Generate texture-ID array + gl.glDeleteTextures(1, textureIDs, 0); + gl.glGenTextures(1, textureIDs, 0); + // this is a 2D texture + gl.glBindTexture(GL10.GL_TEXTURE_2D, textureIDs[0]); + // Set up texture filters --> nice smoothing + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + } + + // Call back after onSurfaceCreated() or whenever the window's size changes + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + + if(Utils.DEBUG()) Log.d(TAG, "onSurfaceChanged()"); + + // Set the viewport (display area) to cover the entire window + gl.glViewport(0, 0, width, height); + + // Setup orthographic projection + gl.glMatrixMode(GL10.GL_PROJECTION); // Select projection matrix + gl.glLoadIdentity(); // Reset projection matrix + gl.glOrthox(0, width, height, 0, 0, 100); + + + gl.glMatrixMode(GL10.GL_MODELVIEW); // Select model-view matrix + gl.glLoadIdentity(); // Reset + } + + @Override + public void onDrawFrame(GL10 gl) { + + // TODO optimize: texSUBimage ? + // pbuffer: http://blog.shayanjaved.com/2011/05/13/android-opengl-es-2-0-render-to-texture/ + + try{ + gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + + if(vncConn.getFramebufferWidth() >0 && vncConn.getFramebufferHeight() > 0) { + vncConn.lockFramebuffer(); + prepareTexture(vncConn.rfbClient); + vncConn.unlockFramebuffer(); + } + + /* + * The crop rectangle is given as Ucr, Vcr, Wcr, Hcr. + * That is, "left"/"bottom"/width/height, although you can + * also have negative width and height to flip the image. + * + * This is the part of the framebuffer we show on-screen. + * + * If absolute[XY]Position are negative that means the framebuffer + * is smaller than our viewer window. + * + */ + mTexCrop[0] = absoluteXPosition >= 0 ? absoluteXPosition : 0; // don't let this be <0 + mTexCrop[1] = absoluteYPosition >= 0 ? (int)(absoluteYPosition + VncCanvas.this.getHeight() / getScale()) : vncConn.getFramebufferHeight(); + mTexCrop[2] = (int) (VncCanvas.this.getWidth() < vncConn.getFramebufferWidth()*getScale() ? VncCanvas.this.getWidth() / getScale() : vncConn.getFramebufferWidth()); + mTexCrop[3] = (int) -(VncCanvas.this.getHeight() < vncConn.getFramebufferHeight()*getScale() ? VncCanvas.this.getHeight() / getScale() : vncConn.getFramebufferHeight()); + + if(Utils.DEBUG()) Log.d(TAG, "cropRect: u:" + mTexCrop[0] + " v:" + mTexCrop[1] + " w:" + mTexCrop[2] + " h:" + mTexCrop[3]); + + ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, mTexCrop, 0); + + /* + * Very fast, but very basic transforming: only transpose, flip and scale. + * Uses the GL_OES_draw_texture extension to draw sprites on the screen without + * any sort of projection or vertex buffers involved. + * + * See http://www.khronos.org/registry/gles/extensions/OES/OES_draw_texture.txt + * + * All parameters in GL screen coordinates! + */ + int x = (int) (VncCanvas.this.getWidth() < vncConn.getFramebufferWidth()*getScale() ? 0 : VncCanvas.this.getWidth()/2 - (vncConn.getFramebufferWidth()*getScale())/2); + int y = (int) (VncCanvas.this.getHeight() < vncConn.getFramebufferHeight()*getScale() ? 0 : VncCanvas.this.getHeight()/2 - (vncConn.getFramebufferHeight()*getScale())/2); + int w = (int) (VncCanvas.this.getWidth() < vncConn.getFramebufferWidth()*getScale() ? VncCanvas.this.getWidth(): vncConn.getFramebufferWidth()*getScale()); + int h =(int) (VncCanvas.this.getHeight() < vncConn.getFramebufferHeight()*getScale() ? VncCanvas.this.getHeight(): vncConn.getFramebufferHeight()*getScale()); + gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // opaque! + ((GL11Ext) gl).glDrawTexfOES(x, y, 0, w, h); + + if(Utils.DEBUG()) Log.d(TAG, "drawing to screen: x " + x + " y " + y + " w " + w + " h " + h); + + + /* + * do pointer highlight overlay + */ + if(doPointerHighLight) { + gl.glEnable(GL10.GL_BLEND); + int mouseXonScreen = (int)(getScale()*(mouseX-absoluteXPosition)); + int mouseYonScreen = (int)(getScale()*(mouseY-absoluteYPosition)); + + gl.glLoadIdentity(); // Reset model-view matrix + gl.glTranslatex( mouseXonScreen, mouseYonScreen, 0); + gl.glColor4f(1.0f, 0.2f, 1.0f, 0.1f); + + // simulate some anti-aliasing by drawing the shape 3x + gl.glScalef(0.001f, 0.001f, 0.0f); + circle.draw(gl); + + gl.glScalef(0.99f, 0.99f, 0.0f); + circle.draw(gl); + + gl.glScalef(0.99f, 0.99f, 0.0f); + circle.draw(gl); + + gl.glDisable(GL10.GL_BLEND); + } + + } + catch(NullPointerException e) { + } + + } + + } + + + /** + * Constructor used by the inflation apparatus + * @param context + */ + public VncCanvas(final Context context, AttributeSet attrs) + { + super(context, attrs); + scrollRunnable = new MouseScrollRunnable(); + + setFocusable(true); + + glRenderer = new VNCGLRenderer(); + setRenderer(glRenderer); + // only render upon request + setRenderMode(RENDERMODE_WHEN_DIRTY); + + int oldprio = android.os.Process.getThreadPriority(android.os.Process.myTid()); + // GIVE US MAGIC POWER, O GREAT FAIR SCHEDULER! + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY); + Log.d(TAG, "Changed prio from " + oldprio + " to " + android.os.Process.getThreadPriority(android.os.Process.myTid())); + } + + /** + * Create a view showing a VNC connection + */ + void initializeVncCanvas(Dialog progressDialog, PointerInputHandler inputHandler, VNCConn conn) { + firstFrameWaitDialog = progressDialog; + this.inputHandler = inputHandler; + vncConn = conn; + } + + /** + * Warp the mouse to x, y in the RFB coordinates + * @param x + * @param y + */ + public void warpMouse(int x, int y) + { + mouseX=vncConn.trimX(x); + mouseY=vncConn.trimY(y); + reDraw(); // update local pointer position + panToMouse(); + vncConn.sendPointerEvent(x, y, 0, VNCConn.MOUSE_BUTTON_NONE); + } + + + private void mouseFollowPan() + { + try { + if (vncConn.getConnSettings().followPan) + { + int scrollx = absoluteXPosition; + int scrolly = absoluteYPosition; + int width = getVisibleWidth(); + int height = getVisibleHeight(); + //Log.i(TAG,"scrollx " + scrollx + " scrolly " + scrolly + " mouseX " + mouseX +" Y " + mouseY + " w " + width + " h " + height); + if (mouseX < scrollx || mouseX >= scrollx + width || mouseY < scrolly || mouseY >= scrolly + height) + { + //Log.i(TAG,"warp to " + scrollx+width/2 + "," + scrolly + height/2); + warpMouse(scrollx + width/2, scrolly + height / 2); + } + }} + catch(NullPointerException e) { + } + } + + + /** + * Apply scroll offset and scaling to convert touch-space coordinates to the corresponding + * point on the full frame. + * @param e MotionEvent with the original, touch space coordinates. This event is altered in place. + * @return e -- The same event passed in, with the coordinates mapped + */ + MotionEvent changeTouchCoordinatesToFullFrame(MotionEvent e) + { + //Log.v(TAG, String.format("tap at %f,%f", e.getX(), e.getY())); + float scale = getScale(); + + // Adjust coordinates for Android notification bar. + e.offsetLocation(0, -1f * getTop()); + + e.setLocation(absoluteXPosition + e.getX() / scale, absoluteYPosition + e.getY() / scale); + + return e; + } + + public void onDestroy() { + Log.v(TAG, "Cleaning up resources"); + try { + vncConn.shutdown(); + } + catch(NullPointerException e) { + } + vncConn = null; + } + + @Override + public void onPause() { + /* + * this is to avoid a deadlock between GUI thread and GLThread: + * + * the GUI thread would call onPause on the GLThread which would never return since + * the GL thread's GLThreadManager object is waiting on the GLThread. + */ + try { + vncConn.unlockFramebuffer(); + } + catch(IllegalMonitorStateException e) { + // thrown when mutex was not locked + } + catch(NullPointerException e) { + // thrown if we fatal out at the very beginning + } + + super.onPause(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + // Synchronize absoluteXPosition & absoluteYPosition with new size. + // We use pan() with delta 0 to trigger their re-calculation. + // + // oldw & oldh will be 0 if we are just now added to activity. + if (oldw != 0 || oldh != 0) { + pan(0, 0); + } + } + + + /* + * f(x,s) is a function that returns the coordinate in screen/scroll space corresponding + * to the coordinate x in full-frame space with scaling s. + * + * This function returns the difference between f(x,s1) and f(x,s2) + * + * f(x,s) = (x - i/2) * s + ((i - w)/2)) * s + * = s (x - i/2 + i/2 + w/2) + * = s (x + w/2) + * + * + * f(x,s) = (x - ((i - w)/2)) * s + * @param oldscaling + * @param scaling + * @param imageDim + * @param windowDim + * @param offset + * @return + */ + + + + /** + * Make sure mouse is visible on displayable part of screen + */ + public void panToMouse() + { + try { + if (!vncConn.getConnSettings().followMouse) + return; + } catch (NullPointerException e) { + return; + } + + int x = mouseX; + int y = mouseY; + int w = getVisibleWidth(); + int h = getVisibleHeight(); + int iw = vncConn.getFramebufferWidth(); + int ih = vncConn.getFramebufferHeight(); + + int newX = absoluteXPosition; + int newY = absoluteYPosition; + + if (x - newX >= w - 5) + { + newX = x - w + 5; + if (newX + w > iw) + newX = iw - w; + } + else if (x < newX + 5) + { + newX = x - 5; + if (newX < 0) + newX = 0; + } + if ( newX != absoluteXPosition ) { + absoluteXPosition = newX; + } + if (y - newY >= h - 5) + { + newY = y - h + 5; + if (newY + h > ih) + newY = ih - h; + } + else if (y < newY + 5) + { + newY = y - 5; + if (newY < 0) + newY = 0; + } + if ( newY != absoluteYPosition ) { + absoluteYPosition = newY; + } + } + + /** + * Pan by a number of pixels (relative pan) + * @param dX + * @param dY + * @return True if the pan changed the view (did not move view out of bounds); false otherwise + */ + public boolean pan(int dX, int dY) { + + try { + double scale = getScale(); + + double sX = (double) dX / scale; + double sY = (double) dY / scale; + + if (absoluteXPosition + sX < 0) + // dX = diff to 0 + sX = -absoluteXPosition; + if (absoluteYPosition + sY < 0) + sY = -absoluteYPosition; + + // Prevent panning right or below desktop image + if (absoluteXPosition + getVisibleWidth() + sX > vncConn.getFramebufferWidth()) + sX = vncConn.getFramebufferWidth() - getVisibleWidth() - absoluteXPosition; + if (absoluteYPosition + getVisibleHeight() + sY > vncConn.getFramebufferHeight()) + sY = vncConn.getFramebufferHeight() - getVisibleHeight() - absoluteYPosition; + + absoluteXPosition += sX; + absoluteYPosition += sY; + + // whene the frame buffer is smaller than the view, + // it is is centered! + if (vncConn.getFramebufferWidth() < getVisibleWidth()) + absoluteXPosition /= 2; + if (vncConn.getFramebufferHeight() < getVisibleHeight()) + absoluteYPosition /= 2; + + + if (sX != 0.0 || sY != 0.0) { + return true; + } + } catch (Exception ignored) { + } + return false; + } + + /* (non-Javadoc) + * @see android.view.View#onScrollChanged(int, int, int, int) + */ + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + try { + mouseFollowPan(); + } + catch(NullPointerException e) { + } + } + + + + @Override + public boolean onTouchEvent(MotionEvent event) { + return inputHandler.onTouchEvent(event); + } + + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return inputHandler.onGenericMotionEvent(event); + } + + public void reDraw() { + + if (repaintsEnabled && vncConn.rfbClient != 0) { + // request a redraw from GL thread + requestRender(); + } + } + + public void disableRepaints() { + repaintsEnabled = false; + } + + public void enableRepaints() { + repaintsEnabled = true; + } + + public void setPointerHighlight(boolean enable) { + doPointerHighLight = enable; + } + + public final boolean getPointerHighlight() { + return doPointerHighLight; + } + + + + public void showConnectionInfo() { + try { + String msg = vncConn.getDesktopName(); + msg += "(" + (vncConn.isEncrypted() ? getContext().getString(R.string.encrypted) : getContext().getString(R.string.unencrypted)) + ")"; + int idx = vncConn.getDesktopName().indexOf("("); + if (idx > -1) { + // Breakup actual desktop name from IP addresses for improved + // readability + String dn = vncConn.getDesktopName().substring(0, idx).trim(); + String ip = vncConn.getDesktopName().substring(idx).trim(); + msg = dn + "\n" + ip; + } + msg += "\n" + vncConn.getFramebufferWidth() + "x" + vncConn.getFramebufferHeight(); + String enc = vncConn.getEncoding(); + // Encoding might not be set when we display this message + if (enc != null && !enc.equals("")) + msg += ", " + vncConn.getEncoding() + " encoding, " + vncConn.getColorModel().toString(); + else + msg += ", " + vncConn.getColorModel().toString(); + Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show(); + } + catch(Exception ignored) { + } + } + + + /** Set which mouse buttons are down. + * @param pointerMask + */ + public void setOverridePointerMask(int pointerMask) { + overridePointerMask = pointerMask; + } + + + /** + * Convert a motion event to a format suitable for sending over the wire + * @param evt motion event; x and y must already have been converted from screen coordinates + * to remote frame buffer coordinates. cameraButton flag is interpreted as second mouse + * button + * @param downEvent True if "mouse button" (touch or trackball button) is down when this happens + * @return true if event was actually sent + */ + public boolean processPointerEvent(MotionEvent evt,boolean downEvent) + { + return processPointerEvent(evt,downEvent,cameraButtonDown); + } + + /** + * Convert a motion event to a format suitable for sending over the wire + * @param evt motion event; x and y must already have been converted from screen coordinates + * to remote frame buffer coordinates. + * @param mouseIsDown True if "mouse button" (touch or trackball button) is down when this happens + * @param useRightButton If true, event is interpreted as happening with right mouse button + * @return true if event was actually sent + */ + public boolean processPointerEvent(MotionEvent evt,boolean mouseIsDown,boolean useRightButton) { + try { + int action = evt.getAction(); + if (action == MotionEvent.ACTION_DOWN || (mouseIsDown && action == MotionEvent.ACTION_MOVE)) { + if (useRightButton) { + if(action == MotionEvent.ACTION_MOVE) + if(Utils.DEBUG()) Log.d(TAG, "Input: moving, right mouse button down"); + else + if(Utils.DEBUG()) Log.d(TAG, "Input: right mouse button down"); + + pointerMask = VNCConn.MOUSE_BUTTON_RIGHT; + } else { + if(action == MotionEvent.ACTION_MOVE) + if(Utils.DEBUG()) Log.d(TAG, "Input: moving, left mouse button down"); + else + if(Utils.DEBUG()) Log.d(TAG, "Input: left mouse button down"); + + pointerMask = VNCConn.MOUSE_BUTTON_LEFT; + } + } else if (action == MotionEvent.ACTION_UP) { + if(Utils.DEBUG()) Log.d(TAG, "Input: all mouse buttons up"); + pointerMask = 0; + } + mouseX = vncConn.trimX((int)evt.getX()); + mouseY = vncConn.trimY((int)evt.getY()); + reDraw(); // update local pointer position + panToMouse(); + if(overridePointerMask > 0) + return vncConn.sendPointerEvent((int)evt.getX(),(int)evt.getY(), evt.getMetaState(), overridePointerMask); + else + return vncConn.sendPointerEvent((int)evt.getX(),(int)evt.getY(), evt.getMetaState(), pointerMask); + + } + catch(NullPointerException e) { + return false; + } + } + + /** + * Process given mouse event. + * + * @param button Which button is pressed/released (0 for no button) + * @param isDown True if button is down + * @param x,y Event position in remote frame buffer coordinates. + */ + public void processMouseEvent(int button, boolean isDown, int x, int y) { + try { + if (isDown) + pointerMask |= button; + else + pointerMask &= ~button; + + mouseX = vncConn.trimX(x); + mouseY = vncConn.trimY(y); + reDraw(); // update local pointer position + panToMouse(); + if (overridePointerMask > 0) + vncConn.sendPointerEvent(x, y, 0, overridePointerMask); + else + vncConn.sendPointerEvent(x, y, 0, pointerMask); + + } catch (NullPointerException ignored) { + } + } + + + + /** + * Moves the scroll while the volume key is held down + * @author Michael A. MacDonald + */ + class MouseScrollRunnable implements Runnable + { + int delay = 100; + + int scrollButton = 0; + + /* (non-Javadoc) + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + vncConn.sendPointerEvent(mouseX, mouseY, 0, scrollButton); + vncConn.sendPointerEvent(mouseX, mouseY, 0, 0); + + handler.postDelayed(this, delay); + } + } + + public boolean processLocalKeyEvent(int keyCode, KeyEvent evt) { + + // prevent actionbar from stealing focus on key down + requestFocus(); + + if (keyCode == KeyEvent.KEYCODE_MENU) + // Ignore menu key + return true; + if (keyCode == KeyEvent.KEYCODE_CAMERA) + { + cameraButtonDown = (evt.getAction() != KeyEvent.ACTION_UP); + } + else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) + { + int mouseChange = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? VNCConn.MOUSE_BUTTON_SCROLL_DOWN : VNCConn.MOUSE_BUTTON_SCROLL_UP; + if (evt.getAction() == KeyEvent.ACTION_DOWN) + { + // If not auto-repeat + if (scrollRunnable.scrollButton != mouseChange) + { + pointerMask |= mouseChange; + scrollRunnable.scrollButton = mouseChange; + handler.postDelayed(scrollRunnable,200); + } + } + else + { + handler.removeCallbacks(scrollRunnable); + scrollRunnable.scrollButton = 0; + pointerMask &= ~mouseChange; + } + + vncConn.sendPointerEvent(mouseX, mouseY, evt.getMetaState(), pointerMask); + + return true; + } + + return vncConn.sendKeyEvent(keyCode, evt, false); + } + + void sendMetaKey(MetaKeyBean meta) + { + Log.d(TAG, "sendMetaKey " + (meta != null ? meta.getKeyDesc() : "none")); + + if(meta == null) + return; + + if (meta.isMouseClick) + { + vncConn.sendPointerEvent(mouseX, mouseY, meta.metaFlags, meta.mouseButtons); + vncConn.sendPointerEvent(mouseX, mouseY, meta.metaFlags, 0); + } + else { + // KeyEvent(downTime, eventTime, action, code, repeat, metaState) + KeyEvent downEvent = new KeyEvent( + System.currentTimeMillis(), + System.currentTimeMillis(), + KeyEvent.ACTION_DOWN, + meta.keySym, + 0, + meta.metaFlags); + vncConn.sendKeyEvent(downEvent.getKeyCode(), downEvent, true); + + // and up again + KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP); + vncConn.sendKeyEvent(upEvent.getKeyCode(), upEvent, true); + + } + } + + float getScale() + { + if (scaling == null) + return 1; + return scaling.getScale(); + } + + /** + * + * @return The smallest scale supported by the implementation; the scale at which + * the bitmap would be smaller than the screen + */ + float getMinimumScale() + { + double scale = 0.75; + int displayWidth = getWidth(); + int displayHeight = getHeight(); + for (; scale >= 0; scale -= 0.25) + { + try { + if (scale * vncConn.getFramebufferWidth() < displayWidth && scale * vncConn.getFramebufferHeight() < displayHeight) + break; + } catch (NullPointerException ignored) { + break; + } + } + return (float)(scale + 0.25); + } + + public int getVisibleWidth() { + return (int)((double)getWidth() / getScale() + 0.5); + } + + public int getVisibleHeight() { + return (int)((double)getHeight() / getScale() + 0.5); + } + + @Override + public void onFramebufferUpdateFinished() { + reDraw(); + + // Hide progress dialog + if (firstFrameWaitDialog.isShowing()) + handler.post(() -> { + try { + firstFrameWaitDialog.dismiss(); + } catch (Exception e){ + //unused + } + }); + } + + @Override + public void onNewFramebufferSize(int w, int h) { + // this triggers an update on what the canvas thinks about cursor position. + // without this, the pointer highlight is off by some value after framebuffer size change + handler.post(() -> { + try { + pan(0, 0); + } catch (Exception ignored) { + } + }); + } + + @Override + public void onRequestCredsFromUser(final ConnectionBean c, boolean isUserNameNeeded) { + // this method is probably called from the vnc thread + post(new Runnable() { + @Override + public void run() { + + LayoutInflater layoutinflater = LayoutInflater.from(getContext()); + View credentialsDialog = layoutinflater.inflate(R.layout.credentials_dialog, null); + if(!isUserNameNeeded) + credentialsDialog.findViewById(R.id.username_row).setVisibility(GONE); + + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle(getContext().getString(R.string.credentials_needed_title)) + .setView(credentialsDialog) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + if(isUserNameNeeded) + c.userName = ((EditText)credentialsDialog.findViewById(R.id.userName)).getText().toString(); + c.password = ((EditText)credentialsDialog.findViewById(R.id.password)).getText().toString(); + synchronized (vncConn) { + vncConn.notify(); + } + } + }).create(); + + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.show(); + } + }); + + } + + @Override + public void onRequestSshFingerprintCheck(String host, byte[] fingerprint, AtomicBoolean doContinue) { + // this method is probably called from the vnc thread + post(() -> { + // look for host, if not found create entry and return ok + SshKnownHost knownHost = VncDatabase.getInstance(getContext()).getSshKnownHostDao().get(host); + if(knownHost == null) { + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle(R.string.ssh_key_new_title) + // always using SHA256 in native part, ok to hardcode this here + .setMessage(Html.fromHtml(getContext().getString(R.string.ssh_key_new_message, + "SHA256:" + Base64.encodeToString(fingerprint,Base64.NO_PADDING|Base64.NO_WRAP)))) + .setCancelable(false) + .setPositiveButton(R.string.ssh_key_new_continue, (dialog12, whichButton) -> { + VncDatabase.getInstance(getContext()).getSshKnownHostDao().insert(new SshKnownHost(0, host, fingerprint)); + doContinue.set(true); + synchronized (vncConn) { + vncConn.notify(); + } + }) + .setNegativeButton(R.string.ssh_key_new_abort, (dialog1, whichButton) -> { + doContinue.set(false); + synchronized (vncConn) { + vncConn.notify(); + } + }) + .create(); + + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.show(); + return; + } + + // host found, check if fingerprint matches + if(Arrays.equals(knownHost.fingerprint, fingerprint)) { + doContinue.set(true); + synchronized (vncConn) { + vncConn.notify(); + } + } else { + // not matching, ask user! + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setTitle(R.string.ssh_key_mismatch_title) + // always using SHA256 in native part, ok to hardcode this here + .setMessage(Html.fromHtml(getContext().getString(R.string.ssh_key_mismatch_message, + "SHA256:" + Base64.encodeToString(fingerprint,Base64.NO_PADDING|Base64.NO_WRAP)))) + .setCancelable(false) + .setPositiveButton(R.string.ssh_key_mismatch_continue, (dialog12, whichButton) -> { + SshKnownHost updatedHost = new SshKnownHost(knownHost.id, host, fingerprint); + VncDatabase.getInstance(getContext()).getSshKnownHostDao().update(updatedHost); + doContinue.set(true); + synchronized (vncConn) { + vncConn.notify(); + } + }) + .setNegativeButton(R.string.ssh_key_mismatch_abort, (dialog1, whichButton) -> { + doContinue.set(false); + synchronized (vncConn) { + vncConn.notify(); + } + }) + .create(); + + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.show(); + } + }); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + Log.d(TAG, "onCreateInputConnection"); // only called when focusableInTouchMode==true + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; // need to set this for some keyboards in landscape mode + return super.onCreateInputConnection(outAttrs); + } +} diff --git a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvasActivity.java b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvasActivity.java index bdd05aec..0e9d5c11 100644 --- a/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvasActivity.java +++ b/android/app/src/main/java/com/coboltforge/dontmind/multivnc/ui/VncCanvasActivity.java @@ -1,888 +1,888 @@ -/* - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -// -// CanvasView is the Activity for showing VNC Desktop. -// -package com.coboltforge.dontmind.multivnc.ui; - -import java.util.List; - - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Build; -import android.text.ClipboardManager; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.DialogInterface.OnDismissListener; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Bundle; -import android.os.SystemClock; -import android.util.Log; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.PopupMenu; -import android.widget.TextView; -import android.widget.Toast; -import android.view.inputmethod.InputMethodManager; -import android.content.Context; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.appcompat.app.AppCompatActivity; - -import com.coboltforge.dontmind.multivnc.COLORMODEL; -import com.coboltforge.dontmind.multivnc.Constants; -import com.coboltforge.dontmind.multivnc.R; -import com.coboltforge.dontmind.multivnc.Utils; -import com.coboltforge.dontmind.multivnc.VNCConn; -import com.coboltforge.dontmind.multivnc.db.ConnectionBean; -import com.coboltforge.dontmind.multivnc.db.MetaKeyBean; -import com.coboltforge.dontmind.multivnc.db.VncDatabase; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.textfield.TextInputLayout; - -@SuppressWarnings("deprecation") -public class VncCanvasActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener { - - - private final static String TAG = "VncCanvasActivity"; - - - VncCanvas vncCanvas; - VncDatabase database; - private ConnectionBean connection; - - ZoomControls zoomer; - TextView zoomLevel; - PointerInputHandler inputHandler; - - ViewGroup mousebuttons; - TouchPointView touchpoints; - Toast notificationToast; - PopupMenu fabMenu; - - public ProgressDialog firstFrameWaitDialog; - - private SharedPreferences prefs; - - private ClipboardManager mClipboardManager; - - @SuppressLint("ShowToast") - @Override - public void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - - // hide title bar, status bar - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - - // hide system ui after softkeyboard close as per https://stackoverflow.com/a/21278040/361413 - final View decorView = getWindow().getDecorView(); - decorView.setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() { - @Override - public void onSystemUiVisibilityChange(int visibility) { - hideSystemUI(); - } - }); - - // Setup resizing when keyboard is visible. - // - // Ideally, we would let Android manage resizing but because we are using a fullscreen window, - // most of the "normal" options don't work for us. - // - // We have to hook into layout phase and manually shift our view up by adding appropriate - // bottom padding. - final View contentView = findViewById(android.R.id.content); - contentView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { - Rect frame = new Rect(); - contentView.getWindowVisibleDisplayFrame(frame); - - int contentBottom = contentView.getBottom(); - int paddingBottom = contentBottom - frame.bottom; - - if (paddingBottom < 0) - paddingBottom = 0; - - //When padding is less then 20% of height, it is most probably navigation bar. - if (paddingBottom > 0 && paddingBottom < contentBottom * .20) - return; - - contentView.setPadding(0, 0, 0, paddingBottom); //Update bottom - }); - - setContentView(R.layout.canvas_activity); - - vncCanvas = (VncCanvas) findViewById(R.id.vnc_canvas); - zoomer = (ZoomControls) findViewById(R.id.zoomer); - zoomLevel = findViewById(R.id.zoomLevel); - mousebuttons = (ViewGroup) findViewById(R.id.virtualmousebuttons); - - prefs = getSharedPreferences(Constants.PREFSNAME, MODE_PRIVATE); - - database = VncDatabase.getInstance(this); - - mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - - // create an empty toast. we do this do be able to cancel - notificationToast = Toast.makeText(this, "", Toast.LENGTH_SHORT); - notificationToast.setGravity(Gravity.TOP, 0, 60); - - inputHandler = new PointerInputHandler(vncCanvas, mousebuttons, notificationToast); - inputHandler.init(); - - /* - * Setup floating action button & associated menu - */ - FloatingActionButton fab = findViewById(R.id.fab); - fab.setOnClickListener(view -> { - Log.d(TAG, "FAB onClick"); - prepareFabMenu(fabMenu); - fabMenu.show(); - }); - fabMenu = new PopupMenu(this, fab); - fabMenu.inflate(R.menu.vnccanvasactivitymenu); - fabMenu.setOnMenuItemClickListener(this); - - - /* - * Setup connection bean. - */ - connection = new ConnectionBean(); - Intent i = getIntent(); - Uri data = i.getData(); - if ((data != null) && (data.getScheme().equals("vnc"))) { // started from outer world - - Log.d(TAG, "Starting via vnc://"); - - // This should not happen according to Uri contract, but bug introduced in Froyo (2.2) - // has made this parsing of host necessary, i.e. getPort() returns -1 and the stuff after the colon is - // still in the host part... - // http://code.google.com/p/android/issues/detail?id=9952 - if(! connection.parseHostPort(data.getHost())) { - // no colons in getHost() - connection.port = data.getPort(); - connection.address = data.getHost(); - } - - if (connection.address.equals(Constants.CONNECTION)) // this is a bookmarked connection - { - Log.d(TAG, "Starting bookmarked connection " + connection.port); - // read in this bookmarked connection - ConnectionBean result = database.getConnectionDao().get(connection.port); - if (result == null) { - Log.e(TAG, "Bookmarked connection " + connection.port + " does not exist!"); - Utils.showFatalErrorMessage(this, getString(R.string.bookmark_invalid)); - return; - } - connection = result; - } - else // well, not a boomarked connection - { - connection.nickname = connection.address; - List path = data.getPathSegments(); - if (path.size() >= 1) { - connection.colorModel = path.get(0); - } - if (path.size() >= 2) { - connection.password = path.get(1); - } - } - } - // Uri == null - else { // i.e. started from main menu - - Bundle extras = i.getExtras(); - - if (extras != null) { - connection = extras.getParcelable(Constants.CONNECTION); - } - if (connection.port == 0) - connection.port = 5900; - - Log.d(TAG, "Got raw intent " + connection.toString()); - - // Parse a HOST:PORT entry - connection.parseHostPort(connection.address); - } - - - /* - * Setup canvas and conn. - */ - VNCConn conn = new VNCConn(vncCanvas, vncCanvas); - // the Android 13 permission launcher and callback handler - ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - // show UI _after_ all the permission handling - showHelpDialog(); - VNCConnService.register(VncCanvasActivity.this, conn); - vncCanvas.showConnectionInfo(); - }); - // the actual connection init - // Startup the VNCConn with a nifty progress dialog - final ProgressDialog pd = new ProgressDialog(this); - pd.setCancelable(false); // on ICS, clicking somewhere cancels the dialog. not what we want... - pd.setTitle("Connecting..."); - pd.setMessage("Establishing handshake.\nPlease wait..."); - pd.setButton(DialogInterface.BUTTON_NEGATIVE, getString(android.R.string.cancel), (dialog, which) -> finish()); - pd.show(); - firstFrameWaitDialog = pd; - vncCanvas.initializeVncCanvas(pd, inputHandler, conn); // add conn to canvas - conn.init(connection, - // onInit - initError -> runOnUiThread(() -> { - if(isFinishing()) { - // this can happen when the activity is finishing, but the runOnUiThread comes later - Log.w(TAG, "VNCConn's onInit run while finishing activity, doing nothing"); - return; - } - - if(initError == null) { - // init success! - if (Build.VERSION.SDK_INT < 33) { - /* - * Show all the on-connect UI directly - */ - showHelpDialog(); - VNCConnService.register(VncCanvasActivity.this, conn); - vncCanvas.showConnectionInfo(); - } else { - /* - permission asking according to the book https://developer.android.com/training/permissions/requesting - */ - // the permission asking logic as per the book https://developer.android.com/training/permissions/requesting - if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { - showHelpDialog(); - VNCConnService.register(VncCanvasActivity.this, conn); - vncCanvas.showConnectionInfo(); - } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { - new AlertDialog.Builder(VncCanvasActivity.this) - .setCancelable(false) - .setTitle(R.string.notification_title) - .setMessage(R.string.notification_msg) - .setPositiveButton(android.R.string.ok, (dialog, which) -> { - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - }) - .setCancelable(false) - .show(); - } else { - // You can directly ask for the permission. - // The registered ActivityResultCallback gets the result of this request. - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - } - } - - setTitle(conn.getDesktopName()); - // actually set scale type with this, otherwise no scaling - setModes(); - firstFrameWaitDialog.setMessage("Downloading first frame.\nPlease wait..."); - // center pointer - vncCanvas.mouseX = conn.getFramebufferWidth() / 2; - vncCanvas.mouseY = conn.getFramebufferHeight() / 2; - } else { - // init fail - try { - firstFrameWaitDialog.dismiss(); - } catch (Exception ignored) { - } - String error = "VNC connection setup failed!"; - if (initError.getMessage() != null && (initError.getMessage().contains("authentication"))) { - error = "VNC authentication failed!"; - } - final String error_ = error + "
" + ((initError.getLocalizedMessage() != null) ? initError.getLocalizedMessage() : ""); - Utils.showFatalErrorMessage(VncCanvasActivity.this, error_); - } - }), - // onDisconnect - disconnectError -> runOnUiThread(() -> { - try { - // Ensure we dismiss the progress dialog - // before we fatal error finish - if (firstFrameWaitDialog.isShowing()) - firstFrameWaitDialog.dismiss(); - } catch (Exception e) { - //unused - } - - if(disconnectError != null) { - String error = "VNC connection failed!"; - final String error_ = error + "
" + ((disconnectError.getLocalizedMessage() != null) ? disconnectError.getLocalizedMessage() : ""); - Utils.showFatalErrorMessage(VncCanvasActivity.this, error_); - } - - // deregister connection - VNCConnService.deregister(VncCanvasActivity.this, conn); - })); - - zoomer.setOnZoomInClickListener(new View.OnClickListener() { - - /* - * (non-Javadoc) - * - * @see android.view.View.OnClickListener#onClick(android.view.View) - */ - @Override - public void onClick(View v) { - try { - vncCanvas.scaling.zoomIn(); - } - catch(NullPointerException e) { - } - } - - }); - zoomer.setOnZoomOutClickListener(new View.OnClickListener() { - - /* - * (non-Javadoc) - * - * @see android.view.View.OnClickListener#onClick(android.view.View) - */ - @Override - public void onClick(View v) { - try { - vncCanvas.scaling.zoomOut(); - } - catch(NullPointerException e) { - } - } - - }); - zoomer.setOnZoomKeyboardClickListener(new View.OnClickListener() { - - /* - * (non-Javadoc) - * - * @see android.view.View.OnClickListener#onClick(android.view.View) - */ - @Override - public void onClick(View v) { - toggleKeyboard(); - } - - }); - - MouseButtonView mousebutton1 = (MouseButtonView) findViewById(R.id.mousebutton1); - MouseButtonView mousebutton2 = (MouseButtonView) findViewById(R.id.mousebutton2); - MouseButtonView mousebutton3 = (MouseButtonView) findViewById(R.id.mousebutton3); - - mousebutton1.init(1, vncCanvas); - mousebutton2.init(2, vncCanvas); - mousebutton3.init(3, vncCanvas); - if(! prefs.getBoolean(Constants.PREFS_KEY_MOUSEBUTTONS, true)) - mousebuttons.setVisibility(View.GONE); - - touchpoints = (TouchPointView) findViewById(R.id.touchpoints); - touchpoints.setInputHandler(inputHandler); - - - if(! prefs.getBoolean(Constants.PREFS_KEY_POINTERHIGHLIGHT, true)) - vncCanvas.setPointerHighlight(false); - } - - /** - * Set modes on start to match what is specified in the ConnectionBean; - * color mode (already done), scaling - */ - public void setModes() { - float minScale = vncCanvas.getMinimumScale(); - vncCanvas.scaling = new ZoomScaling(this, minScale, 4); - } - - ConnectionBean getConnection() { - return connection; - } - - /* - * (non-Javadoc) - * - * @see android.app.Activity#onCreateDialog(int) - */ - @Override - protected Dialog onCreateDialog(int id) { - - - // Default to meta key dialog - return new MetaKeyDialog(this); - } - - /* - * (non-Javadoc) - * - * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog) - */ - @Override - protected void onPrepareDialog(int id, Dialog dialog) { - super.onPrepareDialog(id, dialog); - if (dialog instanceof MetaKeyDialog) - ((MetaKeyDialog) dialog).setConnection(connection); - } - - - @Override - protected void onStop() { - vncCanvas.disableRepaints(); - super.onStop(); - } - - @Override - protected void onRestart() { - vncCanvas.enableRepaints(); - super.onRestart(); - } - - - @Override - protected void onPause() { - super.onPause(); - // needed for the GLSurfaceView - vncCanvas.onPause(); - - // get VNC cuttext and post to Android - if(vncCanvas.vncConn.getCutText() != null) { - try { - mClipboardManager.setText(vncCanvas.vncConn.getCutText()); - } catch (Exception e) { - //unused - } - } - - } - - @Override - protected void onResume() { - super.onResume(); - // needed for the GLSurfaceView - vncCanvas.onResume(); - - // get Android clipboard contents - if (mClipboardManager.hasText()) { - try { - vncCanvas.vncConn.sendCutText(mClipboardManager.getText().toString()); - } - catch(NullPointerException e) { - //unused - } - } - - } - - /** - * Prepare FAB popup menu. - */ - private void prepareFabMenu(PopupMenu popupMenu) { - Menu menu = popupMenu.getMenu(); - if (touchpoints.getVisibility() == View.VISIBLE) { - menu.findItem(R.id.itemColorMode).setVisible(false); - menu.findItem(R.id.itemTogglePointerHighlight).setVisible(false); - } else { - menu.findItem(R.id.itemColorMode).setVisible(true); - menu.findItem(R.id.itemTogglePointerHighlight).setVisible(true); - } - - // changing pixel format without Fence extension (https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#clientfence) not safely possible - menu.findItem(R.id.itemColorMode).setVisible(false); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - - SharedPreferences.Editor ed = prefs.edit(); - - switch (item.getItemId()) { - case R.id.itemInfo: - vncCanvas.showConnectionInfo(); - return true; - case R.id.itemSpecialKeys: - showDialog(R.layout.metakey_dialog); - return true; - case R.id.itemColorMode: - selectColorModel(); - return true; - case R.id.itemToggleFramebufferUpdate: - if(vncCanvas.vncConn.toggleFramebufferUpdates()) // view enabled - { - vncCanvas.setVisibility(View.VISIBLE); - touchpoints.setVisibility(View.GONE); - } - else - { - vncCanvas.setVisibility(View.GONE); - touchpoints.setVisibility(View.VISIBLE); - } - return true; - - case R.id.itemToggleMouseButtons: - if(mousebuttons.getVisibility()== View.VISIBLE) { - mousebuttons.setVisibility(View.GONE); - ed.putBoolean(Constants.PREFS_KEY_MOUSEBUTTONS, false); - } - else { - mousebuttons.setVisibility(View.VISIBLE); - ed.putBoolean(Constants.PREFS_KEY_MOUSEBUTTONS, true); - } - ed.commit(); - return true; - - case R.id.itemTogglePointerHighlight: - if(vncCanvas.getPointerHighlight()) - vncCanvas.setPointerHighlight(false); - else - vncCanvas.setPointerHighlight(true); - - ed.putBoolean(Constants.PREFS_KEY_POINTERHIGHLIGHT, vncCanvas.getPointerHighlight()); - ed.commit(); - return true; - - case R.id.itemToggleKeyboard: - toggleKeyboard(); - return true; - - case R.id.itemSendKeyAgain: - sendSpecialKeyAgain(); - return true; - case R.id.itemSaveBookmark: - - if(connection.id != 0) { - // a bookmarked connection. save it right away - database.getConnectionDao().save(connection); - Toast.makeText(VncCanvasActivity.this, getString(R.string.bookmark_saved), Toast.LENGTH_SHORT).show(); - return true; - } - - final String defaultName = connection.address + ":" + connection.port; - - final EditText input = new EditText(this); - input.setHint(defaultName); - TextInputLayout inputLayout = new TextInputLayout(this); - inputLayout.setPadding( - (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), - 0, - (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), - 0 - ); - inputLayout.addView(input); - - new AlertDialog.Builder(this) - .setMessage(getString(R.string.enterbookmarkname)) - .setView(inputLayout) - .setPositiveButton(getString(android.R.string.ok), (dialog, whichButton) -> { - String name = input.getText().toString(); - if(name.length() == 0) - name = defaultName; - connection.nickname = name; - database.getConnectionDao().save(connection); - Toast.makeText(VncCanvasActivity.this, getString(R.string.bookmark_saved), Toast.LENGTH_SHORT).show(); - }).setNegativeButton(getString(android.R.string.cancel), (dialog, whichButton) -> { - // Do nothing. - }).show(); - return true; - case R.id.itemAbout: - Intent intent = new Intent (this, AboutActivity.class); - this.startActivity(intent); - return true; - case R.id.itemHelp: - Intent helpIntent = new Intent (this, HelpActivity.class); - this.startActivity(helpIntent); - return true; - case R.id.itemDisconnect: - new AlertDialog.Builder(this) - .setMessage(getString(R.string.disconnect_question)) - .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - vncCanvas.vncConn.shutdown(); - finish(); - } - }).setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - // Do nothing. - } - }).show(); - return true; - default: - break; - } - return super.onOptionsItemSelected(item); - } - - MetaKeyBean lastSentKey; - - private void sendSpecialKeyAgain() { - if (lastSentKey == null) { - lastSentKey = database.getMetaKeyDao().get(connection.lastMetaKeyId); - } - vncCanvas.sendMetaKey(lastSentKey); - } - - private void toggleKeyboard() { - InputMethodManager inputMgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - vncCanvas.requestFocus(); - // Android 12 changed something in respect to soft input, we have to add a delay to be able to show :-/ - vncCanvas.postDelayed(() -> inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0), 100); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (isFinishing()) { - try { - inputHandler.shutdown(); - vncCanvas.vncConn.shutdown(); - vncCanvas.onDestroy(); - } - catch(NullPointerException e) { - } - } - } - - - @Override - public boolean onKeyDown(int keyCode, KeyEvent evt) { - if(Utils.DEBUG()) Log.d(TAG, "Input: key down: " + evt.toString()); - - if (keyCode == KeyEvent.KEYCODE_MENU) { - prepareFabMenu(fabMenu); - fabMenu.show(); - return true; - } - - if(keyCode == KeyEvent.KEYCODE_BACK) { - - // handle right mouse button of USB-OTG devices - // Also, https://fossies.org/linux/SDL2/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java line 1943 states: - // 12290 = Samsung DeX mode desktop mouse - // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN - if(evt.getSource() == InputDevice.SOURCE_MOUSE || evt.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { - MotionEvent e = MotionEvent.obtain( - SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_DOWN, - vncCanvas.mouseX, - vncCanvas.mouseY, - 0 - ); - vncCanvas.processPointerEvent(e, true, true); - return true; - } - - if(evt.getFlags() == KeyEvent.FLAG_FROM_SYSTEM) // from hardware keyboard - keyCode = KeyEvent.KEYCODE_ESCAPE; - else { - new AlertDialog.Builder(this) - .setMessage(getString(R.string.disconnect_question)) - .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - vncCanvas.vncConn.shutdown(); - finish(); - } - }).setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - // Do nothing. - } - }).show(); - return true; - } - } - - // use search key to toggle soft keyboard - if (keyCode == KeyEvent.KEYCODE_SEARCH) - toggleKeyboard(); - - if (vncCanvas.processLocalKeyEvent(keyCode, evt)) - return true; - return super.onKeyDown(keyCode, evt); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent evt) { - if(Utils.DEBUG()) Log.d(TAG, "Input: key up: " + evt.toString()); - - if(keyCode == KeyEvent.KEYCODE_BACK) { - // handle right mouse button of USB-OTG devices - if (evt.getSource() == InputDevice.SOURCE_MOUSE) { - MotionEvent e = MotionEvent.obtain( - SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), - MotionEvent.ACTION_UP, - vncCanvas.mouseX, - vncCanvas.mouseY, - 0 - ); - vncCanvas.processPointerEvent(e, false, true); - return true; - } - } - - if (keyCode == KeyEvent.KEYCODE_MENU) - return super.onKeyUp(keyCode, evt); - - if (vncCanvas.processLocalKeyEvent(keyCode, evt)) - return true; - return super.onKeyUp(keyCode, evt); - } - - - // this is called for unicode symbols like € - // multiple duplicate key events have occurred in a row, or a complex string is being delivered. - // If the key code is not KEYCODE_UNKNOWN then the getRepeatCount() method returns the number of - // times the given key code should be executed. - // Otherwise, if the key code is KEYCODE_UNKNOWN, then this is a sequence of characters as returned by getCharacters(). - @Override - public boolean onKeyMultiple (int keyCode, int count, KeyEvent evt) { - if(Utils.DEBUG()) Log.d(TAG, "Input: key mult: " + evt.toString()); - - // we only deal with the special char case for now - if(evt.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { - if (vncCanvas.processLocalKeyEvent(keyCode, evt)) - return true; - } - - return super.onKeyMultiple(keyCode, count, evt); - } - - - - - - private void selectColorModel() { - // Stop repainting the desktop - // because the display is composited! - vncCanvas.disableRepaints(); - - final String[] choices = new String[COLORMODEL.values().length]; - int currentSelection = -1; - for (int i = 0; i < choices.length; i++) { - COLORMODEL cm = COLORMODEL.values()[i]; - choices[i] = cm.toString(); - if(cm.equals(vncCanvas.vncConn.getColorModel())) - currentSelection = i; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setSingleChoiceItems(choices, currentSelection, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int item) { - try{ - dialog.dismiss(); - } - catch(Exception e) { - } - COLORMODEL cm = COLORMODEL.values()[item]; - vncCanvas.vncConn.setColorModel(cm); - connection.colorModel = cm.nameString(); - Toast.makeText(VncCanvasActivity.this, - "Updating Color Model to " + cm.toString(), - Toast.LENGTH_SHORT).show(); - } - }); - AlertDialog dialog = builder.create(); - dialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface arg0) { - Log.i(TAG, "Color Model Selector dismissed"); - // Restore desktop repaints - vncCanvas.enableRepaints(); - } - }); - dialog.show(); - } - - static final long ZOOM_HIDE_DELAY_MS = 2500; - - Runnable hideZoomLevelInstance = () -> zoomLevel.setVisibility(View.INVISIBLE); - public void showZoomLevel() - { - zoomLevel.setText("" + (int)(vncCanvas.getScale()*100) +"%"); - zoomLevel.setVisibility(View.VISIBLE); - vncCanvas.handler.removeCallbacks(hideZoomLevelInstance); - vncCanvas.handler.postDelayed(hideZoomLevelInstance, ZOOM_HIDE_DELAY_MS); - - //Workaround for buggy GLSurfaceView. - //See https://stackoverflow.com/questions/11236336/setvisibilityview-visible-doesnt-always-work-ideas - zoomLevel.requestLayout(); - } - - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - hideSystemUI(); - } - } - - private void hideSystemUI() { - // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE. - // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY - View decorView = getWindow().getDecorView(); - decorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - // Set the content to appear under the system bars so that the - // content doesn't resize when the system bars hide and show. - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - // Hide the nav bar and status bar - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN); - } - - - private void showHelpDialog() { - SharedPreferences settings = getSharedPreferences(Constants.PREFSNAME, 0); - if(settings.getBoolean(Constants.PREFS_KEY_FIRST_CONNECTION, true)) { - new AlertDialog.Builder(this) - .setMessage(R.string.firstrun_help_dialog_text) - .setTitle(R.string.firstrun_help_dialog_title) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - Intent helpIntent = new Intent (VncCanvasActivity.this, HelpActivity.class); - VncCanvasActivity.this.startActivity(helpIntent); - }) - .setNegativeButton(android.R.string.cancel, (dialog, id) -> { - try { - dialog.dismiss(); - } - catch(Exception ignored) { - } - }) - .show(); - // and set this - SharedPreferences.Editor ed = settings.edit(); - ed.putBoolean(Constants.PREFS_KEY_FIRST_CONNECTION, false); - ed.apply(); - } - } - -} +/* + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// +// CanvasView is the Activity for showing VNC Desktop. +// +package com.coboltforge.dontmind.multivnc.ui; + +import java.util.List; + + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.os.Build; +import android.text.ClipboardManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.DialogInterface.OnDismissListener; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; +import android.view.inputmethod.InputMethodManager; +import android.content.Context; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.appcompat.app.AppCompatActivity; + +import com.coboltforge.dontmind.multivnc.COLORMODEL; +import com.coboltforge.dontmind.multivnc.Constants; +import com.coboltforge.dontmind.multivnc.R; +import com.coboltforge.dontmind.multivnc.Utils; +import com.coboltforge.dontmind.multivnc.VNCConn; +import com.coboltforge.dontmind.multivnc.db.ConnectionBean; +import com.coboltforge.dontmind.multivnc.db.MetaKeyBean; +import com.coboltforge.dontmind.multivnc.db.VncDatabase; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.textfield.TextInputLayout; + +@SuppressWarnings("deprecation") +public class VncCanvasActivity extends AppCompatActivity implements PopupMenu.OnMenuItemClickListener { + + + private final static String TAG = "VncCanvasActivity"; + + + VncCanvas vncCanvas; + VncDatabase database; + private ConnectionBean connection; + + ZoomControls zoomer; + TextView zoomLevel; + PointerInputHandler inputHandler; + + ViewGroup mousebuttons; + TouchPointView touchpoints; + Toast notificationToast; + PopupMenu fabMenu; + + public ProgressDialog firstFrameWaitDialog; + + private SharedPreferences prefs; + + private ClipboardManager mClipboardManager; + + @SuppressLint("ShowToast") + @Override + public void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + // hide title bar, status bar + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + // hide system ui after softkeyboard close as per https://stackoverflow.com/a/21278040/361413 + final View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() { + @Override + public void onSystemUiVisibilityChange(int visibility) { + hideSystemUI(); + } + }); + + // Setup resizing when keyboard is visible. + // + // Ideally, we would let Android manage resizing but because we are using a fullscreen window, + // most of the "normal" options don't work for us. + // + // We have to hook into layout phase and manually shift our view up by adding appropriate + // bottom padding. + final View contentView = findViewById(android.R.id.content); + contentView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + Rect frame = new Rect(); + contentView.getWindowVisibleDisplayFrame(frame); + + int contentBottom = contentView.getBottom(); + int paddingBottom = contentBottom - frame.bottom; + + if (paddingBottom < 0) + paddingBottom = 0; + + //When padding is less then 20% of height, it is most probably navigation bar. + if (paddingBottom > 0 && paddingBottom < contentBottom * .20) + return; + + contentView.setPadding(0, 0, 0, paddingBottom); //Update bottom + }); + + setContentView(R.layout.canvas_activity); + + vncCanvas = (VncCanvas) findViewById(R.id.vnc_canvas); + zoomer = (ZoomControls) findViewById(R.id.zoomer); + zoomLevel = findViewById(R.id.zoomLevel); + mousebuttons = (ViewGroup) findViewById(R.id.virtualmousebuttons); + + prefs = getSharedPreferences(Constants.PREFSNAME, MODE_PRIVATE); + + database = VncDatabase.getInstance(this); + + mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + + // create an empty toast. we do this do be able to cancel + notificationToast = Toast.makeText(this, "", Toast.LENGTH_SHORT); + notificationToast.setGravity(Gravity.TOP, 0, 60); + + inputHandler = new PointerInputHandler(vncCanvas, mousebuttons, notificationToast); + inputHandler.init(); + + /* + * Setup floating action button & associated menu + */ + FloatingActionButton fab = findViewById(R.id.fab); + fab.setOnClickListener(view -> { + Log.d(TAG, "FAB onClick"); + prepareFabMenu(fabMenu); + fabMenu.show(); + }); + fabMenu = new PopupMenu(this, fab); + fabMenu.inflate(R.menu.vnccanvasactivitymenu); + fabMenu.setOnMenuItemClickListener(this); + + + /* + * Setup connection bean. + */ + connection = new ConnectionBean(); + Intent i = getIntent(); + Uri data = i.getData(); + if ((data != null) && (data.getScheme().equals("vnc"))) { // started from outer world + + Log.d(TAG, "Starting via vnc://"); + + // This should not happen according to Uri contract, but bug introduced in Froyo (2.2) + // has made this parsing of host necessary, i.e. getPort() returns -1 and the stuff after the colon is + // still in the host part... + // http://code.google.com/p/android/issues/detail?id=9952 + if(! connection.parseHostPort(data.getHost())) { + // no colons in getHost() + connection.port = data.getPort(); + connection.address = data.getHost(); + } + + if (connection.address.equals(Constants.CONNECTION)) // this is a bookmarked connection + { + Log.d(TAG, "Starting bookmarked connection " + connection.port); + // read in this bookmarked connection + ConnectionBean result = database.getConnectionDao().get(connection.port); + if (result == null) { + Log.e(TAG, "Bookmarked connection " + connection.port + " does not exist!"); + Utils.showFatalErrorMessage(this, getString(R.string.bookmark_invalid)); + return; + } + connection = result; + } + else // well, not a boomarked connection + { + connection.nickname = connection.address; + List path = data.getPathSegments(); + if (path.size() >= 1) { + connection.colorModel = path.get(0); + } + if (path.size() >= 2) { + connection.password = path.get(1); + } + } + } + // Uri == null + else { // i.e. started from main menu + + Bundle extras = i.getExtras(); + + if (extras != null) { + connection = extras.getParcelable(Constants.CONNECTION); + } + if (connection.port == 0) + connection.port = 5900; + + Log.d(TAG, "Got raw intent " + connection.toString()); + + // Parse a HOST:PORT entry + connection.parseHostPort(connection.address); + } + + + /* + * Setup canvas and conn. + */ + VNCConn conn = new VNCConn(vncCanvas, vncCanvas); + // the Android 13 permission launcher and callback handler + ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + // show UI _after_ all the permission handling + showHelpDialog(); + VNCConnService.register(VncCanvasActivity.this, conn); + vncCanvas.showConnectionInfo(); + }); + // the actual connection init + // Startup the VNCConn with a nifty progress dialog + final ProgressDialog pd = new ProgressDialog(this); + pd.setCancelable(false); // on ICS, clicking somewhere cancels the dialog. not what we want... + pd.setTitle("Connecting..."); + pd.setMessage("Establishing handshake.\nPlease wait..."); + pd.setButton(DialogInterface.BUTTON_NEGATIVE, getString(android.R.string.cancel), (dialog, which) -> finish()); + pd.show(); + firstFrameWaitDialog = pd; + vncCanvas.initializeVncCanvas(pd, inputHandler, conn); // add conn to canvas + conn.init(connection, + // onInit + initError -> runOnUiThread(() -> { + if(isFinishing()) { + // this can happen when the activity is finishing, but the runOnUiThread comes later + Log.w(TAG, "VNCConn's onInit run while finishing activity, doing nothing"); + return; + } + + if(initError == null) { + // init success! + if (Build.VERSION.SDK_INT < 33) { + /* + * Show all the on-connect UI directly + */ + showHelpDialog(); + VNCConnService.register(VncCanvasActivity.this, conn); + vncCanvas.showConnectionInfo(); + } else { + /* + permission asking according to the book https://developer.android.com/training/permissions/requesting + */ + // the permission asking logic as per the book https://developer.android.com/training/permissions/requesting + if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { + showHelpDialog(); + VNCConnService.register(VncCanvasActivity.this, conn); + vncCanvas.showConnectionInfo(); + } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { + new AlertDialog.Builder(VncCanvasActivity.this) + .setCancelable(false) + .setTitle(R.string.notification_title) + .setMessage(R.string.notification_msg) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + }) + .setCancelable(false) + .show(); + } else { + // You can directly ask for the permission. + // The registered ActivityResultCallback gets the result of this request. + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } + } + + setTitle(conn.getDesktopName()); + // actually set scale type with this, otherwise no scaling + setModes(); + firstFrameWaitDialog.setMessage("Downloading first frame.\nPlease wait..."); + // center pointer + vncCanvas.mouseX = conn.getFramebufferWidth() / 2; + vncCanvas.mouseY = conn.getFramebufferHeight() / 2; + } else { + // init fail + try { + firstFrameWaitDialog.dismiss(); + } catch (Exception ignored) { + } + String error = "VNC connection setup failed!"; + if (initError.getMessage() != null && (initError.getMessage().contains("authentication"))) { + error = "VNC authentication failed!"; + } + final String error_ = error + "
" + ((initError.getLocalizedMessage() != null) ? initError.getLocalizedMessage() : ""); + Utils.showFatalErrorMessage(VncCanvasActivity.this, error_); + } + }), + // onDisconnect + disconnectError -> runOnUiThread(() -> { + try { + // Ensure we dismiss the progress dialog + // before we fatal error finish + if (firstFrameWaitDialog.isShowing()) + firstFrameWaitDialog.dismiss(); + } catch (Exception e) { + //unused + } + + if(disconnectError != null) { + String error = "VNC connection failed!"; + final String error_ = error + "
" + ((disconnectError.getLocalizedMessage() != null) ? disconnectError.getLocalizedMessage() : ""); + Utils.showFatalErrorMessage(VncCanvasActivity.this, error_); + } + + // deregister connection + VNCConnService.deregister(VncCanvasActivity.this, conn); + })); + + zoomer.setOnZoomInClickListener(new View.OnClickListener() { + + /* + * (non-Javadoc) + * + * @see android.view.View.OnClickListener#onClick(android.view.View) + */ + @Override + public void onClick(View v) { + try { + vncCanvas.scaling.zoomIn(); + } + catch(NullPointerException e) { + } + } + + }); + zoomer.setOnZoomOutClickListener(new View.OnClickListener() { + + /* + * (non-Javadoc) + * + * @see android.view.View.OnClickListener#onClick(android.view.View) + */ + @Override + public void onClick(View v) { + try { + vncCanvas.scaling.zoomOut(); + } + catch(NullPointerException e) { + } + } + + }); + zoomer.setOnZoomKeyboardClickListener(new View.OnClickListener() { + + /* + * (non-Javadoc) + * + * @see android.view.View.OnClickListener#onClick(android.view.View) + */ + @Override + public void onClick(View v) { + toggleKeyboard(); + } + + }); + + MouseButtonView mousebutton1 = (MouseButtonView) findViewById(R.id.mousebutton1); + MouseButtonView mousebutton2 = (MouseButtonView) findViewById(R.id.mousebutton2); + MouseButtonView mousebutton3 = (MouseButtonView) findViewById(R.id.mousebutton3); + + mousebutton1.init(1, vncCanvas); + mousebutton2.init(2, vncCanvas); + mousebutton3.init(3, vncCanvas); + if(! prefs.getBoolean(Constants.PREFS_KEY_MOUSEBUTTONS, true)) + mousebuttons.setVisibility(View.GONE); + + touchpoints = (TouchPointView) findViewById(R.id.touchpoints); + touchpoints.setInputHandler(inputHandler); + + + if(! prefs.getBoolean(Constants.PREFS_KEY_POINTERHIGHLIGHT, true)) + vncCanvas.setPointerHighlight(false); + } + + /** + * Set modes on start to match what is specified in the ConnectionBean; + * color mode (already done), scaling + */ + public void setModes() { + float minScale = vncCanvas.getMinimumScale(); + vncCanvas.scaling = new ZoomScaling(this, minScale, 4); + } + + ConnectionBean getConnection() { + return connection; + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onCreateDialog(int) + */ + @Override + protected Dialog onCreateDialog(int id) { + + + // Default to meta key dialog + return new MetaKeyDialog(this); + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog) + */ + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + super.onPrepareDialog(id, dialog); + if (dialog instanceof MetaKeyDialog) + ((MetaKeyDialog) dialog).setConnection(connection); + } + + + @Override + protected void onStop() { + vncCanvas.disableRepaints(); + super.onStop(); + } + + @Override + protected void onRestart() { + vncCanvas.enableRepaints(); + super.onRestart(); + } + + + @Override + protected void onPause() { + super.onPause(); + // needed for the GLSurfaceView + vncCanvas.onPause(); + + // get VNC cuttext and post to Android + if(vncCanvas.vncConn.getCutText() != null) { + try { + mClipboardManager.setText(vncCanvas.vncConn.getCutText()); + } catch (Exception e) { + //unused + } + } + + } + + @Override + protected void onResume() { + super.onResume(); + // needed for the GLSurfaceView + vncCanvas.onResume(); + + // get Android clipboard contents + if (mClipboardManager.hasText()) { + try { + vncCanvas.vncConn.sendCutText(mClipboardManager.getText().toString()); + } + catch(NullPointerException e) { + //unused + } + } + + } + + /** + * Prepare FAB popup menu. + */ + private void prepareFabMenu(PopupMenu popupMenu) { + Menu menu = popupMenu.getMenu(); + if (touchpoints.getVisibility() == View.VISIBLE) { + menu.findItem(R.id.itemColorMode).setVisible(false); + menu.findItem(R.id.itemTogglePointerHighlight).setVisible(false); + } else { + menu.findItem(R.id.itemColorMode).setVisible(true); + menu.findItem(R.id.itemTogglePointerHighlight).setVisible(true); + } + + // changing pixel format without Fence extension (https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#clientfence) not safely possible + menu.findItem(R.id.itemColorMode).setVisible(false); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + + SharedPreferences.Editor ed = prefs.edit(); + + switch (item.getItemId()) { + case R.id.itemInfo: + vncCanvas.showConnectionInfo(); + return true; + case R.id.itemSpecialKeys: + showDialog(R.layout.metakey_dialog); + return true; + case R.id.itemColorMode: + selectColorModel(); + return true; + case R.id.itemToggleFramebufferUpdate: + if(vncCanvas.vncConn.toggleFramebufferUpdates()) // view enabled + { + vncCanvas.setVisibility(View.VISIBLE); + touchpoints.setVisibility(View.GONE); + } + else + { + vncCanvas.setVisibility(View.GONE); + touchpoints.setVisibility(View.VISIBLE); + } + return true; + + case R.id.itemToggleMouseButtons: + if(mousebuttons.getVisibility()== View.VISIBLE) { + mousebuttons.setVisibility(View.GONE); + ed.putBoolean(Constants.PREFS_KEY_MOUSEBUTTONS, false); + } + else { + mousebuttons.setVisibility(View.VISIBLE); + ed.putBoolean(Constants.PREFS_KEY_MOUSEBUTTONS, true); + } + ed.commit(); + return true; + + case R.id.itemTogglePointerHighlight: + if(vncCanvas.getPointerHighlight()) + vncCanvas.setPointerHighlight(false); + else + vncCanvas.setPointerHighlight(true); + + ed.putBoolean(Constants.PREFS_KEY_POINTERHIGHLIGHT, vncCanvas.getPointerHighlight()); + ed.commit(); + return true; + + case R.id.itemToggleKeyboard: + toggleKeyboard(); + return true; + + case R.id.itemSendKeyAgain: + sendSpecialKeyAgain(); + return true; + case R.id.itemSaveBookmark: + + if(connection.id != 0) { + // a bookmarked connection. save it right away + database.getConnectionDao().save(connection); + Toast.makeText(VncCanvasActivity.this, getString(R.string.bookmark_saved), Toast.LENGTH_SHORT).show(); + return true; + } + + final String defaultName = connection.address + ":" + connection.port; + + final EditText input = new EditText(this); + input.setHint(defaultName); + TextInputLayout inputLayout = new TextInputLayout(this); + inputLayout.setPadding( + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), + 0, + (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics()), + 0 + ); + inputLayout.addView(input); + + new AlertDialog.Builder(this) + .setMessage(getString(R.string.enterbookmarkname)) + .setView(inputLayout) + .setPositiveButton(getString(android.R.string.ok), (dialog, whichButton) -> { + String name = input.getText().toString(); + if(name.length() == 0) + name = defaultName; + connection.nickname = name; + database.getConnectionDao().save(connection); + Toast.makeText(VncCanvasActivity.this, getString(R.string.bookmark_saved), Toast.LENGTH_SHORT).show(); + }).setNegativeButton(getString(android.R.string.cancel), (dialog, whichButton) -> { + // Do nothing. + }).show(); + return true; + case R.id.itemAbout: + Intent intent = new Intent (this, AboutActivity.class); + this.startActivity(intent); + return true; + case R.id.itemHelp: + Intent helpIntent = new Intent (this, HelpActivity.class); + this.startActivity(helpIntent); + return true; + case R.id.itemDisconnect: + new AlertDialog.Builder(this) + .setMessage(getString(R.string.disconnect_question)) + .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + vncCanvas.vncConn.shutdown(); + finish(); + } + }).setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Do nothing. + } + }).show(); + return true; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + MetaKeyBean lastSentKey; + + private void sendSpecialKeyAgain() { + if (lastSentKey == null) { + lastSentKey = database.getMetaKeyDao().get(connection.lastMetaKeyId); + } + vncCanvas.sendMetaKey(lastSentKey); + } + + private void toggleKeyboard() { + InputMethodManager inputMgr = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + vncCanvas.requestFocus(); + // Android 12 changed something in respect to soft input, we have to add a delay to be able to show :-/ + vncCanvas.postDelayed(() -> inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0), 100); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isFinishing()) { + try { + inputHandler.shutdown(); + vncCanvas.vncConn.shutdown(); + vncCanvas.onDestroy(); + } + catch(NullPointerException e) { + } + } + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent evt) { + if(Utils.DEBUG()) Log.d(TAG, "Input: key down: " + evt.toString()); + + if (keyCode == KeyEvent.KEYCODE_MENU) { + prepareFabMenu(fabMenu); + fabMenu.show(); + return true; + } + + if(keyCode == KeyEvent.KEYCODE_BACK) { + + // handle right mouse button of USB-OTG devices + // Also, https://fossies.org/linux/SDL2/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java line 1943 states: + // 12290 = Samsung DeX mode desktop mouse + // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN + if(evt.getSource() == InputDevice.SOURCE_MOUSE || evt.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { + MotionEvent e = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_DOWN, + vncCanvas.mouseX, + vncCanvas.mouseY, + 0 + ); + vncCanvas.processPointerEvent(e, true, true); + return true; + } + + if(evt.getFlags() == KeyEvent.FLAG_FROM_SYSTEM) // from hardware keyboard + keyCode = KeyEvent.KEYCODE_ESCAPE; + else { + new AlertDialog.Builder(this) + .setMessage(getString(R.string.disconnect_question)) + .setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + vncCanvas.vncConn.shutdown(); + finish(); + } + }).setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + // Do nothing. + } + }).show(); + return true; + } + } + + // use search key to toggle soft keyboard + if (keyCode == KeyEvent.KEYCODE_SEARCH) + toggleKeyboard(); + + if (vncCanvas.processLocalKeyEvent(keyCode, evt)) + return true; + return super.onKeyDown(keyCode, evt); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent evt) { + if(Utils.DEBUG()) Log.d(TAG, "Input: key up: " + evt.toString()); + + if(keyCode == KeyEvent.KEYCODE_BACK) { + // handle right mouse button of USB-OTG devices + if (evt.getSource() == InputDevice.SOURCE_MOUSE) { + MotionEvent e = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, + vncCanvas.mouseX, + vncCanvas.mouseY, + 0 + ); + vncCanvas.processPointerEvent(e, false, true); + return true; + } + } + + if (keyCode == KeyEvent.KEYCODE_MENU) + return super.onKeyUp(keyCode, evt); + + if (vncCanvas.processLocalKeyEvent(keyCode, evt)) + return true; + return super.onKeyUp(keyCode, evt); + } + + + // this is called for unicode symbols like € + // multiple duplicate key events have occurred in a row, or a complex string is being delivered. + // If the key code is not KEYCODE_UNKNOWN then the getRepeatCount() method returns the number of + // times the given key code should be executed. + // Otherwise, if the key code is KEYCODE_UNKNOWN, then this is a sequence of characters as returned by getCharacters(). + @Override + public boolean onKeyMultiple (int keyCode, int count, KeyEvent evt) { + if(Utils.DEBUG()) Log.d(TAG, "Input: key mult: " + evt.toString()); + + // we only deal with the special char case for now + if(evt.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { + if (vncCanvas.processLocalKeyEvent(keyCode, evt)) + return true; + } + + return super.onKeyMultiple(keyCode, count, evt); + } + + + + + + private void selectColorModel() { + // Stop repainting the desktop + // because the display is composited! + vncCanvas.disableRepaints(); + + final String[] choices = new String[COLORMODEL.values().length]; + int currentSelection = -1; + for (int i = 0; i < choices.length; i++) { + COLORMODEL cm = COLORMODEL.values()[i]; + choices[i] = cm.toString(); + if(cm.equals(vncCanvas.vncConn.getColorModel())) + currentSelection = i; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setSingleChoiceItems(choices, currentSelection, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + try{ + dialog.dismiss(); + } + catch(Exception e) { + } + COLORMODEL cm = COLORMODEL.values()[item]; + vncCanvas.vncConn.setColorModel(cm); + connection.colorModel = cm.nameString(); + Toast.makeText(VncCanvasActivity.this, + "Updating Color Model to " + cm.toString(), + Toast.LENGTH_SHORT).show(); + } + }); + AlertDialog dialog = builder.create(); + dialog.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface arg0) { + Log.i(TAG, "Color Model Selector dismissed"); + // Restore desktop repaints + vncCanvas.enableRepaints(); + } + }); + dialog.show(); + } + + static final long ZOOM_HIDE_DELAY_MS = 2500; + + Runnable hideZoomLevelInstance = () -> zoomLevel.setVisibility(View.INVISIBLE); + public void showZoomLevel() + { + zoomLevel.setText("" + (int)(vncCanvas.getScale()*100) +"%"); + zoomLevel.setVisibility(View.VISIBLE); + vncCanvas.handler.removeCallbacks(hideZoomLevelInstance); + vncCanvas.handler.postDelayed(hideZoomLevelInstance, ZOOM_HIDE_DELAY_MS); + + //Workaround for buggy GLSurfaceView. + //See https://stackoverflow.com/questions/11236336/setvisibilityview-visible-doesnt-always-work-ideas + zoomLevel.requestLayout(); + } + + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideSystemUI(); + } + } + + private void hideSystemUI() { + // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE. + // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + // Set the content to appear under the system bars so that the + // content doesn't resize when the system bars hide and show. + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + // Hide the nav bar and status bar + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); + } + + + private void showHelpDialog() { + SharedPreferences settings = getSharedPreferences(Constants.PREFSNAME, 0); + if(settings.getBoolean(Constants.PREFS_KEY_FIRST_CONNECTION, true)) { + new AlertDialog.Builder(this) + .setMessage(R.string.firstrun_help_dialog_text) + .setTitle(R.string.firstrun_help_dialog_title) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + Intent helpIntent = new Intent (VncCanvasActivity.this, HelpActivity.class); + VncCanvasActivity.this.startActivity(helpIntent); + }) + .setNegativeButton(android.R.string.cancel, (dialog, id) -> { + try { + dialog.dismiss(); + } + catch(Exception ignored) { + } + }) + .show(); + // and set this + SharedPreferences.Editor ed = settings.edit(); + ed.putBoolean(Constants.PREFS_KEY_FIRST_CONNECTION, false); + ed.apply(); + } + } + +} diff --git a/android/app/src/main/res/values-ca/strings.xml b/android/app/src/main/res/values-ca/strings.xml index ea8f0fc8..eea911fd 100644 --- a/android/app/src/main/res/values-ca/strings.xml +++ b/android/app/src/main/res/values-ca/strings.xml @@ -1,121 +1,121 @@ - - - Direcció - Alt - Super - MultiVNC - Servidor VNC trobat - Servidor VNC no trobat - Reiniciar el descobriment del servidor - Escalar ara -MultiVNC per a Android va ser desenvolupat per a vostè per Christian Beier (christianbeier.net).\n\nLa pàgina principal del projecte està en https://github.com/bk138/multivnc\n\nSi desitja informar d\'un error o sol·licitar una funció, dirigeixi\'s https://github.com/bk138/multivnc/issues\n\nEl registre de canvis es pot trobar en https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -Manera de color -Establir la manera de color -Conectar -Ctrl -Eliminar -Desconectar -Canviar la vista -Canviar els botons del ratolí -Canviar el ressaltat del punter -Canviar el teclat - -Exportar -¡Error en obrir la base de dades! -Importar/Exportar Configuració -Importar - S\'ha donat una URL incorrecta: - Configuració de la lectura d\'errors d\'I/O - Configuració de la lectura d\'errors de format o XML - -Informació de la connexió -Enviar combinació de tecles -Connexió nova -Marcadors -Servidors descoberts -Marcadors -Guardar com a marcador -Marcador guardat! -Això ja no és un marcador vàlid! -Contrasenya -Mantenir -Port -ID en el repetidor -Identificació del servidor quan es connecta a un repetidor UltraVNC - Túneles SSH - Servidor SSH - Nom d\'usuari SSH - Contrasenya - Clau privada - Contrasenya SSH - Importa la clau - Sobreescriure la clau - S\'ha importat correctament la clau privada SSH. - No s\'ha pogut importar la clau privada SSH. - Contrasenya clau - Guardar com a còpia - Es necessiten credencials... - -Enviar -Envíar la combinació de tecles de nou -Canvi -Drecera de la connexió VNC - No hi ha marcadors encara -Combinació de tecles -Usuari -Per a l\'autenticació de Windows/Mac -Editar Marcadors -Si us plau, introdueixi un nom de marcador... -Vol afegir aquest servidor als marcadors? -Està segur que vol esborrar aquest marcador? -Està segur que vol desconnectar? -Sobre -Ajuda -Ajuda - Guardar - - Error! - -Si us plau, col·labori amb MultiVNC -Si li agrada MultiVNC, per què no considera col·laborar amb el seu desenvolupament? De moment, pot fer-ho fàcilment qualificant l\'aplicació o donant. Gràcies. - -Si, bona idea! -No, ara no. -No tornar a preguntar! - -Mostrar ajuda? -Sembla que és la primera vegada que usa MultiVNC. Vol llegir el manual de l\'aplicació? No és molt llarg, però és informatiu... - -Si li agrada MultiVNC, per què no mostrar una mica d\'amor fent clic en el cor? -Què hi ha de nou...? - -Registre de canvis - en Línia. - ]]> - - Connectat a %1$s - %1$s i - xifrat - sense xifrar - - Codificació preferida - D\'acord - Cancel · lar - Nivell de compressió - Nivell de qualitat - - L\'amfitrió SSH remot encara no es coneix - L\'empremta digital <b>%s</b> del servidor SSH encara no es coneix. Si us plau, comproveu que és l\'empremta digital correcta del servidor SSH al qual voleu connectar-vos. - L\'empremta digital és vàlida, continua - No estic segur, avorta - - La identificació de l\'amfitrió SSH remot ha canviat! - L\'empremta digital <b>%s</b> del servidor SSH no coincideix amb la ja coneguda. Això significa que algú està suplantant la identitat del servidor SSH correcte (atac man-in-the-middle) o que s\'ha canviat la clau d\'amfitrió del servidor correcte. Poseu-vos en contacte amb l\'administrador del servidor SSH per verificar la clau correcta. Voleu continuar o avortar la configuració de la connexió SSH? - Estic segur que això és legítim, continua - Comprovaré l\'empremta correcta del servidor SSH, avortaré - - Notificacions desactivades - Per poder mostrar la informació de l\'estat del client i proporcionar un mitjà per tornar al client més fàcilment, es recomana habilitar les notificacions. - - + + + Direcció + Alt + Super + MultiVNC + Servidor VNC trobat + Servidor VNC no trobat + Reiniciar el descobriment del servidor + Escalar ara +MultiVNC per a Android va ser desenvolupat per a vostè per Christian Beier (christianbeier.net).\n\nLa pàgina principal del projecte està en https://github.com/bk138/multivnc\n\nSi desitja informar d\'un error o sol·licitar una funció, dirigeixi\'s https://github.com/bk138/multivnc/issues\n\nEl registre de canvis es pot trobar en https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +Manera de color +Establir la manera de color +Conectar +Ctrl +Eliminar +Desconectar +Canviar la vista +Canviar els botons del ratolí +Canviar el ressaltat del punter +Canviar el teclat + +Exportar +¡Error en obrir la base de dades! +Importar/Exportar Configuració +Importar + S\'ha donat una URL incorrecta: + Configuració de la lectura d\'errors d\'I/O + Configuració de la lectura d\'errors de format o XML + +Informació de la connexió +Enviar combinació de tecles +Connexió nova +Marcadors +Servidors descoberts +Marcadors +Guardar com a marcador +Marcador guardat! +Això ja no és un marcador vàlid! +Contrasenya +Mantenir +Port +ID en el repetidor +Identificació del servidor quan es connecta a un repetidor UltraVNC + Túneles SSH + Servidor SSH + Nom d\'usuari SSH + Contrasenya + Clau privada + Contrasenya SSH + Importa la clau + Sobreescriure la clau + S\'ha importat correctament la clau privada SSH. + No s\'ha pogut importar la clau privada SSH. + Contrasenya clau + Guardar com a còpia + Es necessiten credencials... + +Enviar +Envíar la combinació de tecles de nou +Canvi +Drecera de la connexió VNC + No hi ha marcadors encara +Combinació de tecles +Usuari +Per a l\'autenticació de Windows/Mac +Editar Marcadors +Si us plau, introdueixi un nom de marcador... +Vol afegir aquest servidor als marcadors? +Està segur que vol esborrar aquest marcador? +Està segur que vol desconnectar? +Sobre +Ajuda +Ajuda + Guardar + + Error! + +Si us plau, col·labori amb MultiVNC +Si li agrada MultiVNC, per què no considera col·laborar amb el seu desenvolupament? De moment, pot fer-ho fàcilment qualificant l\'aplicació o donant. Gràcies. + +Si, bona idea! +No, ara no. +No tornar a preguntar! + +Mostrar ajuda? +Sembla que és la primera vegada que usa MultiVNC. Vol llegir el manual de l\'aplicació? No és molt llarg, però és informatiu... + +Si li agrada MultiVNC, per què no mostrar una mica d\'amor fent clic en el cor? +Què hi ha de nou...? + +Registre de canvis + en Línia. + ]]> + + Connectat a %1$s + %1$s i + xifrat + sense xifrar + + Codificació preferida + D\'acord + Cancel · lar + Nivell de compressió + Nivell de qualitat + + L\'amfitrió SSH remot encara no es coneix + L\'empremta digital <b>%s</b> del servidor SSH encara no es coneix. Si us plau, comproveu que és l\'empremta digital correcta del servidor SSH al qual voleu connectar-vos. + L\'empremta digital és vàlida, continua + No estic segur, avorta + + La identificació de l\'amfitrió SSH remot ha canviat! + L\'empremta digital <b>%s</b> del servidor SSH no coincideix amb la ja coneguda. Això significa que algú està suplantant la identitat del servidor SSH correcte (atac man-in-the-middle) o que s\'ha canviat la clau d\'amfitrió del servidor correcte. Poseu-vos en contacte amb l\'administrador del servidor SSH per verificar la clau correcta. Voleu continuar o avortar la configuració de la connexió SSH? + Estic segur que això és legítim, continua + Comprovaré l\'empremta correcta del servidor SSH, avortaré + + Notificacions desactivades + Per poder mostrar la informació de l\'estat del client i proporcionar un mitjà per tornar al client més fàcilment, es recomana habilitar les notificacions. + + diff --git a/android/app/src/main/res/values-de/strings.xml b/android/app/src/main/res/values-de/strings.xml index d0639f7e..0747dd6e 100644 --- a/android/app/src/main/res/values-de/strings.xml +++ b/android/app/src/main/res/values-de/strings.xml @@ -1,120 +1,120 @@ - - "Adresse" - "Alt" - Super - "VNC-Server gefunden" - "VNC-Server verschwunden" - "Server-Erkennung neu starten" - "Skaliert auf " -"MultiVNC für Android wird von Christian Beier (christianbeier.net) für Sie bereitgestellt.\n\nDie Projekt-Homepage befindet sich unter https://github.com/bk138/multivnc\n\nWenn Sie einen Fehler melden oder eine Funktion anfordern möchten, gehen Sie zu https://github.com/bk138/multivnc/issues\n\nDas Änderungsprotokoll kann unter https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md gefunden werden." -"Farbmodus" -"Farbmodus einstellen" -"Verbinden" -"Ctrl" -"Löschen" -"Trennen" -"Ansicht umschalten" -"Maustasten umschalten" -"Cursor-Highlight umschalten" -"Tastatur umschalten" - -"Exportieren" -"Fehler beim Öffnen der Datenbank!" -Einstellungen Importieren/Exportieren -"Importieren" - Ungültige URL: - Ein-/Ausgabe-Fehler beim Importieren der Konfiguration - Das XML-Format der zu importierenden Konfiguration ist ungültig - -"Info zur Verbindung" -"Tastenkombination senden" -"Neue Verbindung" -"Lesezeichen" -"Erkannte Server" -"Lesezeichen" -"Lesezeichen speichern" -"Lesezeichen gespeichert!" -"Dies ist kein gültiges Lesezeichen mehr!" -"Passwort" -"Behalten" -"Port" -"ID am Repeater" -"Server-ID bei Verbindung zu einem UltraVNC-Repeater" - SSH-Tunnel - SSH-Server - SSH-Username - Passwort - Privater Schlüssel - SSH-Passwort - Schlüssel importieren - Schlüssel überschreiben - Der private SSH-Schlüssel wurde erfolgreich importiert. - Importieren des privaten SSH-Schlüssels fehlgeschlagen. - Schlüssel-Passwort -"Als Kopie speichern" - Anmeldeinformationen erforderlich… - -"Senden" -"Tastenkombination erneut senden" -"Shift" -"VNC-Verbindungsverknüpfung" - Keine Lesezeichen vorhanden -"Tastenkombination" -"Nutzername" -"Für die Windows/Mac-Authentifizierung" -"Lesezeichen bearbeiten" -"Bitte geben Sie einen Lesezeichennamen ein…" -"Möchten Sie diesen Server als Lesezeichen speichern?" -"Möchten Sie dieses Lesezeichen wirklich löschen?" -"Möchten Sie die Verbindung wirklich trennen?" -"Über" -"Hilfe" -"Bearbeiten" - Speichern - - Fehler! - -"Bitte unterstützen Sie MultiVNC" -"Wenn Ihnen MultiVNC gefällt, können Sie die Entwicklung unterstützen, indem Sie die App bewerten oder eine Kleinigkeit spenden. Vielen Dank!" - -"Ja, gute Idee!" -"Nein, nicht jetzt." -"Nicht noch einmal nachfragen." - -"Hilfe anzeigen?" -"Es scheint, dass Sie MultiVNC zum ersten Mal verwenden. Möchten Sie das App-Handbuch lesen? Es ist noch nicht sehr umfangreich, aber trotzdem informativ…" - -"Wenn Ihnen MultiVNC gefällt, klicken Sie einfach auf das Herz." -"Was gibt\'s Neues…" - -Änderungsprotokoll (in Englisch) werfen. - ]]> - - Verbunden mit %1$s - %1$s und - - verschlüsselt - unverschlüsselt - - Bevorzugte Kodierung - OK - Abbrechen - Komprimierung - Qualitätsstufe - - Remote-SSH-Host noch nicht bekannt - Der Fingerabdruck des SSH-Servers <b>%s</b> ist noch nicht bekannt. Bitte überprüfen Sie, ob es sich um den richtigen Fingerabdruck des SSH-Servers handelt, mit dem Sie sich verbinden möchten. - Der Fingerabdruck ist gültig, weiter - Ich bin mir nicht sicher, abbrechen - - Remote-SSH-Host-Identifikation hat sich geändert! - Der Fingerabdruck <b>%s</b> des SSH-Servers stimmt nicht mit dem bereits bekannten überein. Dies bedeutet entweder, dass jemand den richtigen SSH-Server imitiert (Man-in-the-Middle-Angriff) oder dass der Hostschlüssel des richtigen Servers geändert wurde. Bitte wenden Sie sich an den Administrator des SSH-Servers, um den richtigen Schlüssel zu überprüfen. Möchten Sie den SSH-Verbindungsaufbau fortsetzen oder abbrechen? - Ich bin zuversichtlich, dass dies legitim ist, fortfahren - Ich werde den korrekten Fingerabdruck des SSH-Servers noch einmal überprüfen, abbrechen - - Benachrichtigungen deaktiviert - Um Client-Statusinformationen anzeigen zu können und einfacher zum Client zurücknavigieren zu können, wird empfohlen, Benachrichtigungen zu aktivieren. - + + "Adresse" + "Alt" + Super + "VNC-Server gefunden" + "VNC-Server verschwunden" + "Server-Erkennung neu starten" + "Skaliert auf " +"MultiVNC für Android wird von Christian Beier (christianbeier.net) für Sie bereitgestellt.\n\nDie Projekt-Homepage befindet sich unter https://github.com/bk138/multivnc\n\nWenn Sie einen Fehler melden oder eine Funktion anfordern möchten, gehen Sie zu https://github.com/bk138/multivnc/issues\n\nDas Änderungsprotokoll kann unter https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md gefunden werden." +"Farbmodus" +"Farbmodus einstellen" +"Verbinden" +"Ctrl" +"Löschen" +"Trennen" +"Ansicht umschalten" +"Maustasten umschalten" +"Cursor-Highlight umschalten" +"Tastatur umschalten" + +"Exportieren" +"Fehler beim Öffnen der Datenbank!" +Einstellungen Importieren/Exportieren +"Importieren" + Ungültige URL: + Ein-/Ausgabe-Fehler beim Importieren der Konfiguration + Das XML-Format der zu importierenden Konfiguration ist ungültig + +"Info zur Verbindung" +"Tastenkombination senden" +"Neue Verbindung" +"Lesezeichen" +"Erkannte Server" +"Lesezeichen" +"Lesezeichen speichern" +"Lesezeichen gespeichert!" +"Dies ist kein gültiges Lesezeichen mehr!" +"Passwort" +"Behalten" +"Port" +"ID am Repeater" +"Server-ID bei Verbindung zu einem UltraVNC-Repeater" + SSH-Tunnel + SSH-Server + SSH-Username + Passwort + Privater Schlüssel + SSH-Passwort + Schlüssel importieren + Schlüssel überschreiben + Der private SSH-Schlüssel wurde erfolgreich importiert. + Importieren des privaten SSH-Schlüssels fehlgeschlagen. + Schlüssel-Passwort +"Als Kopie speichern" + Anmeldeinformationen erforderlich… + +"Senden" +"Tastenkombination erneut senden" +"Shift" +"VNC-Verbindungsverknüpfung" + Keine Lesezeichen vorhanden +"Tastenkombination" +"Nutzername" +"Für die Windows/Mac-Authentifizierung" +"Lesezeichen bearbeiten" +"Bitte geben Sie einen Lesezeichennamen ein…" +"Möchten Sie diesen Server als Lesezeichen speichern?" +"Möchten Sie dieses Lesezeichen wirklich löschen?" +"Möchten Sie die Verbindung wirklich trennen?" +"Über" +"Hilfe" +"Bearbeiten" + Speichern + + Fehler! + +"Bitte unterstützen Sie MultiVNC" +"Wenn Ihnen MultiVNC gefällt, können Sie die Entwicklung unterstützen, indem Sie die App bewerten oder eine Kleinigkeit spenden. Vielen Dank!" + +"Ja, gute Idee!" +"Nein, nicht jetzt." +"Nicht noch einmal nachfragen." + +"Hilfe anzeigen?" +"Es scheint, dass Sie MultiVNC zum ersten Mal verwenden. Möchten Sie das App-Handbuch lesen? Es ist noch nicht sehr umfangreich, aber trotzdem informativ…" + +"Wenn Ihnen MultiVNC gefällt, klicken Sie einfach auf das Herz." +"Was gibt\'s Neues…" + +Änderungsprotokoll (in Englisch) werfen. + ]]> + + Verbunden mit %1$s + %1$s und + + verschlüsselt + unverschlüsselt + + Bevorzugte Kodierung + OK + Abbrechen + Komprimierung + Qualitätsstufe + + Remote-SSH-Host noch nicht bekannt + Der Fingerabdruck des SSH-Servers <b>%s</b> ist noch nicht bekannt. Bitte überprüfen Sie, ob es sich um den richtigen Fingerabdruck des SSH-Servers handelt, mit dem Sie sich verbinden möchten. + Der Fingerabdruck ist gültig, weiter + Ich bin mir nicht sicher, abbrechen + + Remote-SSH-Host-Identifikation hat sich geändert! + Der Fingerabdruck <b>%s</b> des SSH-Servers stimmt nicht mit dem bereits bekannten überein. Dies bedeutet entweder, dass jemand den richtigen SSH-Server imitiert (Man-in-the-Middle-Angriff) oder dass der Hostschlüssel des richtigen Servers geändert wurde. Bitte wenden Sie sich an den Administrator des SSH-Servers, um den richtigen Schlüssel zu überprüfen. Möchten Sie den SSH-Verbindungsaufbau fortsetzen oder abbrechen? + Ich bin zuversichtlich, dass dies legitim ist, fortfahren + Ich werde den korrekten Fingerabdruck des SSH-Servers noch einmal überprüfen, abbrechen + + Benachrichtigungen deaktiviert + Um Client-Statusinformationen anzeigen zu können und einfacher zum Client zurücknavigieren zu können, wird empfohlen, Benachrichtigungen zu aktivieren. + \ No newline at end of file diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml index 6af79d57..9347b477 100644 --- a/android/app/src/main/res/values-es/strings.xml +++ b/android/app/src/main/res/values-es/strings.xml @@ -1,121 +1,121 @@ - - - Dirección - Alt - Super - MultiVNC - Servidor VNC encontrado - Servidor VNC no encontrado - Reiniciar el descubrimiento del servidor - Escalar ahora -MultiVNC para Android fue desarrollado para usted por Christian Beier (christianbeier.net).\n\nLa página principal del proyecto está en https://github.com/bk138/multivnc\n\nSi desea informar de un error o solicitar una función, diríjase a https://github.com/bk138/multivnc/issues\n\nEl registro de cambios se puede encontrar en https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -Modo de color -Establecer el modo de color -Conectar -Ctrl -Eliminar -Desconectar -Cambiar la vista -Cambiar los botones del ratón -Cambiar el resaltado del puntero -Cambiar el teclado - -Exportar -¡Error al abrir la base de datos! -Importar/Exportar Configuración -Importar - Se ha dado una URL incorrecta: - Configuración de la lectura de errores de I/O - Configuración de la lectura de errores de formato XML - -Información de la conexión -Enviar combinación de teclas -Conexión nueva -Marcadores -Servidores descubiertos -Marcadores -Guardar como marcador -¡Marcador guardado! -¡Esto ya no es un marcador válido! -Contraseña -Mantener -Puerto -ID en el repetidor -Identificación del servidor cuando se conecta a un repetidor UltraVNC - Tunelización SSH - Servidor SSH - Nombre de usuario SSH - Clave - Llave privada - Contraseña SSH - Clave de importación - Sobrescribir clave - Clave privada SSH importada con éxito. - No se pudo importar la clave privada SSH. - Contraseña clave - Guardar como copia - Se necesitan credenciales… - -Enviar -Envíar la combinación de teclas de nuevo -Cambio -Atajo de la conexión VNC - No hay marcadores todavía -Combinación de teclas -Usuario -Para la autentificación de Windows/Mac -Editar Marcadores -Por favor, introduzca un nombre de marcador... -¿Quiere añadir este servidor a los marcadores? -¿Está seguro de que quiere borrar este marcador? -¿Está seguro de que quiere desconectar? -Acerca de -Ayuda -Editar - Guardar - - Error! - -Por favor, colabore con MultiVNC -Si le gusta MultiVNC, ¿por qué no considera colaborar con su desarrollo? Por el momento, puede hacerlo fácilmente calificando la aplicación o donando. Gracias. - -Si, ¡buena idea! -¡No, ahora no! -¡No volver a preguntar! - -¿Mostrar Ayuda? -Parece que es la primera vez que usa MultiVNC. ¿Quiere leer el manual de la aplicación? No es muy largo, pero es informativo... - -Si le gusta MultiVNC, ¿por qué no mostrar algo de amor haciendo clic en el corazón? -¿Qué hay de nuevo...? - -Registro de cambios - en Línea. - ]]> - - Conectado a %1$s - %1$s y - encriptado - sin cifrar - - Codificación preferida - DE ACUERDO - Cancelar - Nivel de compresión - Nivel de calidad - - Host SSH remoto aún no conocido - La huella digital del servidor SSH <b>%s</b> aún no se conoce. Verifique que sea la huella digital correcta del servidor SSH al que desea conectarse. - La huella dactilar es válida, continuar - No estoy seguro, cancelar - - ¡La identificación del host SSH remoto ha cambiado! - La huella dactilar <b>%s</b> del servidor SSH no coincide con la ya conocida. Esto significa que alguien se está haciendo pasar por el servidor SSH correcto (ataque de intermediario) o que se cambió la clave de host del servidor correcto. Póngase en contacto con el administrador del servidor SSH para verificar la clave correcta. ¿Desea continuar o cancelar la configuración de la conexión SSH? - Estoy seguro de que esto es legítimo, continuar - Verificaré dos veces la huella digital correcta del servidor SSH, cancelaré - - Notificaciones deshabilitadas - Para poder mostrar información del estado del cliente y proporcionar un medio para volver al cliente más fácilmente, se recomienda habilitar las notificaciones. - - + + + Dirección + Alt + Super + MultiVNC + Servidor VNC encontrado + Servidor VNC no encontrado + Reiniciar el descubrimiento del servidor + Escalar ahora +MultiVNC para Android fue desarrollado para usted por Christian Beier (christianbeier.net).\n\nLa página principal del proyecto está en https://github.com/bk138/multivnc\n\nSi desea informar de un error o solicitar una función, diríjase a https://github.com/bk138/multivnc/issues\n\nEl registro de cambios se puede encontrar en https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +Modo de color +Establecer el modo de color +Conectar +Ctrl +Eliminar +Desconectar +Cambiar la vista +Cambiar los botones del ratón +Cambiar el resaltado del puntero +Cambiar el teclado + +Exportar +¡Error al abrir la base de datos! +Importar/Exportar Configuración +Importar + Se ha dado una URL incorrecta: + Configuración de la lectura de errores de I/O + Configuración de la lectura de errores de formato XML + +Información de la conexión +Enviar combinación de teclas +Conexión nueva +Marcadores +Servidores descubiertos +Marcadores +Guardar como marcador +¡Marcador guardado! +¡Esto ya no es un marcador válido! +Contraseña +Mantener +Puerto +ID en el repetidor +Identificación del servidor cuando se conecta a un repetidor UltraVNC + Tunelización SSH + Servidor SSH + Nombre de usuario SSH + Clave + Llave privada + Contraseña SSH + Clave de importación + Sobrescribir clave + Clave privada SSH importada con éxito. + No se pudo importar la clave privada SSH. + Contraseña clave + Guardar como copia + Se necesitan credenciales… + +Enviar +Envíar la combinación de teclas de nuevo +Cambio +Atajo de la conexión VNC + No hay marcadores todavía +Combinación de teclas +Usuario +Para la autentificación de Windows/Mac +Editar Marcadores +Por favor, introduzca un nombre de marcador... +¿Quiere añadir este servidor a los marcadores? +¿Está seguro de que quiere borrar este marcador? +¿Está seguro de que quiere desconectar? +Acerca de +Ayuda +Editar + Guardar + + Error! + +Por favor, colabore con MultiVNC +Si le gusta MultiVNC, ¿por qué no considera colaborar con su desarrollo? Por el momento, puede hacerlo fácilmente calificando la aplicación o donando. Gracias. + +Si, ¡buena idea! +¡No, ahora no! +¡No volver a preguntar! + +¿Mostrar Ayuda? +Parece que es la primera vez que usa MultiVNC. ¿Quiere leer el manual de la aplicación? No es muy largo, pero es informativo... + +Si le gusta MultiVNC, ¿por qué no mostrar algo de amor haciendo clic en el corazón? +¿Qué hay de nuevo...? + +Registro de cambios + en Línea. + ]]> + + Conectado a %1$s + %1$s y + encriptado + sin cifrar + + Codificación preferida + DE ACUERDO + Cancelar + Nivel de compresión + Nivel de calidad + + Host SSH remoto aún no conocido + La huella digital del servidor SSH <b>%s</b> aún no se conoce. Verifique que sea la huella digital correcta del servidor SSH al que desea conectarse. + La huella dactilar es válida, continuar + No estoy seguro, cancelar + + ¡La identificación del host SSH remoto ha cambiado! + La huella dactilar <b>%s</b> del servidor SSH no coincide con la ya conocida. Esto significa que alguien se está haciendo pasar por el servidor SSH correcto (ataque de intermediario) o que se cambió la clave de host del servidor correcto. Póngase en contacto con el administrador del servidor SSH para verificar la clave correcta. ¿Desea continuar o cancelar la configuración de la conexión SSH? + Estoy seguro de que esto es legítimo, continuar + Verificaré dos veces la huella digital correcta del servidor SSH, cancelaré + + Notificaciones deshabilitadas + Para poder mostrar información del estado del cliente y proporcionar un medio para volver al cliente más fácilmente, se recomienda habilitar las notificaciones. + + diff --git a/android/app/src/main/res/values-gl/strings.xml b/android/app/src/main/res/values-gl/strings.xml index cc56d4dc..d2347e65 100644 --- a/android/app/src/main/res/values-gl/strings.xml +++ b/android/app/src/main/res/values-gl/strings.xml @@ -1,121 +1,121 @@ - - - Dirección - Alt - Super - MultiVNC - Servidor VNC atopado - Servidor VNC non atopado - Reiniciar o descubrimento do servidor - Escalar ahora -MultiVNC para Android foi desenvolvido para vostede por Christian Beier (christianbeier.net).\n\nA páxina principal do proxecto está en https://github.com/bk138/multivnc\n\nSe desexa informar dun erro ou solicitar unha función, diríxase a https://github.com/bk138/multivnc/issues\n\nO rexistro de cambios pódese atopar en https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -Modo de cor -Establecer o modo de cor -Conectar -Ctrl -Eliminar -Desconectar -Mudar a vista -Mudar os botóns do rato -Mudar o resaltado do punteiro -Mudar o teclado - -Exportar -Erro ao abrir a base de datos! -Importar/Exportar Configuración -Importar - Deuse una URL incorrecta: - Configuración da lectura de erros de I/O - Configuración da lectura de erros de formato XML - -Información da conexión -Enviar combinación de teclas -Conexión nova -Marcadores -Servidores descubertos -Marcadores -Gardar como marcador -¡Marcador gardado! -¡Isto xa non é un marcador válido! -Contraseña -Manter -Porto -ID no repetidor -Identificación do servidor cando se conecta a un repetidor UltraVNC - Túneles SSH - Servidor SSH - Nome de usuario SSH - Contrasinal - Chave privada - Contrasinal SSH - Importar clave - Chave de sobrescritura - A clave privada SSH importouse correctamente. - Produciuse un erro ao importar a clave privada SSH. - Contrasinal clave -Gardar como copia - Necesítanse credenciais... - -Enviar -Envíar a combinación de teclas de novo -Cambio -Atallo da conexión VNC - Non hai marcadores todavía -Combinación de teclas -Usuario -Para a autentificación de Windows/Mac -Editar Marcadores -Por favor, introduza un nome de marcador... -Quere engadir este servidor aos marcadores? -Está seguro de que quere borrar este marcador? -Está seguro de que quere desconectar? -Acerca de -Axuda -Editar - Gardar - - Error! - -Por favor, colabore con MultiVNC -Si lle gusta MultiVNC, ¿por qué non considera colaborar co seu desenvolvemento? Polo momento, pode facelo facilmente cualificando a aplicación ou doando. Grazas. - -Si, boa idea! -Non, agora non! -Non volver preguntar! - -¿Mostrar axuda? -Seica é a primeira vez que usa MultiVNC. Quere ler o manual da aplicación? Non é moi longo, pero é informativo... - -Se lle gusta MultiVNC, por que non mostrar algo de amor facendo clic no corazón? -¿Qué hai de novo...? - -Rexistro de cambios - en Línea. - ]]> - - Conectado a %1$s - %1$s e - cifrado - sen cifrar - - Codificación preferida - Ok - Cancelar - Nivel de compresión - Nivel de calidade - - Aínda non se coñece o host SSH remoto - Aínda non se coñece a pegada dixital <b>%s</b> do servidor SSH. Comprobe que é a pegada dixital correcta do servidor SSH ao que quere conectarse. - A impresión dixital é válida, continúa - Non estou seguro, aborta - - A identificación do host SSH remoto cambiou! - A pegada dixital <b>%s</b> do servidor SSH non coincide coa xa coñecida. Isto significa que alguén está suplantando o servidor SSH correcto (ataque home-in-the-middle) ou que se cambiou a clave de host do servidor correcto. Póñase en contacto co administrador do servidor SSH para verificar a clave correcta. Queres continuar ou abortar a configuración da conexión SSH? - Confío en que isto é lexítimo, continúa - Comprobarei dúas veces a pegada dixital correcta do servidor SSH, abortarei - - Notificacións desactivadas - Para poder mostrar a información do estado do cliente e proporcionar un medio para volver ao cliente máis facilmente, recoméndase habilitar as notificacións. - - + + + Dirección + Alt + Super + MultiVNC + Servidor VNC atopado + Servidor VNC non atopado + Reiniciar o descubrimento do servidor + Escalar ahora +MultiVNC para Android foi desenvolvido para vostede por Christian Beier (christianbeier.net).\n\nA páxina principal do proxecto está en https://github.com/bk138/multivnc\n\nSe desexa informar dun erro ou solicitar unha función, diríxase a https://github.com/bk138/multivnc/issues\n\nO rexistro de cambios pódese atopar en https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +Modo de cor +Establecer o modo de cor +Conectar +Ctrl +Eliminar +Desconectar +Mudar a vista +Mudar os botóns do rato +Mudar o resaltado do punteiro +Mudar o teclado + +Exportar +Erro ao abrir a base de datos! +Importar/Exportar Configuración +Importar + Deuse una URL incorrecta: + Configuración da lectura de erros de I/O + Configuración da lectura de erros de formato XML + +Información da conexión +Enviar combinación de teclas +Conexión nova +Marcadores +Servidores descubertos +Marcadores +Gardar como marcador +¡Marcador gardado! +¡Isto xa non é un marcador válido! +Contraseña +Manter +Porto +ID no repetidor +Identificación do servidor cando se conecta a un repetidor UltraVNC + Túneles SSH + Servidor SSH + Nome de usuario SSH + Contrasinal + Chave privada + Contrasinal SSH + Importar clave + Chave de sobrescritura + A clave privada SSH importouse correctamente. + Produciuse un erro ao importar a clave privada SSH. + Contrasinal clave +Gardar como copia + Necesítanse credenciais... + +Enviar +Envíar a combinación de teclas de novo +Cambio +Atallo da conexión VNC + Non hai marcadores todavía +Combinación de teclas +Usuario +Para a autentificación de Windows/Mac +Editar Marcadores +Por favor, introduza un nome de marcador... +Quere engadir este servidor aos marcadores? +Está seguro de que quere borrar este marcador? +Está seguro de que quere desconectar? +Acerca de +Axuda +Editar + Gardar + + Error! + +Por favor, colabore con MultiVNC +Si lle gusta MultiVNC, ¿por qué non considera colaborar co seu desenvolvemento? Polo momento, pode facelo facilmente cualificando a aplicación ou doando. Grazas. + +Si, boa idea! +Non, agora non! +Non volver preguntar! + +¿Mostrar axuda? +Seica é a primeira vez que usa MultiVNC. Quere ler o manual da aplicación? Non é moi longo, pero é informativo... + +Se lle gusta MultiVNC, por que non mostrar algo de amor facendo clic no corazón? +¿Qué hai de novo...? + +Rexistro de cambios + en Línea. + ]]> + + Conectado a %1$s + %1$s e + cifrado + sen cifrar + + Codificación preferida + Ok + Cancelar + Nivel de compresión + Nivel de calidade + + Aínda non se coñece o host SSH remoto + Aínda non se coñece a pegada dixital <b>%s</b> do servidor SSH. Comprobe que é a pegada dixital correcta do servidor SSH ao que quere conectarse. + A impresión dixital é válida, continúa + Non estou seguro, aborta + + A identificación do host SSH remoto cambiou! + A pegada dixital <b>%s</b> do servidor SSH non coincide coa xa coñecida. Isto significa que alguén está suplantando o servidor SSH correcto (ataque home-in-the-middle) ou que se cambiou a clave de host do servidor correcto. Póñase en contacto co administrador do servidor SSH para verificar a clave correcta. Queres continuar ou abortar a configuración da conexión SSH? + Confío en que isto é lexítimo, continúa + Comprobarei dúas veces a pegada dixital correcta do servidor SSH, abortarei + + Notificacións desactivadas + Para poder mostrar a información do estado do cliente e proporcionar un medio para volver ao cliente máis facilmente, recoméndase habilitar as notificacións. + + diff --git a/android/app/src/main/res/values-it/strings.xml b/android/app/src/main/res/values-it/strings.xml index a8865ff7..1843e93d 100644 --- a/android/app/src/main/res/values-it/strings.xml +++ b/android/app/src/main/res/values-it/strings.xml @@ -1,117 +1,117 @@ - - "Indirizzo" - "Alt" - Super - "Trovato server VNC" - "VNC Server è scomparso" - "Riavvia Server Discovery" - "Scala ora" -"MultiVNC su Android è offerto da Christian Beier (christianbeier.net).\n\nLa home page del progetto è https://github.com/bk138/multivnc\n\nSe volete segnalare un bug o richiedere una funzione, andate su https://github.com/bk138/multivnc/issues\n\nIl registro delle modifiche può essere trovato su https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md" -"Modalità colore" -"Imposta la modalità colore" -"Collegare" -"Ctrl" -"Elimina" -"Disconnect" -"Attiva / disattiva vista" -"Attiva o disattiva i pulsanti del mouse" -"Attiva / disattiva l'evidenziazione del puntatore" -"Attiva / disattiva la tastiera" - -"Esportare" -"Errore durante l'apertura del database!" -Impostazioni Importa/Esporta -"Importare" - "URL improprio dato:" - "Errore di I / O durante la lettura della configurazione" - "Configurazione della lettura dell'errore XML o di formato" - -"Informazioni di connessione" -"Invia chiave combinata" -"Nuova connessione" -"Segnalibro" -"Server scoperti" -"Segnalibri" -"Salva segnalibro" -"Segnalibro salvato!" -"Questo non è più un segnalibro valido!" -"Parola d'ordine" -"Conservare" -"Port" -"ID sul ripetitore" -"ID server quando ci si collega a un ripetitore UltraVNC" - Tunneling SSH - Server SSH - Nome utente SSH - Parola d\'ordine - Chiave privata - Password SSH - Chiave di importazione - Sovrascrivi chiave - Chiave privata SSH importata correttamente. - Impossibile importare la chiave privata SSH. - Chiave d\'ordine -"Salva come copia" - Credenziali necessarie… - -"Inviare" -"Invia nuovamente Combo chiave" -"Cambio" -"Collegamento alla connessione VNC" - "Nessun segnalibro ancora" -"Chiave combinata" -"Nome utente" -"Per l'autenticazione di Windows/Mac" -"Modifica segnalibro" -"Si prega di inserire il nome di un segnalibro …" -"Vuoi aggiungere questo server ai segnalibri?" -"Sei sicuro di voler eliminare questo segnalibro?" -"Sei sicuro di voler disconnettere?" -"Di" -"Aiuto" -"Modificare" - Salvare - - "Errore!" - -"Si prega di supportare MultiVNC" -"Se ti piace il MultiVNC, perché non prendere in considerazione il supporto del suo sviluppo? Per ora, puoi farlo facilmente valutando l'app o donando. Grazie!" -"Sì buona idea!" -"No non ora." -"Non chiedere mai più!" - -"Mostra aiuto?" -"Sembra che sia la prima volta che usi MultiVNC. Vuoi leggere il manuale dell'app? Non è molto lungo ma informativo …" - -"Se ti piace il MultiVNC, perché non mostrare un po 'di amore facendo clic sul cuore?" -"Cosa c'è di nuovo…" - -registro delle modifiche disponibili (in inglese). - ]]> - - Collegato a %1$s - %1$s e - crittografato - non crittografato - - Codifica preferita - OK - Annulla - Comprimi livello - Livello di qualità - - Host SSH remoto non ancora noto - L\'impronta digitale <b>%s</b> del server SSH non è ancora nota. Verifica che sia l\'impronta digitale corretta del server SSH a cui desideri connetterti. - L\'impronta digitale è valida, continua - Non sono sicuro, abortisci - - L\'identificazione dell\'host SSH remoto è cambiata! - L\'impronta digitale <b>%s</b> del server SSH non corrisponde a quella già nota. Ciò significa che qualcuno sta impersonando il server SSH corretto (attacco man-in-the-middle) o che la chiave host del server corretto è stata modificata. Si prega di contattare l\'amministratore del server SSH per verificare la chiave corretta. Vuoi continuare o interrompere la configurazione della connessione SSH? - Sono fiducioso che sia legittimo, continua - Ricontrollerò l\'impronta digitale del server SSH corretta, abortisco - - Notifiche disabilitate - Per poter mostrare le informazioni sullo stato del client e fornire un mezzo per tornare più facilmente al client, si consiglia di abilitare le notifiche. - + + "Indirizzo" + "Alt" + Super + "Trovato server VNC" + "VNC Server è scomparso" + "Riavvia Server Discovery" + "Scala ora" +"MultiVNC su Android è offerto da Christian Beier (christianbeier.net).\n\nLa home page del progetto è https://github.com/bk138/multivnc\n\nSe volete segnalare un bug o richiedere una funzione, andate su https://github.com/bk138/multivnc/issues\n\nIl registro delle modifiche può essere trovato su https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md" +"Modalità colore" +"Imposta la modalità colore" +"Collegare" +"Ctrl" +"Elimina" +"Disconnect" +"Attiva / disattiva vista" +"Attiva o disattiva i pulsanti del mouse" +"Attiva / disattiva l'evidenziazione del puntatore" +"Attiva / disattiva la tastiera" + +"Esportare" +"Errore durante l'apertura del database!" +Impostazioni Importa/Esporta +"Importare" + "URL improprio dato:" + "Errore di I / O durante la lettura della configurazione" + "Configurazione della lettura dell'errore XML o di formato" + +"Informazioni di connessione" +"Invia chiave combinata" +"Nuova connessione" +"Segnalibro" +"Server scoperti" +"Segnalibri" +"Salva segnalibro" +"Segnalibro salvato!" +"Questo non è più un segnalibro valido!" +"Parola d'ordine" +"Conservare" +"Port" +"ID sul ripetitore" +"ID server quando ci si collega a un ripetitore UltraVNC" + Tunneling SSH + Server SSH + Nome utente SSH + Parola d\'ordine + Chiave privata + Password SSH + Chiave di importazione + Sovrascrivi chiave + Chiave privata SSH importata correttamente. + Impossibile importare la chiave privata SSH. + Chiave d\'ordine +"Salva come copia" + Credenziali necessarie… + +"Inviare" +"Invia nuovamente Combo chiave" +"Cambio" +"Collegamento alla connessione VNC" + "Nessun segnalibro ancora" +"Chiave combinata" +"Nome utente" +"Per l'autenticazione di Windows/Mac" +"Modifica segnalibro" +"Si prega di inserire il nome di un segnalibro …" +"Vuoi aggiungere questo server ai segnalibri?" +"Sei sicuro di voler eliminare questo segnalibro?" +"Sei sicuro di voler disconnettere?" +"Di" +"Aiuto" +"Modificare" + Salvare + + "Errore!" + +"Si prega di supportare MultiVNC" +"Se ti piace il MultiVNC, perché non prendere in considerazione il supporto del suo sviluppo? Per ora, puoi farlo facilmente valutando l'app o donando. Grazie!" +"Sì buona idea!" +"No non ora." +"Non chiedere mai più!" + +"Mostra aiuto?" +"Sembra che sia la prima volta che usi MultiVNC. Vuoi leggere il manuale dell'app? Non è molto lungo ma informativo …" + +"Se ti piace il MultiVNC, perché non mostrare un po 'di amore facendo clic sul cuore?" +"Cosa c'è di nuovo…" + +registro delle modifiche disponibili (in inglese). + ]]> + + Collegato a %1$s + %1$s e + crittografato + non crittografato + + Codifica preferita + OK + Annulla + Comprimi livello + Livello di qualità + + Host SSH remoto non ancora noto + L\'impronta digitale <b>%s</b> del server SSH non è ancora nota. Verifica che sia l\'impronta digitale corretta del server SSH a cui desideri connetterti. + L\'impronta digitale è valida, continua + Non sono sicuro, abortisci + + L\'identificazione dell\'host SSH remoto è cambiata! + L\'impronta digitale <b>%s</b> del server SSH non corrisponde a quella già nota. Ciò significa che qualcuno sta impersonando il server SSH corretto (attacco man-in-the-middle) o che la chiave host del server corretto è stata modificata. Si prega di contattare l\'amministratore del server SSH per verificare la chiave corretta. Vuoi continuare o interrompere la configurazione della connessione SSH? + Sono fiducioso che sia legittimo, continua + Ricontrollerò l\'impronta digitale del server SSH corretta, abortisco + + Notifiche disabilitate + Per poter mostrare le informazioni sullo stato del client e fornire un mezzo per tornare più facilmente al client, si consiglia di abilitare le notifiche. + \ No newline at end of file diff --git a/android/app/src/main/res/values-ja/strings.xml b/android/app/src/main/res/values-ja/strings.xml index 230e6687..39548f20 100644 --- a/android/app/src/main/res/values-ja/strings.xml +++ b/android/app/src/main/res/values-ja/strings.xml @@ -1,116 +1,116 @@ - - - -アドレス - Alt - Super - MultiVNC - VNCサーバーが見つかりました - VNCサーバーが消えた - サーバー検出を再起動します - 今すぐスケール -Android上のMultiVNCは、Christian Beier(christianbeier.net)によって提供されています。\n\nプロジェクトのホームページはhttps://github.com/bk138/multivnc \n\nバグを報告したり、機能をリクエストしたりする場合、https://github.com/bk138/multivnc/issuesにアクセスします\n\n変更ログはhttps://github.com/bk138/multivnc/blob/master/android/ChangeLog.mdで確認できます。 -カラーモード -カラーモードを設定する -接続する -Ctrl -削除 -切断する -ビューの切り替え -マウスボタンの切り替え -トグルポインターの強調表示 -キーボードの切り替え - -書き出す -データベースを開くときにエラーが発生しました! -設定インポート/エクスポート -インポート - 与えられた不適切なURL: - I /Oエラー読み取り構成 - XMLまたはフォーマットエラー読み取り構成 - -接続情報 -キーコンボを送信 -新しい接続 -ブックマーク -検出されたサーバー -ブックマーク -ブックマークを保存 -ブックマークを保存しました! -これはもう有効なブックマークではありません! -パスワード -保つ -ポート -リピーターのID -UltraVNCリピーターに接続するときのサーバーID - SSH トンネリング - SSHサーバー - SSH ユーザー名 - パスワード - 秘密鍵 - SSH パスワード - キーのインポート - キーの上書き - SSH 秘密鍵が正常にインポートされました。 - SSH 秘密鍵のインポートに失敗しました。 - キーパスワード -コピーとして保存 - 必要な資格情報… - -送信 -キーコンボを再度送信する -シフト -VNC接続のショートカット - ブックマークはまだありません -キーコンボ -ユーザー名 -Windows /Mac認証の場合 -ブックマークの編集 -ブックマーク名を入力してください… -このサーバーをブックマークしますか? -このブックマークを削除してもよろしいですか? -切断してもよろしいですか? -MultiVNCについて -ヘルプ -編集 - 保存 - - エラー! - -MultiVNCをサポートしてください -MultiVNCが好きなら、その開発をサポートすることを検討してみませんか?当面は、アプリを評価するか寄付することで簡単に行うことができます。ありがとう! - -はい、良い考えですね! -いいえ、今は違います。 -二度と聞かないで! - -ヘルプを表示しますか? -MultiVNCを使用するのは初めてのようです。アプリのマニュアルを読みたいですか?それほど長くはありませんが、有益です… - -MultiVNCがお好きなら、ハートをクリックして愛を示してみませんか? -新着情報… - オンライン 変更ログ 。 ]]> - - - %1$sに接続 - %1$sと - 暗号化された - 暗号化されていない - 優先するエンコーディング - OK - キャンセル - 圧縮レベル - 画質レベル - リモート SSH ホストがまだ不明 - SSH サーバーのフィンガープリント <b>%s</b> はまだ不明です。接続先の SSH サーバーの正しいフィンガープリントであることを確認してください。 - 指紋は有効です。続行します - よくわかりません、中止します - リモート SSH ホスト ID が変更されました! - SSH サーバーのフィンガープリント <b>%s</b> は既知のものと一致しません。これは、誰かが正しい SSH サーバーになりすましている (中間者攻撃) か、正しいサーバーのホスト キーが変更されたことを意味します。 SSH サーバーの管理者に連絡して、正しいキーを確認してください。 SSH 接続のセットアップを続行しますか、それとも中止しますか? - これは合法であると確信しています。続行してください - 正しい SSH サーバーのフィンガープリントを再確認し、中止します - - 通知が無効になっています - クライアントのステータス情報を表示し、より簡単にクライアントに戻る手段を提供できるようにするには、通知を有効にすることをお勧めします。 - - + + + +アドレス + Alt + Super + MultiVNC + VNCサーバーが見つかりました + VNCサーバーが消えた + サーバー検出を再起動します + 今すぐスケール +Android上のMultiVNCは、Christian Beier(christianbeier.net)によって提供されています。\n\nプロジェクトのホームページはhttps://github.com/bk138/multivnc \n\nバグを報告したり、機能をリクエストしたりする場合、https://github.com/bk138/multivnc/issuesにアクセスします\n\n変更ログはhttps://github.com/bk138/multivnc/blob/master/android/ChangeLog.mdで確認できます。 +カラーモード +カラーモードを設定する +接続する +Ctrl +削除 +切断する +ビューの切り替え +マウスボタンの切り替え +トグルポインターの強調表示 +キーボードの切り替え + +書き出す +データベースを開くときにエラーが発生しました! +設定インポート/エクスポート +インポート + 与えられた不適切なURL: + I /Oエラー読み取り構成 + XMLまたはフォーマットエラー読み取り構成 + +接続情報 +キーコンボを送信 +新しい接続 +ブックマーク +検出されたサーバー +ブックマーク +ブックマークを保存 +ブックマークを保存しました! +これはもう有効なブックマークではありません! +パスワード +保つ +ポート +リピーターのID +UltraVNCリピーターに接続するときのサーバーID + SSH トンネリング + SSHサーバー + SSH ユーザー名 + パスワード + 秘密鍵 + SSH パスワード + キーのインポート + キーの上書き + SSH 秘密鍵が正常にインポートされました。 + SSH 秘密鍵のインポートに失敗しました。 + キーパスワード +コピーとして保存 + 必要な資格情報… + +送信 +キーコンボを再度送信する +シフト +VNC接続のショートカット + ブックマークはまだありません +キーコンボ +ユーザー名 +Windows /Mac認証の場合 +ブックマークの編集 +ブックマーク名を入力してください… +このサーバーをブックマークしますか? +このブックマークを削除してもよろしいですか? +切断してもよろしいですか? +MultiVNCについて +ヘルプ +編集 + 保存 + + エラー! + +MultiVNCをサポートしてください +MultiVNCが好きなら、その開発をサポートすることを検討してみませんか?当面は、アプリを評価するか寄付することで簡単に行うことができます。ありがとう! + +はい、良い考えですね! +いいえ、今は違います。 +二度と聞かないで! + +ヘルプを表示しますか? +MultiVNCを使用するのは初めてのようです。アプリのマニュアルを読みたいですか?それほど長くはありませんが、有益です… + +MultiVNCがお好きなら、ハートをクリックして愛を示してみませんか? +新着情報… + オンライン 変更ログ 。 ]]> + + + %1$sに接続 + %1$sと + 暗号化された + 暗号化されていない + 優先するエンコーディング + OK + キャンセル + 圧縮レベル + 画質レベル + リモート SSH ホストがまだ不明 + SSH サーバーのフィンガープリント <b>%s</b> はまだ不明です。接続先の SSH サーバーの正しいフィンガープリントであることを確認してください。 + 指紋は有効です。続行します + よくわかりません、中止します + リモート SSH ホスト ID が変更されました! + SSH サーバーのフィンガープリント <b>%s</b> は既知のものと一致しません。これは、誰かが正しい SSH サーバーになりすましている (中間者攻撃) か、正しいサーバーのホスト キーが変更されたことを意味します。 SSH サーバーの管理者に連絡して、正しいキーを確認してください。 SSH 接続のセットアップを続行しますか、それとも中止しますか? + これは合法であると確信しています。続行してください + 正しい SSH サーバーのフィンガープリントを再確認し、中止します + + 通知が無効になっています + クライアントのステータス情報を表示し、より簡単にクライアントに戻る手段を提供できるようにするには、通知を有効にすることをお勧めします。 + + diff --git a/android/app/src/main/res/values-pt/strings.xml b/android/app/src/main/res/values-pt/strings.xml index 01f7ac2f..10094666 100644 --- a/android/app/src/main/res/values-pt/strings.xml +++ b/android/app/src/main/res/values-pt/strings.xml @@ -1,118 +1,118 @@ - - - Endereço - Alt - Super - MultiVNC - Servidor VNC encontrado - Servidor VNC não encontrado - Reiniciar a descoberta do servidor - Escalar agora -MultiVNC no Android foi desenvolvido para você por Christian Beier (christianbeier.net).\n\nO site do projeto está no https://github.com/bk138/multivnc\n\nSe você quiser relatar um problema ou solicitar uma funcionalidade, visite https://github.com/bk138/multivnc/issues\n\nO registro de mudanças pode ser encontrado no https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -Modo de cor -Definir modo de cor -Conectar -Ctrl -Excluir -Desconectar -Alternar a vista -Alternar botões do mouse -Alternar destaque do ponteiro -Alternar teclado - -Exportar -Erro ao abrir base de dados! -Configurações de Importar/Exportar -Importar - O endereço URL inválido: - Configuração de leitura de I/O erros - Configuração de XML ou leitura de erros de formato - -Informações sobre a conexão -Enviar combinação de teclas -Nova conexão -Favoritos -Servidores encontrados -Favoritos -Salvar como favorito -Favorito salvo! -Não é mais um favorito válido! -Senha -Guardar -Porta -ID no repetidor -ID do servidor ao conectar-se a um repetidor-UltraVNC - Túnel SSH - Servidor SSH - Nome de usuário SSH - Senha - Chave Privada - Senha SSH - Importar chave - Sobrescrever chave - Chave privada SSH importada com sucesso. - Falha ao importar a chave privada SSH. - Senha da chave -Salvar como cópia - Credenciais necessárias… - -Enviar -Enviar combinação de teclas de novo -Shift -Atalho de conexão do VNC - Ainda sem favoritos -Combinação de teclas -Nome de usuário -Para autenticação no Windows/Mac -Editar favoritos -Por favor, digite um nome de favorito… -Você quer salvar este servidor? -Tem certeza que quer excluir este favorito? -Realmente quer desconectar? -Sobre -Ajuda -Editar - Salvar - - Erro! - -Por favor, considere apoiar o MultiVNC -Se você gosta do MultiVNC, por que não considere apoiar seu desenvolvimento? Por enquanto, você pode fazer isso escrevendo uma avaliação do app ou doando. Obrigado! - -Sim, boa idéia! -Não, agora não.. -Não pergunte novamente! - -Mostrar ajuda? -Parece que é a primeira vez que você usa o MultiVNC. Você quer ler o manual do app? Não é muito longo, porém bem esclarecedor... - -Se você gosta do MultiVNC, porque não mostrar um pouco de amor, clicando no coração? -Novidades… - -online - Histórico de modificações. - ]]> - - Conectado ao %1$s - %1$s e - criptografado - não criptografado - Codificação preferida - OK - Cancelar - Nível de compactação - Nível de qualidade - Host SSH remoto ainda não conhecido - A impressão digital do servidor SSH <b>%s</b> ainda não é conhecida. Verifique se é a impressão digital correta do servidor SSH ao qual você deseja se conectar. - A impressão digital é válida, continue - Não tenho certeza, abortar - A identificação do host SSH remoto mudou! - A impressão digital <b>%s</b> do servidor SSH não corresponde à já conhecida. Isso significa que alguém está representando o servidor SSH correto (ataque man-in-the-middle) ou que a chave de host do servidor correto foi alterada. Entre em contato com o administrador do servidor SSH para verificar a chave correta. Deseja continuar ou abortar a configuração da conexão SSH? - Tenho certeza de que isso é legítimo, continue - Vou verificar novamente a impressão digital correta do servidor SSH, abortar - - Notificações desativadas - Para poder mostrar informações de status do cliente e fornecer um meio de navegar de volta ao cliente com mais facilidade, é recomendável ativar as notificações. - - + + + Endereço + Alt + Super + MultiVNC + Servidor VNC encontrado + Servidor VNC não encontrado + Reiniciar a descoberta do servidor + Escalar agora +MultiVNC no Android foi desenvolvido para você por Christian Beier (christianbeier.net).\n\nO site do projeto está no https://github.com/bk138/multivnc\n\nSe você quiser relatar um problema ou solicitar uma funcionalidade, visite https://github.com/bk138/multivnc/issues\n\nO registro de mudanças pode ser encontrado no https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +Modo de cor +Definir modo de cor +Conectar +Ctrl +Excluir +Desconectar +Alternar a vista +Alternar botões do mouse +Alternar destaque do ponteiro +Alternar teclado + +Exportar +Erro ao abrir base de dados! +Configurações de Importar/Exportar +Importar + O endereço URL inválido: + Configuração de leitura de I/O erros + Configuração de XML ou leitura de erros de formato + +Informações sobre a conexão +Enviar combinação de teclas +Nova conexão +Favoritos +Servidores encontrados +Favoritos +Salvar como favorito +Favorito salvo! +Não é mais um favorito válido! +Senha +Guardar +Porta +ID no repetidor +ID do servidor ao conectar-se a um repetidor-UltraVNC + Túnel SSH + Servidor SSH + Nome de usuário SSH + Senha + Chave Privada + Senha SSH + Importar chave + Sobrescrever chave + Chave privada SSH importada com sucesso. + Falha ao importar a chave privada SSH. + Senha da chave +Salvar como cópia + Credenciais necessárias… + +Enviar +Enviar combinação de teclas de novo +Shift +Atalho de conexão do VNC + Ainda sem favoritos +Combinação de teclas +Nome de usuário +Para autenticação no Windows/Mac +Editar favoritos +Por favor, digite um nome de favorito… +Você quer salvar este servidor? +Tem certeza que quer excluir este favorito? +Realmente quer desconectar? +Sobre +Ajuda +Editar + Salvar + + Erro! + +Por favor, considere apoiar o MultiVNC +Se você gosta do MultiVNC, por que não considere apoiar seu desenvolvimento? Por enquanto, você pode fazer isso escrevendo uma avaliação do app ou doando. Obrigado! + +Sim, boa idéia! +Não, agora não.. +Não pergunte novamente! + +Mostrar ajuda? +Parece que é a primeira vez que você usa o MultiVNC. Você quer ler o manual do app? Não é muito longo, porém bem esclarecedor... + +Se você gosta do MultiVNC, porque não mostrar um pouco de amor, clicando no coração? +Novidades… + +online + Histórico de modificações. + ]]> + + Conectado ao %1$s + %1$s e + criptografado + não criptografado + Codificação preferida + OK + Cancelar + Nível de compactação + Nível de qualidade + Host SSH remoto ainda não conhecido + A impressão digital do servidor SSH <b>%s</b> ainda não é conhecida. Verifique se é a impressão digital correta do servidor SSH ao qual você deseja se conectar. + A impressão digital é válida, continue + Não tenho certeza, abortar + A identificação do host SSH remoto mudou! + A impressão digital <b>%s</b> do servidor SSH não corresponde à já conhecida. Isso significa que alguém está representando o servidor SSH correto (ataque man-in-the-middle) ou que a chave de host do servidor correto foi alterada. Entre em contato com o administrador do servidor SSH para verificar a chave correta. Deseja continuar ou abortar a configuração da conexão SSH? + Tenho certeza de que isso é legítimo, continue + Vou verificar novamente a impressão digital correta do servidor SSH, abortar + + Notificações desativadas + Para poder mostrar informações de status do cliente e fornecer um meio de navegar de volta ao cliente com mais facilidade, é recomendável ativar as notificações. + + diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml index 3b270683..84d79383 100644 --- a/android/app/src/main/res/values-ru/strings.xml +++ b/android/app/src/main/res/values-ru/strings.xml @@ -1,117 +1,117 @@ - - "Адрес" - "Alt" - Super - "Найден VNC-сервер" - "VNC сервер недоступен" - "Перезапустите Обнаружение Сервера" - "Масштаб сейчас" -"MultiVNC на Android разрабатывается Кристианом Бейером (christianbeier.net).\n\nСам проект доступен по адресу https://github.com/bk138/multivnc\n\nЕсли вы хотите сообщить об ошибке или запросить функцию, перейдите по адресу https://github.com/bk138/multivnc/issues.\n\nЖурнал изменений доступен по адресу https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md" -"Цветовой режим" -"Установить цветовой режим" -"Подключиться" -"Ctrl" -"Удалить" -"Отключить" -"Переключить вид" -"Переключить кнопки мыши" -"Подсветка указателя мыши" -"Переключить клавиатуру" - -"экспорт" -"Ошибка открытия базы данных!" -"Настройки Импорт / Экспорт" -"Импорт" - "Указан неправильный URL:" - "Конфигурация чтения ошибок ввода / вывода" - "XML или формат ошибки чтения конфигурации" - -"Информация о соединении" -"Отправить ключ комбо" -"Новое соединение" -"Закладка" -"Известные сервера" -"Закладки" -"Сохранить закладку" -"Закладка сохранена!" -"Это закладка больше не действительна!" -"Пароль" -"Запомнить" -"Порт" -"ID на ретрансляторе" -"ID сервера при подключении к UltraVNC-ретранслятору" - Тунелювання SSH - Сервер SSH - Ім\'я користувача SSH - Пароль - Приватний ключ - Пароль SSH - Ключ імпорту - Перезаписати ключ - Приватний ключ SSH успішно імпортовано. - Не вдалося імпортувати закритий ключ SSH. - Пароль ключа -"Сохранить как копию" - необходимы учетные данные… - -"Отправить" -"Отправить комбинацию клавиш еще раз" -"Shift" -"Ярлык соединения VNC" - "Еще нет закладок" -"ключевая комбинация" -"Имя пользователя" -"Для проверки подлинности Windows/Mac" -"Изменить закладку" -"Пожалуйста, введите название закладки…" -"Вы хотите добавить этот сервер в закладки?" -"Вы уверены, что хотите удалить эту закладку?" -"Вы уверены, что хотите отключиться?" -"О MultiVNC" -"Помощь" -"Редактировать" - Сохранить - - "Ошибка!" - -"Поддержите MultiVNC" -"Вы можете выразить брагодарность проекту MultiVNC, поддержав его разработку! Это просто, оцените приложение в Google Play или пожертвуйте на https://github.com/bk138/multivnc. Согласны?" -"Да, хорошая идея!" -"Нет, не сейчас." -"Больше не спрашивать." - -"Уже знакомы с работой MultiVNC?" -"Наши сенсоры засекли что Вы запустили MultiVNC в первый раз. Хотите прочитать краткое руководство к приложению?" - -"Вам понравился MultiVNC? Несомненно, вы можете поделиться этим щелкнув по сердцу." -"Что нового…" - -по ссылке онлайн. - ]]> - - Подключено к %1$s - %1$s и - зашифрований - незашифрований - - Бажане кодування - гаразд - Скасувати - Рівень стиснення - Рівень якості - - Віддалений хост SSH ще не відомий - Відбиток пальця сервера SSH <b>%s</b> ще не відомий. Перевірте, чи є правильний відбиток SSH-сервера, до якого ви хочете підключитися. - Відбиток пальця дійсний, продовжуйте - Я не впевнений, переривайте - - Ідентифікація віддаленого хоста SSH змінена! - Відбиток <b>%s</b> SSH-сервера не збігається з уже відомим. Це означає, що хтось видає себе за правильний SSH-сервер (атака «людина посередині») або що правильний ключ хоста сервера було змінено. Зверніться до адміністратора SSH-сервера, щоб перевірити правильний ключ. Бажаєте продовжити чи припинити налаштування з’єднання SSH? - Я впевнений, що це законно, продовжуйте - Я ще раз перевірю правильний відбиток сервера SSH, перерву - - Уведомления отключены - Чтобы иметь возможность отображать информацию о состоянии клиента и обеспечивать возможность более удобного возврата к клиенту, рекомендуется включить уведомления. - - + + "Адрес" + "Alt" + Super + "Найден VNC-сервер" + "VNC сервер недоступен" + "Перезапустите Обнаружение Сервера" + "Масштаб сейчас" +"MultiVNC на Android разрабатывается Кристианом Бейером (christianbeier.net).\n\nСам проект доступен по адресу https://github.com/bk138/multivnc\n\nЕсли вы хотите сообщить об ошибке или запросить функцию, перейдите по адресу https://github.com/bk138/multivnc/issues.\n\nЖурнал изменений доступен по адресу https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md" +"Цветовой режим" +"Установить цветовой режим" +"Подключиться" +"Ctrl" +"Удалить" +"Отключить" +"Переключить вид" +"Переключить кнопки мыши" +"Подсветка указателя мыши" +"Переключить клавиатуру" + +"экспорт" +"Ошибка открытия базы данных!" +"Настройки Импорт / Экспорт" +"Импорт" + "Указан неправильный URL:" + "Конфигурация чтения ошибок ввода / вывода" + "XML или формат ошибки чтения конфигурации" + +"Информация о соединении" +"Отправить ключ комбо" +"Новое соединение" +"Закладка" +"Известные сервера" +"Закладки" +"Сохранить закладку" +"Закладка сохранена!" +"Это закладка больше не действительна!" +"Пароль" +"Запомнить" +"Порт" +"ID на ретрансляторе" +"ID сервера при подключении к UltraVNC-ретранслятору" + Тунелювання SSH + Сервер SSH + Ім\'я користувача SSH + Пароль + Приватний ключ + Пароль SSH + Ключ імпорту + Перезаписати ключ + Приватний ключ SSH успішно імпортовано. + Не вдалося імпортувати закритий ключ SSH. + Пароль ключа +"Сохранить как копию" + необходимы учетные данные… + +"Отправить" +"Отправить комбинацию клавиш еще раз" +"Shift" +"Ярлык соединения VNC" + "Еще нет закладок" +"ключевая комбинация" +"Имя пользователя" +"Для проверки подлинности Windows/Mac" +"Изменить закладку" +"Пожалуйста, введите название закладки…" +"Вы хотите добавить этот сервер в закладки?" +"Вы уверены, что хотите удалить эту закладку?" +"Вы уверены, что хотите отключиться?" +"О MultiVNC" +"Помощь" +"Редактировать" + Сохранить + + "Ошибка!" + +"Поддержите MultiVNC" +"Вы можете выразить брагодарность проекту MultiVNC, поддержав его разработку! Это просто, оцените приложение в Google Play или пожертвуйте на https://github.com/bk138/multivnc. Согласны?" +"Да, хорошая идея!" +"Нет, не сейчас." +"Больше не спрашивать." + +"Уже знакомы с работой MultiVNC?" +"Наши сенсоры засекли что Вы запустили MultiVNC в первый раз. Хотите прочитать краткое руководство к приложению?" + +"Вам понравился MultiVNC? Несомненно, вы можете поделиться этим щелкнув по сердцу." +"Что нового…" + +по ссылке онлайн. + ]]> + + Подключено к %1$s + %1$s и + зашифрований + незашифрований + + Бажане кодування + гаразд + Скасувати + Рівень стиснення + Рівень якості + + Віддалений хост SSH ще не відомий + Відбиток пальця сервера SSH <b>%s</b> ще не відомий. Перевірте, чи є правильний відбиток SSH-сервера, до якого ви хочете підключитися. + Відбиток пальця дійсний, продовжуйте + Я не впевнений, переривайте + + Ідентифікація віддаленого хоста SSH змінена! + Відбиток <b>%s</b> SSH-сервера не збігається з уже відомим. Це означає, що хтось видає себе за правильний SSH-сервер (атака «людина посередині») або що правильний ключ хоста сервера було змінено. Зверніться до адміністратора SSH-сервера, щоб перевірити правильний ключ. Бажаєте продовжити чи припинити налаштування з’єднання SSH? + Я впевнений, що це законно, продовжуйте + Я ще раз перевірю правильний відбиток сервера SSH, перерву + + Уведомления отключены + Чтобы иметь возможность отображать информацию о состоянии клиента и обеспечивать возможность более удобного возврата к клиенту, рекомендуется включить уведомления. + + diff --git a/android/app/src/main/res/values-uk/strings.xml b/android/app/src/main/res/values-uk/strings.xml index 2008de08..623f11ab 100644 --- a/android/app/src/main/res/values-uk/strings.xml +++ b/android/app/src/main/res/values-uk/strings.xml @@ -1,119 +1,119 @@ - - - Адреса - Alt - Super - Знайдено VNC сервер - VNC сервер зник - Перезапустити виявлення сервера - Масштаб зараз -MultiVNC на Android розробляється Крістіаном Бейером (christianbeier.net).\n\nДомашня сторінка проекту знаходиться за адресою https://github.com/bk138/multivnc\n\nЯкщо Ви хочете повідомити про помилку або запитати нову функцію, перейдіть за посиланням https://github.com/bk138/multivnc/issues\n\nЖурнал змін можна знайти за адресою https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -Режим кольору -Встановити режим кольору -Підключитися -Ctrl -Видалити -Відключитися -Переключити перегляд -Переключити кнопки миші -Переключити підсвічування вказівника -Переключити клавіатуру - -Експортувати -Помилка відкриття бази даних! -Налаштування Імпорт/Експорт -Імпортувати - Вказано неправильну URL-адресу: - Помилка I/O під час читання кофігурації - Формат XML імпортованої кофігурації недійсний - -Інформація про з\'єднання -Надіслати комбінацію клавіш -Нове з\'єднання -Закладка -Виявлені сервери -Закладки -Зберегти закладку -Закладку збережено! -Це вже недійсна закладка! -Пароль -Запам\'ятати -Порт -ID на ретрансляторі -ID сервера при підключенні до ретранслятора UltraVNC - Тунелювання SSH - Сервер SSH - Ім\'я користувача SSH - Пароль - Приватний ключ - Пароль SSH - Імпортувати ключ - Перезаписати ключ - Приватний ключ SSH успішно імпортовано. - Не вдалося імпортувати приватний ключ SSH. - Пароль ключа -Зберегти як копію - Потрібні облікові дані… - -Надіслати -Надіслати комбінацію клавіш знову -Shift -Ярлик підключення VNC - Закладок ще немає -Комбінація клавіш -Ім\'я користувача -Для автентифікації Windows/Mac -Редагувати закладку -Будь ласка, введіть назву закладки… -Ви хочете зробити закладку для цього сервера? -Ви впевнені, що хочете видалити цю закладку? -Ви впевнені, що хочете відключитися? -Про -Довідка -Редагувати - Зберегти - - Помилка! - -Будь ласка, підтримайте MultiVNC -Якщо Вам подобається MultiVNC, чому б не розглянути можливість підтримки його розробки? Наразі це можна зробити легко, оцінивши додаток або пожертвувавши. Дякую! - -Так, гарна ідея! -Ні, не зараз. -Ніколи більше не питати! - -Показати довідку? -Здається, Ви впереше використовуєте MultiVNC. Хочете прочитати посібник із програми? Це не дуже довго, але інформативно… - -Якщо Вам подобається MultiVNC, чому б не проявити любов, натиснувши на серце? -Що нового… - -журнал змін онлайн. - ]]> - - Під’єднано до %1$s - %1$s і - зашифровано - незашифровано - - Бажане кодування - OK - Скасувати - Рівень стиснення - Рівень якості - - Віддалений хост SSH ще не відомий - Відбиток пальця сервера SSH <b>%s</b> ще не відомий. Перевірте, чи є правильний відбиток SSH-сервера, до якого ви хочете підключитися. - Відбиток дійсний, продовжити - Я не впевнений, припинити - - Ідентифікація віддаленого SSH хоста змінена! - Відбиток <b>%s</b> SSH-сервера не збігається з уже відомим. Це означає, що хтось видає себе за правильний SSH-сервер (атака «людина посередині») або що правильний ключ хоста сервера було змінено. Зверніться до адміністратора SSH-сервера, щоб перевірити правильний ключ. Бажаєте продовжити чи припинити налаштування з’єднання SSH? - Я впевнений, що все вірно, продовжити - Я ще раз перевірю правильний відбиток SSH-сервера, припинити - - Сповіщення вимкнено - Щоб мати можливість відображати інформацію про стан клієнта та надавати засоби для простішого переходу до клієнта, рекомендується ввімкнути сповіщення. - - + + + Адреса + Alt + Super + Знайдено VNC сервер + VNC сервер зник + Перезапустити виявлення сервера + Масштаб зараз +MultiVNC на Android розробляється Крістіаном Бейером (christianbeier.net).\n\nДомашня сторінка проекту знаходиться за адресою https://github.com/bk138/multivnc\n\nЯкщо Ви хочете повідомити про помилку або запитати нову функцію, перейдіть за посиланням https://github.com/bk138/multivnc/issues\n\nЖурнал змін можна знайти за адресою https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +Режим кольору +Встановити режим кольору +Підключитися +Ctrl +Видалити +Відключитися +Переключити перегляд +Переключити кнопки миші +Переключити підсвічування вказівника +Переключити клавіатуру + +Експортувати +Помилка відкриття бази даних! +Налаштування Імпорт/Експорт +Імпортувати + Вказано неправильну URL-адресу: + Помилка I/O під час читання кофігурації + Формат XML імпортованої кофігурації недійсний + +Інформація про з\'єднання +Надіслати комбінацію клавіш +Нове з\'єднання +Закладка +Виявлені сервери +Закладки +Зберегти закладку +Закладку збережено! +Це вже недійсна закладка! +Пароль +Запам\'ятати +Порт +ID на ретрансляторі +ID сервера при підключенні до ретранслятора UltraVNC + Тунелювання SSH + Сервер SSH + Ім\'я користувача SSH + Пароль + Приватний ключ + Пароль SSH + Імпортувати ключ + Перезаписати ключ + Приватний ключ SSH успішно імпортовано. + Не вдалося імпортувати приватний ключ SSH. + Пароль ключа +Зберегти як копію + Потрібні облікові дані… + +Надіслати +Надіслати комбінацію клавіш знову +Shift +Ярлик підключення VNC + Закладок ще немає +Комбінація клавіш +Ім\'я користувача +Для автентифікації Windows/Mac +Редагувати закладку +Будь ласка, введіть назву закладки… +Ви хочете зробити закладку для цього сервера? +Ви впевнені, що хочете видалити цю закладку? +Ви впевнені, що хочете відключитися? +Про +Довідка +Редагувати + Зберегти + + Помилка! + +Будь ласка, підтримайте MultiVNC +Якщо Вам подобається MultiVNC, чому б не розглянути можливість підтримки його розробки? Наразі це можна зробити легко, оцінивши додаток або пожертвувавши. Дякую! + +Так, гарна ідея! +Ні, не зараз. +Ніколи більше не питати! + +Показати довідку? +Здається, Ви впереше використовуєте MultiVNC. Хочете прочитати посібник із програми? Це не дуже довго, але інформативно… + +Якщо Вам подобається MultiVNC, чому б не проявити любов, натиснувши на серце? +Що нового… + +журнал змін онлайн. + ]]> + + Під’єднано до %1$s + %1$s і + зашифровано + незашифровано + + Бажане кодування + OK + Скасувати + Рівень стиснення + Рівень якості + + Віддалений хост SSH ще не відомий + Відбиток пальця сервера SSH <b>%s</b> ще не відомий. Перевірте, чи є правильний відбиток SSH-сервера, до якого ви хочете підключитися. + Відбиток дійсний, продовжити + Я не впевнений, припинити + + Ідентифікація віддаленого SSH хоста змінена! + Відбиток <b>%s</b> SSH-сервера не збігається з уже відомим. Це означає, що хтось видає себе за правильний SSH-сервер (атака «людина посередині») або що правильний ключ хоста сервера було змінено. Зверніться до адміністратора SSH-сервера, щоб перевірити правильний ключ. Бажаєте продовжити чи припинити налаштування з’єднання SSH? + Я впевнений, що все вірно, продовжити + Я ще раз перевірю правильний відбиток SSH-сервера, припинити + + Сповіщення вимкнено + Щоб мати можливість відображати інформацію про стан клієнта та надавати засоби для простішого переходу до клієнта, рекомендується ввімкнути сповіщення. + + diff --git a/android/app/src/main/res/values-zh-rTW/strings.xml b/android/app/src/main/res/values-zh-rTW/strings.xml index 1f0919ac..b0f11938 100644 --- a/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/android/app/src/main/res/values-zh-rTW/strings.xml @@ -1,121 +1,121 @@ - - - 位址 - Alt - Super - MultiVNC - 找到 VNC 伺服器 - VNC 伺服器已消失 - 重新啟動伺服器探索 - 立即縮放 -Android 版 MultiVNC 由 Christian Beier (christianbeier.net) 為您呈獻。\n\n專案首頁位於 https://github.com/bk138/multivnc\n\n如欲報告錯誤或請求新功能,請前往 https://github.com/bk138/multivnc/issues\n\n變更紀錄可參見 https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -色彩模式 -設定色彩模式 -連線 -Ctrl -刪除 -中斷連線 -切換檢視 -切換滑鼠按鈕 -切換滑鼠指標醒目提示 -切換鍵盤 - -匯出 -開啟資料庫時發生錯誤! -設定匯入/匯出 -匯入 - 提供的 URL 不當: - 讀取組態設定時發生 I/O 錯誤 - 讀取組態設定時發生 XML 或格式錯誤 - -連線資訊 -傳送按鍵組合 -新增連線 -加入書籤 -探索到的伺服器 -書籤 -儲存書籤 -書籤已儲存! -這不再是有效的書籤! -密碼 -保存 -連接埠 -中繼器上的 ID -連線到 UltraVNC 中繼器時使用的伺服器 ID - SSH 隧道 - SSH 服务器 - SSH 用户名 - 密码 - 私钥 - SSH密码 - 导入密钥 - 覆盖密钥 - 成功导入 SSH 私钥。 - 导入 SSH 私钥失败。 - 密钥密码 -另存副本 - 需要認證… - -傳送 -再次傳送按鍵組合 -Shift -VNC 連線捷徑 - 未有任何書籤 -按鍵組合 -使用者名稱 -用於 Windows/Mac 驗證 -編輯書籤 -請輸入書籤名稱… -您要將此伺服器加入書籤嗎? -您確定要刪除此書籤嗎? -您確定要中斷連線嗎? -關於 -說明 -編輯 - 儲存 - - 錯誤! - -請支持 MultiVNC -若您喜歡 MultiVNC,何不考慮支持開發?坐言起行很簡單,您可為此應用程式評個分或捐個款。謝謝! - -好,馬上去! -稍後再說。 -不要再詢問! - -要顯示說明嗎? -您似乎是第一次使用 MultiVNC。您要閱讀應用程式使用指南嗎?篇幅尚不太長,卻算是應有盡有... - -若您喜歡 MultiVNC,何不點按一個心、派送一點愛? -版本更新… - -線上 - 變更紀錄。 - ]]> - - 已連線到 %1$s - %1$s 和 - 加密的 - 未加密 - - 首选编码 - 好的 - 取消 - 压缩级别 - 质量等级 - - 远程 SSH 主机未知 - SSH 服务器的指纹 <b>%s</b> 尚不清楚。请检查您要连接的 SSH 服务器的指纹是否正确。 - 指纹有效,继续 - 我不确定,中止 - - 远程 SSH 主机标识已更改! - SSH 服务器的指纹 <b>%s</b> 与已知指纹不匹配。这要么意味着有人在冒充正确的 SSH 服务器(中间人攻击),要么意味着更改了正确服务器的主机密钥。请联系 SSH 服务器的管理员以验证正确的密钥。您要继续还是中止 SSH 连接设置? - 我相信这是合法的,继续 - 我会仔细检查正确的 SSH 服务器指纹,中止 - - 通知已禁用 - 为了能够显示客户端状态信息并提供更轻松地导航回客户端的方法,建议启用通知。 - - + + + 位址 + Alt + Super + MultiVNC + 找到 VNC 伺服器 + VNC 伺服器已消失 + 重新啟動伺服器探索 + 立即縮放 +Android 版 MultiVNC 由 Christian Beier (christianbeier.net) 為您呈獻。\n\n專案首頁位於 https://github.com/bk138/multivnc\n\n如欲報告錯誤或請求新功能,請前往 https://github.com/bk138/multivnc/issues\n\n變更紀錄可參見 https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +色彩模式 +設定色彩模式 +連線 +Ctrl +刪除 +中斷連線 +切換檢視 +切換滑鼠按鈕 +切換滑鼠指標醒目提示 +切換鍵盤 + +匯出 +開啟資料庫時發生錯誤! +設定匯入/匯出 +匯入 + 提供的 URL 不當: + 讀取組態設定時發生 I/O 錯誤 + 讀取組態設定時發生 XML 或格式錯誤 + +連線資訊 +傳送按鍵組合 +新增連線 +加入書籤 +探索到的伺服器 +書籤 +儲存書籤 +書籤已儲存! +這不再是有效的書籤! +密碼 +保存 +連接埠 +中繼器上的 ID +連線到 UltraVNC 中繼器時使用的伺服器 ID + SSH 隧道 + SSH 服务器 + SSH 用户名 + 密码 + 私钥 + SSH密码 + 导入密钥 + 覆盖密钥 + 成功导入 SSH 私钥。 + 导入 SSH 私钥失败。 + 密钥密码 +另存副本 + 需要認證… + +傳送 +再次傳送按鍵組合 +Shift +VNC 連線捷徑 + 未有任何書籤 +按鍵組合 +使用者名稱 +用於 Windows/Mac 驗證 +編輯書籤 +請輸入書籤名稱… +您要將此伺服器加入書籤嗎? +您確定要刪除此書籤嗎? +您確定要中斷連線嗎? +關於 +說明 +編輯 + 儲存 + + 錯誤! + +請支持 MultiVNC +若您喜歡 MultiVNC,何不考慮支持開發?坐言起行很簡單,您可為此應用程式評個分或捐個款。謝謝! + +好,馬上去! +稍後再說。 +不要再詢問! + +要顯示說明嗎? +您似乎是第一次使用 MultiVNC。您要閱讀應用程式使用指南嗎?篇幅尚不太長,卻算是應有盡有... + +若您喜歡 MultiVNC,何不點按一個心、派送一點愛? +版本更新… + +線上 + 變更紀錄。 + ]]> + + 已連線到 %1$s + %1$s 和 + 加密的 + 未加密 + + 首选编码 + 好的 + 取消 + 压缩级别 + 质量等级 + + 远程 SSH 主机未知 + SSH 服务器的指纹 <b>%s</b> 尚不清楚。请检查您要连接的 SSH 服务器的指纹是否正确。 + 指纹有效,继续 + 我不确定,中止 + + 远程 SSH 主机标识已更改! + SSH 服务器的指纹 <b>%s</b> 与已知指纹不匹配。这要么意味着有人在冒充正确的 SSH 服务器(中间人攻击),要么意味着更改了正确服务器的主机密钥。请联系 SSH 服务器的管理员以验证正确的密钥。您要继续还是中止 SSH 连接设置? + 我相信这是合法的,继续 + 我会仔细检查正确的 SSH 服务器指纹,中止 + + 通知已禁用 + 为了能够显示客户端状态信息并提供更轻松地导航回客户端的方法,建议启用通知。 + + diff --git a/android/app/src/main/res/values-zh/strings.xml b/android/app/src/main/res/values-zh/strings.xml index 73ee7d6c..33f9fdbc 100644 --- a/android/app/src/main/res/values-zh/strings.xml +++ b/android/app/src/main/res/values-zh/strings.xml @@ -1,116 +1,116 @@ - - -地址 - Alt - Super - MultiVNC - 找到VNC服务器 - VNC服务器已消失 - 重新启动服务器发现 - 立刻缩放 - Christian Beier(christianbeier.net)为您提供了Android上的MultiVNC。\n\n项目主页位于https://github.com/bk138/multivnc\n\n如果您要报告错误或请求功能,请访问https://github.com/bk138/multivnc/issues\n\n更改日志可以在https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md上找到 -色彩模式 -设置色彩模式 -连接 -Ctrl -删除 -断开连接 -切换视图 -启用/禁用鼠标按钮 -启用/禁用指针高亮显示 -启用/禁用键盘 - -导出 -打开数据库时出错! -设置导入/导出 -导入 - 网址不正确: - 读取配置时I/O错误 - 读取配置时XML或格式错误 - -连接信息 -发送组合键 -新连接 -书签 -发现的服务器 -书签 -保存书签 -书签已保存! -这不再是有效的书签! -密码 -保存 -端口 -中继器上的ID -连接到UltraVNC中继器时的服务器ID - SSH 隧道 - SSH 服务器 - SSH 用户名 - 密码 - 私钥 - SSH密码 - 导入密钥 - 覆盖密钥 - 成功导入 SSH 私钥。 - 导入 SSH 私钥失败。 - 密钥密码 -另存为副本 - 需要凭证… - -发送 -再次发送组合键 -Shift -VNC连接快捷方式 - 尚无书签 -组合键 -用户名 -用于Windows/Mac身份验证 -编辑书签 -请输入书签名称… -您要为该服务器添加书签吗? -您确定要删除此书签吗? -您确定要断开连接吗? -关于 -帮助 -编辑 - 保存 - - 错误! - -请支持MultiVNC -如果您喜欢MultiVNC,支持其开发怎么样?目前,您可以对应用程序进行评级或捐赠。谢谢! - -好,好主意! -不,不是现在。 -不再询问! - -显示帮助? -看来这是您第一次使用MultiVNC。您要阅读应用手册吗?它还不是很长,但是内容很丰富… - -如果您喜欢MultiVNC,为什么不点击爱心来表达喜爱呢? -新功能… - 变更记录 。 ]]> - 已连接到%1$s - %1$s和 - 加密的 - 未加密 - - 首选编码 - 好的 - 取消 - 压缩级别 - 质量等级 - - 远程 SSH 主机未知 - SSH 服务器的指纹 <b>%s</b> 尚不清楚。请检查您要连接的 SSH 服务器的指纹是否正确。 - 指纹有效,继续 - 我不确定,中止 - - 远程 SSH 主机标识已更改! - SSH 服务器的指纹 <b>%s</b> 与已知指纹不匹配。这要么意味着有人在冒充正确的 SSH 服务器(中间人攻击),要么意味着更改了正确服务器的主机密钥。请联系 SSH 服务器的管理员以验证正确的密钥。您要继续还是中止 SSH 连接设置? - 我相信这是合法的,继续 - 我会仔细检查正确的 SSH 服务器指纹,中止 - - 通知已禁用 - 为了能够显示客户端状态信息并提供更轻松地导航回客户端的方法,建议启用通知。 - - + + +地址 + Alt + Super + MultiVNC + 找到VNC服务器 + VNC服务器已消失 + 重新启动服务器发现 + 立刻缩放 + Christian Beier(christianbeier.net)为您提供了Android上的MultiVNC。\n\n项目主页位于https://github.com/bk138/multivnc\n\n如果您要报告错误或请求功能,请访问https://github.com/bk138/multivnc/issues\n\n更改日志可以在https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md上找到 +色彩模式 +设置色彩模式 +连接 +Ctrl +删除 +断开连接 +切换视图 +启用/禁用鼠标按钮 +启用/禁用指针高亮显示 +启用/禁用键盘 + +导出 +打开数据库时出错! +设置导入/导出 +导入 + 网址不正确: + 读取配置时I/O错误 + 读取配置时XML或格式错误 + +连接信息 +发送组合键 +新连接 +书签 +发现的服务器 +书签 +保存书签 +书签已保存! +这不再是有效的书签! +密码 +保存 +端口 +中继器上的ID +连接到UltraVNC中继器时的服务器ID + SSH 隧道 + SSH 服务器 + SSH 用户名 + 密码 + 私钥 + SSH密码 + 导入密钥 + 覆盖密钥 + 成功导入 SSH 私钥。 + 导入 SSH 私钥失败。 + 密钥密码 +另存为副本 + 需要凭证… + +发送 +再次发送组合键 +Shift +VNC连接快捷方式 + 尚无书签 +组合键 +用户名 +用于Windows/Mac身份验证 +编辑书签 +请输入书签名称… +您要为该服务器添加书签吗? +您确定要删除此书签吗? +您确定要断开连接吗? +关于 +帮助 +编辑 + 保存 + + 错误! + +请支持MultiVNC +如果您喜欢MultiVNC,支持其开发怎么样?目前,您可以对应用程序进行评级或捐赠。谢谢! + +好,好主意! +不,不是现在。 +不再询问! + +显示帮助? +看来这是您第一次使用MultiVNC。您要阅读应用手册吗?它还不是很长,但是内容很丰富… + +如果您喜欢MultiVNC,为什么不点击爱心来表达喜爱呢? +新功能… + 变更记录 。 ]]> + 已连接到%1$s + %1$s和 + 加密的 + 未加密 + + 首选编码 + 好的 + 取消 + 压缩级别 + 质量等级 + + 远程 SSH 主机未知 + SSH 服务器的指纹 <b>%s</b> 尚不清楚。请检查您要连接的 SSH 服务器的指纹是否正确。 + 指纹有效,继续 + 我不确定,中止 + + 远程 SSH 主机标识已更改! + SSH 服务器的指纹 <b>%s</b> 与已知指纹不匹配。这要么意味着有人在冒充正确的 SSH 服务器(中间人攻击),要么意味着更改了正确服务器的主机密钥。请联系 SSH 服务器的管理员以验证正确的密钥。您要继续还是中止 SSH 连接设置? + 我相信这是合法的,继续 + 我会仔细检查正确的 SSH 服务器指纹,中止 + + 通知已禁用 + 为了能够显示客户端状态信息并提供更轻松地导航回客户端的方法,建议启用通知。 + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 28164917..466c9a0d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,121 +1,121 @@ - - - Address - Alt - Super - MultiVNC - Found VNC Server - VNC Server disappeared - Restart Server Discovery - Scale now -MultiVNC on Android is brought to you by Christian Beier (christianbeier.net).\n\nThe project home page is at https://github.com/bk138/multivnc\n\nIf you want to report a bug or request a feature, go to https://github.com/bk138/multivnc/issues\n\nThe change log can be fount at https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md -Color Mode -Set Color Mode -Connect -Ctrl -Delete -Disconnect -Toggle View -Toggle Mouse Buttons -Toggle Pointer Highlighting -Toggle Keyboard - -Export -Error opening database! -Settings Import/Export -Import - Improper URL given: - I/O error reading configuration - XML or format error reading configuration - -Connection Info -Send Key Combo -New Connection -Bookmark -Discovered Servers -Bookmarks -Save Bookmark -Bookmark saved! -This is not a valid bookmark anymore! -Password -Keep -Port -ID On Repeater -Server ID when connecting to an UltraVNC-repeater - SSH Tunneling - SSH Server - SSH Username - Password - Private Key - SSH Password - Import Key - Overwrite Key - Successfully imported SSH private key. - Failed importing SSH private key. - Key Password - Save as Copy - Credentials Needed… - -Send -Send Key Combo Again -Shift -VNC Connection Shortcut - No bookmarks yet -Key Combo -Username -For Windows/Mac authentication -Edit Bookmark -Please enter a bookmark name… -Do you want to bookmark this server? -Are you sure you want to delete this bookmark? -Are you sure you want to disconnect? -About -Help -Edit - Save - - Error! - -Please Support MultiVNC -If you like MultiVNC, why not consider supporting its development? For the time being, you can do so easily by rating the app or by donating. Thanks! - -Yes, good idea! -No, not now. -Never ask again! - -Show Help? -Seems it\'s the first time you use MultiVNC. Do you want to read the app manual? It\'s not very long yet informative... - -If you like MultiVNC, why not show some love by clicking the heart? -What\'s new… - -online - Change Log. - ]]> - - Connected to %1$s - %1$s and - encrypted - unencrypted - - Preferred Encoding - OK - Cancel - Compress Level - Quality Level - - Remote SSH host not yet known - The SSH server\'s fingerprint <b>%s</b> is not yet known. Please check that is the correct fingerprint of the SSH server you want to connect to. - The fingerprint is valid, continue - I am unsure, abort - - Remote SSH host identification has changed! - The fingerprint <b>%s</b> of the SSH server does not match the already known one. This either means someone is impersonating the correct SSH server (man-in-the-middle attack) or that the correct server\'s host key was changed. Please contact the SSH server\'s administrator to verify the correct key. Do you want to continue or abort the SSH connection setup? - I\'m confident this is legit, continue - I\'ll double-check the correct SSH server fingerprint, abort - - Notifications disabled - To be able to show client status information and provide a means of navigating back to the client more easily, it is recommended to enable notifications. - - + + + Address + Alt + Super + MultiVNC + Found VNC Server + VNC Server disappeared + Restart Server Discovery + Scale now +MultiVNC on Android is brought to you by Christian Beier (christianbeier.net).\n\nThe project home page is at https://github.com/bk138/multivnc\n\nIf you want to report a bug or request a feature, go to https://github.com/bk138/multivnc/issues\n\nThe change log can be fount at https://github.com/bk138/multivnc/blob/master/android/ChangeLog.md +Color Mode +Set Color Mode +Connect +Ctrl +Delete +Disconnect +Toggle View +Toggle Mouse Buttons +Toggle Pointer Highlighting +Toggle Keyboard + +Export +Error opening database! +Settings Import/Export +Import + Improper URL given: + I/O error reading configuration + XML or format error reading configuration + +Connection Info +Send Key Combo +New Connection +Bookmark +Discovered Servers +Bookmarks +Save Bookmark +Bookmark saved! +This is not a valid bookmark anymore! +Password +Keep +Port +ID On Repeater +Server ID when connecting to an UltraVNC-repeater + SSH Tunneling + SSH Server + SSH Username + Password + Private Key + SSH Password + Import Key + Overwrite Key + Successfully imported SSH private key. + Failed importing SSH private key. + Key Password + Save as Copy + Credentials Needed… + +Send +Send Key Combo Again +Shift +VNC Connection Shortcut + No bookmarks yet +Key Combo +Username +For Windows/Mac authentication +Edit Bookmark +Please enter a bookmark name… +Do you want to bookmark this server? +Are you sure you want to delete this bookmark? +Are you sure you want to disconnect? +About +Help +Edit + Save + + Error! + +Please Support MultiVNC +If you like MultiVNC, why not consider supporting its development? For the time being, you can do so easily by rating the app or by donating. Thanks! + +Yes, good idea! +No, not now. +Never ask again! + +Show Help? +Seems it\'s the first time you use MultiVNC. Do you want to read the app manual? It\'s not very long yet informative... + +If you like MultiVNC, why not show some love by clicking the heart? +What\'s new… + +online + Change Log. + ]]> + + Connected to %1$s + %1$s and + encrypted + unencrypted + + Preferred Encoding + OK + Cancel + Compress Level + Quality Level + + Remote SSH host not yet known + The SSH server\'s fingerprint <b>%s</b> is not yet known. Please check that is the correct fingerprint of the SSH server you want to connect to. + The fingerprint is valid, continue + I am unsure, abort + + Remote SSH host identification has changed! + The fingerprint <b>%s</b> of the SSH server does not match the already known one. This either means someone is impersonating the correct SSH server (man-in-the-middle attack) or that the correct server\'s host key was changed. Please contact the SSH server\'s administrator to verify the correct key. Do you want to continue or abort the SSH connection setup? + I\'m confident this is legit, continue + I\'ll double-check the correct SSH server fingerprint, abort + + Notifications disabled + To be able to show client status information and provide a means of navigating back to the client more easily, it is recommended to enable notifications. + + diff --git a/android/build.gradle b/android/build.gradle index f1642040..d948b4fe 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,22 +1,22 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext { - kotlin_version = '1.9.20' - } - repositories { - jcenter() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - } -} - -allprojects { - repositories { - jcenter() - google() - } -} +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext { + kotlin_version = '1.9.20' + } + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.7.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +allprojects { + repositories { + jcenter() + google() + } +} diff --git a/android/gradle.properties b/android/gradle.properties index ac69e8b6..79d44055 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,7 +1,7 @@ -android.defaults.buildfeatures.buildconfig=true -android.enableJetifier=true -android.nonFinalResIds=false -android.nonTransitiveRClass=false -android.useAndroidX=true -# See CMake output +android.defaults.buildfeatures.buildconfig=true +android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false +android.useAndroidX=true +# See CMake output android.native.buildOutput=verbose \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 1dfc67c3..f02c5e37 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Oct 30 22:02:55 CET 2020 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +#Fri Oct 30 22:02:55 CET 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip diff --git a/flatpak/net.christianbeier.MultiVNC.appdata.xml b/flatpak/net.christianbeier.MultiVNC.appdata.xml index 7079b9b6..edba7576 100644 --- a/flatpak/net.christianbeier.MultiVNC.appdata.xml +++ b/flatpak/net.christianbeier.MultiVNC.appdata.xml @@ -1,153 +1,153 @@ - - - - net.christianbeier.MultiVNC - - MultiVNC - Multicast-enabled VNC viewer - - CC-BY-4.0 - GPL-3.0-only - - - - - https://github.com/bk138/multivnc - https://github.com/bk138/multivnc/issues - - Christian Beier - - - pointing - keyboard - - - - #E8F3E2 - #1A2E1D - - - -

- MultiVNC is a cross-platform Multicast-enabled VNC viewer. -

-

Key features include:

-
    -
  • Support for most encodings including Tight.
  • -
  • TLS support, i.e. AnonTLS and VeNCrypt.
  • -
  • Discovery of VNC servers advertising themselves via ZeroConf.
  • -
  • Bookmarking of connections.
  • -
  • Supports server framebuffer resize.
  • -
  • Several connections with one viewer using tabs.
  • -
  • Listen mode (Reverse VNC). Via tabs it's possible to listen for and serve multiple incoming connections.
  • -
  • Record and replay of user input macros.
  • -
  • Under X11, seamless control of the remote side by moving pointer over the (default upper) screen edge.
  • -
  • Simple, loggable statistics.
  • -
-
- - net.christianbeier.MultiVNC.desktop - - - https://raw.githubusercontent.com/bk138/multivnc/master/flatpak/screenshot-hello-world.png - MultiVNC connected to an example VNC server - - - - - - -
    -
  • Improved bookmark editing/deleting by adding a context menu.
  • -
  • Added UTF-8 cuttext handling.
  • -
  • Improved log window by making it wider.
  • -
  • Fixed a bug where MultiVNC on Apple Silicon would not be able to connect to TLS-enabled servers.
  • -
-
-
- - - - -
    -
  • Added a scale-to-fit view mode which is now the default. Previous 1-to-1 view mode can still be toggled.
  • -
  • Improved fullscreen mode to show less controls and more of the remote view.
  • -
  • Improved toolbar icons to use scalable resources that look good for any display resolution as well as light and dark display mode.
  • -
  • Added MacOS packaging.
  • -
-
-
- - - -
    -
  • Added Swedish translation thanks to Åke Engelbrektson.
  • -
  • Added more tooltips to more UI elements.
  • -
  • Added keyboard shortcut for making a new connection.
  • -
  • Added secret store use for credentials without user name.
  • -
  • Fixed error dialog sometimes being not shown.
  • -
  • Fixed drawing on MacOS and Wayland. The flatpak now uses Wayland.
  • -
  • Fixed hang when connecting to unreachable servers.
  • -
-
-
- - - -

- Added record/replay of user input macros, login credential saving and translations into German and Spanish. Fixed a lot of small paper cut bugs. -

-
-
- - - -

- Added support for Apple Remote Desktop servers, VNC encoding selection, keyboard grabbing. -

-
-
- - - -

- Fixes to MulticastVNC and window sharing. -

-
-
- - - -

- Added support for IPv6 and QoS tagging. -

-
-
- - - -

- Added Seamless Edge Connector, FastRequest, experimental window sharing. -

-
-
- - - -

- Added MulticastVNC, reverse VNC and bookmarking of connections. -

-
-
- - - -

- A usable tight-enabled VNC viewer with support for server framebuffer resize, several connections with one viewer using tabs, - simple, loggable statistics, and discovery of VNC servers advertising themselves via ZeroConf. -

-
-
- -
-
+ + + + net.christianbeier.MultiVNC + + MultiVNC + Multicast-enabled VNC viewer + + CC-BY-4.0 + GPL-3.0-only + + + + + https://github.com/bk138/multivnc + https://github.com/bk138/multivnc/issues + + Christian Beier + + + pointing + keyboard + + + + #E8F3E2 + #1A2E1D + + + +

+ MultiVNC is a cross-platform Multicast-enabled VNC viewer. +

+

Key features include:

+
    +
  • Support for most encodings including Tight.
  • +
  • TLS support, i.e. AnonTLS and VeNCrypt.
  • +
  • Discovery of VNC servers advertising themselves via ZeroConf.
  • +
  • Bookmarking of connections.
  • +
  • Supports server framebuffer resize.
  • +
  • Several connections with one viewer using tabs.
  • +
  • Listen mode (Reverse VNC). Via tabs it's possible to listen for and serve multiple incoming connections.
  • +
  • Record and replay of user input macros.
  • +
  • Under X11, seamless control of the remote side by moving pointer over the (default upper) screen edge.
  • +
  • Simple, loggable statistics.
  • +
+
+ + net.christianbeier.MultiVNC.desktop + + + https://raw.githubusercontent.com/bk138/multivnc/master/flatpak/screenshot-hello-world.png + MultiVNC connected to an example VNC server + + + + + + +
    +
  • Improved bookmark editing/deleting by adding a context menu.
  • +
  • Added UTF-8 cuttext handling.
  • +
  • Improved log window by making it wider.
  • +
  • Fixed a bug where MultiVNC on Apple Silicon would not be able to connect to TLS-enabled servers.
  • +
+
+
+ + + + +
    +
  • Added a scale-to-fit view mode which is now the default. Previous 1-to-1 view mode can still be toggled.
  • +
  • Improved fullscreen mode to show less controls and more of the remote view.
  • +
  • Improved toolbar icons to use scalable resources that look good for any display resolution as well as light and dark display mode.
  • +
  • Added MacOS packaging.
  • +
+
+
+ + + +
    +
  • Added Swedish translation thanks to Åke Engelbrektson.
  • +
  • Added more tooltips to more UI elements.
  • +
  • Added keyboard shortcut for making a new connection.
  • +
  • Added secret store use for credentials without user name.
  • +
  • Fixed error dialog sometimes being not shown.
  • +
  • Fixed drawing on MacOS and Wayland. The flatpak now uses Wayland.
  • +
  • Fixed hang when connecting to unreachable servers.
  • +
+
+
+ + + +

+ Added record/replay of user input macros, login credential saving and translations into German and Spanish. Fixed a lot of small paper cut bugs. +

+
+
+ + + +

+ Added support for Apple Remote Desktop servers, VNC encoding selection, keyboard grabbing. +

+
+
+ + + +

+ Fixes to MulticastVNC and window sharing. +

+
+
+ + + +

+ Added support for IPv6 and QoS tagging. +

+
+
+ + + +

+ Added Seamless Edge Connector, FastRequest, experimental window sharing. +

+
+
+ + + +

+ Added MulticastVNC, reverse VNC and bookmarking of connections. +

+
+
+ + + +

+ A usable tight-enabled VNC viewer with support for server framebuffer resize, several connections with one viewer using tabs, + simple, loggable statistics, and discovery of VNC servers advertising themselves via ZeroConf. +

+
+
+ +
+
diff --git a/flatpak/net.christianbeier.MultiVNC.yml b/flatpak/net.christianbeier.MultiVNC.yml index f1c73a01..29064ed3 100644 --- a/flatpak/net.christianbeier.MultiVNC.yml +++ b/flatpak/net.christianbeier.MultiVNC.yml @@ -1,44 +1,44 @@ -id: net.christianbeier.MultiVNC -runtime: org.gnome.Platform # using GNOME since it contains libsecret already built -runtime-version: '46' -sdk: org.gnome.Sdk -command: multivnc -finish-args: - - --share=ipc - - --socket=wayland - - --socket=fallback-x11 - - --share=network - - --talk-name=org.freedesktop.secrets - - --filesystem=home # for saving screenshots and logs -modules: - - name: wxwidgets - rm-configure: true - config-opts: - - --with-gtk=3 - - --enable-secretstore - - --enable-debug=no - - --enable-aui=no - - --enable-html=no - - --enable-mediactrl=no - - --enable-propgrid=no - - --enable-ribbon=no - - --enable-richtext=no - - --enable-stc=no - - --enable-xrc=no - cleanup: - - /bin - - /include - - /lib/wx - - /share/bakefile - - /share/aclocal - sources: - - type: archive - url: https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.tar.bz2 - sha1: 44371dfdcc96a0e3d5e03d2d726470f645035619 - - name: multivnc - buildsystem: cmake-ninja - config-opts: - - -DCMAKE_BUILD_TYPE=Release - sources: - - type: dir - path: .. +id: net.christianbeier.MultiVNC +runtime: org.gnome.Platform # using GNOME since it contains libsecret already built +runtime-version: '46' +sdk: org.gnome.Sdk +command: multivnc +finish-args: + - --share=ipc + - --socket=wayland + - --socket=fallback-x11 + - --share=network + - --talk-name=org.freedesktop.secrets + - --filesystem=home # for saving screenshots and logs +modules: + - name: wxwidgets + rm-configure: true + config-opts: + - --with-gtk=3 + - --enable-secretstore + - --enable-debug=no + - --enable-aui=no + - --enable-html=no + - --enable-mediactrl=no + - --enable-propgrid=no + - --enable-ribbon=no + - --enable-richtext=no + - --enable-stc=no + - --enable-xrc=no + cleanup: + - /bin + - /include + - /lib/wx + - /share/bakefile + - /share/aclocal + sources: + - type: archive + url: https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.6/wxWidgets-3.2.6.tar.bz2 + sha1: 44371dfdcc96a0e3d5e03d2d726470f645035619 + - name: multivnc + buildsystem: cmake-ninja + config-opts: + - -DCMAKE_BUILD_TYPE=Release + sources: + - type: dir + path: .. diff --git a/macos/MultiVNC.entitlements b/macos/MultiVNC.entitlements index 85c03d7b..c63feb92 100644 --- a/macos/MultiVNC.entitlements +++ b/macos/MultiVNC.entitlements @@ -1,14 +1,14 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - - + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/macos/README.md b/macos/README.md index 6343b5f9..f3cf0270 100644 --- a/macos/README.md +++ b/macos/README.md @@ -1,9 +1,9 @@ -# MacOS Release Build Instructions - -For the time being, we're building an Intel binary only. Universal builds to come later. - -- Install build tools: `brew install cmake gettext` -- Install build dependencies: `brew install wxwidgets jpeg-turbo libressl` -- Building a release app bundle, [signing the app](https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac), - [building an installer package for App Store distribution](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution) as well as - [validating and uploading the package](https://help.apple.com/asc/appsaltool) is all done by the `build-sign-validate-upload.sh` script. +# MacOS Release Build Instructions + +For the time being, we're building an Intel binary only. Universal builds to come later. + +- Install build tools: `brew install cmake gettext` +- Install build dependencies: `brew install wxwidgets jpeg-turbo libressl` +- Building a release app bundle, [signing the app](https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac), + [building an installer package for App Store distribution](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution) as well as + [validating and uploading the package](https://help.apple.com/asc/appsaltool) is all done by the `build-sign-validate-upload.sh` script. diff --git a/macos/build-sign-validate-upload.sh b/macos/build-sign-validate-upload.sh index cae84788..71710d5d 100755 --- a/macos/build-sign-validate-upload.sh +++ b/macos/build-sign-validate-upload.sh @@ -1,73 +1,73 @@ -#!/bin/sh - -[ -z "$CODESIGN_ID_DISTRIBUTION" ] && { - echo "Please set CODESIGN_ID_DISTRIBUTION env var. You can get it via 'security find-identity -p codesigning -v'" - exit 1 -} - -[ -z "$CODESIGN_ID_INSTALLER" ] && { - echo "Please set CODESIGN_ID_INSTALLER env var. You can get it via 'security find-identity -v'" - exit 1 -} - -[ -z "$APPLE_ID_EMAIL" ] && { - echo "Please set APPLE_ID_EMAIL env var." - exit 1 -} - -[ -z "$APPLE_ID_PASSWORD" ] && { - echo "Please set APPLE_ID_PASSWORD env var. Might need an app-specific password if using 2FA, which you can create at https://appstoreconnect.apple.com/apps" - exit 1 -} - -# workaround until we're installing the whole framework https://github.com/bk138/multivnc/issues/244 -[ -z "$WX_LOCALES_PATH" ] && { - echo "Please set WX_LOCALES_PATH env var." - exit 1 -} - -set -e - -echo -echo "Build release app bundle" -echo -mkdir -p build-dir -cd build-dir -MACOSX_DEPLOYMENT_TARGET=10.15 cmake ../.. -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=$(brew --prefix libressl) -make -j$(nproc) -cmake --install . --prefix . -# workaround until we're installing the whole framework https://github.com/bk138/multivnc/issues/244 -cp $WX_LOCALES_PATH/de/LC_MESSAGES/wxstd-3.2.mo MultiVNC.app/Contents/Resources/de.lproj/ -cp $WX_LOCALES_PATH/es/LC_MESSAGES/wxstd-3.2.mo MultiVNC.app/Contents/Resources/es.lproj/ -cp $WX_LOCALES_PATH/sv/LC_MESSAGES/wxstd-3.2.mo MultiVNC.app/Contents/Resources/sv.lproj/ - -echo -echo "Sign embedded libs" -echo -codesign -s $CODESIGN_ID_DISTRIBUTION -f -i net.christianbeier.MultiVNC.libs MultiVNC.app/Contents/Frameworks/* - -echo -echo "Sign app" -echo -codesign -s $CODESIGN_ID_DISTRIBUTION --entitlements ../MultiVNC.entitlements MultiVNC.app - - -echo -echo "Verify signing" -echo -codesign -d -vv MultiVNC.app - -echo -echo "Build an installer package for App Store distribution" -echo -productbuild --sign $CODESIGN_ID_INSTALLER --component MultiVNC.app /Applications MultiVNC.pkg - -echo -echo "Validate package" -echo -xcrun altool --validate-app -f MultiVNC.pkg -t osx -u $APPLE_ID_EMAIL -p $APPLE_ID_PASSWORD --output-format xml - -echo -echo "Upload package" -echo -xcrun altool --upload-app -f MultiVNC.pkg -t osx -u $APPLE_ID_EMAIL -p $APPLE_ID_PASSWORD --output-format xml +#!/bin/sh + +[ -z "$CODESIGN_ID_DISTRIBUTION" ] && { + echo "Please set CODESIGN_ID_DISTRIBUTION env var. You can get it via 'security find-identity -p codesigning -v'" + exit 1 +} + +[ -z "$CODESIGN_ID_INSTALLER" ] && { + echo "Please set CODESIGN_ID_INSTALLER env var. You can get it via 'security find-identity -v'" + exit 1 +} + +[ -z "$APPLE_ID_EMAIL" ] && { + echo "Please set APPLE_ID_EMAIL env var." + exit 1 +} + +[ -z "$APPLE_ID_PASSWORD" ] && { + echo "Please set APPLE_ID_PASSWORD env var. Might need an app-specific password if using 2FA, which you can create at https://appstoreconnect.apple.com/apps" + exit 1 +} + +# workaround until we're installing the whole framework https://github.com/bk138/multivnc/issues/244 +[ -z "$WX_LOCALES_PATH" ] && { + echo "Please set WX_LOCALES_PATH env var." + exit 1 +} + +set -e + +echo +echo "Build release app bundle" +echo +mkdir -p build-dir +cd build-dir +MACOSX_DEPLOYMENT_TARGET=10.15 cmake ../.. -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=$(brew --prefix libressl) +make -j$(nproc) +cmake --install . --prefix . +# workaround until we're installing the whole framework https://github.com/bk138/multivnc/issues/244 +cp $WX_LOCALES_PATH/de/LC_MESSAGES/wxstd-3.2.mo MultiVNC.app/Contents/Resources/de.lproj/ +cp $WX_LOCALES_PATH/es/LC_MESSAGES/wxstd-3.2.mo MultiVNC.app/Contents/Resources/es.lproj/ +cp $WX_LOCALES_PATH/sv/LC_MESSAGES/wxstd-3.2.mo MultiVNC.app/Contents/Resources/sv.lproj/ + +echo +echo "Sign embedded libs" +echo +codesign -s $CODESIGN_ID_DISTRIBUTION -f -i net.christianbeier.MultiVNC.libs MultiVNC.app/Contents/Frameworks/* + +echo +echo "Sign app" +echo +codesign -s $CODESIGN_ID_DISTRIBUTION --entitlements ../MultiVNC.entitlements MultiVNC.app + + +echo +echo "Verify signing" +echo +codesign -d -vv MultiVNC.app + +echo +echo "Build an installer package for App Store distribution" +echo +productbuild --sign $CODESIGN_ID_INSTALLER --component MultiVNC.app /Applications MultiVNC.pkg + +echo +echo "Validate package" +echo +xcrun altool --validate-app -f MultiVNC.pkg -t osx -u $APPLE_ID_EMAIL -p $APPLE_ID_PASSWORD --output-format xml + +echo +echo "Upload package" +echo +xcrun altool --upload-app -f MultiVNC.pkg -t osx -u $APPLE_ID_EMAIL -p $APPLE_ID_PASSWORD --output-format xml diff --git a/po/.gitignore b/po/.gitignore index 6e2d5591..13a12dff 100644 --- a/po/.gitignore +++ b/po/.gitignore @@ -1,2 +1,2 @@ -*.pot -*.mo +*.pot +*.mo diff --git a/po/InfoPlist.strings.sv b/po/InfoPlist.strings.sv index d44d8b10..114926e7 100644 --- a/po/InfoPlist.strings.sv +++ b/po/InfoPlist.strings.sv @@ -1 +1 @@ -"NSLocalNetworkUsageDescription" = "MultiVNC kräver åtkomst till ditt lokala nätverk för att kunna upptäcka och ansluta till VNC-servrar för fjärrstyrning av skrivbordet."; +"NSLocalNetworkUsageDescription" = "MultiVNC kräver åtkomst till ditt lokala nätverk för att kunna upptäcka och ansluta till VNC-servrar för fjärrstyrning av skrivbordet."; diff --git a/po/README.md b/po/README.md index 07beb32d..fdb2b1d5 100644 --- a/po/README.md +++ b/po/README.md @@ -1,9 +1,9 @@ -# Add or Update Translation - -1. Run `xgettext --keyword=_ --sort-output --package-name=MultiVNC ../src/*.cpp ../src/gui/*.cpp -o multivnc.pot` - from this directory which will create a `multivnc.pot` translation template file. -2. Use poedit or a similar tool to create or update a translation, save the resulting .po file in this directory. - - Make sure to add a `translator-credits` entry as in the other .po files so your name automatically shows up in the About dialog. -4. Adjust CMakeLists.txt to build and install the corresponding .mo file. -5. Make sure to add an InfoPlist.strings. file translating the relevant Info.plist strings for MacOS. -6. Adjust CMakeLists.txt to install the InfoPlist.strings. file. +# Add or Update Translation + +1. Run `xgettext --keyword=_ --sort-output --package-name=MultiVNC ../src/*.cpp ../src/gui/*.cpp -o multivnc.pot` + from this directory which will create a `multivnc.pot` translation template file. +2. Use poedit or a similar tool to create or update a translation, save the resulting .po file in this directory. + - Make sure to add a `translator-credits` entry as in the other .po files so your name automatically shows up in the About dialog. +4. Adjust CMakeLists.txt to build and install the corresponding .mo file. +5. Make sure to add an InfoPlist.strings. file translating the relevant Info.plist strings for MacOS. +6. Adjust CMakeLists.txt to install the InfoPlist.strings. file. diff --git a/po/de.po b/po/de.po index f9ad256d..0bf89287 100644 --- a/po/de.po +++ b/po/de.po @@ -1,893 +1,893 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the MultiVNC package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: MultiVNC\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-06 18:13+0200\n" -"PO-Revision-Date: 2024-10-06 18:17+0200\n" -"Last-Translator: Christian Beier \n" -"Language-Team: \n" -"Language: de\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.2.2\n" - -#: ../src/gui/FrameMain.cpp:81 -msgid "&Add Bookmark" -msgstr "Lesezeichen &hinzufügen" - -#: ../src/gui/FrameMain.cpp:88 -msgid "&Bookmarks" -msgstr "&Lesezeichen" - -#: ../src/gui/FrameMain.cpp:31 -msgid "&Connect...\tCtrl-T" -msgstr "&Verbindung...\tStrg-T" - -#: ../src/gui/FrameMain.cpp:96 -msgid "&Contents" -msgstr "&Inhalt" - -#: ../src/gui/FrameMain.cpp:86 -msgid "&Delete Bookmark" -msgstr "Lesezeichen &löschen" - -#: ../src/gui/FrameMain.cpp:35 -msgid "&Disconnect" -msgstr "Verbindung &trennen" - -#: ../src/gui/FrameMain.cpp:84 -msgid "&Edit Bookmark" -msgstr "Lesezeichen &bearbeiten" - -#: ../src/gui/FrameMain.cpp:102 -msgid "&Help" -msgstr "&Hilfe" - -#: ../src/gui/FrameMain.cpp:33 -msgid "&Listen" -msgstr "&Eingehende Verbindung" - -#: ../src/gui/FrameMain.cpp:52 -msgid "&Machine" -msgstr "&Maschine" - -#: ../src/gui/FrameMain.cpp:90 -msgid "&Share a Window" -msgstr "&Ein Fenster teilen" - -#: ../src/gui/FrameMain.cpp:79 -msgid "&View" -msgstr "&Ansicht" - -#: ../src/gui/MyFrameMain.cpp:890 -msgid "(Reverse Connection)" -msgstr "(Eingehende Verbindung)" - -#: ../src/gui/MyFrameMain.cpp:1725 -msgid "A bookmark with this name already exists!" -msgstr "Ein Lesezeichen mit diesem Namen existiert bereits!" - -#: ../src/gui/MyFrameMain.cpp:310 -msgid "Authentication canceled." -msgstr "Anmeldung abgebrochen" - -#: ../src/gui/DialogSettings.cpp:102 -msgid "Autosave statistics on close" -msgstr "Statistiken beim Schließen automatisch speichern" - -#: ../src/gui/FrameMain.cpp:153 -msgid "Available VNC Servers" -msgstr "Verfügbare VNC-Server" - -#: ../src/gui/FrameMain.cpp:90 -msgid "Beam a window to the server." -msgstr "Ein Fenster auf den Server senden." - -#: ../src/gui/MyFrameMain.cpp:1187 -msgid "Bookmark" -msgstr "Lesezeichen" - -#: ../src/gui/MyFrameMain.cpp:1991 -#, c-format -msgid "Bookmark %s" -msgstr "Lesezeichen %s" - -#: ../src/gui/FrameMain.cpp:58 ../src/gui/FrameMain.cpp:158 -#: ../src/gui/MyFrameMain.cpp:1145 -msgid "Bookmarks" -msgstr "Lesezeichen" - -#: ../src/gui/MyFrameMain.cpp:1822 -msgid "Built with" -msgstr "Gebaut mit" - -#: ../src/gui/MyFrameMain.cpp:769 ../src/gui/MyFrameMain.cpp:1405 -#: ../src/gui/MyFrameMain.cpp:1492 -msgid "CSV files|*.csv" -msgstr "CSV-Dateien|*.csv" - -#: ../src/gui/DialogLogin.cpp:37 -msgid "Cancel" -msgstr "Abbrechen" - -#: ../src/gui/MyFrameMain.cpp:1713 -msgid "Cannot bookmark a reverse connection!" -msgstr "Für eine eingehende Verbindung kann kein Lesezeichen angelegt werden!" - -#: ../src/gui/FrameMain.cpp:39 -msgid "Change preferences." -msgstr "Einstellungen ändern." - -#: ../src/gui/DialogSettings.cpp:69 -msgid "CoRRE" -msgstr "CoRRE" - -#: ../src/gui/DialogSettings.cpp:87 -msgid "Compression level for 'Tight' and 'Zlib' encodings:" -msgstr "Komprimierungsgrad für die Kodierungen \"Tight\" und \"Zlib\":" - -#: ../src/gui/FrameMain.cpp:117 -msgid "Connect" -msgstr "Verbindung" - -#: ../src/gui/FrameMain.cpp:31 ../src/gui/FrameMain.cpp:117 -#: ../src/gui/MyFrameMain.cpp:1215 -msgid "Connect to a specific host." -msgstr "Verbindung zu einem bestimmten Host." - -#: ../src/gui/MyFrameMain.cpp:854 -#, c-format -msgid "Connecting to %s..." -msgstr "Verbindung zu %s..." - -#: ../src/gui/MyFrameMain.cpp:312 -msgid "Connection failed." -msgstr "Verbindung fehlgeschlagen." - -#: ../src/gui/MyFrameMain.cpp:1055 -msgid "Connection terminated." -msgstr "Verbindung abgebrochen." - -#: ../src/gui/MyFrameMain.cpp:377 -#, c-format -msgid "Connection to %s is now multicast." -msgstr "Verbindung zu %s ist jetzt Multicast." - -#: ../src/gui/MyFrameMain.cpp:388 -#, c-format -msgid "Connection to %s is now unicast." -msgstr "Verbindung zu %s ist jetzt Unicast." - -#: ../src/gui/MyFrameMain.cpp:386 -#, c-format -msgid "Connection to %s switched to unicast." -msgstr "Verbindung zu %s auf Unicast umgestellt." - -#: ../src/gui/MyFrameMain.cpp:481 ../src/gui/MyFrameMain.cpp:491 -#, c-format -msgid "Connection to %s:%s terminated." -msgstr "Verbindung zu %s:%s wurde beendet." - -#: ../src/gui/DialogSettings.cpp:27 -msgid "Connections" -msgstr "Verbindungen" - -#: ../src/gui/DialogSettings.cpp:36 -msgid "" -"Continously ask the server for updates instead of just asking after each " -"received server message. Use this on high latency links." -msgstr "" -"Den Server kontinuierlich nach Aktualisierungen fragen, anstatt nur nach " -"jeder empfangenen Server-Nachricht zu fragen. Verwenden Sie dies bei " -"Verbindungen mit hoher Latenz." - -#: ../src/gui/DialogSettings.cpp:33 -msgid "Continously request updates at the specified milisecond interval:" -msgstr "" -"Fordert fortlaufend Aktualisierungen im angegebenen Millisekunden-Intervall " -"an:" - -#: ../src/gui/DialogSettings.cpp:63 -msgid "CopyRect" -msgstr "CopyRect" - -#: ../src/gui/MyFrameMain.cpp:994 -msgid "Could not autosave statistics!" -msgstr "Die Statistiken konnten nicht automatisch gespeichert werden!" - -#: ../src/VNCConn.cpp:1114 -msgid "Could not create VNC listener thread!" -msgstr "Konnte keinen VNC-Listener-Thread erstellen!" - -#: ../src/VNCConn.cpp:1178 -msgid "Could not create VNC thread!" -msgstr "VNC-Thread konnte nicht erstellt werden!" - -#: ../src/MultiVNCApp.cpp:112 -msgid "Could not open config file!" -msgstr "Konnte die Konfigurationsdatei nicht öffnen!" - -#: ../src/gui/MyFrameMain.cpp:1504 -msgid "Could not open file!" -msgstr "Datei konnte nicht geöffnet werden!" - -#: ../src/gui/MyFrameMain.cpp:626 ../src/gui/MyFrameMain.cpp:627 -msgid "Could not run window share helper." -msgstr "Der Window Share Helper konnte nicht ausgeführt werden." - -#: ../src/MultiVNCApp.cpp:162 -msgid "Could not save config file!" -msgstr "Konnte die Konfigurationsdatei nicht speichern!" - -#: ../src/gui/MyFrameMain.cpp:781 ../src/gui/MyFrameMain.cpp:1416 -msgid "Could not save file!" -msgstr "Datei konnte nicht gespeichert werden!" - -#: ../src/gui/MyFrameMain.cpp:2107 -msgid "Could not share window, external program execution failed." -msgstr "" -"Fenster konnte nicht mit Server geteilt werden, externe Programmausführung " -"fehlgeschlagen." - -#: ../src/VNCConn.cpp:1122 -msgid "Could not start VNC listener thread!" -msgstr "Der VNC-Listener-Thread konnte nicht gestartet werden!" - -#: ../src/VNCConn.cpp:1186 -msgid "Could not start VNC thread!" -msgstr "VNC-Thread kann nicht gestartet werden!" - -#: ../src/gui/MyFrameMain.cpp:718 ../src/gui/MyFrameMain.cpp:727 -msgid "Credentials required..." -msgstr "Anmeldedaten erforderlich..." - -#: ../src/gui/MyFrameMain.cpp:1259 -msgid "Detailed VNC Log" -msgstr "Ausführliches VNC-Protokoll" - -#: ../src/gui/FrameMain.cpp:71 -msgid "Disabled" -msgstr "Aus" - -#: ../src/gui/FrameMain.cpp:122 -msgid "Disconnect" -msgstr "Verbindung trennen" - -#: ../src/gui/FrameMain.cpp:56 -msgid "Discovered Servers" -msgstr "Entdeckte Server" - -#: ../src/gui/FrameMain.cpp:65 -msgid "East" -msgstr "Ost" - -#: ../src/gui/FrameMain.cpp:73 -msgid "Edge Connector" -msgstr "Edge Connector" - -#: ../src/gui/MyFrameMain.cpp:1764 -msgid "Edit bookmark" -msgstr "Lesezeichen bearbeiten" - -#: ../src/gui/ViewerWindow.cpp:436 -msgid "Eff. KB/s:" -msgstr "Eff. KB/s:" - -#: ../src/gui/DialogSettings.cpp:40 -msgid "Enable Expedited Forwarding tagging for sent data" -msgstr "Expedited-Forward-Tagging für gesendete Daten aktivieren" - -#: ../src/gui/DialogSettings.cpp:31 -msgid "Enable FastRequest" -msgstr "FastRequest einschalten" - -#: ../src/gui/DialogSettings.cpp:44 -msgid "Enable MulticastVNC" -msgstr "MulticastVNC aktivieren" - -#: ../src/gui/DialogSettings.cpp:59 -msgid "Enabled Encodings" -msgstr "Aktivierte VNC-Kodierungen" - -#: ../src/gui/DialogSettings.cpp:57 -msgid "Encodings" -msgstr "VNC-Kodierungen" - -#: ../src/gui/MyFrameMain.cpp:1717 -msgid "Enter bookmark name:" -msgstr "Name des Lesezeichens eingeben:" - -#: ../src/gui/MyFrameMain.cpp:1214 -msgid "Enter host to connect to:" -msgstr "" -"Geben Sie den Host ein, mit dem eine Verbindung hergestellt werden soll:" - -#: ../src/gui/MyFrameMain.cpp:2075 -msgid "Enter name of window to share:" -msgstr "Geben Sie den Namen des freizugebenden Fensters ein:" - -#: ../src/gui/MyFrameMain.cpp:697 -msgid "Enter password:" -msgstr "Passwort eingeben:" - -#: ../src/gui/MyFrameMain.cpp:1160 -#, c-format -msgid "Error reading hostname of bookmark '%s'!" -msgstr "Fehler beim Lesen des Hostnamens des Lesezeichens \"%s\"!" - -#: ../src/gui/MyFrameMain.cpp:1167 -#, c-format -msgid "Error reading port of bookmark '%s'!" -msgstr "Fehler beim Lesen des Ports des Lesezeichens \"%s\"!" - -#: ../src/gui/FrameMain.cpp:50 -msgid "Exit MultiVNC." -msgstr "MultiVNC verlassen." - -#: ../src/gui/MyFrameMain.cpp:1739 -msgid "Failed to save credentials to the system secret store." -msgstr "" -"Die Anmeldeinformationen konnten nicht im sicheren Speicher des Systems " -"gespeichert werden." - -#: ../src/VNCConn.cpp:294 -#, c-format -msgid "Failure connecting to server at %s:%d!" -msgstr "Verbindung zu Server %s:%d fehlgeschlagen!" - -#: ../src/VNCConn.cpp:257 -msgid "Failure setting up framebuffer: wrong BPP!" -msgstr "Fehler bei der Einrichtung des Framebuffers: falsche BPP!" - -#: ../src/gui/DialogSettings.cpp:29 -msgid "FastRequest" -msgstr "FastRequest" - -#: ../src/gui/MyFrameMain.cpp:1428 -msgid "" -"From now on, all your mouse and keyboard input will be recorded. Click the " -"stop button to finish recording and save your input." -msgstr "" -"Von nun an werden alle Ihre Maus- und Tastatureingaben aufgezeichnet. " -"Klicken Sie auf die Schaltfläche \"Stopp\", um die Aufzeichnung zu beenden " -"und Ihre Eingaben zu speichern." - -#: ../src/gui/FrameMain.cpp:134 -msgid "Fullscreen" -msgstr "Vollbild" - -#: ../src/gui/FrameMain.cpp:75 -msgid "Fullscreen\tF11" -msgstr "Vollbild\tF11" - -#: ../src/MultiVNCApp.cpp:178 -msgid "GAAH! Got an unhandled exception! This should not happen." -msgstr "Unbehandelte Ausnahme erhalten! Dies sollte nicht passieren." - -#: ../src/gui/FrameMain.cpp:124 -msgid "Grab Keyboard" -msgstr "Alle Tastatureingaben abfangen" - -#: ../src/gui/DialogSettings.cpp:65 -msgid "Hextile" -msgstr "Hextile" - -#: ../src/gui/MyFrameMain.cpp:1863 -msgid "" -"If there are VNC servers advertising themselves via ZeroConf, you can select " -"a host in the'Available VNC Servers' list. Otherwise, use the 'Connect' " -"button or menu item.\n" -"\n" -"When connected, a blue or green icon on the tab label shows if you are " -"running in unicast or multicast mode." -msgstr "" -"Wenn es VNC-Server gibt, die sich über ZeroConf/Bonjour melden, können Sie " -"einen Host in der Liste \"Verfügbare VNC-Server\" auswählen. Andernfalls " -"verwenden Sie die Schaltfläche \"Verbinden\" oder den Menüpunkt " -"\"Verbinden\".\n" -"\n" -"Wenn Sie verbunden sind, zeigt ein blaues oder grünes Symbol auf der " -"Registerkarte an, ob Sie im Unicast- oder Multicast-Modus arbeiten." - -#: ../src/gui/MyFrameMain.cpp:514 -msgid "Incoming Connection." -msgstr "Eingehende Verbindung." - -#: ../src/gui/FrameMain.cpp:124 -msgid "" -"Intercept all keyboard input. Allows you to use special keys that would " -"otherwise be interpreted by the local computer." -msgstr "" -"Alle Tastatureingaben abfangen. Ermöglicht die Verwendung von Sondertasten, " -"die sonst vom lokalen Computer interpretiert werden würden." - -#: ../src/gui/ViewerWindow.cpp:440 -msgid "Latency ms:" -msgstr "Latenz ms:" - -#: ../src/gui/FrameMain.cpp:119 -msgid "Listen" -msgstr "Eingehende Verbindung" - -#: ../src/gui/FrameMain.cpp:33 ../src/gui/FrameMain.cpp:119 -msgid "Listen for an incoming connection." -msgstr "Eingehende Verbindung." - -#: ../src/gui/MyFrameMain.cpp:848 ../src/gui/MyFrameMain.cpp:926 -msgid "Listening on port" -msgstr "Eingehende Verbindung auf Port" - -#: ../src/gui/MyFrameMain.cpp:1488 -msgid "Load recorded input..." -msgstr "Aufgezeichnete Eingaben laden..." - -#: ../src/gui/DialogSettings.cpp:98 -msgid "Logging" -msgstr "Protokollierung" - -#: ../src/gui/DialogLogin.cpp:39 -msgid "Login" -msgstr "Anmelden" - -#: ../src/gui/MyFrameMain.cpp:1903 -msgid "Looking up host address..." -msgstr "Nachschlagen der Host-Adresse..." - -#: ../src/gui/ViewerWindow.cpp:442 -msgid "Loss Ratio:" -msgstr "Paketverlustverhältnis:" - -#: ../src/gui/DialogSettings.cpp:85 -msgid "Lossy Encodings Settings" -msgstr "Einstellungen für verlustbehaftete VNC-Kodierungen" - -#: ../src/gui/FrameMain.cpp:24 -msgid "MultiVNC" -msgstr "MultiVNC" - -#: ../src/MultiVNCApp.cpp:186 -msgid "MultiVNC crashed" -msgstr "MultiVNC ist abgestürzt" - -#: ../src/gui/MyFrameMain.cpp:1820 -msgid "MultiVNC is a cross-platform Multicast-enabled VNC client." -msgstr "" -"MultiVNC ist ein plattformübergreifender, Multicast-fähiger VNC-Client." - -#: ../src/gui/DialogSettings.cpp:42 -msgid "MulticastVNC" -msgstr "MulticastVNC" - -#: ../src/gui/MyFrameMain.cpp:1763 -msgid "New bookmark name:" -msgstr "Name des neuen Lesezeichens:" - -#: ../src/gui/MyFrameMain.cpp:1759 ../src/gui/MyFrameMain.cpp:1788 -msgid "No bookmark selected!" -msgstr "Kein Lesezeichen ausgewählt!" - -#: ../src/gui/MyFrameMain.cpp:1794 -msgid "No bookmark with this name!" -msgstr "Kein Lesezeichen mit diesem Namen!" - -#: ../src/gui/FrameMain.cpp:63 -msgid "North" -msgstr "Nord" - -#: ../src/gui/MyFrameMain.cpp:753 ../src/gui/MyFrameMain.cpp:1390 -msgid "Nothing to save!" -msgstr "Nichts zu speichern!" - -#: ../src/MultiVNCApp.cpp:186 -msgid "" -"Ouch! MultiVNC crashed. This should not happen. Do you want to generate a " -"bug report?" -msgstr "" -"MultiVNC ist abgestürzt. Das sollte nicht passieren. Möchten Sie einen " -"Fehlerbericht erstellen?" - -#: ../src/gui/MyFrameMain.cpp:1324 -msgid "PNG files|*.png" -msgstr "PNG-Dateien|*.png" - -#: ../src/gui/MyFrameMain.cpp:697 -msgid "Password required!" -msgstr "Passwort erforderlich!" - -#: ../src/gui/DialogLogin.cpp:23 -msgid "Password:" -msgstr "Kennwort:" - -#: ../src/gui/MyFrameMain.cpp:717 -#, c-format -msgid "Please enter password for user '%s'" -msgstr "Bitte Passwort für Benutzer '%s' eingeben" - -#: ../src/gui/MyFrameMain.cpp:1272 -msgid "Preferences" -msgstr "Einstellungen" - -#: ../src/gui/DialogSettings.cpp:92 -msgid "Quality level for 'Tight' and 'ZYWRLE' encoding:" -msgstr "Qualitätsstufe für die Kodierung \"Tight\" und \"ZYWRLE\":" - -#: ../src/gui/DialogSettings.cpp:38 -msgid "Quality of Service" -msgstr "Quality of Service" - -#: ../src/gui/DialogSettings.cpp:67 -msgid "RRE" -msgstr "RRE" - -#: ../src/gui/ViewerWindow.cpp:444 -msgid "Rcv Buffer:" -msgstr "Empfangspuffer:" - -#: ../src/gui/DialogSettings.cpp:51 -msgid "Receive Buffer Size (kB):" -msgstr "Größe des Empfangspuffers (kB):" - -#: ../src/gui/FrameMain.cpp:45 ../src/gui/FrameMain.cpp:129 -#: ../src/gui/MyFrameMain.cpp:1374 ../src/gui/MyFrameMain.cpp:1382 -msgid "Record Input" -msgstr "Eingaben aufzeichnen" - -#: ../src/gui/FrameMain.cpp:129 -msgid "Record mouse and keyboard input for later replay as a macro." -msgstr "" -"Aufzeichnen von Maus- und Tastatureingaben, um sie später als Makro " -"wiederzugeben." - -#: ../src/gui/MyFrameMain.cpp:1440 -msgid "Recording user input..." -msgstr "Aufzeichnung von Benutzereingaben..." - -#: ../src/gui/FrameMain.cpp:47 ../src/gui/FrameMain.cpp:131 -#: ../src/gui/MyFrameMain.cpp:414 ../src/gui/MyFrameMain.cpp:418 -#: ../src/gui/MyFrameMain.cpp:1470 ../src/gui/MyFrameMain.cpp:1474 -msgid "Replay Input" -msgstr "Eingaben abspielen" - -#: ../src/gui/FrameMain.cpp:131 -msgid "" -"Replay a recorded mouse and keyboard input macro. If is held down " -"while clicking this button, the macro is replayed in a loop." -msgstr "" -"Abspielen aufgezeichneter Maus- und Tastatureingabemakro. Wenn beim Klicken " -"auf diese Schaltfläche die Umschalttaste gedrückt gehalten wird, wird das " -"Makro in einer Schleife abgespielt." - -#: ../src/gui/MyFrameMain.cpp:425 ../src/gui/MyFrameMain.cpp:426 -msgid "Replay finished!" -msgstr "Abspielen aufgezeichneter Eingaben beendet!" - -#: ../src/gui/MyFrameMain.cpp:1528 -msgid "Replaying user input in loop..." -msgstr "Abspielen aufgezeichneter Eingaben in einer Schleife..." - -#: ../src/gui/MyFrameMain.cpp:1530 -msgid "Replaying user input..." -msgstr "Apspielen von Benutzereingaben..." - -#: ../src/gui/FrameMain.cpp:98 -msgid "Request a Feature / Report a Bug" -msgstr "Feature wünschen / Bug melden" - -#: ../src/gui/MyFrameMain.cpp:483 ../src/gui/MyFrameMain.cpp:493 -msgid "Reverse connection terminated." -msgstr "Eingehende Verbindung abgebrochen." - -#: ../src/gui/FrameMain.cpp:92 -msgid "S&top Sharing Window" -msgstr "Teilen eines Fensters &beenden" - -#: ../src/gui/FrameMain.cpp:43 -msgid "Save Statistics..." -msgstr "Statistik speichern..." - -#: ../src/gui/MyFrameLog.cpp:93 -msgid "Save log as..." -msgstr "Protokoll speichern unter..." - -#: ../src/gui/MyFrameMain.cpp:1401 -msgid "Save recorded input..." -msgstr "Aufgenommene Eingaben speichern..." - -#: ../src/gui/MyFrameMain.cpp:1320 -msgid "Save screenshot..." -msgstr "Bildschirmfoto speichern..." - -#: ../src/gui/MyFrameMain.cpp:765 -#, c-format -msgid "Saving %s statistics..." -msgstr "Speichern der %s-Statistik..." - -#: ../src/gui/MyFrameMain.cpp:1718 -msgid "Saving bookmark" -msgstr "Lesezeichen speichern" - -#: ../src/gui/DialogSettings.cpp:54 -msgid "" -"Set the multicast receive buffer size. Increasing the value may help against " -"packet loss. The size of this buffer is independent of the operating system." -msgstr "" -"Die Größe des Multicast-Empfangspuffers. Eine Vergrößerung des Wertes kann " -"gegen Paketverluste helfen. Die Größe dieses Puffers ist unabhängig vom " -"Betriebssystem." - -#: ../src/gui/DialogSettings.cpp:49 -msgid "" -"Set the multicast socket receive buffer size. Increasing the value may help " -"against packet loss. Note that the maximum value is operating system " -"dependent." -msgstr "" -"Die Größe des Empfangspuffers für den Multicast-Socket. Eine Erhöhung des " -"Wertes kann gegen Paketverluste helfen. Beachten Sie, dass der Maximalwert " -"betriebssystemabhängig ist." - -#: ../src/gui/MyFrameMain.cpp:2075 ../src/gui/MyFrameMain.cpp:2080 -msgid "Share a Window" -msgstr "Ein Fenster teilen" - -#: ../src/gui/MyFrameMain.cpp:2115 -#, c-format -msgid "Sharing window with %s" -msgstr "Fenster wird geteilt mit %s" - -#: ../src/gui/FrameMain.cpp:37 -msgid "Show &Log" -msgstr "Protokoll &anzeigen" - -#: ../src/gui/FrameMain.cpp:96 -msgid "Show Help." -msgstr "Hilfe anzeigen." - -#: ../src/gui/FrameMain.cpp:37 -msgid "Show detailed log." -msgstr "Detailliertes Protokoll anzeigen." - -#: ../src/gui/DialogSettings.cpp:46 -msgid "Socket Receive Buffer Size (kB):" -msgstr "Größe des Socket-Empfangspuffers (kB):" - -#: ../src/gui/FrameMain.cpp:69 -msgid "South" -msgstr "Süd" - -#: ../src/gui/FrameMain.cpp:60 ../src/gui/ViewerWindow.cpp:431 -msgid "Statistics" -msgstr "Statistiken" - -#: ../src/gui/FrameMain.cpp:110 -msgid "Status" -msgstr "Status" - -#: ../src/gui/MyFrameMain.cpp:1437 ../src/gui/MyFrameMain.cpp:1524 -msgid "Stop" -msgstr "Stopp" - -#: ../src/gui/MyFrameMain.cpp:1433 -msgid "Stop Recording" -msgstr "Aufnahme stoppen" - -#: ../src/gui/MyFrameMain.cpp:1520 -msgid "Stop Replaying" -msgstr "Abspielen stoppen" - -#: ../src/gui/FrameMain.cpp:92 -msgid "Stop Window Sharing." -msgstr "Teilen des Fenster beenden." - -#: ../src/gui/MyFrameMain.cpp:1381 -msgid "Stopped recording user input!" -msgstr "Aufzeichnung von Benutzereingaben gestoppt!" - -#: ../src/gui/MyFrameMain.cpp:1477 -msgid "Stopped replaying user input!" -msgstr "Abspielen der aufgezeichneten Eingaben gestoppt!" - -#: ../src/gui/MyFrameMain.cpp:2156 -#, c-format -msgid "Stopped sharing window with %s" -msgstr "Teilen des Fensters mit %s beendet" - -#: ../src/gui/MyFrameMain.cpp:1834 -msgid "Supported Encodings:" -msgstr "Unterstützte Kodierungen:" - -#: ../src/gui/MyFrameMain.cpp:1824 -msgid "Supported Security Types:" -msgstr "Unterstützte Sicherheitsarten:" - -#: ../src/gui/FrameMain.cpp:41 ../src/gui/FrameMain.cpp:126 -msgid "Take Screenshot" -msgstr "Screenshot aufnehmen" - -#: ../src/gui/FrameMain.cpp:126 -msgid "Take a screenshot of the current connection." -msgstr "Einen Screenshot der aktuellen Verbindung machen." - -#: ../src/gui/FrameMain.cpp:35 ../src/gui/FrameMain.cpp:122 -msgid "Terminate connection." -msgstr "Verbindung beenden." - -#: ../src/gui/MyFrameLog.cpp:94 -msgid "Text files (*.txt)|*.txt" -msgstr "Textdateien (*.txt)|*.txt" - -#: ../src/gui/MyFrameMain.cpp:2079 -msgid "" -"The MultiVNC window will be minimized and a cross-shaped cursor will appear. " -"Use it to select the window you want to share." -msgstr "" -"Das MultiVNC-Fenster wird minimiert und ein kreuzförmiger Cursor wird " -"angezeigt. Verwenden Sie ihn, um das Fenster auszuwählen, das Sie teilen " -"möchten." - -#: ../src/gui/DialogSettings.cpp:81 -msgid "Tight" -msgstr "Tight" - -#: ../src/gui/MyFrameMain.cpp:1957 ../src/gui/MyFrameMain.cpp:1958 -msgid "Timeout looking up IP address." -msgstr "Timeout bei der Suche nach der IP-Adresse." - -#: ../src/gui/MyFrameMain.cpp:1918 ../src/gui/MyFrameMain.cpp:1919 -msgid "Timeout looking up hostname." -msgstr "Timeout bei der Suche nach dem Hostnamen." - -#: ../src/gui/FrameMain.cpp:136 -msgid "Toggle 1:1 view, disabling all scaling." -msgstr "Zwischen 1:1-Ansicht und skalierter Ansicht wechseln." - -#: ../src/gui/FrameMain.cpp:134 -msgid "Toggle fullscreen view." -msgstr "Vollbildansicht umschalten." - -#: ../src/gui/FrameMain.cpp:54 -msgid "Toolbar" -msgstr "Symbolleiste" - -#: ../src/gui/DialogSettings.cpp:79 -msgid "Ultra" -msgstr "Ultra" - -#: ../src/gui/ViewerWindow.cpp:438 -msgid "Updates/s:" -msgstr "Updates/s:" - -#: ../src/gui/DialogSettings.cpp:90 -msgid "" -"Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. " -"Level 1 uses minimum of CPU time and achieves weak compression ratios, while " -"level 9 offers best compression but is slow in terms of CPU time consumption " -"on the server side. Use high levels with very slow network connections, and " -"low levels when working over high-speed LANs." -msgstr "" -"Komprimierungsstufe (0..9) für die Kodierungen \"Tight\" und \"Zlib\". Stufe " -"1 verbraucht ein Minimum an CPU-Zeit und erzielt schwache " -"Komprimierungsraten, während Stufe 9 die beste Komprimierung bietet, aber " -"langsam ist, was den CPU-Zeitverbrauch auf der Serverseite angeht. Verwenden " -"Sie hohe Stufen bei sehr langsamen Netzwerkverbindungen und niedrige Stufen, " -"wenn Sie über Hochgeschwindigkeits-LANs arbeiten." - -#: ../src/gui/DialogSettings.cpp:95 -msgid "" -"Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' " -"encodings. Quality level 0 denotes bad image quality but very impressive " -"compression ratios, while level 9 offers very good image quality at lower " -"compression ratios. Note that the \"tight\" encoder uses JPEG to encode only " -"those screen areas that look suitable for lossy compression, so quality " -"level 0 does not always mean unacceptable image quality." -msgstr "" -"Qualitätsstufe (0..9) für die Kodierungen \"Tight\" und \"ZYWRLE\". " -"Qualitätsstufe 0 bedeutet schlechte Bildqualität, aber hohe " -"Kompressionsraten, während Stufe 9 sehr gute Bildqualität bei niedrigeren " -"Kompressionsraten bietet. Beachten Sie, dass der \"Tight\"-Encoder nur die " -"Bildbereiche in JPEG kodiert, die für eine verlustbehaftete Komprimierung " -"geeignet erscheinen, so dass Qualitätsstufe 0 nicht immer eine inakzeptable " -"Bildqualität bedeutet." - -#: ../src/gui/DialogLogin.cpp:58 -msgid "Username or password must not empty" -msgstr "Benutzername oder Passwort dürfen nicht leer sein" - -#: ../src/gui/DialogLogin.cpp:15 -msgid "Username:" -msgstr "Benutzername:" - -#: ../src/gui/MyFrameMain.cpp:1826 -msgid "VNC Authentication" -msgstr "VNC-Authentifizierung" - -#: ../src/gui/MyFrameMain.cpp:1890 -msgid "VNC Server" -msgstr "VNC Server" - -#: ../src/gui/FrameMain.cpp:77 ../src/gui/FrameMain.cpp:136 -msgid "View 1:1" -msgstr "1:1-Ansicht" - -#: ../src/gui/DialogLogin.cpp:58 -msgid "Warning!" -msgstr "Warnung!" - -#: ../src/gui/FrameMain.cpp:67 -msgid "West" -msgstr "West" - -#: ../src/gui/FrameMain.cpp:94 ../src/gui/MyFrameMain.cpp:134 -#: ../src/gui/MyFrameMain.cpp:139 -msgid "Window &Sharing" -msgstr "Fenster &Teilen" - -#: ../src/gui/MyFrameMain.cpp:611 -msgid "" -"Window share helper exited without an associated connection. That should not " -"happen." -msgstr "" -"Der Dienst zum Teilen eines Fenster wurde ohne eine zugehörige Verbindung " -"beendet. Das sollte nicht passieren." - -#: ../src/gui/MyFrameMain.cpp:2106 -msgid "Window sharing helper execution failed." -msgstr "" -"Die Ausführung des Dienstes zum Teilen eines Fensters ist fehlgeschlagen." - -#: ../src/gui/MyFrameMain.cpp:623 -msgid "Window sharing stopped. Shared window was closed." -msgstr "" -"Das Teilen eines Fensters wurde beendet. Gemeinsames Fenster wurde " -"geschlossen." - -#: ../src/gui/MyFrameMain.cpp:617 -#, c-format -msgid "" -"Window sharing with %s stopped. Either the other side does not support " -"receiving windows or the window was closed there." -msgstr "" -"Teilen eines Fensters mit %s beendet. Entweder unterstützt die andere Seite " -"den Empfang von Fenstern nicht oder das Fenster wurde dort geschlossen." - -#: ../src/gui/DialogSettings.cpp:100 -msgid "Write VNC log to logfile (MultiVNC.log)" -msgstr "VNC-Protokoll in Logdatei schreiben (MultiVNC.log)" - -#: ../src/gui/DialogSettings.cpp:75 -msgid "ZRLE" -msgstr "ZRLE" - -#: ../src/gui/DialogSettings.cpp:77 -msgid "ZYWRLE" -msgstr "ZYWRLE" - -#: ../src/gui/DialogSettings.cpp:71 -msgid "Zlib" -msgstr "Zlib" - -#: ../src/gui/DialogSettings.cpp:73 -msgid "ZlibHex" -msgstr "ZlibHex" - -msgid "translator-credits" -msgstr "Christian Beier " - -#, fuzzy -#~| msgid "Window &Sharing" -#~ msgid "Window Sharing" -#~ msgstr "Fenster &Teilen" - -#, fuzzy -#~| msgid "Connect to a specific host." -#~ msgid "Connect to a specific host" -#~ msgstr "Verbindung zu einem bestimmten Host." - -#~ msgid "Connect to specific host" -#~ msgstr "Verbindung zu einem bestimmten Host" +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the MultiVNC package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: MultiVNC\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-06 18:13+0200\n" +"PO-Revision-Date: 2024-10-06 18:17+0200\n" +"Last-Translator: Christian Beier \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.2.2\n" + +#: ../src/gui/FrameMain.cpp:81 +msgid "&Add Bookmark" +msgstr "Lesezeichen &hinzufügen" + +#: ../src/gui/FrameMain.cpp:88 +msgid "&Bookmarks" +msgstr "&Lesezeichen" + +#: ../src/gui/FrameMain.cpp:31 +msgid "&Connect...\tCtrl-T" +msgstr "&Verbindung...\tStrg-T" + +#: ../src/gui/FrameMain.cpp:96 +msgid "&Contents" +msgstr "&Inhalt" + +#: ../src/gui/FrameMain.cpp:86 +msgid "&Delete Bookmark" +msgstr "Lesezeichen &löschen" + +#: ../src/gui/FrameMain.cpp:35 +msgid "&Disconnect" +msgstr "Verbindung &trennen" + +#: ../src/gui/FrameMain.cpp:84 +msgid "&Edit Bookmark" +msgstr "Lesezeichen &bearbeiten" + +#: ../src/gui/FrameMain.cpp:102 +msgid "&Help" +msgstr "&Hilfe" + +#: ../src/gui/FrameMain.cpp:33 +msgid "&Listen" +msgstr "&Eingehende Verbindung" + +#: ../src/gui/FrameMain.cpp:52 +msgid "&Machine" +msgstr "&Maschine" + +#: ../src/gui/FrameMain.cpp:90 +msgid "&Share a Window" +msgstr "&Ein Fenster teilen" + +#: ../src/gui/FrameMain.cpp:79 +msgid "&View" +msgstr "&Ansicht" + +#: ../src/gui/MyFrameMain.cpp:890 +msgid "(Reverse Connection)" +msgstr "(Eingehende Verbindung)" + +#: ../src/gui/MyFrameMain.cpp:1725 +msgid "A bookmark with this name already exists!" +msgstr "Ein Lesezeichen mit diesem Namen existiert bereits!" + +#: ../src/gui/MyFrameMain.cpp:310 +msgid "Authentication canceled." +msgstr "Anmeldung abgebrochen" + +#: ../src/gui/DialogSettings.cpp:102 +msgid "Autosave statistics on close" +msgstr "Statistiken beim Schließen automatisch speichern" + +#: ../src/gui/FrameMain.cpp:153 +msgid "Available VNC Servers" +msgstr "Verfügbare VNC-Server" + +#: ../src/gui/FrameMain.cpp:90 +msgid "Beam a window to the server." +msgstr "Ein Fenster auf den Server senden." + +#: ../src/gui/MyFrameMain.cpp:1187 +msgid "Bookmark" +msgstr "Lesezeichen" + +#: ../src/gui/MyFrameMain.cpp:1991 +#, c-format +msgid "Bookmark %s" +msgstr "Lesezeichen %s" + +#: ../src/gui/FrameMain.cpp:58 ../src/gui/FrameMain.cpp:158 +#: ../src/gui/MyFrameMain.cpp:1145 +msgid "Bookmarks" +msgstr "Lesezeichen" + +#: ../src/gui/MyFrameMain.cpp:1822 +msgid "Built with" +msgstr "Gebaut mit" + +#: ../src/gui/MyFrameMain.cpp:769 ../src/gui/MyFrameMain.cpp:1405 +#: ../src/gui/MyFrameMain.cpp:1492 +msgid "CSV files|*.csv" +msgstr "CSV-Dateien|*.csv" + +#: ../src/gui/DialogLogin.cpp:37 +msgid "Cancel" +msgstr "Abbrechen" + +#: ../src/gui/MyFrameMain.cpp:1713 +msgid "Cannot bookmark a reverse connection!" +msgstr "Für eine eingehende Verbindung kann kein Lesezeichen angelegt werden!" + +#: ../src/gui/FrameMain.cpp:39 +msgid "Change preferences." +msgstr "Einstellungen ändern." + +#: ../src/gui/DialogSettings.cpp:69 +msgid "CoRRE" +msgstr "CoRRE" + +#: ../src/gui/DialogSettings.cpp:87 +msgid "Compression level for 'Tight' and 'Zlib' encodings:" +msgstr "Komprimierungsgrad für die Kodierungen \"Tight\" und \"Zlib\":" + +#: ../src/gui/FrameMain.cpp:117 +msgid "Connect" +msgstr "Verbindung" + +#: ../src/gui/FrameMain.cpp:31 ../src/gui/FrameMain.cpp:117 +#: ../src/gui/MyFrameMain.cpp:1215 +msgid "Connect to a specific host." +msgstr "Verbindung zu einem bestimmten Host." + +#: ../src/gui/MyFrameMain.cpp:854 +#, c-format +msgid "Connecting to %s..." +msgstr "Verbindung zu %s..." + +#: ../src/gui/MyFrameMain.cpp:312 +msgid "Connection failed." +msgstr "Verbindung fehlgeschlagen." + +#: ../src/gui/MyFrameMain.cpp:1055 +msgid "Connection terminated." +msgstr "Verbindung abgebrochen." + +#: ../src/gui/MyFrameMain.cpp:377 +#, c-format +msgid "Connection to %s is now multicast." +msgstr "Verbindung zu %s ist jetzt Multicast." + +#: ../src/gui/MyFrameMain.cpp:388 +#, c-format +msgid "Connection to %s is now unicast." +msgstr "Verbindung zu %s ist jetzt Unicast." + +#: ../src/gui/MyFrameMain.cpp:386 +#, c-format +msgid "Connection to %s switched to unicast." +msgstr "Verbindung zu %s auf Unicast umgestellt." + +#: ../src/gui/MyFrameMain.cpp:481 ../src/gui/MyFrameMain.cpp:491 +#, c-format +msgid "Connection to %s:%s terminated." +msgstr "Verbindung zu %s:%s wurde beendet." + +#: ../src/gui/DialogSettings.cpp:27 +msgid "Connections" +msgstr "Verbindungen" + +#: ../src/gui/DialogSettings.cpp:36 +msgid "" +"Continously ask the server for updates instead of just asking after each " +"received server message. Use this on high latency links." +msgstr "" +"Den Server kontinuierlich nach Aktualisierungen fragen, anstatt nur nach " +"jeder empfangenen Server-Nachricht zu fragen. Verwenden Sie dies bei " +"Verbindungen mit hoher Latenz." + +#: ../src/gui/DialogSettings.cpp:33 +msgid "Continously request updates at the specified milisecond interval:" +msgstr "" +"Fordert fortlaufend Aktualisierungen im angegebenen Millisekunden-Intervall " +"an:" + +#: ../src/gui/DialogSettings.cpp:63 +msgid "CopyRect" +msgstr "CopyRect" + +#: ../src/gui/MyFrameMain.cpp:994 +msgid "Could not autosave statistics!" +msgstr "Die Statistiken konnten nicht automatisch gespeichert werden!" + +#: ../src/VNCConn.cpp:1114 +msgid "Could not create VNC listener thread!" +msgstr "Konnte keinen VNC-Listener-Thread erstellen!" + +#: ../src/VNCConn.cpp:1178 +msgid "Could not create VNC thread!" +msgstr "VNC-Thread konnte nicht erstellt werden!" + +#: ../src/MultiVNCApp.cpp:112 +msgid "Could not open config file!" +msgstr "Konnte die Konfigurationsdatei nicht öffnen!" + +#: ../src/gui/MyFrameMain.cpp:1504 +msgid "Could not open file!" +msgstr "Datei konnte nicht geöffnet werden!" + +#: ../src/gui/MyFrameMain.cpp:626 ../src/gui/MyFrameMain.cpp:627 +msgid "Could not run window share helper." +msgstr "Der Window Share Helper konnte nicht ausgeführt werden." + +#: ../src/MultiVNCApp.cpp:162 +msgid "Could not save config file!" +msgstr "Konnte die Konfigurationsdatei nicht speichern!" + +#: ../src/gui/MyFrameMain.cpp:781 ../src/gui/MyFrameMain.cpp:1416 +msgid "Could not save file!" +msgstr "Datei konnte nicht gespeichert werden!" + +#: ../src/gui/MyFrameMain.cpp:2107 +msgid "Could not share window, external program execution failed." +msgstr "" +"Fenster konnte nicht mit Server geteilt werden, externe Programmausführung " +"fehlgeschlagen." + +#: ../src/VNCConn.cpp:1122 +msgid "Could not start VNC listener thread!" +msgstr "Der VNC-Listener-Thread konnte nicht gestartet werden!" + +#: ../src/VNCConn.cpp:1186 +msgid "Could not start VNC thread!" +msgstr "VNC-Thread kann nicht gestartet werden!" + +#: ../src/gui/MyFrameMain.cpp:718 ../src/gui/MyFrameMain.cpp:727 +msgid "Credentials required..." +msgstr "Anmeldedaten erforderlich..." + +#: ../src/gui/MyFrameMain.cpp:1259 +msgid "Detailed VNC Log" +msgstr "Ausführliches VNC-Protokoll" + +#: ../src/gui/FrameMain.cpp:71 +msgid "Disabled" +msgstr "Aus" + +#: ../src/gui/FrameMain.cpp:122 +msgid "Disconnect" +msgstr "Verbindung trennen" + +#: ../src/gui/FrameMain.cpp:56 +msgid "Discovered Servers" +msgstr "Entdeckte Server" + +#: ../src/gui/FrameMain.cpp:65 +msgid "East" +msgstr "Ost" + +#: ../src/gui/FrameMain.cpp:73 +msgid "Edge Connector" +msgstr "Edge Connector" + +#: ../src/gui/MyFrameMain.cpp:1764 +msgid "Edit bookmark" +msgstr "Lesezeichen bearbeiten" + +#: ../src/gui/ViewerWindow.cpp:436 +msgid "Eff. KB/s:" +msgstr "Eff. KB/s:" + +#: ../src/gui/DialogSettings.cpp:40 +msgid "Enable Expedited Forwarding tagging for sent data" +msgstr "Expedited-Forward-Tagging für gesendete Daten aktivieren" + +#: ../src/gui/DialogSettings.cpp:31 +msgid "Enable FastRequest" +msgstr "FastRequest einschalten" + +#: ../src/gui/DialogSettings.cpp:44 +msgid "Enable MulticastVNC" +msgstr "MulticastVNC aktivieren" + +#: ../src/gui/DialogSettings.cpp:59 +msgid "Enabled Encodings" +msgstr "Aktivierte VNC-Kodierungen" + +#: ../src/gui/DialogSettings.cpp:57 +msgid "Encodings" +msgstr "VNC-Kodierungen" + +#: ../src/gui/MyFrameMain.cpp:1717 +msgid "Enter bookmark name:" +msgstr "Name des Lesezeichens eingeben:" + +#: ../src/gui/MyFrameMain.cpp:1214 +msgid "Enter host to connect to:" +msgstr "" +"Geben Sie den Host ein, mit dem eine Verbindung hergestellt werden soll:" + +#: ../src/gui/MyFrameMain.cpp:2075 +msgid "Enter name of window to share:" +msgstr "Geben Sie den Namen des freizugebenden Fensters ein:" + +#: ../src/gui/MyFrameMain.cpp:697 +msgid "Enter password:" +msgstr "Passwort eingeben:" + +#: ../src/gui/MyFrameMain.cpp:1160 +#, c-format +msgid "Error reading hostname of bookmark '%s'!" +msgstr "Fehler beim Lesen des Hostnamens des Lesezeichens \"%s\"!" + +#: ../src/gui/MyFrameMain.cpp:1167 +#, c-format +msgid "Error reading port of bookmark '%s'!" +msgstr "Fehler beim Lesen des Ports des Lesezeichens \"%s\"!" + +#: ../src/gui/FrameMain.cpp:50 +msgid "Exit MultiVNC." +msgstr "MultiVNC verlassen." + +#: ../src/gui/MyFrameMain.cpp:1739 +msgid "Failed to save credentials to the system secret store." +msgstr "" +"Die Anmeldeinformationen konnten nicht im sicheren Speicher des Systems " +"gespeichert werden." + +#: ../src/VNCConn.cpp:294 +#, c-format +msgid "Failure connecting to server at %s:%d!" +msgstr "Verbindung zu Server %s:%d fehlgeschlagen!" + +#: ../src/VNCConn.cpp:257 +msgid "Failure setting up framebuffer: wrong BPP!" +msgstr "Fehler bei der Einrichtung des Framebuffers: falsche BPP!" + +#: ../src/gui/DialogSettings.cpp:29 +msgid "FastRequest" +msgstr "FastRequest" + +#: ../src/gui/MyFrameMain.cpp:1428 +msgid "" +"From now on, all your mouse and keyboard input will be recorded. Click the " +"stop button to finish recording and save your input." +msgstr "" +"Von nun an werden alle Ihre Maus- und Tastatureingaben aufgezeichnet. " +"Klicken Sie auf die Schaltfläche \"Stopp\", um die Aufzeichnung zu beenden " +"und Ihre Eingaben zu speichern." + +#: ../src/gui/FrameMain.cpp:134 +msgid "Fullscreen" +msgstr "Vollbild" + +#: ../src/gui/FrameMain.cpp:75 +msgid "Fullscreen\tF11" +msgstr "Vollbild\tF11" + +#: ../src/MultiVNCApp.cpp:178 +msgid "GAAH! Got an unhandled exception! This should not happen." +msgstr "Unbehandelte Ausnahme erhalten! Dies sollte nicht passieren." + +#: ../src/gui/FrameMain.cpp:124 +msgid "Grab Keyboard" +msgstr "Alle Tastatureingaben abfangen" + +#: ../src/gui/DialogSettings.cpp:65 +msgid "Hextile" +msgstr "Hextile" + +#: ../src/gui/MyFrameMain.cpp:1863 +msgid "" +"If there are VNC servers advertising themselves via ZeroConf, you can select " +"a host in the'Available VNC Servers' list. Otherwise, use the 'Connect' " +"button or menu item.\n" +"\n" +"When connected, a blue or green icon on the tab label shows if you are " +"running in unicast or multicast mode." +msgstr "" +"Wenn es VNC-Server gibt, die sich über ZeroConf/Bonjour melden, können Sie " +"einen Host in der Liste \"Verfügbare VNC-Server\" auswählen. Andernfalls " +"verwenden Sie die Schaltfläche \"Verbinden\" oder den Menüpunkt " +"\"Verbinden\".\n" +"\n" +"Wenn Sie verbunden sind, zeigt ein blaues oder grünes Symbol auf der " +"Registerkarte an, ob Sie im Unicast- oder Multicast-Modus arbeiten." + +#: ../src/gui/MyFrameMain.cpp:514 +msgid "Incoming Connection." +msgstr "Eingehende Verbindung." + +#: ../src/gui/FrameMain.cpp:124 +msgid "" +"Intercept all keyboard input. Allows you to use special keys that would " +"otherwise be interpreted by the local computer." +msgstr "" +"Alle Tastatureingaben abfangen. Ermöglicht die Verwendung von Sondertasten, " +"die sonst vom lokalen Computer interpretiert werden würden." + +#: ../src/gui/ViewerWindow.cpp:440 +msgid "Latency ms:" +msgstr "Latenz ms:" + +#: ../src/gui/FrameMain.cpp:119 +msgid "Listen" +msgstr "Eingehende Verbindung" + +#: ../src/gui/FrameMain.cpp:33 ../src/gui/FrameMain.cpp:119 +msgid "Listen for an incoming connection." +msgstr "Eingehende Verbindung." + +#: ../src/gui/MyFrameMain.cpp:848 ../src/gui/MyFrameMain.cpp:926 +msgid "Listening on port" +msgstr "Eingehende Verbindung auf Port" + +#: ../src/gui/MyFrameMain.cpp:1488 +msgid "Load recorded input..." +msgstr "Aufgezeichnete Eingaben laden..." + +#: ../src/gui/DialogSettings.cpp:98 +msgid "Logging" +msgstr "Protokollierung" + +#: ../src/gui/DialogLogin.cpp:39 +msgid "Login" +msgstr "Anmelden" + +#: ../src/gui/MyFrameMain.cpp:1903 +msgid "Looking up host address..." +msgstr "Nachschlagen der Host-Adresse..." + +#: ../src/gui/ViewerWindow.cpp:442 +msgid "Loss Ratio:" +msgstr "Paketverlustverhältnis:" + +#: ../src/gui/DialogSettings.cpp:85 +msgid "Lossy Encodings Settings" +msgstr "Einstellungen für verlustbehaftete VNC-Kodierungen" + +#: ../src/gui/FrameMain.cpp:24 +msgid "MultiVNC" +msgstr "MultiVNC" + +#: ../src/MultiVNCApp.cpp:186 +msgid "MultiVNC crashed" +msgstr "MultiVNC ist abgestürzt" + +#: ../src/gui/MyFrameMain.cpp:1820 +msgid "MultiVNC is a cross-platform Multicast-enabled VNC client." +msgstr "" +"MultiVNC ist ein plattformübergreifender, Multicast-fähiger VNC-Client." + +#: ../src/gui/DialogSettings.cpp:42 +msgid "MulticastVNC" +msgstr "MulticastVNC" + +#: ../src/gui/MyFrameMain.cpp:1763 +msgid "New bookmark name:" +msgstr "Name des neuen Lesezeichens:" + +#: ../src/gui/MyFrameMain.cpp:1759 ../src/gui/MyFrameMain.cpp:1788 +msgid "No bookmark selected!" +msgstr "Kein Lesezeichen ausgewählt!" + +#: ../src/gui/MyFrameMain.cpp:1794 +msgid "No bookmark with this name!" +msgstr "Kein Lesezeichen mit diesem Namen!" + +#: ../src/gui/FrameMain.cpp:63 +msgid "North" +msgstr "Nord" + +#: ../src/gui/MyFrameMain.cpp:753 ../src/gui/MyFrameMain.cpp:1390 +msgid "Nothing to save!" +msgstr "Nichts zu speichern!" + +#: ../src/MultiVNCApp.cpp:186 +msgid "" +"Ouch! MultiVNC crashed. This should not happen. Do you want to generate a " +"bug report?" +msgstr "" +"MultiVNC ist abgestürzt. Das sollte nicht passieren. Möchten Sie einen " +"Fehlerbericht erstellen?" + +#: ../src/gui/MyFrameMain.cpp:1324 +msgid "PNG files|*.png" +msgstr "PNG-Dateien|*.png" + +#: ../src/gui/MyFrameMain.cpp:697 +msgid "Password required!" +msgstr "Passwort erforderlich!" + +#: ../src/gui/DialogLogin.cpp:23 +msgid "Password:" +msgstr "Kennwort:" + +#: ../src/gui/MyFrameMain.cpp:717 +#, c-format +msgid "Please enter password for user '%s'" +msgstr "Bitte Passwort für Benutzer '%s' eingeben" + +#: ../src/gui/MyFrameMain.cpp:1272 +msgid "Preferences" +msgstr "Einstellungen" + +#: ../src/gui/DialogSettings.cpp:92 +msgid "Quality level for 'Tight' and 'ZYWRLE' encoding:" +msgstr "Qualitätsstufe für die Kodierung \"Tight\" und \"ZYWRLE\":" + +#: ../src/gui/DialogSettings.cpp:38 +msgid "Quality of Service" +msgstr "Quality of Service" + +#: ../src/gui/DialogSettings.cpp:67 +msgid "RRE" +msgstr "RRE" + +#: ../src/gui/ViewerWindow.cpp:444 +msgid "Rcv Buffer:" +msgstr "Empfangspuffer:" + +#: ../src/gui/DialogSettings.cpp:51 +msgid "Receive Buffer Size (kB):" +msgstr "Größe des Empfangspuffers (kB):" + +#: ../src/gui/FrameMain.cpp:45 ../src/gui/FrameMain.cpp:129 +#: ../src/gui/MyFrameMain.cpp:1374 ../src/gui/MyFrameMain.cpp:1382 +msgid "Record Input" +msgstr "Eingaben aufzeichnen" + +#: ../src/gui/FrameMain.cpp:129 +msgid "Record mouse and keyboard input for later replay as a macro." +msgstr "" +"Aufzeichnen von Maus- und Tastatureingaben, um sie später als Makro " +"wiederzugeben." + +#: ../src/gui/MyFrameMain.cpp:1440 +msgid "Recording user input..." +msgstr "Aufzeichnung von Benutzereingaben..." + +#: ../src/gui/FrameMain.cpp:47 ../src/gui/FrameMain.cpp:131 +#: ../src/gui/MyFrameMain.cpp:414 ../src/gui/MyFrameMain.cpp:418 +#: ../src/gui/MyFrameMain.cpp:1470 ../src/gui/MyFrameMain.cpp:1474 +msgid "Replay Input" +msgstr "Eingaben abspielen" + +#: ../src/gui/FrameMain.cpp:131 +msgid "" +"Replay a recorded mouse and keyboard input macro. If is held down " +"while clicking this button, the macro is replayed in a loop." +msgstr "" +"Abspielen aufgezeichneter Maus- und Tastatureingabemakro. Wenn beim Klicken " +"auf diese Schaltfläche die Umschalttaste gedrückt gehalten wird, wird das " +"Makro in einer Schleife abgespielt." + +#: ../src/gui/MyFrameMain.cpp:425 ../src/gui/MyFrameMain.cpp:426 +msgid "Replay finished!" +msgstr "Abspielen aufgezeichneter Eingaben beendet!" + +#: ../src/gui/MyFrameMain.cpp:1528 +msgid "Replaying user input in loop..." +msgstr "Abspielen aufgezeichneter Eingaben in einer Schleife..." + +#: ../src/gui/MyFrameMain.cpp:1530 +msgid "Replaying user input..." +msgstr "Apspielen von Benutzereingaben..." + +#: ../src/gui/FrameMain.cpp:98 +msgid "Request a Feature / Report a Bug" +msgstr "Feature wünschen / Bug melden" + +#: ../src/gui/MyFrameMain.cpp:483 ../src/gui/MyFrameMain.cpp:493 +msgid "Reverse connection terminated." +msgstr "Eingehende Verbindung abgebrochen." + +#: ../src/gui/FrameMain.cpp:92 +msgid "S&top Sharing Window" +msgstr "Teilen eines Fensters &beenden" + +#: ../src/gui/FrameMain.cpp:43 +msgid "Save Statistics..." +msgstr "Statistik speichern..." + +#: ../src/gui/MyFrameLog.cpp:93 +msgid "Save log as..." +msgstr "Protokoll speichern unter..." + +#: ../src/gui/MyFrameMain.cpp:1401 +msgid "Save recorded input..." +msgstr "Aufgenommene Eingaben speichern..." + +#: ../src/gui/MyFrameMain.cpp:1320 +msgid "Save screenshot..." +msgstr "Bildschirmfoto speichern..." + +#: ../src/gui/MyFrameMain.cpp:765 +#, c-format +msgid "Saving %s statistics..." +msgstr "Speichern der %s-Statistik..." + +#: ../src/gui/MyFrameMain.cpp:1718 +msgid "Saving bookmark" +msgstr "Lesezeichen speichern" + +#: ../src/gui/DialogSettings.cpp:54 +msgid "" +"Set the multicast receive buffer size. Increasing the value may help against " +"packet loss. The size of this buffer is independent of the operating system." +msgstr "" +"Die Größe des Multicast-Empfangspuffers. Eine Vergrößerung des Wertes kann " +"gegen Paketverluste helfen. Die Größe dieses Puffers ist unabhängig vom " +"Betriebssystem." + +#: ../src/gui/DialogSettings.cpp:49 +msgid "" +"Set the multicast socket receive buffer size. Increasing the value may help " +"against packet loss. Note that the maximum value is operating system " +"dependent." +msgstr "" +"Die Größe des Empfangspuffers für den Multicast-Socket. Eine Erhöhung des " +"Wertes kann gegen Paketverluste helfen. Beachten Sie, dass der Maximalwert " +"betriebssystemabhängig ist." + +#: ../src/gui/MyFrameMain.cpp:2075 ../src/gui/MyFrameMain.cpp:2080 +msgid "Share a Window" +msgstr "Ein Fenster teilen" + +#: ../src/gui/MyFrameMain.cpp:2115 +#, c-format +msgid "Sharing window with %s" +msgstr "Fenster wird geteilt mit %s" + +#: ../src/gui/FrameMain.cpp:37 +msgid "Show &Log" +msgstr "Protokoll &anzeigen" + +#: ../src/gui/FrameMain.cpp:96 +msgid "Show Help." +msgstr "Hilfe anzeigen." + +#: ../src/gui/FrameMain.cpp:37 +msgid "Show detailed log." +msgstr "Detailliertes Protokoll anzeigen." + +#: ../src/gui/DialogSettings.cpp:46 +msgid "Socket Receive Buffer Size (kB):" +msgstr "Größe des Socket-Empfangspuffers (kB):" + +#: ../src/gui/FrameMain.cpp:69 +msgid "South" +msgstr "Süd" + +#: ../src/gui/FrameMain.cpp:60 ../src/gui/ViewerWindow.cpp:431 +msgid "Statistics" +msgstr "Statistiken" + +#: ../src/gui/FrameMain.cpp:110 +msgid "Status" +msgstr "Status" + +#: ../src/gui/MyFrameMain.cpp:1437 ../src/gui/MyFrameMain.cpp:1524 +msgid "Stop" +msgstr "Stopp" + +#: ../src/gui/MyFrameMain.cpp:1433 +msgid "Stop Recording" +msgstr "Aufnahme stoppen" + +#: ../src/gui/MyFrameMain.cpp:1520 +msgid "Stop Replaying" +msgstr "Abspielen stoppen" + +#: ../src/gui/FrameMain.cpp:92 +msgid "Stop Window Sharing." +msgstr "Teilen des Fenster beenden." + +#: ../src/gui/MyFrameMain.cpp:1381 +msgid "Stopped recording user input!" +msgstr "Aufzeichnung von Benutzereingaben gestoppt!" + +#: ../src/gui/MyFrameMain.cpp:1477 +msgid "Stopped replaying user input!" +msgstr "Abspielen der aufgezeichneten Eingaben gestoppt!" + +#: ../src/gui/MyFrameMain.cpp:2156 +#, c-format +msgid "Stopped sharing window with %s" +msgstr "Teilen des Fensters mit %s beendet" + +#: ../src/gui/MyFrameMain.cpp:1834 +msgid "Supported Encodings:" +msgstr "Unterstützte Kodierungen:" + +#: ../src/gui/MyFrameMain.cpp:1824 +msgid "Supported Security Types:" +msgstr "Unterstützte Sicherheitsarten:" + +#: ../src/gui/FrameMain.cpp:41 ../src/gui/FrameMain.cpp:126 +msgid "Take Screenshot" +msgstr "Screenshot aufnehmen" + +#: ../src/gui/FrameMain.cpp:126 +msgid "Take a screenshot of the current connection." +msgstr "Einen Screenshot der aktuellen Verbindung machen." + +#: ../src/gui/FrameMain.cpp:35 ../src/gui/FrameMain.cpp:122 +msgid "Terminate connection." +msgstr "Verbindung beenden." + +#: ../src/gui/MyFrameLog.cpp:94 +msgid "Text files (*.txt)|*.txt" +msgstr "Textdateien (*.txt)|*.txt" + +#: ../src/gui/MyFrameMain.cpp:2079 +msgid "" +"The MultiVNC window will be minimized and a cross-shaped cursor will appear. " +"Use it to select the window you want to share." +msgstr "" +"Das MultiVNC-Fenster wird minimiert und ein kreuzförmiger Cursor wird " +"angezeigt. Verwenden Sie ihn, um das Fenster auszuwählen, das Sie teilen " +"möchten." + +#: ../src/gui/DialogSettings.cpp:81 +msgid "Tight" +msgstr "Tight" + +#: ../src/gui/MyFrameMain.cpp:1957 ../src/gui/MyFrameMain.cpp:1958 +msgid "Timeout looking up IP address." +msgstr "Timeout bei der Suche nach der IP-Adresse." + +#: ../src/gui/MyFrameMain.cpp:1918 ../src/gui/MyFrameMain.cpp:1919 +msgid "Timeout looking up hostname." +msgstr "Timeout bei der Suche nach dem Hostnamen." + +#: ../src/gui/FrameMain.cpp:136 +msgid "Toggle 1:1 view, disabling all scaling." +msgstr "Zwischen 1:1-Ansicht und skalierter Ansicht wechseln." + +#: ../src/gui/FrameMain.cpp:134 +msgid "Toggle fullscreen view." +msgstr "Vollbildansicht umschalten." + +#: ../src/gui/FrameMain.cpp:54 +msgid "Toolbar" +msgstr "Symbolleiste" + +#: ../src/gui/DialogSettings.cpp:79 +msgid "Ultra" +msgstr "Ultra" + +#: ../src/gui/ViewerWindow.cpp:438 +msgid "Updates/s:" +msgstr "Updates/s:" + +#: ../src/gui/DialogSettings.cpp:90 +msgid "" +"Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. " +"Level 1 uses minimum of CPU time and achieves weak compression ratios, while " +"level 9 offers best compression but is slow in terms of CPU time consumption " +"on the server side. Use high levels with very slow network connections, and " +"low levels when working over high-speed LANs." +msgstr "" +"Komprimierungsstufe (0..9) für die Kodierungen \"Tight\" und \"Zlib\". Stufe " +"1 verbraucht ein Minimum an CPU-Zeit und erzielt schwache " +"Komprimierungsraten, während Stufe 9 die beste Komprimierung bietet, aber " +"langsam ist, was den CPU-Zeitverbrauch auf der Serverseite angeht. Verwenden " +"Sie hohe Stufen bei sehr langsamen Netzwerkverbindungen und niedrige Stufen, " +"wenn Sie über Hochgeschwindigkeits-LANs arbeiten." + +#: ../src/gui/DialogSettings.cpp:95 +msgid "" +"Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' " +"encodings. Quality level 0 denotes bad image quality but very impressive " +"compression ratios, while level 9 offers very good image quality at lower " +"compression ratios. Note that the \"tight\" encoder uses JPEG to encode only " +"those screen areas that look suitable for lossy compression, so quality " +"level 0 does not always mean unacceptable image quality." +msgstr "" +"Qualitätsstufe (0..9) für die Kodierungen \"Tight\" und \"ZYWRLE\". " +"Qualitätsstufe 0 bedeutet schlechte Bildqualität, aber hohe " +"Kompressionsraten, während Stufe 9 sehr gute Bildqualität bei niedrigeren " +"Kompressionsraten bietet. Beachten Sie, dass der \"Tight\"-Encoder nur die " +"Bildbereiche in JPEG kodiert, die für eine verlustbehaftete Komprimierung " +"geeignet erscheinen, so dass Qualitätsstufe 0 nicht immer eine inakzeptable " +"Bildqualität bedeutet." + +#: ../src/gui/DialogLogin.cpp:58 +msgid "Username or password must not empty" +msgstr "Benutzername oder Passwort dürfen nicht leer sein" + +#: ../src/gui/DialogLogin.cpp:15 +msgid "Username:" +msgstr "Benutzername:" + +#: ../src/gui/MyFrameMain.cpp:1826 +msgid "VNC Authentication" +msgstr "VNC-Authentifizierung" + +#: ../src/gui/MyFrameMain.cpp:1890 +msgid "VNC Server" +msgstr "VNC Server" + +#: ../src/gui/FrameMain.cpp:77 ../src/gui/FrameMain.cpp:136 +msgid "View 1:1" +msgstr "1:1-Ansicht" + +#: ../src/gui/DialogLogin.cpp:58 +msgid "Warning!" +msgstr "Warnung!" + +#: ../src/gui/FrameMain.cpp:67 +msgid "West" +msgstr "West" + +#: ../src/gui/FrameMain.cpp:94 ../src/gui/MyFrameMain.cpp:134 +#: ../src/gui/MyFrameMain.cpp:139 +msgid "Window &Sharing" +msgstr "Fenster &Teilen" + +#: ../src/gui/MyFrameMain.cpp:611 +msgid "" +"Window share helper exited without an associated connection. That should not " +"happen." +msgstr "" +"Der Dienst zum Teilen eines Fenster wurde ohne eine zugehörige Verbindung " +"beendet. Das sollte nicht passieren." + +#: ../src/gui/MyFrameMain.cpp:2106 +msgid "Window sharing helper execution failed." +msgstr "" +"Die Ausführung des Dienstes zum Teilen eines Fensters ist fehlgeschlagen." + +#: ../src/gui/MyFrameMain.cpp:623 +msgid "Window sharing stopped. Shared window was closed." +msgstr "" +"Das Teilen eines Fensters wurde beendet. Gemeinsames Fenster wurde " +"geschlossen." + +#: ../src/gui/MyFrameMain.cpp:617 +#, c-format +msgid "" +"Window sharing with %s stopped. Either the other side does not support " +"receiving windows or the window was closed there." +msgstr "" +"Teilen eines Fensters mit %s beendet. Entweder unterstützt die andere Seite " +"den Empfang von Fenstern nicht oder das Fenster wurde dort geschlossen." + +#: ../src/gui/DialogSettings.cpp:100 +msgid "Write VNC log to logfile (MultiVNC.log)" +msgstr "VNC-Protokoll in Logdatei schreiben (MultiVNC.log)" + +#: ../src/gui/DialogSettings.cpp:75 +msgid "ZRLE" +msgstr "ZRLE" + +#: ../src/gui/DialogSettings.cpp:77 +msgid "ZYWRLE" +msgstr "ZYWRLE" + +#: ../src/gui/DialogSettings.cpp:71 +msgid "Zlib" +msgstr "Zlib" + +#: ../src/gui/DialogSettings.cpp:73 +msgid "ZlibHex" +msgstr "ZlibHex" + +msgid "translator-credits" +msgstr "Christian Beier " + +#, fuzzy +#~| msgid "Window &Sharing" +#~ msgid "Window Sharing" +#~ msgstr "Fenster &Teilen" + +#, fuzzy +#~| msgid "Connect to a specific host." +#~ msgid "Connect to a specific host" +#~ msgstr "Verbindung zu einem bestimmten Host." + +#~ msgid "Connect to specific host" +#~ msgstr "Verbindung zu einem bestimmten Host" diff --git a/po/es.po b/po/es.po index dc1b83d4..16a2d05c 100644 --- a/po/es.po +++ b/po/es.po @@ -1,872 +1,872 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the MultiVNC package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: MultiVNC\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-06 18:13+0200\n" -"PO-Revision-Date: 2024-10-06 20:29+0200\n" -"Last-Translator: Christian Beier \n" -"Language-Team: \n" -"Language: es\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.2.2\n" - -#: ../src/gui/FrameMain.cpp:81 -msgid "&Add Bookmark" -msgstr "&Añadir marcador" - -#: ../src/gui/FrameMain.cpp:88 -msgid "&Bookmarks" -msgstr "&Marcadores" - -#: ../src/gui/FrameMain.cpp:31 -msgid "&Connect...\tCtrl-T" -msgstr "&Conectar...\tCtrl-T" - -#: ../src/gui/FrameMain.cpp:96 -msgid "&Contents" -msgstr "&Contenido" - -#: ../src/gui/FrameMain.cpp:86 -msgid "&Delete Bookmark" -msgstr "&Borrar marcador" - -#: ../src/gui/FrameMain.cpp:35 -msgid "&Disconnect" -msgstr "&Desconectar" - -#: ../src/gui/FrameMain.cpp:84 -msgid "&Edit Bookmark" -msgstr "&Editar marcador" - -#: ../src/gui/FrameMain.cpp:102 -msgid "&Help" -msgstr "&Ayuda" - -#: ../src/gui/FrameMain.cpp:33 -msgid "&Listen" -msgstr "Conexión &entrante" - -#: ../src/gui/FrameMain.cpp:52 -msgid "&Machine" -msgstr "&Máquina" - -#: ../src/gui/FrameMain.cpp:90 -msgid "&Share a Window" -msgstr "&Compartir una ventana" - -#: ../src/gui/FrameMain.cpp:79 -msgid "&View" -msgstr "Ver" - -#: ../src/gui/MyFrameMain.cpp:890 -msgid "(Reverse Connection)" -msgstr "(Conexión entrante)" - -#: ../src/gui/MyFrameMain.cpp:1725 -msgid "A bookmark with this name already exists!" -msgstr "Ya existe un marcador con este nombre." - -#: ../src/gui/MyFrameMain.cpp:310 -msgid "Authentication canceled." -msgstr "Autenticación cancelada." - -#: ../src/gui/DialogSettings.cpp:102 -msgid "Autosave statistics on close" -msgstr "Autoguardar estadísticas al cerrar" - -#: ../src/gui/FrameMain.cpp:153 -msgid "Available VNC Servers" -msgstr "Servidores descubiertos" - -#: ../src/gui/FrameMain.cpp:90 -msgid "Beam a window to the server." -msgstr "Transmita una ventana al servidor." - -#: ../src/gui/MyFrameMain.cpp:1187 -msgid "Bookmark" -msgstr "Marcador" - -#: ../src/gui/MyFrameMain.cpp:1991 -#, c-format -msgid "Bookmark %s" -msgstr "Marcador %s" - -#: ../src/gui/FrameMain.cpp:58 ../src/gui/FrameMain.cpp:158 -#: ../src/gui/MyFrameMain.cpp:1145 -msgid "Bookmarks" -msgstr "Marcadores" - -#: ../src/gui/MyFrameMain.cpp:1822 -msgid "Built with" -msgstr "Construido con" - -#: ../src/gui/MyFrameMain.cpp:769 ../src/gui/MyFrameMain.cpp:1405 -#: ../src/gui/MyFrameMain.cpp:1492 -msgid "CSV files|*.csv" -msgstr "Archivos CSV|*.csv" - -#: ../src/gui/DialogLogin.cpp:37 -msgid "Cancel" -msgstr "Cancelar" - -#: ../src/gui/MyFrameMain.cpp:1713 -msgid "Cannot bookmark a reverse connection!" -msgstr "No se puede marcar una conexión inversa." - -#: ../src/gui/FrameMain.cpp:39 -msgid "Change preferences." -msgstr "Cambiar preferencias." - -#: ../src/gui/DialogSettings.cpp:69 -msgid "CoRRE" -msgstr "CoRRE" - -#: ../src/gui/DialogSettings.cpp:87 -msgid "Compression level for 'Tight' and 'Zlib' encodings:" -msgstr "Nivel de compresión para las codificaciones 'Tight' y 'Zlib':" - -#: ../src/gui/FrameMain.cpp:117 -msgid "Connect" -msgstr "Conectar" - -#: ../src/gui/FrameMain.cpp:31 ../src/gui/FrameMain.cpp:117 -#: ../src/gui/MyFrameMain.cpp:1215 -msgid "Connect to a specific host." -msgstr "Conectarse a un host específico." - -#: ../src/gui/MyFrameMain.cpp:854 -#, c-format -msgid "Connecting to %s..." -msgstr "Conectando con %s..." - -#: ../src/gui/MyFrameMain.cpp:312 -msgid "Connection failed." -msgstr "Conexión fallida." - -#: ../src/gui/MyFrameMain.cpp:1055 -msgid "Connection terminated." -msgstr "Conexión terminada." - -#: ../src/gui/MyFrameMain.cpp:377 -#, c-format -msgid "Connection to %s is now multicast." -msgstr "La conexión a %s es ahora multidifusión." - -#: ../src/gui/MyFrameMain.cpp:388 -#, c-format -msgid "Connection to %s is now unicast." -msgstr "La conexión a %s es ahora unidifusión." - -#: ../src/gui/MyFrameMain.cpp:386 -#, c-format -msgid "Connection to %s switched to unicast." -msgstr "Conexión a %s cambiada a unidifusión." - -#: ../src/gui/MyFrameMain.cpp:481 ../src/gui/MyFrameMain.cpp:491 -#, c-format -msgid "Connection to %s:%s terminated." -msgstr "Conexión a %s:%s terminada." - -#: ../src/gui/DialogSettings.cpp:27 -msgid "Connections" -msgstr "Conexiones" - -#: ../src/gui/DialogSettings.cpp:36 -msgid "" -"Continously ask the server for updates instead of just asking after each " -"received server message. Use this on high latency links." -msgstr "" -"Pregunta continuamente al servidor por actualizaciones en lugar de preguntar " -"sólo después de cada mensaje recibido del servidor. Utilícelo en enlaces de " -"alta latencia." - -#: ../src/gui/DialogSettings.cpp:33 -msgid "Continously request updates at the specified milisecond interval:" -msgstr "" -"Solicita continuamente actualizaciones en el intervalo de milisegundos " -"especificado:" - -#: ../src/gui/DialogSettings.cpp:63 -msgid "CopyRect" -msgstr "CopyRect" - -#: ../src/gui/MyFrameMain.cpp:994 -msgid "Could not autosave statistics!" -msgstr "No se han podido autoguardar las estadísticas." - -#: ../src/VNCConn.cpp:1114 -msgid "Could not create VNC listener thread!" -msgstr "No se ha podido crear un hilo de escucha VNC." - -#: ../src/VNCConn.cpp:1178 -msgid "Could not create VNC thread!" -msgstr "No se ha podido crear el hilo VNC." - -#: ../src/MultiVNCApp.cpp:112 -msgid "Could not open config file!" -msgstr "No se ha podido abrir el archivo de configuración." - -#: ../src/gui/MyFrameMain.cpp:1504 -msgid "Could not open file!" -msgstr "No se ha podido abrir el archivo." - -#: ../src/gui/MyFrameMain.cpp:626 ../src/gui/MyFrameMain.cpp:627 -msgid "Could not run window share helper." -msgstr "No se ha podido ejecutar el ayudante para compartir ventanas." - -#: ../src/MultiVNCApp.cpp:162 -msgid "Could not save config file!" -msgstr "No se ha podido guardar el archivo de configuración." - -#: ../src/gui/MyFrameMain.cpp:781 ../src/gui/MyFrameMain.cpp:1416 -msgid "Could not save file!" -msgstr "No se ha podido guardar el archivo." - -#: ../src/gui/MyFrameMain.cpp:2107 -msgid "Could not share window, external program execution failed." -msgstr "" -"No se pudo compartir la ventana, falló la ejecución del programa externo." - -#: ../src/VNCConn.cpp:1122 -msgid "Could not start VNC listener thread!" -msgstr "No se ha podido iniciar el hilo de escucha VNC." - -#: ../src/VNCConn.cpp:1186 -msgid "Could not start VNC thread!" -msgstr "No se ha podido iniciar el hilo VNC" - -#: ../src/gui/MyFrameMain.cpp:718 ../src/gui/MyFrameMain.cpp:727 -msgid "Credentials required..." -msgstr "Credenciales requeridas..." - -#: ../src/gui/MyFrameMain.cpp:1259 -msgid "Detailed VNC Log" -msgstr "Registro VNC detallado" - -#: ../src/gui/FrameMain.cpp:71 -msgid "Disabled" -msgstr "Desactivada" - -#: ../src/gui/FrameMain.cpp:122 -msgid "Disconnect" -msgstr "Desconecte" - -#: ../src/gui/FrameMain.cpp:56 -msgid "Discovered Servers" -msgstr "Servidores descubiertos" - -#: ../src/gui/FrameMain.cpp:65 -msgid "East" -msgstr "Este" - -#: ../src/gui/FrameMain.cpp:73 -msgid "Edge Connector" -msgstr "Conector de borde" - -#: ../src/gui/MyFrameMain.cpp:1764 -msgid "Edit bookmark" -msgstr "Editar marcador" - -#: ../src/gui/ViewerWindow.cpp:436 -msgid "Eff. KB/s:" -msgstr "Eff. KB/s:" - -#: ../src/gui/DialogSettings.cpp:40 -msgid "Enable Expedited Forwarding tagging for sent data" -msgstr "Activar el etiquetado Expedited Forwarding para los datos enviados" - -#: ../src/gui/DialogSettings.cpp:31 -msgid "Enable FastRequest" -msgstr "Activar FastRequest" - -#: ../src/gui/DialogSettings.cpp:44 -msgid "Enable MulticastVNC" -msgstr "Activar MulticastVNC" - -#: ../src/gui/DialogSettings.cpp:59 -msgid "Enabled Encodings" -msgstr "Codificaciones activadas" - -#: ../src/gui/DialogSettings.cpp:57 -msgid "Encodings" -msgstr "Codificaciones" - -#: ../src/gui/MyFrameMain.cpp:1717 -msgid "Enter bookmark name:" -msgstr "Introduzca el nombre del marcador:" - -#: ../src/gui/MyFrameMain.cpp:1214 -msgid "Enter host to connect to:" -msgstr "Introduzca el host al que desea conectarse:" - -#: ../src/gui/MyFrameMain.cpp:2075 -msgid "Enter name of window to share:" -msgstr "Introduzca el nombre de la ventana que desea compartir:" - -#: ../src/gui/MyFrameMain.cpp:697 -msgid "Enter password:" -msgstr "Introduce la contraseña:" - -#: ../src/gui/MyFrameMain.cpp:1160 -#, c-format -msgid "Error reading hostname of bookmark '%s'!" -msgstr "¡Error al leer el nombre de host del marcador '%s'!" - -#: ../src/gui/MyFrameMain.cpp:1167 -#, c-format -msgid "Error reading port of bookmark '%s'!" -msgstr "¡Error al leer el puerto del marcador '%s'!" - -#: ../src/gui/FrameMain.cpp:50 -msgid "Exit MultiVNC." -msgstr "Salga de MultiVNC." - -#: ../src/gui/MyFrameMain.cpp:1739 -msgid "Failed to save credentials to the system secret store." -msgstr "Error al guardar las credenciales en el almacén secreto del sistema." - -#: ../src/VNCConn.cpp:294 -#, c-format -msgid "Failure connecting to server at %s:%d!" -msgstr "¡Fallo al conectar con el servidor en %s:%d!" - -#: ../src/VNCConn.cpp:257 -msgid "Failure setting up framebuffer: wrong BPP!" -msgstr "Fallo al configurar el framebuffer: ¡BPP incorrecto!" - -#: ../src/gui/DialogSettings.cpp:29 -msgid "FastRequest" -msgstr "FastRequest" - -#: ../src/gui/MyFrameMain.cpp:1428 -msgid "" -"From now on, all your mouse and keyboard input will be recorded. Click the " -"stop button to finish recording and save your input." -msgstr "" -"A partir de ahora, todas las entradas de ratón y teclado se grabarán. Haga " -"clic en el botón de parada para finalizar la grabación y guardar sus " -"entradas." - -#: ../src/gui/FrameMain.cpp:134 -msgid "Fullscreen" -msgstr "Pantalla completa" - -#: ../src/gui/FrameMain.cpp:75 -msgid "Fullscreen\tF11" -msgstr "Pantalla completa\tF11" - -#: ../src/MultiVNCApp.cpp:178 -msgid "GAAH! Got an unhandled exception! This should not happen." -msgstr "¡Tengo una excepción no manejada! Esto no debería ocurrir." - -#: ../src/gui/FrameMain.cpp:124 -msgid "Grab Keyboard" -msgstr "Agarrar teclado" - -#: ../src/gui/DialogSettings.cpp:65 -msgid "Hextile" -msgstr "Hextile" - -#: ../src/gui/MyFrameMain.cpp:1863 -msgid "" -"If there are VNC servers advertising themselves via ZeroConf, you can select " -"a host in the'Available VNC Servers' list. Otherwise, use the 'Connect' " -"button or menu item.\n" -"\n" -"When connected, a blue or green icon on the tab label shows if you are " -"running in unicast or multicast mode." -msgstr "" -"Si hay servidores VNC que se anuncian a través de ZeroConf, puede " -"seleccionar un host en la lista 'Servidores VNC disponibles'. De lo " -"contrario, utilice el botón \"Conectar\" o el elemento de menú.\n" -"\n" -"Cuando está conectado, un icono azul o verde en la etiqueta de la pestaña " -"muestra si se está ejecutando en modo unidifusión o multidifusión." - -#: ../src/gui/MyFrameMain.cpp:514 -msgid "Incoming Connection." -msgstr "Conexión entrante." - -#: ../src/gui/FrameMain.cpp:124 -msgid "" -"Intercept all keyboard input. Allows you to use special keys that would " -"otherwise be interpreted by the local computer." -msgstr "" -"Intercepta todas las entradas de teclado. Permite utilizar teclas especiales " -"que, de otro modo, serían interpretadas por el ordenador local." - -#: ../src/gui/ViewerWindow.cpp:440 -msgid "Latency ms:" -msgstr "Latencia ms:" - -#: ../src/gui/FrameMain.cpp:119 -msgid "Listen" -msgstr "Conexión entrante" - -#: ../src/gui/FrameMain.cpp:33 ../src/gui/FrameMain.cpp:119 -msgid "Listen for an incoming connection." -msgstr "Espere una conexión entrante." - -#: ../src/gui/MyFrameMain.cpp:848 ../src/gui/MyFrameMain.cpp:926 -msgid "Listening on port" -msgstr "Conexión entrante en el puerto" - -#: ../src/gui/MyFrameMain.cpp:1488 -msgid "Load recorded input..." -msgstr "Carga la entrada grabada..." - -#: ../src/gui/DialogSettings.cpp:98 -msgid "Logging" -msgstr "Registro" - -#: ../src/gui/DialogLogin.cpp:39 -msgid "Login" -msgstr "Inicio de sesión" - -#: ../src/gui/MyFrameMain.cpp:1903 -msgid "Looking up host address..." -msgstr "Buscando la dirección del host..." - -#: ../src/gui/ViewerWindow.cpp:442 -msgid "Loss Ratio:" -msgstr "Ratio de pérdidas:" - -#: ../src/gui/DialogSettings.cpp:85 -msgid "Lossy Encodings Settings" -msgstr "Ajustes de codificación con pérdidas" - -#: ../src/gui/FrameMain.cpp:24 -msgid "MultiVNC" -msgstr "MultiVNC" - -#: ../src/MultiVNCApp.cpp:186 -msgid "MultiVNC crashed" -msgstr "MultiVNC se bloquea" - -#: ../src/gui/MyFrameMain.cpp:1820 -msgid "MultiVNC is a cross-platform Multicast-enabled VNC client." -msgstr "" -"MultiVNC es un cliente VNC multiplataforma compatible con multidifusión." - -#: ../src/gui/DialogSettings.cpp:42 -msgid "MulticastVNC" -msgstr "MulticastVNC" - -#: ../src/gui/MyFrameMain.cpp:1763 -msgid "New bookmark name:" -msgstr "Nuevo nombre de marcador:" - -#: ../src/gui/MyFrameMain.cpp:1759 ../src/gui/MyFrameMain.cpp:1788 -msgid "No bookmark selected!" -msgstr "No se ha seleccionado ningún marcador." - -#: ../src/gui/MyFrameMain.cpp:1794 -msgid "No bookmark with this name!" -msgstr "¡Ningún marcador con este nombre!" - -#: ../src/gui/FrameMain.cpp:63 -msgid "North" -msgstr "Norte" - -#: ../src/gui/MyFrameMain.cpp:753 ../src/gui/MyFrameMain.cpp:1390 -msgid "Nothing to save!" -msgstr "¡No hay nada que salvar!" - -#: ../src/MultiVNCApp.cpp:186 -msgid "" -"Ouch! MultiVNC crashed. This should not happen. Do you want to generate a " -"bug report?" -msgstr "" -"¡Ay! MultiVNC se estrelló. Esto no debería ocurrir. ¿Quiere generar un " -"informe de error?" - -#: ../src/gui/MyFrameMain.cpp:1324 -msgid "PNG files|*.png" -msgstr "Archivos PNG|*.png" - -#: ../src/gui/MyFrameMain.cpp:697 -msgid "Password required!" -msgstr "¡Contraseña obligatoria!" - -#: ../src/gui/DialogLogin.cpp:23 -msgid "Password:" -msgstr "Contraseña:" - -#: ../src/gui/MyFrameMain.cpp:717 -#, c-format -msgid "Please enter password for user '%s'" -msgstr "Introduzca la contraseña del usuario '%s'" - -#: ../src/gui/MyFrameMain.cpp:1272 -msgid "Preferences" -msgstr "Preferencias" - -#: ../src/gui/DialogSettings.cpp:92 -msgid "Quality level for 'Tight' and 'ZYWRLE' encoding:" -msgstr "Nivel de calidad para la codificación 'Tight' y 'ZYWRLE':" - -#: ../src/gui/DialogSettings.cpp:38 -msgid "Quality of Service" -msgstr "Calidad del servicio" - -#: ../src/gui/DialogSettings.cpp:67 -msgid "RRE" -msgstr "RRE" - -#: ../src/gui/ViewerWindow.cpp:444 -msgid "Rcv Buffer:" -msgstr "Búfer Rcpt:" - -#: ../src/gui/DialogSettings.cpp:51 -msgid "Receive Buffer Size (kB):" -msgstr "Tamaño del búfer de recepción (kB):" - -#: ../src/gui/FrameMain.cpp:45 ../src/gui/FrameMain.cpp:129 -#: ../src/gui/MyFrameMain.cpp:1374 ../src/gui/MyFrameMain.cpp:1382 -msgid "Record Input" -msgstr "Registro de entrada" - -#: ../src/gui/FrameMain.cpp:129 -msgid "Record mouse and keyboard input for later replay as a macro." -msgstr "" -"Graba la entrada del mouse y del teclado para reproducirla más tarde como " -"una macro." - -#: ../src/gui/MyFrameMain.cpp:1440 -msgid "Recording user input..." -msgstr "Grabación de la entrada del usuario..." - -#: ../src/gui/FrameMain.cpp:47 ../src/gui/FrameMain.cpp:131 -#: ../src/gui/MyFrameMain.cpp:414 ../src/gui/MyFrameMain.cpp:418 -#: ../src/gui/MyFrameMain.cpp:1470 ../src/gui/MyFrameMain.cpp:1474 -msgid "Replay Input" -msgstr "Repetición de la entrada" - -#: ../src/gui/FrameMain.cpp:131 -msgid "" -"Replay a recorded mouse and keyboard input macro. If is held down " -"while clicking this button, the macro is replayed in a loop." -msgstr "" -"Reproduce una macro de entrada de teclado y mouse grabada. Si se mantiene " -"presionado mientras se hace clic en este botón, la macro se " -"reproduce en un bucle." - -#: ../src/gui/MyFrameMain.cpp:425 ../src/gui/MyFrameMain.cpp:426 -msgid "Replay finished!" -msgstr "¡Repetición finalizada!" - -#: ../src/gui/MyFrameMain.cpp:1528 -msgid "Replaying user input in loop..." -msgstr "Reproducir la entrada del usuario en bucle..." - -#: ../src/gui/MyFrameMain.cpp:1530 -msgid "Replaying user input..." -msgstr "Reproducir la entrada del usuario..." - -#: ../src/gui/FrameMain.cpp:98 -msgid "Request a Feature / Report a Bug" -msgstr "Solicitar una función / Informar de un error" - -#: ../src/gui/MyFrameMain.cpp:483 ../src/gui/MyFrameMain.cpp:493 -msgid "Reverse connection terminated." -msgstr "Conexión entrante terminada." - -#: ../src/gui/FrameMain.cpp:92 -msgid "S&top Sharing Window" -msgstr "Finalizar Compartir Ventana" - -#: ../src/gui/FrameMain.cpp:43 -msgid "Save Statistics..." -msgstr "Guardar estadísticas..." - -#: ../src/gui/MyFrameLog.cpp:93 -msgid "Save log as..." -msgstr "Guardar registro como..." - -#: ../src/gui/MyFrameMain.cpp:1401 -msgid "Save recorded input..." -msgstr "Guardar la entrada grabada..." - -#: ../src/gui/MyFrameMain.cpp:1320 -msgid "Save screenshot..." -msgstr "Guardar captura de pantalla..." - -#: ../src/gui/MyFrameMain.cpp:765 -#, c-format -msgid "Saving %s statistics..." -msgstr "Guardando estadísticas %s..." - -#: ../src/gui/MyFrameMain.cpp:1718 -msgid "Saving bookmark" -msgstr "Guardar marcador" - -#: ../src/gui/DialogSettings.cpp:54 -msgid "" -"Set the multicast receive buffer size. Increasing the value may help against " -"packet loss. The size of this buffer is independent of the operating system." -msgstr "" -"Establece el tamaño del búfer de recepción de multidifusión. Aumentar el " -"valor puede ayudar contra la pérdida de paquetes. El tamaño de este búfer es " -"independiente del sistema operativo." - -#: ../src/gui/DialogSettings.cpp:49 -msgid "" -"Set the multicast socket receive buffer size. Increasing the value may help " -"against packet loss. Note that the maximum value is operating system " -"dependent." -msgstr "" -"Establece el tamaño del búfer de recepción del socket multidifusión. " -"Aumentar el valor puede ayudar contra la pérdida de paquetes. Tenga en " -"cuenta que el valor máximo depende del sistema operativo." - -#: ../src/gui/MyFrameMain.cpp:2075 ../src/gui/MyFrameMain.cpp:2080 -msgid "Share a Window" -msgstr "Compartir una ventana" - -#: ../src/gui/MyFrameMain.cpp:2115 -#, c-format -msgid "Sharing window with %s" -msgstr "Ventana compartida con %s" - -#: ../src/gui/FrameMain.cpp:37 -msgid "Show &Log" -msgstr "Mostrar ®istro" - -#: ../src/gui/FrameMain.cpp:96 -msgid "Show Help." -msgstr "Mostrar Ayuda." - -#: ../src/gui/FrameMain.cpp:37 -msgid "Show detailed log." -msgstr "Mostrar registro detallado." - -#: ../src/gui/DialogSettings.cpp:46 -msgid "Socket Receive Buffer Size (kB):" -msgstr "Tamaño del búfer de recepción del socket (kB):" - -#: ../src/gui/FrameMain.cpp:69 -msgid "South" -msgstr "Sur" - -#: ../src/gui/FrameMain.cpp:60 ../src/gui/ViewerWindow.cpp:431 -msgid "Statistics" -msgstr "Estadísticas" - -#: ../src/gui/FrameMain.cpp:110 -msgid "Status" -msgstr "Estado" - -#: ../src/gui/MyFrameMain.cpp:1437 ../src/gui/MyFrameMain.cpp:1524 -msgid "Stop" -msgstr "Finalizar" - -#: ../src/gui/MyFrameMain.cpp:1433 -msgid "Stop Recording" -msgstr "Detener grabación" - -#: ../src/gui/MyFrameMain.cpp:1520 -msgid "Stop Replaying" -msgstr "Detener la reproducción" - -#: ../src/gui/FrameMain.cpp:92 -msgid "Stop Window Sharing." -msgstr "Detener el uso compartido de ventanas." - -#: ../src/gui/MyFrameMain.cpp:1381 -msgid "Stopped recording user input!" -msgstr "Se ha dejado de grabar la entrada del usuario." - -#: ../src/gui/MyFrameMain.cpp:1477 -msgid "Stopped replaying user input!" -msgstr "Se ha detenido la reproducción de la entrada del usuario." - -#: ../src/gui/MyFrameMain.cpp:2156 -#, c-format -msgid "Stopped sharing window with %s" -msgstr "Ventana compartida detenida con %s" - -#: ../src/gui/MyFrameMain.cpp:1834 -msgid "Supported Encodings:" -msgstr "Codificaciones admitidas:" - -#: ../src/gui/MyFrameMain.cpp:1824 -msgid "Supported Security Types:" -msgstr "Tipos de seguridad admitidos:" - -#: ../src/gui/FrameMain.cpp:41 ../src/gui/FrameMain.cpp:126 -msgid "Take Screenshot" -msgstr "Hacer una captura de pantalla" - -#: ../src/gui/FrameMain.cpp:126 -msgid "Take a screenshot of the current connection." -msgstr "Toma una captura de pantalla de la conexión actual." - -#: ../src/gui/FrameMain.cpp:35 ../src/gui/FrameMain.cpp:122 -msgid "Terminate connection." -msgstr "Finaliza la conexión." - -#: ../src/gui/MyFrameLog.cpp:94 -msgid "Text files (*.txt)|*.txt" -msgstr "Archivos de texto (*.txt)|*.txt" - -#: ../src/gui/MyFrameMain.cpp:2079 -msgid "" -"The MultiVNC window will be minimized and a cross-shaped cursor will appear. " -"Use it to select the window you want to share." -msgstr "" -"La ventana MultiVNC se minimizará y aparecerá un cursor en forma de cruz. " -"Utilícelo para seleccionar la ventana que desea compartir." - -#: ../src/gui/DialogSettings.cpp:81 -msgid "Tight" -msgstr "Tight" - -#: ../src/gui/MyFrameMain.cpp:1957 ../src/gui/MyFrameMain.cpp:1958 -msgid "Timeout looking up IP address." -msgstr "Tiempo de espera para buscar la dirección IP." - -#: ../src/gui/MyFrameMain.cpp:1918 ../src/gui/MyFrameMain.cpp:1919 -msgid "Timeout looking up hostname." -msgstr "Tiempo de espera para buscar el nombre de host." - -#: ../src/gui/FrameMain.cpp:136 -msgid "Toggle 1:1 view, disabling all scaling." -msgstr "Cambie entre vista 1:1 y vista escalada." - -#: ../src/gui/FrameMain.cpp:134 -msgid "Toggle fullscreen view." -msgstr "Alterna la vista de pantalla completa." - -#: ../src/gui/FrameMain.cpp:54 -msgid "Toolbar" -msgstr "Barra de herramientas" - -#: ../src/gui/DialogSettings.cpp:79 -msgid "Ultra" -msgstr "Ultra" - -#: ../src/gui/ViewerWindow.cpp:438 -msgid "Updates/s:" -msgstr "Actualizaciónes/s:" - -#: ../src/gui/DialogSettings.cpp:90 -msgid "" -"Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. " -"Level 1 uses minimum of CPU time and achieves weak compression ratios, while " -"level 9 offers best compression but is slow in terms of CPU time consumption " -"on the server side. Use high levels with very slow network connections, and " -"low levels when working over high-speed LANs." -msgstr "" -"Utilice el nivel de compresión especificado (0..9) para las codificaciones " -"'Tight' y 'Zlib'. El nivel 1 consume un mínimo de tiempo de CPU y logra " -"ratios de compresión débiles, mientras que el nivel 9 ofrece la mejor " -"compresión pero es lento en términos de consumo de tiempo de CPU en el lado " -"del servidor. Utilice niveles altos con conexiones de red muy lentas, y " -"niveles bajos cuando trabaje sobre redes LAN de alta velocidad." - -#: ../src/gui/DialogSettings.cpp:95 -msgid "" -"Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' " -"encodings. Quality level 0 denotes bad image quality but very impressive " -"compression ratios, while level 9 offers very good image quality at lower " -"compression ratios. Note that the \"tight\" encoder uses JPEG to encode only " -"those screen areas that look suitable for lossy compression, so quality " -"level 0 does not always mean unacceptable image quality." -msgstr "" -"Utilice el nivel de calidad especificado (0..9) para las codificaciones " -"'Tight' y 'ZYWRLE'. El nivel de calidad 0 denota una mala calidad de imagen " -"pero unos ratios de compresión muy impresionantes, mientras que el nivel 9 " -"ofrece una calidad de imagen muy buena con ratios de compresión más bajos. " -"Tenga en cuenta que el codificador \"tight\" utiliza JPEG para codificar " -"sólo las zonas de la pantalla que parecen adecuadas para la compresión con " -"pérdidas, por lo que el nivel de calidad 0 no siempre significa una calidad " -"de imagen inaceptable." - -#: ../src/gui/DialogLogin.cpp:58 -msgid "Username or password must not empty" -msgstr "El nombre de usuario o la contraseña no deben estar vacíos" - -#: ../src/gui/DialogLogin.cpp:15 -msgid "Username:" -msgstr "Nombre de usuario:" - -#: ../src/gui/MyFrameMain.cpp:1826 -msgid "VNC Authentication" -msgstr "Autenticación VNC" - -#: ../src/gui/MyFrameMain.cpp:1890 -msgid "VNC Server" -msgstr "Servidor VNC" - -#: ../src/gui/FrameMain.cpp:77 ../src/gui/FrameMain.cpp:136 -msgid "View 1:1" -msgstr "Vista 1:1" - -#: ../src/gui/DialogLogin.cpp:58 -msgid "Warning!" -msgstr "¡Atención!" - -#: ../src/gui/FrameMain.cpp:67 -msgid "West" -msgstr "Oeste" - -#: ../src/gui/FrameMain.cpp:94 ../src/gui/MyFrameMain.cpp:134 -#: ../src/gui/MyFrameMain.cpp:139 -msgid "Window &Sharing" -msgstr "&Compartir Ventana" - -#: ../src/gui/MyFrameMain.cpp:611 -msgid "" -"Window share helper exited without an associated connection. That should not " -"happen." -msgstr "" -"El asistente para compartir ventanas salió sin una conexión asociada. Eso no " -"debería suceder." - -#: ../src/gui/MyFrameMain.cpp:2106 -msgid "Window sharing helper execution failed." -msgstr "Fallo en la ejecución del asistente de compartición de ventanas." - -#: ../src/gui/MyFrameMain.cpp:623 -msgid "Window sharing stopped. Shared window was closed." -msgstr "La ventana compartida se detuvo. Se ha cerrado la ventana compartida." - -#: ../src/gui/MyFrameMain.cpp:617 -#, c-format -msgid "" -"Window sharing with %s stopped. Either the other side does not support " -"receiving windows or the window was closed there." -msgstr "" -"Se ha detenido el uso compartido de ventanas con %s. O bien el otro lado no " -"admite la recepción de ventanas o la ventana se cerró allí." - -#: ../src/gui/DialogSettings.cpp:100 -msgid "Write VNC log to logfile (MultiVNC.log)" -msgstr "Escribir el registro de VNC en un archivo de registro (MultiVNC.log)" - -#: ../src/gui/DialogSettings.cpp:75 -msgid "ZRLE" -msgstr "ZRLE" - -#: ../src/gui/DialogSettings.cpp:77 -msgid "ZYWRLE" -msgstr "ZYWRLE" - -#: ../src/gui/DialogSettings.cpp:71 -msgid "Zlib" -msgstr "Zlib" - -#: ../src/gui/DialogSettings.cpp:73 -msgid "ZlibHex" -msgstr "ZlibHex" - -msgid "translator-credits" -msgstr "Christian Beier " +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the MultiVNC package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: MultiVNC\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-06 18:13+0200\n" +"PO-Revision-Date: 2024-10-06 20:29+0200\n" +"Last-Translator: Christian Beier \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.2.2\n" + +#: ../src/gui/FrameMain.cpp:81 +msgid "&Add Bookmark" +msgstr "&Añadir marcador" + +#: ../src/gui/FrameMain.cpp:88 +msgid "&Bookmarks" +msgstr "&Marcadores" + +#: ../src/gui/FrameMain.cpp:31 +msgid "&Connect...\tCtrl-T" +msgstr "&Conectar...\tCtrl-T" + +#: ../src/gui/FrameMain.cpp:96 +msgid "&Contents" +msgstr "&Contenido" + +#: ../src/gui/FrameMain.cpp:86 +msgid "&Delete Bookmark" +msgstr "&Borrar marcador" + +#: ../src/gui/FrameMain.cpp:35 +msgid "&Disconnect" +msgstr "&Desconectar" + +#: ../src/gui/FrameMain.cpp:84 +msgid "&Edit Bookmark" +msgstr "&Editar marcador" + +#: ../src/gui/FrameMain.cpp:102 +msgid "&Help" +msgstr "&Ayuda" + +#: ../src/gui/FrameMain.cpp:33 +msgid "&Listen" +msgstr "Conexión &entrante" + +#: ../src/gui/FrameMain.cpp:52 +msgid "&Machine" +msgstr "&Máquina" + +#: ../src/gui/FrameMain.cpp:90 +msgid "&Share a Window" +msgstr "&Compartir una ventana" + +#: ../src/gui/FrameMain.cpp:79 +msgid "&View" +msgstr "Ver" + +#: ../src/gui/MyFrameMain.cpp:890 +msgid "(Reverse Connection)" +msgstr "(Conexión entrante)" + +#: ../src/gui/MyFrameMain.cpp:1725 +msgid "A bookmark with this name already exists!" +msgstr "Ya existe un marcador con este nombre." + +#: ../src/gui/MyFrameMain.cpp:310 +msgid "Authentication canceled." +msgstr "Autenticación cancelada." + +#: ../src/gui/DialogSettings.cpp:102 +msgid "Autosave statistics on close" +msgstr "Autoguardar estadísticas al cerrar" + +#: ../src/gui/FrameMain.cpp:153 +msgid "Available VNC Servers" +msgstr "Servidores descubiertos" + +#: ../src/gui/FrameMain.cpp:90 +msgid "Beam a window to the server." +msgstr "Transmita una ventana al servidor." + +#: ../src/gui/MyFrameMain.cpp:1187 +msgid "Bookmark" +msgstr "Marcador" + +#: ../src/gui/MyFrameMain.cpp:1991 +#, c-format +msgid "Bookmark %s" +msgstr "Marcador %s" + +#: ../src/gui/FrameMain.cpp:58 ../src/gui/FrameMain.cpp:158 +#: ../src/gui/MyFrameMain.cpp:1145 +msgid "Bookmarks" +msgstr "Marcadores" + +#: ../src/gui/MyFrameMain.cpp:1822 +msgid "Built with" +msgstr "Construido con" + +#: ../src/gui/MyFrameMain.cpp:769 ../src/gui/MyFrameMain.cpp:1405 +#: ../src/gui/MyFrameMain.cpp:1492 +msgid "CSV files|*.csv" +msgstr "Archivos CSV|*.csv" + +#: ../src/gui/DialogLogin.cpp:37 +msgid "Cancel" +msgstr "Cancelar" + +#: ../src/gui/MyFrameMain.cpp:1713 +msgid "Cannot bookmark a reverse connection!" +msgstr "No se puede marcar una conexión inversa." + +#: ../src/gui/FrameMain.cpp:39 +msgid "Change preferences." +msgstr "Cambiar preferencias." + +#: ../src/gui/DialogSettings.cpp:69 +msgid "CoRRE" +msgstr "CoRRE" + +#: ../src/gui/DialogSettings.cpp:87 +msgid "Compression level for 'Tight' and 'Zlib' encodings:" +msgstr "Nivel de compresión para las codificaciones 'Tight' y 'Zlib':" + +#: ../src/gui/FrameMain.cpp:117 +msgid "Connect" +msgstr "Conectar" + +#: ../src/gui/FrameMain.cpp:31 ../src/gui/FrameMain.cpp:117 +#: ../src/gui/MyFrameMain.cpp:1215 +msgid "Connect to a specific host." +msgstr "Conectarse a un host específico." + +#: ../src/gui/MyFrameMain.cpp:854 +#, c-format +msgid "Connecting to %s..." +msgstr "Conectando con %s..." + +#: ../src/gui/MyFrameMain.cpp:312 +msgid "Connection failed." +msgstr "Conexión fallida." + +#: ../src/gui/MyFrameMain.cpp:1055 +msgid "Connection terminated." +msgstr "Conexión terminada." + +#: ../src/gui/MyFrameMain.cpp:377 +#, c-format +msgid "Connection to %s is now multicast." +msgstr "La conexión a %s es ahora multidifusión." + +#: ../src/gui/MyFrameMain.cpp:388 +#, c-format +msgid "Connection to %s is now unicast." +msgstr "La conexión a %s es ahora unidifusión." + +#: ../src/gui/MyFrameMain.cpp:386 +#, c-format +msgid "Connection to %s switched to unicast." +msgstr "Conexión a %s cambiada a unidifusión." + +#: ../src/gui/MyFrameMain.cpp:481 ../src/gui/MyFrameMain.cpp:491 +#, c-format +msgid "Connection to %s:%s terminated." +msgstr "Conexión a %s:%s terminada." + +#: ../src/gui/DialogSettings.cpp:27 +msgid "Connections" +msgstr "Conexiones" + +#: ../src/gui/DialogSettings.cpp:36 +msgid "" +"Continously ask the server for updates instead of just asking after each " +"received server message. Use this on high latency links." +msgstr "" +"Pregunta continuamente al servidor por actualizaciones en lugar de preguntar " +"sólo después de cada mensaje recibido del servidor. Utilícelo en enlaces de " +"alta latencia." + +#: ../src/gui/DialogSettings.cpp:33 +msgid "Continously request updates at the specified milisecond interval:" +msgstr "" +"Solicita continuamente actualizaciones en el intervalo de milisegundos " +"especificado:" + +#: ../src/gui/DialogSettings.cpp:63 +msgid "CopyRect" +msgstr "CopyRect" + +#: ../src/gui/MyFrameMain.cpp:994 +msgid "Could not autosave statistics!" +msgstr "No se han podido autoguardar las estadísticas." + +#: ../src/VNCConn.cpp:1114 +msgid "Could not create VNC listener thread!" +msgstr "No se ha podido crear un hilo de escucha VNC." + +#: ../src/VNCConn.cpp:1178 +msgid "Could not create VNC thread!" +msgstr "No se ha podido crear el hilo VNC." + +#: ../src/MultiVNCApp.cpp:112 +msgid "Could not open config file!" +msgstr "No se ha podido abrir el archivo de configuración." + +#: ../src/gui/MyFrameMain.cpp:1504 +msgid "Could not open file!" +msgstr "No se ha podido abrir el archivo." + +#: ../src/gui/MyFrameMain.cpp:626 ../src/gui/MyFrameMain.cpp:627 +msgid "Could not run window share helper." +msgstr "No se ha podido ejecutar el ayudante para compartir ventanas." + +#: ../src/MultiVNCApp.cpp:162 +msgid "Could not save config file!" +msgstr "No se ha podido guardar el archivo de configuración." + +#: ../src/gui/MyFrameMain.cpp:781 ../src/gui/MyFrameMain.cpp:1416 +msgid "Could not save file!" +msgstr "No se ha podido guardar el archivo." + +#: ../src/gui/MyFrameMain.cpp:2107 +msgid "Could not share window, external program execution failed." +msgstr "" +"No se pudo compartir la ventana, falló la ejecución del programa externo." + +#: ../src/VNCConn.cpp:1122 +msgid "Could not start VNC listener thread!" +msgstr "No se ha podido iniciar el hilo de escucha VNC." + +#: ../src/VNCConn.cpp:1186 +msgid "Could not start VNC thread!" +msgstr "No se ha podido iniciar el hilo VNC" + +#: ../src/gui/MyFrameMain.cpp:718 ../src/gui/MyFrameMain.cpp:727 +msgid "Credentials required..." +msgstr "Credenciales requeridas..." + +#: ../src/gui/MyFrameMain.cpp:1259 +msgid "Detailed VNC Log" +msgstr "Registro VNC detallado" + +#: ../src/gui/FrameMain.cpp:71 +msgid "Disabled" +msgstr "Desactivada" + +#: ../src/gui/FrameMain.cpp:122 +msgid "Disconnect" +msgstr "Desconecte" + +#: ../src/gui/FrameMain.cpp:56 +msgid "Discovered Servers" +msgstr "Servidores descubiertos" + +#: ../src/gui/FrameMain.cpp:65 +msgid "East" +msgstr "Este" + +#: ../src/gui/FrameMain.cpp:73 +msgid "Edge Connector" +msgstr "Conector de borde" + +#: ../src/gui/MyFrameMain.cpp:1764 +msgid "Edit bookmark" +msgstr "Editar marcador" + +#: ../src/gui/ViewerWindow.cpp:436 +msgid "Eff. KB/s:" +msgstr "Eff. KB/s:" + +#: ../src/gui/DialogSettings.cpp:40 +msgid "Enable Expedited Forwarding tagging for sent data" +msgstr "Activar el etiquetado Expedited Forwarding para los datos enviados" + +#: ../src/gui/DialogSettings.cpp:31 +msgid "Enable FastRequest" +msgstr "Activar FastRequest" + +#: ../src/gui/DialogSettings.cpp:44 +msgid "Enable MulticastVNC" +msgstr "Activar MulticastVNC" + +#: ../src/gui/DialogSettings.cpp:59 +msgid "Enabled Encodings" +msgstr "Codificaciones activadas" + +#: ../src/gui/DialogSettings.cpp:57 +msgid "Encodings" +msgstr "Codificaciones" + +#: ../src/gui/MyFrameMain.cpp:1717 +msgid "Enter bookmark name:" +msgstr "Introduzca el nombre del marcador:" + +#: ../src/gui/MyFrameMain.cpp:1214 +msgid "Enter host to connect to:" +msgstr "Introduzca el host al que desea conectarse:" + +#: ../src/gui/MyFrameMain.cpp:2075 +msgid "Enter name of window to share:" +msgstr "Introduzca el nombre de la ventana que desea compartir:" + +#: ../src/gui/MyFrameMain.cpp:697 +msgid "Enter password:" +msgstr "Introduce la contraseña:" + +#: ../src/gui/MyFrameMain.cpp:1160 +#, c-format +msgid "Error reading hostname of bookmark '%s'!" +msgstr "¡Error al leer el nombre de host del marcador '%s'!" + +#: ../src/gui/MyFrameMain.cpp:1167 +#, c-format +msgid "Error reading port of bookmark '%s'!" +msgstr "¡Error al leer el puerto del marcador '%s'!" + +#: ../src/gui/FrameMain.cpp:50 +msgid "Exit MultiVNC." +msgstr "Salga de MultiVNC." + +#: ../src/gui/MyFrameMain.cpp:1739 +msgid "Failed to save credentials to the system secret store." +msgstr "Error al guardar las credenciales en el almacén secreto del sistema." + +#: ../src/VNCConn.cpp:294 +#, c-format +msgid "Failure connecting to server at %s:%d!" +msgstr "¡Fallo al conectar con el servidor en %s:%d!" + +#: ../src/VNCConn.cpp:257 +msgid "Failure setting up framebuffer: wrong BPP!" +msgstr "Fallo al configurar el framebuffer: ¡BPP incorrecto!" + +#: ../src/gui/DialogSettings.cpp:29 +msgid "FastRequest" +msgstr "FastRequest" + +#: ../src/gui/MyFrameMain.cpp:1428 +msgid "" +"From now on, all your mouse and keyboard input will be recorded. Click the " +"stop button to finish recording and save your input." +msgstr "" +"A partir de ahora, todas las entradas de ratón y teclado se grabarán. Haga " +"clic en el botón de parada para finalizar la grabación y guardar sus " +"entradas." + +#: ../src/gui/FrameMain.cpp:134 +msgid "Fullscreen" +msgstr "Pantalla completa" + +#: ../src/gui/FrameMain.cpp:75 +msgid "Fullscreen\tF11" +msgstr "Pantalla completa\tF11" + +#: ../src/MultiVNCApp.cpp:178 +msgid "GAAH! Got an unhandled exception! This should not happen." +msgstr "¡Tengo una excepción no manejada! Esto no debería ocurrir." + +#: ../src/gui/FrameMain.cpp:124 +msgid "Grab Keyboard" +msgstr "Agarrar teclado" + +#: ../src/gui/DialogSettings.cpp:65 +msgid "Hextile" +msgstr "Hextile" + +#: ../src/gui/MyFrameMain.cpp:1863 +msgid "" +"If there are VNC servers advertising themselves via ZeroConf, you can select " +"a host in the'Available VNC Servers' list. Otherwise, use the 'Connect' " +"button or menu item.\n" +"\n" +"When connected, a blue or green icon on the tab label shows if you are " +"running in unicast or multicast mode." +msgstr "" +"Si hay servidores VNC que se anuncian a través de ZeroConf, puede " +"seleccionar un host en la lista 'Servidores VNC disponibles'. De lo " +"contrario, utilice el botón \"Conectar\" o el elemento de menú.\n" +"\n" +"Cuando está conectado, un icono azul o verde en la etiqueta de la pestaña " +"muestra si se está ejecutando en modo unidifusión o multidifusión." + +#: ../src/gui/MyFrameMain.cpp:514 +msgid "Incoming Connection." +msgstr "Conexión entrante." + +#: ../src/gui/FrameMain.cpp:124 +msgid "" +"Intercept all keyboard input. Allows you to use special keys that would " +"otherwise be interpreted by the local computer." +msgstr "" +"Intercepta todas las entradas de teclado. Permite utilizar teclas especiales " +"que, de otro modo, serían interpretadas por el ordenador local." + +#: ../src/gui/ViewerWindow.cpp:440 +msgid "Latency ms:" +msgstr "Latencia ms:" + +#: ../src/gui/FrameMain.cpp:119 +msgid "Listen" +msgstr "Conexión entrante" + +#: ../src/gui/FrameMain.cpp:33 ../src/gui/FrameMain.cpp:119 +msgid "Listen for an incoming connection." +msgstr "Espere una conexión entrante." + +#: ../src/gui/MyFrameMain.cpp:848 ../src/gui/MyFrameMain.cpp:926 +msgid "Listening on port" +msgstr "Conexión entrante en el puerto" + +#: ../src/gui/MyFrameMain.cpp:1488 +msgid "Load recorded input..." +msgstr "Carga la entrada grabada..." + +#: ../src/gui/DialogSettings.cpp:98 +msgid "Logging" +msgstr "Registro" + +#: ../src/gui/DialogLogin.cpp:39 +msgid "Login" +msgstr "Inicio de sesión" + +#: ../src/gui/MyFrameMain.cpp:1903 +msgid "Looking up host address..." +msgstr "Buscando la dirección del host..." + +#: ../src/gui/ViewerWindow.cpp:442 +msgid "Loss Ratio:" +msgstr "Ratio de pérdidas:" + +#: ../src/gui/DialogSettings.cpp:85 +msgid "Lossy Encodings Settings" +msgstr "Ajustes de codificación con pérdidas" + +#: ../src/gui/FrameMain.cpp:24 +msgid "MultiVNC" +msgstr "MultiVNC" + +#: ../src/MultiVNCApp.cpp:186 +msgid "MultiVNC crashed" +msgstr "MultiVNC se bloquea" + +#: ../src/gui/MyFrameMain.cpp:1820 +msgid "MultiVNC is a cross-platform Multicast-enabled VNC client." +msgstr "" +"MultiVNC es un cliente VNC multiplataforma compatible con multidifusión." + +#: ../src/gui/DialogSettings.cpp:42 +msgid "MulticastVNC" +msgstr "MulticastVNC" + +#: ../src/gui/MyFrameMain.cpp:1763 +msgid "New bookmark name:" +msgstr "Nuevo nombre de marcador:" + +#: ../src/gui/MyFrameMain.cpp:1759 ../src/gui/MyFrameMain.cpp:1788 +msgid "No bookmark selected!" +msgstr "No se ha seleccionado ningún marcador." + +#: ../src/gui/MyFrameMain.cpp:1794 +msgid "No bookmark with this name!" +msgstr "¡Ningún marcador con este nombre!" + +#: ../src/gui/FrameMain.cpp:63 +msgid "North" +msgstr "Norte" + +#: ../src/gui/MyFrameMain.cpp:753 ../src/gui/MyFrameMain.cpp:1390 +msgid "Nothing to save!" +msgstr "¡No hay nada que salvar!" + +#: ../src/MultiVNCApp.cpp:186 +msgid "" +"Ouch! MultiVNC crashed. This should not happen. Do you want to generate a " +"bug report?" +msgstr "" +"¡Ay! MultiVNC se estrelló. Esto no debería ocurrir. ¿Quiere generar un " +"informe de error?" + +#: ../src/gui/MyFrameMain.cpp:1324 +msgid "PNG files|*.png" +msgstr "Archivos PNG|*.png" + +#: ../src/gui/MyFrameMain.cpp:697 +msgid "Password required!" +msgstr "¡Contraseña obligatoria!" + +#: ../src/gui/DialogLogin.cpp:23 +msgid "Password:" +msgstr "Contraseña:" + +#: ../src/gui/MyFrameMain.cpp:717 +#, c-format +msgid "Please enter password for user '%s'" +msgstr "Introduzca la contraseña del usuario '%s'" + +#: ../src/gui/MyFrameMain.cpp:1272 +msgid "Preferences" +msgstr "Preferencias" + +#: ../src/gui/DialogSettings.cpp:92 +msgid "Quality level for 'Tight' and 'ZYWRLE' encoding:" +msgstr "Nivel de calidad para la codificación 'Tight' y 'ZYWRLE':" + +#: ../src/gui/DialogSettings.cpp:38 +msgid "Quality of Service" +msgstr "Calidad del servicio" + +#: ../src/gui/DialogSettings.cpp:67 +msgid "RRE" +msgstr "RRE" + +#: ../src/gui/ViewerWindow.cpp:444 +msgid "Rcv Buffer:" +msgstr "Búfer Rcpt:" + +#: ../src/gui/DialogSettings.cpp:51 +msgid "Receive Buffer Size (kB):" +msgstr "Tamaño del búfer de recepción (kB):" + +#: ../src/gui/FrameMain.cpp:45 ../src/gui/FrameMain.cpp:129 +#: ../src/gui/MyFrameMain.cpp:1374 ../src/gui/MyFrameMain.cpp:1382 +msgid "Record Input" +msgstr "Registro de entrada" + +#: ../src/gui/FrameMain.cpp:129 +msgid "Record mouse and keyboard input for later replay as a macro." +msgstr "" +"Graba la entrada del mouse y del teclado para reproducirla más tarde como " +"una macro." + +#: ../src/gui/MyFrameMain.cpp:1440 +msgid "Recording user input..." +msgstr "Grabación de la entrada del usuario..." + +#: ../src/gui/FrameMain.cpp:47 ../src/gui/FrameMain.cpp:131 +#: ../src/gui/MyFrameMain.cpp:414 ../src/gui/MyFrameMain.cpp:418 +#: ../src/gui/MyFrameMain.cpp:1470 ../src/gui/MyFrameMain.cpp:1474 +msgid "Replay Input" +msgstr "Repetición de la entrada" + +#: ../src/gui/FrameMain.cpp:131 +msgid "" +"Replay a recorded mouse and keyboard input macro. If is held down " +"while clicking this button, the macro is replayed in a loop." +msgstr "" +"Reproduce una macro de entrada de teclado y mouse grabada. Si se mantiene " +"presionado mientras se hace clic en este botón, la macro se " +"reproduce en un bucle." + +#: ../src/gui/MyFrameMain.cpp:425 ../src/gui/MyFrameMain.cpp:426 +msgid "Replay finished!" +msgstr "¡Repetición finalizada!" + +#: ../src/gui/MyFrameMain.cpp:1528 +msgid "Replaying user input in loop..." +msgstr "Reproducir la entrada del usuario en bucle..." + +#: ../src/gui/MyFrameMain.cpp:1530 +msgid "Replaying user input..." +msgstr "Reproducir la entrada del usuario..." + +#: ../src/gui/FrameMain.cpp:98 +msgid "Request a Feature / Report a Bug" +msgstr "Solicitar una función / Informar de un error" + +#: ../src/gui/MyFrameMain.cpp:483 ../src/gui/MyFrameMain.cpp:493 +msgid "Reverse connection terminated." +msgstr "Conexión entrante terminada." + +#: ../src/gui/FrameMain.cpp:92 +msgid "S&top Sharing Window" +msgstr "Finalizar Compartir Ventana" + +#: ../src/gui/FrameMain.cpp:43 +msgid "Save Statistics..." +msgstr "Guardar estadísticas..." + +#: ../src/gui/MyFrameLog.cpp:93 +msgid "Save log as..." +msgstr "Guardar registro como..." + +#: ../src/gui/MyFrameMain.cpp:1401 +msgid "Save recorded input..." +msgstr "Guardar la entrada grabada..." + +#: ../src/gui/MyFrameMain.cpp:1320 +msgid "Save screenshot..." +msgstr "Guardar captura de pantalla..." + +#: ../src/gui/MyFrameMain.cpp:765 +#, c-format +msgid "Saving %s statistics..." +msgstr "Guardando estadísticas %s..." + +#: ../src/gui/MyFrameMain.cpp:1718 +msgid "Saving bookmark" +msgstr "Guardar marcador" + +#: ../src/gui/DialogSettings.cpp:54 +msgid "" +"Set the multicast receive buffer size. Increasing the value may help against " +"packet loss. The size of this buffer is independent of the operating system." +msgstr "" +"Establece el tamaño del búfer de recepción de multidifusión. Aumentar el " +"valor puede ayudar contra la pérdida de paquetes. El tamaño de este búfer es " +"independiente del sistema operativo." + +#: ../src/gui/DialogSettings.cpp:49 +msgid "" +"Set the multicast socket receive buffer size. Increasing the value may help " +"against packet loss. Note that the maximum value is operating system " +"dependent." +msgstr "" +"Establece el tamaño del búfer de recepción del socket multidifusión. " +"Aumentar el valor puede ayudar contra la pérdida de paquetes. Tenga en " +"cuenta que el valor máximo depende del sistema operativo." + +#: ../src/gui/MyFrameMain.cpp:2075 ../src/gui/MyFrameMain.cpp:2080 +msgid "Share a Window" +msgstr "Compartir una ventana" + +#: ../src/gui/MyFrameMain.cpp:2115 +#, c-format +msgid "Sharing window with %s" +msgstr "Ventana compartida con %s" + +#: ../src/gui/FrameMain.cpp:37 +msgid "Show &Log" +msgstr "Mostrar ®istro" + +#: ../src/gui/FrameMain.cpp:96 +msgid "Show Help." +msgstr "Mostrar Ayuda." + +#: ../src/gui/FrameMain.cpp:37 +msgid "Show detailed log." +msgstr "Mostrar registro detallado." + +#: ../src/gui/DialogSettings.cpp:46 +msgid "Socket Receive Buffer Size (kB):" +msgstr "Tamaño del búfer de recepción del socket (kB):" + +#: ../src/gui/FrameMain.cpp:69 +msgid "South" +msgstr "Sur" + +#: ../src/gui/FrameMain.cpp:60 ../src/gui/ViewerWindow.cpp:431 +msgid "Statistics" +msgstr "Estadísticas" + +#: ../src/gui/FrameMain.cpp:110 +msgid "Status" +msgstr "Estado" + +#: ../src/gui/MyFrameMain.cpp:1437 ../src/gui/MyFrameMain.cpp:1524 +msgid "Stop" +msgstr "Finalizar" + +#: ../src/gui/MyFrameMain.cpp:1433 +msgid "Stop Recording" +msgstr "Detener grabación" + +#: ../src/gui/MyFrameMain.cpp:1520 +msgid "Stop Replaying" +msgstr "Detener la reproducción" + +#: ../src/gui/FrameMain.cpp:92 +msgid "Stop Window Sharing." +msgstr "Detener el uso compartido de ventanas." + +#: ../src/gui/MyFrameMain.cpp:1381 +msgid "Stopped recording user input!" +msgstr "Se ha dejado de grabar la entrada del usuario." + +#: ../src/gui/MyFrameMain.cpp:1477 +msgid "Stopped replaying user input!" +msgstr "Se ha detenido la reproducción de la entrada del usuario." + +#: ../src/gui/MyFrameMain.cpp:2156 +#, c-format +msgid "Stopped sharing window with %s" +msgstr "Ventana compartida detenida con %s" + +#: ../src/gui/MyFrameMain.cpp:1834 +msgid "Supported Encodings:" +msgstr "Codificaciones admitidas:" + +#: ../src/gui/MyFrameMain.cpp:1824 +msgid "Supported Security Types:" +msgstr "Tipos de seguridad admitidos:" + +#: ../src/gui/FrameMain.cpp:41 ../src/gui/FrameMain.cpp:126 +msgid "Take Screenshot" +msgstr "Hacer una captura de pantalla" + +#: ../src/gui/FrameMain.cpp:126 +msgid "Take a screenshot of the current connection." +msgstr "Toma una captura de pantalla de la conexión actual." + +#: ../src/gui/FrameMain.cpp:35 ../src/gui/FrameMain.cpp:122 +msgid "Terminate connection." +msgstr "Finaliza la conexión." + +#: ../src/gui/MyFrameLog.cpp:94 +msgid "Text files (*.txt)|*.txt" +msgstr "Archivos de texto (*.txt)|*.txt" + +#: ../src/gui/MyFrameMain.cpp:2079 +msgid "" +"The MultiVNC window will be minimized and a cross-shaped cursor will appear. " +"Use it to select the window you want to share." +msgstr "" +"La ventana MultiVNC se minimizará y aparecerá un cursor en forma de cruz. " +"Utilícelo para seleccionar la ventana que desea compartir." + +#: ../src/gui/DialogSettings.cpp:81 +msgid "Tight" +msgstr "Tight" + +#: ../src/gui/MyFrameMain.cpp:1957 ../src/gui/MyFrameMain.cpp:1958 +msgid "Timeout looking up IP address." +msgstr "Tiempo de espera para buscar la dirección IP." + +#: ../src/gui/MyFrameMain.cpp:1918 ../src/gui/MyFrameMain.cpp:1919 +msgid "Timeout looking up hostname." +msgstr "Tiempo de espera para buscar el nombre de host." + +#: ../src/gui/FrameMain.cpp:136 +msgid "Toggle 1:1 view, disabling all scaling." +msgstr "Cambie entre vista 1:1 y vista escalada." + +#: ../src/gui/FrameMain.cpp:134 +msgid "Toggle fullscreen view." +msgstr "Alterna la vista de pantalla completa." + +#: ../src/gui/FrameMain.cpp:54 +msgid "Toolbar" +msgstr "Barra de herramientas" + +#: ../src/gui/DialogSettings.cpp:79 +msgid "Ultra" +msgstr "Ultra" + +#: ../src/gui/ViewerWindow.cpp:438 +msgid "Updates/s:" +msgstr "Actualizaciónes/s:" + +#: ../src/gui/DialogSettings.cpp:90 +msgid "" +"Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. " +"Level 1 uses minimum of CPU time and achieves weak compression ratios, while " +"level 9 offers best compression but is slow in terms of CPU time consumption " +"on the server side. Use high levels with very slow network connections, and " +"low levels when working over high-speed LANs." +msgstr "" +"Utilice el nivel de compresión especificado (0..9) para las codificaciones " +"'Tight' y 'Zlib'. El nivel 1 consume un mínimo de tiempo de CPU y logra " +"ratios de compresión débiles, mientras que el nivel 9 ofrece la mejor " +"compresión pero es lento en términos de consumo de tiempo de CPU en el lado " +"del servidor. Utilice niveles altos con conexiones de red muy lentas, y " +"niveles bajos cuando trabaje sobre redes LAN de alta velocidad." + +#: ../src/gui/DialogSettings.cpp:95 +msgid "" +"Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' " +"encodings. Quality level 0 denotes bad image quality but very impressive " +"compression ratios, while level 9 offers very good image quality at lower " +"compression ratios. Note that the \"tight\" encoder uses JPEG to encode only " +"those screen areas that look suitable for lossy compression, so quality " +"level 0 does not always mean unacceptable image quality." +msgstr "" +"Utilice el nivel de calidad especificado (0..9) para las codificaciones " +"'Tight' y 'ZYWRLE'. El nivel de calidad 0 denota una mala calidad de imagen " +"pero unos ratios de compresión muy impresionantes, mientras que el nivel 9 " +"ofrece una calidad de imagen muy buena con ratios de compresión más bajos. " +"Tenga en cuenta que el codificador \"tight\" utiliza JPEG para codificar " +"sólo las zonas de la pantalla que parecen adecuadas para la compresión con " +"pérdidas, por lo que el nivel de calidad 0 no siempre significa una calidad " +"de imagen inaceptable." + +#: ../src/gui/DialogLogin.cpp:58 +msgid "Username or password must not empty" +msgstr "El nombre de usuario o la contraseña no deben estar vacíos" + +#: ../src/gui/DialogLogin.cpp:15 +msgid "Username:" +msgstr "Nombre de usuario:" + +#: ../src/gui/MyFrameMain.cpp:1826 +msgid "VNC Authentication" +msgstr "Autenticación VNC" + +#: ../src/gui/MyFrameMain.cpp:1890 +msgid "VNC Server" +msgstr "Servidor VNC" + +#: ../src/gui/FrameMain.cpp:77 ../src/gui/FrameMain.cpp:136 +msgid "View 1:1" +msgstr "Vista 1:1" + +#: ../src/gui/DialogLogin.cpp:58 +msgid "Warning!" +msgstr "¡Atención!" + +#: ../src/gui/FrameMain.cpp:67 +msgid "West" +msgstr "Oeste" + +#: ../src/gui/FrameMain.cpp:94 ../src/gui/MyFrameMain.cpp:134 +#: ../src/gui/MyFrameMain.cpp:139 +msgid "Window &Sharing" +msgstr "&Compartir Ventana" + +#: ../src/gui/MyFrameMain.cpp:611 +msgid "" +"Window share helper exited without an associated connection. That should not " +"happen." +msgstr "" +"El asistente para compartir ventanas salió sin una conexión asociada. Eso no " +"debería suceder." + +#: ../src/gui/MyFrameMain.cpp:2106 +msgid "Window sharing helper execution failed." +msgstr "Fallo en la ejecución del asistente de compartición de ventanas." + +#: ../src/gui/MyFrameMain.cpp:623 +msgid "Window sharing stopped. Shared window was closed." +msgstr "La ventana compartida se detuvo. Se ha cerrado la ventana compartida." + +#: ../src/gui/MyFrameMain.cpp:617 +#, c-format +msgid "" +"Window sharing with %s stopped. Either the other side does not support " +"receiving windows or the window was closed there." +msgstr "" +"Se ha detenido el uso compartido de ventanas con %s. O bien el otro lado no " +"admite la recepción de ventanas o la ventana se cerró allí." + +#: ../src/gui/DialogSettings.cpp:100 +msgid "Write VNC log to logfile (MultiVNC.log)" +msgstr "Escribir el registro de VNC en un archivo de registro (MultiVNC.log)" + +#: ../src/gui/DialogSettings.cpp:75 +msgid "ZRLE" +msgstr "ZRLE" + +#: ../src/gui/DialogSettings.cpp:77 +msgid "ZYWRLE" +msgstr "ZYWRLE" + +#: ../src/gui/DialogSettings.cpp:71 +msgid "Zlib" +msgstr "Zlib" + +#: ../src/gui/DialogSettings.cpp:73 +msgid "ZlibHex" +msgstr "ZlibHex" + +msgid "translator-credits" +msgstr "Christian Beier " diff --git a/po/sv.po b/po/sv.po index 9080b29a..a09e49f5 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1,865 +1,865 @@ -# Swedish translation for MultiVNC. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the MultiVNC package. -# Åke Engelbrektson , 2023. -# -msgid "" -msgstr "" -"Project-Id-Version: MultiVNC\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-06 18:13+0200\n" -"PO-Revision-Date: 2024-10-06 20:30+0200\n" -"Last-Translator: Christian Beier \n" -"Language-Team: \n" -"Language: sv\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.2.2\n" - -#: ../src/gui/FrameMain.cpp:81 -msgid "&Add Bookmark" -msgstr "&Lägg till bokmärke" - -#: ../src/gui/FrameMain.cpp:88 -msgid "&Bookmarks" -msgstr "&Bokmärken" - -#: ../src/gui/FrameMain.cpp:31 -msgid "&Connect...\tCtrl-T" -msgstr "&Anslut...\tCtrl-T" - -#: ../src/gui/FrameMain.cpp:96 -msgid "&Contents" -msgstr "&Innehåll" - -#: ../src/gui/FrameMain.cpp:86 -msgid "&Delete Bookmark" -msgstr "&Ta bort bokmärke" - -#: ../src/gui/FrameMain.cpp:35 -msgid "&Disconnect" -msgstr "&Koppla från" - -#: ../src/gui/FrameMain.cpp:84 -msgid "&Edit Bookmark" -msgstr "&Redigera bokmärke" - -#: ../src/gui/FrameMain.cpp:102 -msgid "&Help" -msgstr "&Hjälp" - -#: ../src/gui/FrameMain.cpp:33 -msgid "&Listen" -msgstr "&Lyssna" - -#: ../src/gui/FrameMain.cpp:52 -msgid "&Machine" -msgstr "&Maskin" - -#: ../src/gui/FrameMain.cpp:90 -msgid "&Share a Window" -msgstr "&Dela ett fönster" - -#: ../src/gui/FrameMain.cpp:79 -msgid "&View" -msgstr "&Visa" - -#: ../src/gui/MyFrameMain.cpp:890 -msgid "(Reverse Connection)" -msgstr "(Omvänd anslutning)" - -#: ../src/gui/MyFrameMain.cpp:1725 -msgid "A bookmark with this name already exists!" -msgstr "Det finns redan ett bokmärke med det här namnet!" - -#: ../src/gui/MyFrameMain.cpp:310 -msgid "Authentication canceled." -msgstr "Autentiseringen avbröts." - -#: ../src/gui/DialogSettings.cpp:102 -msgid "Autosave statistics on close" -msgstr "Spara statistik automatiskt vid avslut" - -#: ../src/gui/FrameMain.cpp:153 -msgid "Available VNC Servers" -msgstr "Tillgängliga VNC-servrar" - -#: ../src/gui/FrameMain.cpp:90 -msgid "Beam a window to the server." -msgstr "Stråla ett fönster till servern." - -#: ../src/gui/MyFrameMain.cpp:1187 -msgid "Bookmark" -msgstr "Bokmärk" - -#: ../src/gui/MyFrameMain.cpp:1991 -#, c-format -msgid "Bookmark %s" -msgstr "Bokmärk %s" - -#: ../src/gui/FrameMain.cpp:58 ../src/gui/FrameMain.cpp:158 -#: ../src/gui/MyFrameMain.cpp:1145 -msgid "Bookmarks" -msgstr "Bokmärken" - -#: ../src/gui/MyFrameMain.cpp:1822 -msgid "Built with" -msgstr "Byggt med" - -#: ../src/gui/MyFrameMain.cpp:769 ../src/gui/MyFrameMain.cpp:1405 -#: ../src/gui/MyFrameMain.cpp:1492 -msgid "CSV files|*.csv" -msgstr "CSV-filer|*.csv" - -#: ../src/gui/DialogLogin.cpp:37 -msgid "Cancel" -msgstr "Avbryt" - -#: ../src/gui/MyFrameMain.cpp:1713 -msgid "Cannot bookmark a reverse connection!" -msgstr "Kan inte bokmärka en omvänd anslutning!" - -#: ../src/gui/FrameMain.cpp:39 -msgid "Change preferences." -msgstr "Ändra inställningar." - -#: ../src/gui/DialogSettings.cpp:69 -msgid "CoRRE" -msgstr "CoRRE" - -#: ../src/gui/DialogSettings.cpp:87 -msgid "Compression level for 'Tight' and 'Zlib' encodings:" -msgstr "Komprimeringsnivå för Tight- och Zlib-kodningar:" - -#: ../src/gui/FrameMain.cpp:117 -msgid "Connect" -msgstr "Anslut" - -#: ../src/gui/FrameMain.cpp:31 ../src/gui/FrameMain.cpp:117 -#: ../src/gui/MyFrameMain.cpp:1215 -msgid "Connect to a specific host." -msgstr "Anslut en specifik värddator." - -#: ../src/gui/MyFrameMain.cpp:854 -#, c-format -msgid "Connecting to %s..." -msgstr "Ansluter %s..." - -#: ../src/gui/MyFrameMain.cpp:312 -msgid "Connection failed." -msgstr "Anslutningen misslyckades." - -#: ../src/gui/MyFrameMain.cpp:1055 -msgid "Connection terminated." -msgstr "Anslutningen avbröts." - -#: ../src/gui/MyFrameMain.cpp:377 -#, c-format -msgid "Connection to %s is now multicast." -msgstr "Anslutningen till %s är nu multicast." - -#: ../src/gui/MyFrameMain.cpp:388 -#, c-format -msgid "Connection to %s is now unicast." -msgstr "Anslutningen till %s är nu unicast." - -#: ../src/gui/MyFrameMain.cpp:386 -#, c-format -msgid "Connection to %s switched to unicast." -msgstr "Anslutningen till %s ändrades till unicast." - -#: ../src/gui/MyFrameMain.cpp:481 ../src/gui/MyFrameMain.cpp:491 -#, c-format -msgid "Connection to %s:%s terminated." -msgstr "Anslutningen till %s:%s avbröts." - -#: ../src/gui/DialogSettings.cpp:27 -msgid "Connections" -msgstr "Anslutningar" - -#: ../src/gui/DialogSettings.cpp:36 -msgid "" -"Continously ask the server for updates instead of just asking after each " -"received server message. Use this on high latency links." -msgstr "" -"Fråga kontinuerligt servern om uppdateringar i stället för att bara fråga " -"efter varje mottaget servermeddelande. Använd detta på länkar med hög " -"latenstid." - -#: ../src/gui/DialogSettings.cpp:33 -msgid "Continously request updates at the specified milisecond interval:" -msgstr "Begär kontinuerliga uppdateringar med angivet milisekundintervall:" - -#: ../src/gui/DialogSettings.cpp:63 -msgid "CopyRect" -msgstr "CopyRect" - -#: ../src/gui/MyFrameMain.cpp:994 -msgid "Could not autosave statistics!" -msgstr "Kunde inte spara statistik automatiskt!" - -#: ../src/VNCConn.cpp:1114 -msgid "Could not create VNC listener thread!" -msgstr "Kunde inte skapa VNC-lyssnartråd!" - -#: ../src/VNCConn.cpp:1178 -msgid "Could not create VNC thread!" -msgstr "Kunde inte skapa VNC-tråd!" - -#: ../src/MultiVNCApp.cpp:112 -msgid "Could not open config file!" -msgstr "Kunde öppna config-fil!" - -#: ../src/gui/MyFrameMain.cpp:1504 -msgid "Could not open file!" -msgstr "Kunde öppna fil!" - -#: ../src/gui/MyFrameMain.cpp:626 ../src/gui/MyFrameMain.cpp:627 -msgid "Could not run window share helper." -msgstr "Kunde inte köra fönsterdelningshjälpen." - -#: ../src/MultiVNCApp.cpp:162 -msgid "Could not save config file!" -msgstr "Kunde inte spara config-fil!" - -#: ../src/gui/MyFrameMain.cpp:781 ../src/gui/MyFrameMain.cpp:1416 -msgid "Could not save file!" -msgstr "Kunde inte spara fil!" - -#: ../src/gui/MyFrameMain.cpp:2107 -msgid "Could not share window, external program execution failed." -msgstr "Kunde inte dela fönster, extern programstart misslyckades." - -#: ../src/VNCConn.cpp:1122 -msgid "Could not start VNC listener thread!" -msgstr "Kunde inte starta VNC-lyssnartråd!" - -#: ../src/VNCConn.cpp:1186 -msgid "Could not start VNC thread!" -msgstr "Kunde inte starta VNC-tråd!" - -#: ../src/gui/MyFrameMain.cpp:718 ../src/gui/MyFrameMain.cpp:727 -msgid "Credentials required..." -msgstr "Autentiseringsuppgifter krävs..." - -#: ../src/gui/MyFrameMain.cpp:1259 -msgid "Detailed VNC Log" -msgstr "Detaljerad VNC-logg" - -#: ../src/gui/FrameMain.cpp:71 -msgid "Disabled" -msgstr "Inaktiverad" - -#: ../src/gui/FrameMain.cpp:122 -msgid "Disconnect" -msgstr "Koppla från" - -#: ../src/gui/FrameMain.cpp:56 -msgid "Discovered Servers" -msgstr "Identifierade servrar" - -#: ../src/gui/FrameMain.cpp:65 -msgid "East" -msgstr "Öst" - -#: ../src/gui/FrameMain.cpp:73 -msgid "Edge Connector" -msgstr "Edge Connector" - -#: ../src/gui/MyFrameMain.cpp:1764 -msgid "Edit bookmark" -msgstr "Redigera bokmärke" - -#: ../src/gui/ViewerWindow.cpp:436 -msgid "Eff. KB/s:" -msgstr "Eff. KB/s:" - -#: ../src/gui/DialogSettings.cpp:40 -msgid "Enable Expedited Forwarding tagging for sent data" -msgstr "Aktivera Expedited Forwarding-taggning för skickad data" - -#: ../src/gui/DialogSettings.cpp:31 -msgid "Enable FastRequest" -msgstr "Aktivera FastRequest" - -#: ../src/gui/DialogSettings.cpp:44 -msgid "Enable MulticastVNC" -msgstr "Aktivera MulticastVNC" - -#: ../src/gui/DialogSettings.cpp:59 -msgid "Enabled Encodings" -msgstr "Aktiverade kodningar" - -#: ../src/gui/DialogSettings.cpp:57 -msgid "Encodings" -msgstr "Kodningar" - -#: ../src/gui/MyFrameMain.cpp:1717 -msgid "Enter bookmark name:" -msgstr "Ange bokmärkesnamn:" - -#: ../src/gui/MyFrameMain.cpp:1214 -msgid "Enter host to connect to:" -msgstr "Ange värddator att ansluta:" - -#: ../src/gui/MyFrameMain.cpp:2075 -msgid "Enter name of window to share:" -msgstr "Ange namn för fönster att dela:" - -#: ../src/gui/MyFrameMain.cpp:697 -msgid "Enter password:" -msgstr "Ange lösenord:" - -#: ../src/gui/MyFrameMain.cpp:1160 -#, c-format -msgid "Error reading hostname of bookmark '%s'!" -msgstr "Fel vid inläsning av värdnamn för bokmärket %s!" - -#: ../src/gui/MyFrameMain.cpp:1167 -#, c-format -msgid "Error reading port of bookmark '%s'!" -msgstr "Fel vid inläsning av port för bokmärket %s!" - -#: ../src/gui/FrameMain.cpp:50 -msgid "Exit MultiVNC." -msgstr "Avsluta MultiVNC." - -#: ../src/gui/MyFrameMain.cpp:1739 -msgid "Failed to save credentials to the system secret store." -msgstr "Kunde inte spara autentiseringsuppgifter i systemets hemliga arkiv." - -#: ../src/VNCConn.cpp:294 -#, c-format -msgid "Failure connecting to server at %s:%d!" -msgstr "Kunde inte ansluta till servern på %s:%d!" - -#: ../src/VNCConn.cpp:257 -msgid "Failure setting up framebuffer: wrong BPP!" -msgstr "Fel i inställningen av rambuffert: Fel BPP!" - -#: ../src/gui/DialogSettings.cpp:29 -msgid "FastRequest" -msgstr "FastRequest" - -#: ../src/gui/MyFrameMain.cpp:1428 -msgid "" -"From now on, all your mouse and keyboard input will be recorded. Click the " -"stop button to finish recording and save your input." -msgstr "" -"Från och med nu kommer alla dina mus- och tangentbordsinmatningar att " -"registreras. Klicka på stoppknappen för att avsluta inspelningen och spara " -"din inmatning." - -#: ../src/gui/FrameMain.cpp:134 -msgid "Fullscreen" -msgstr "Fullskärm" - -#: ../src/gui/FrameMain.cpp:75 -msgid "Fullscreen\tF11" -msgstr "Fullskärm\tF11" - -#: ../src/MultiVNCApp.cpp:178 -msgid "GAAH! Got an unhandled exception! This should not happen." -msgstr "GAAH! Vi fick ett obehandlat undantag! Detta borde inte inträffa." - -#: ../src/gui/FrameMain.cpp:124 -msgid "Grab Keyboard" -msgstr "Ta över tangentbordet" - -#: ../src/gui/DialogSettings.cpp:65 -msgid "Hextile" -msgstr "Hextile" - -#: ../src/gui/MyFrameMain.cpp:1863 -msgid "" -"If there are VNC servers advertising themselves via ZeroConf, you can select " -"a host in the'Available VNC Servers' list. Otherwise, use the 'Connect' " -"button or menu item.\n" -"\n" -"When connected, a blue or green icon on the tab label shows if you are " -"running in unicast or multicast mode." -msgstr "" -"Om det finns VNC-servrar som annonserar sig själva via ZeroConf kan du välja " -"en värddator i listan \"Tillgängliga VNC-servrar\". Annars kan du använda " -"knappen \"Anslut\" eller menyalternativet.\n" -"\n" -"När du är ansluten visar en blå eller grön ikon på fliketiketten om du kör i " -"unicast- eller multicast-läge." - -#: ../src/gui/MyFrameMain.cpp:514 -msgid "Incoming Connection." -msgstr "Inkommande anslutning." - -#: ../src/gui/FrameMain.cpp:124 -msgid "" -"Intercept all keyboard input. Allows you to use special keys that would " -"otherwise be interpreted by the local computer." -msgstr "" -"Uppfattar all tangentbordsinmatning. Gör det möjligt att använda speciella " -"tangenter som annars skulle tolkas av den lokala datorn." - -#: ../src/gui/ViewerWindow.cpp:440 -msgid "Latency ms:" -msgstr "Latens ms:" - -#: ../src/gui/FrameMain.cpp:119 -msgid "Listen" -msgstr "Lyssna" - -#: ../src/gui/FrameMain.cpp:33 ../src/gui/FrameMain.cpp:119 -msgid "Listen for an incoming connection." -msgstr "Lyssnar efter inkommande anslutning." - -#: ../src/gui/MyFrameMain.cpp:848 ../src/gui/MyFrameMain.cpp:926 -msgid "Listening on port" -msgstr "Lyssnar på port" - -#: ../src/gui/MyFrameMain.cpp:1488 -msgid "Load recorded input..." -msgstr "Läs in inspelad indata..." - -#: ../src/gui/DialogSettings.cpp:98 -msgid "Logging" -msgstr "Loggning" - -#: ../src/gui/DialogLogin.cpp:39 -msgid "Login" -msgstr "Inloggning" - -#: ../src/gui/MyFrameMain.cpp:1903 -msgid "Looking up host address..." -msgstr "Söker upp värdadress..." - -#: ../src/gui/ViewerWindow.cpp:442 -msgid "Loss Ratio:" -msgstr "Förlustkvot:" - -#: ../src/gui/DialogSettings.cpp:85 -msgid "Lossy Encodings Settings" -msgstr "Inställningar för förstörande kodningar" - -#: ../src/gui/FrameMain.cpp:24 -msgid "MultiVNC" -msgstr "MultiVNC" - -#: ../src/MultiVNCApp.cpp:186 -msgid "MultiVNC crashed" -msgstr "MultiVNC kraschade" - -#: ../src/gui/MyFrameMain.cpp:1820 -msgid "MultiVNC is a cross-platform Multicast-enabled VNC client." -msgstr "MultiVNC är en plattformsöverskridande Multicast-aktiverad VNC-klient." - -#: ../src/gui/DialogSettings.cpp:42 -msgid "MulticastVNC" -msgstr "MulticastVNC" - -#: ../src/gui/MyFrameMain.cpp:1763 -msgid "New bookmark name:" -msgstr "Nytt bokmärkesnamn:" - -#: ../src/gui/MyFrameMain.cpp:1759 ../src/gui/MyFrameMain.cpp:1788 -msgid "No bookmark selected!" -msgstr "Inget bokmärke valt!" - -#: ../src/gui/MyFrameMain.cpp:1794 -msgid "No bookmark with this name!" -msgstr "Det finns inget bokmärke med det namnet!" - -#: ../src/gui/FrameMain.cpp:63 -msgid "North" -msgstr "Norr" - -#: ../src/gui/MyFrameMain.cpp:753 ../src/gui/MyFrameMain.cpp:1390 -msgid "Nothing to save!" -msgstr "Inget att spara!" - -#: ../src/MultiVNCApp.cpp:186 -msgid "" -"Ouch! MultiVNC crashed. This should not happen. Do you want to generate a " -"bug report?" -msgstr "" -"Ajaj! MultiVNC kraschade. Detta borde inte hända. Vill du generera en " -"felrapport?" - -#: ../src/gui/MyFrameMain.cpp:1324 -msgid "PNG files|*.png" -msgstr "PNG-filer|*.png" - -#: ../src/gui/MyFrameMain.cpp:697 -msgid "Password required!" -msgstr "Lösenord krävs!" - -#: ../src/gui/DialogLogin.cpp:23 -msgid "Password:" -msgstr "Lösenord:" - -#: ../src/gui/MyFrameMain.cpp:717 -#, c-format -msgid "Please enter password for user '%s'" -msgstr "Ange lösenord för användare '%s'" - -#: ../src/gui/MyFrameMain.cpp:1272 -msgid "Preferences" -msgstr "Inställningar" - -#: ../src/gui/DialogSettings.cpp:92 -msgid "Quality level for 'Tight' and 'ZYWRLE' encoding:" -msgstr "Kvalitetsnivå för Tight- och ZYWRLE-kodning:" - -#: ../src/gui/DialogSettings.cpp:38 -msgid "Quality of Service" -msgstr "Tjänstens kvalitet" - -#: ../src/gui/DialogSettings.cpp:67 -msgid "RRE" -msgstr "RRE" - -#: ../src/gui/ViewerWindow.cpp:444 -msgid "Rcv Buffer:" -msgstr "Rcv-buffert:" - -#: ../src/gui/DialogSettings.cpp:51 -msgid "Receive Buffer Size (kB):" -msgstr "Storlek på mottagningsbuffert (kB):" - -#: ../src/gui/FrameMain.cpp:45 ../src/gui/FrameMain.cpp:129 -#: ../src/gui/MyFrameMain.cpp:1374 ../src/gui/MyFrameMain.cpp:1382 -msgid "Record Input" -msgstr "Spela in indata" - -#: ../src/gui/FrameMain.cpp:129 -msgid "Record mouse and keyboard input for later replay as a macro." -msgstr "" -"Spelar in mus- och tangentbordsinmatning för senare uppspelning som ett " -"makro." - -#: ../src/gui/MyFrameMain.cpp:1440 -msgid "Recording user input..." -msgstr "Spelar in användarindata..." - -#: ../src/gui/FrameMain.cpp:47 ../src/gui/FrameMain.cpp:131 -#: ../src/gui/MyFrameMain.cpp:414 ../src/gui/MyFrameMain.cpp:418 -#: ../src/gui/MyFrameMain.cpp:1470 ../src/gui/MyFrameMain.cpp:1474 -msgid "Replay Input" -msgstr "Återupprepa indata" - -#: ../src/gui/FrameMain.cpp:131 -msgid "" -"Replay a recorded mouse and keyboard input macro. If is held down " -"while clicking this button, the macro is replayed in a loop." -msgstr "" -"Spelar om ett inspelat inmatningsmakro för mus och tangentbord. Om " -"hålls nedtryckt medan du klickar på den här knappen, spelas makrot upp i en " -"loop." - -#: ../src/gui/MyFrameMain.cpp:425 ../src/gui/MyFrameMain.cpp:426 -msgid "Replay finished!" -msgstr "Återupprepning slutförd!" - -#: ../src/gui/MyFrameMain.cpp:1528 -msgid "Replaying user input in loop..." -msgstr "Återupprepar användarinmatning i loop..." - -#: ../src/gui/MyFrameMain.cpp:1530 -msgid "Replaying user input..." -msgstr "Återupprepar användarinmatning..." - -#: ../src/gui/FrameMain.cpp:98 -msgid "Request a Feature / Report a Bug" -msgstr "Begär en funktion / Rapportera ett fel" - -#: ../src/gui/MyFrameMain.cpp:483 ../src/gui/MyFrameMain.cpp:493 -msgid "Reverse connection terminated." -msgstr "Omvänd anslutning avslutad." - -#: ../src/gui/FrameMain.cpp:92 -msgid "S&top Sharing Window" -msgstr "Stoppa fönsterdelning" - -#: ../src/gui/FrameMain.cpp:43 -msgid "Save Statistics..." -msgstr "Spara statistik..." - -#: ../src/gui/MyFrameLog.cpp:93 -msgid "Save log as..." -msgstr "Spara logg som..." - -#: ../src/gui/MyFrameMain.cpp:1401 -msgid "Save recorded input..." -msgstr "Spara inspelad indata..." - -#: ../src/gui/MyFrameMain.cpp:1320 -msgid "Save screenshot..." -msgstr "Spara skärmklipp..." - -#: ../src/gui/MyFrameMain.cpp:765 -#, c-format -msgid "Saving %s statistics..." -msgstr "Sparar %s statistik..." - -#: ../src/gui/MyFrameMain.cpp:1718 -msgid "Saving bookmark" -msgstr "Sparar bokmärke" - -#: ../src/gui/DialogSettings.cpp:54 -msgid "" -"Set the multicast receive buffer size. Increasing the value may help against " -"packet loss. The size of this buffer is independent of the operating system." -msgstr "" -"Ställ in storleken på bufferten för multicast-mottagning. Om du ökar värdet " -"kan det hjälpa mot paketförlust. Storleken på denna buffert är oberoende av " -"operativsystemet." - -#: ../src/gui/DialogSettings.cpp:49 -msgid "" -"Set the multicast socket receive buffer size. Increasing the value may help " -"against packet loss. Note that the maximum value is operating system " -"dependent." -msgstr "" -"Ställ in storleken på multicast mottagningsbuffert för socket. Om du ökar " -"värdet kan det hjälpa mot paketförlust. Observera att det maximala värdet " -"beror på operativsystemet." - -#: ../src/gui/MyFrameMain.cpp:2075 ../src/gui/MyFrameMain.cpp:2080 -msgid "Share a Window" -msgstr "Dela ett fönster" - -#: ../src/gui/MyFrameMain.cpp:2115 -#, c-format -msgid "Sharing window with %s" -msgstr "Delar fönster med %s" - -#: ../src/gui/FrameMain.cpp:37 -msgid "Show &Log" -msgstr "Visa &loggen" - -#: ../src/gui/FrameMain.cpp:96 -msgid "Show Help." -msgstr "Visa hjälpen." - -#: ../src/gui/FrameMain.cpp:37 -msgid "Show detailed log." -msgstr "Visa detaljerad logg." - -#: ../src/gui/DialogSettings.cpp:46 -msgid "Socket Receive Buffer Size (kB):" -msgstr "Storlek på mottagningsbuffert för socket (kB):" - -#: ../src/gui/FrameMain.cpp:69 -msgid "South" -msgstr "Syd" - -#: ../src/gui/FrameMain.cpp:60 ../src/gui/ViewerWindow.cpp:431 -msgid "Statistics" -msgstr "Statistik" - -#: ../src/gui/FrameMain.cpp:110 -msgid "Status" -msgstr "Status" - -#: ../src/gui/MyFrameMain.cpp:1437 ../src/gui/MyFrameMain.cpp:1524 -msgid "Stop" -msgstr "Stopp" - -#: ../src/gui/MyFrameMain.cpp:1433 -msgid "Stop Recording" -msgstr "Stoppa inspelning" - -#: ../src/gui/MyFrameMain.cpp:1520 -msgid "Stop Replaying" -msgstr "Stoppa återuppspelning" - -#: ../src/gui/FrameMain.cpp:92 -msgid "Stop Window Sharing." -msgstr "Stoppa fönsterdelning." - -#: ../src/gui/MyFrameMain.cpp:1381 -msgid "Stopped recording user input!" -msgstr "Stoppade inspelning av användarindata!" - -#: ../src/gui/MyFrameMain.cpp:1477 -msgid "Stopped replaying user input!" -msgstr "Stoppade återuppspelning av användarindata!" - -#: ../src/gui/MyFrameMain.cpp:2156 -#, c-format -msgid "Stopped sharing window with %s" -msgstr "Stoppade fönsterdelning med %s" - -#: ../src/gui/MyFrameMain.cpp:1834 -msgid "Supported Encodings:" -msgstr "Kodningar som stöds:" - -#: ../src/gui/MyFrameMain.cpp:1824 -msgid "Supported Security Types:" -msgstr "Säkerhetstyper som stöds:" - -#: ../src/gui/FrameMain.cpp:41 ../src/gui/FrameMain.cpp:126 -msgid "Take Screenshot" -msgstr "Ta ett skärmklipp" - -#: ../src/gui/FrameMain.cpp:126 -msgid "Take a screenshot of the current connection." -msgstr "Tar en skärmdump av den aktuella anslutningen." - -#: ../src/gui/FrameMain.cpp:35 ../src/gui/FrameMain.cpp:122 -msgid "Terminate connection." -msgstr "Avbryt anslutningen." - -#: ../src/gui/MyFrameLog.cpp:94 -msgid "Text files (*.txt)|*.txt" -msgstr "Textfiler (*.txt)|*.txt" - -#: ../src/gui/MyFrameMain.cpp:2079 -msgid "" -"The MultiVNC window will be minimized and a cross-shaped cursor will appear. " -"Use it to select the window you want to share." -msgstr "" -"MultiVNC-fönstret minimeras och en korsformad markör visas. Använd den för " -"att välja det fönster du vill dela." - -#: ../src/gui/DialogSettings.cpp:81 -msgid "Tight" -msgstr "Tight" - -#: ../src/gui/MyFrameMain.cpp:1957 ../src/gui/MyFrameMain.cpp:1958 -msgid "Timeout looking up IP address." -msgstr "Tidsgränsen för IP-uppslagning överskreds." - -#: ../src/gui/MyFrameMain.cpp:1918 ../src/gui/MyFrameMain.cpp:1919 -msgid "Timeout looking up hostname." -msgstr "Tidsgränsen för värdnamnsuppslagning överskreds." - -#: ../src/gui/FrameMain.cpp:136 -msgid "Toggle 1:1 view, disabling all scaling." -msgstr "Växla mellan 1:1-vy och skalad vy." - -#: ../src/gui/FrameMain.cpp:134 -msgid "Toggle fullscreen view." -msgstr "Växlar helskärmsvy." - -#: ../src/gui/FrameMain.cpp:54 -msgid "Toolbar" -msgstr "Verktygsfält" - -#: ../src/gui/DialogSettings.cpp:79 -msgid "Ultra" -msgstr "Ultra" - -#: ../src/gui/ViewerWindow.cpp:438 -msgid "Updates/s:" -msgstr "Uppdateringar/s:" - -#: ../src/gui/DialogSettings.cpp:90 -msgid "" -"Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. " -"Level 1 uses minimum of CPU time and achieves weak compression ratios, while " -"level 9 offers best compression but is slow in terms of CPU time consumption " -"on the server side. Use high levels with very slow network connections, and " -"low levels when working over high-speed LANs." -msgstr "" -"Använd angiven komprimeringsnivå (0..9) för Tight- och Zlib-kodningar. Nivå " -"1 använder minsta möjliga CPU-tid och uppnår svaga kompressionsförhållanden, " -"medan nivå 9 ger bästa komprimering men är långsam när det gäller CPU-tid på " -"serversidan. Använd höga nivåer vid mycket långsamma nätverksanslutningar " -"och låga nivåer när du arbetar över höghastighets-LAN." - -#: ../src/gui/DialogSettings.cpp:95 -msgid "" -"Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' " -"encodings. Quality level 0 denotes bad image quality but very impressive " -"compression ratios, while level 9 offers very good image quality at lower " -"compression ratios. Note that the \"tight\" encoder uses JPEG to encode only " -"those screen areas that look suitable for lossy compression, so quality " -"level 0 does not always mean unacceptable image quality." -msgstr "" -"Använd angiven kvalitetsnivå (0..9) för kodningarna Tight och ZYWRLE. " -"Kvalitetsnivå 0 innebär dålig bildkvalitet men mycket imponerande " -"kompressionsförhållanden, medan nivå 9 ger mycket god bildkvalitet vid lägre " -"kompressionsförhållanden. Observera att tight-kodaren använder JPEG för att " -"koda endast de skärmområden som ser lämpliga ut för förlustkomprimering, så " -"kvalitetsnivå 0 innebär inte alltid oacceptabel bildkvalitet." - -#: ../src/gui/DialogLogin.cpp:58 -msgid "Username or password must not empty" -msgstr "Användarnamn och lösenord kan inte vara tomt" - -#: ../src/gui/DialogLogin.cpp:15 -msgid "Username:" -msgstr "Användarnamn:" - -#: ../src/gui/MyFrameMain.cpp:1826 -msgid "VNC Authentication" -msgstr "VNC-autentisering" - -#: ../src/gui/MyFrameMain.cpp:1890 -msgid "VNC Server" -msgstr "VNC-server" - -#: ../src/gui/FrameMain.cpp:77 ../src/gui/FrameMain.cpp:136 -msgid "View 1:1" -msgstr "Vy 1:1" - -#: ../src/gui/DialogLogin.cpp:58 -msgid "Warning!" -msgstr "Varning!" - -#: ../src/gui/FrameMain.cpp:67 -msgid "West" -msgstr "Väst" - -#: ../src/gui/FrameMain.cpp:94 ../src/gui/MyFrameMain.cpp:134 -#: ../src/gui/MyFrameMain.cpp:139 -msgid "Window &Sharing" -msgstr "&Fönsterdelning" - -#: ../src/gui/MyFrameMain.cpp:611 -msgid "" -"Window share helper exited without an associated connection. That should not " -"happen." -msgstr "" -"Hjälparen för fönsterdelning avslutades utan associerad anslutning. Det " -"borde inte hända." - -#: ../src/gui/MyFrameMain.cpp:2106 -msgid "Window sharing helper execution failed." -msgstr "Exekvering av fönsterdelningshjälpen misslyckades." - -#: ../src/gui/MyFrameMain.cpp:623 -msgid "Window sharing stopped. Shared window was closed." -msgstr "Fönsterdelningen har stoppats. Det delade fönstret stängdes." - -#: ../src/gui/MyFrameMain.cpp:617 -#, c-format -msgid "" -"Window sharing with %s stopped. Either the other side does not support " -"receiving windows or the window was closed there." -msgstr "" -"Fönsterdelning med %s har stoppats. Antingen har den andra sidan inte stöd " -"för att ta emot fönster eller också stängdes fönstret där." - -#: ../src/gui/DialogSettings.cpp:100 -msgid "Write VNC log to logfile (MultiVNC.log)" -msgstr "Skriv VNC-logg till loggfil (MultiVNC.log)" - -#: ../src/gui/DialogSettings.cpp:75 -msgid "ZRLE" -msgstr "ZRLE" - -#: ../src/gui/DialogSettings.cpp:77 -msgid "ZYWRLE" -msgstr "ZYWRLE" - -#: ../src/gui/DialogSettings.cpp:71 -msgid "Zlib" -msgstr "Zlib" - -#: ../src/gui/DialogSettings.cpp:73 -msgid "ZlibHex" -msgstr "ZlibHex" - -msgid "translator-credits" -msgstr "Åke Engelbrektson " +# Swedish translation for MultiVNC. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the MultiVNC package. +# Åke Engelbrektson , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: MultiVNC\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-06 18:13+0200\n" +"PO-Revision-Date: 2024-10-06 20:30+0200\n" +"Last-Translator: Christian Beier \n" +"Language-Team: \n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.2.2\n" + +#: ../src/gui/FrameMain.cpp:81 +msgid "&Add Bookmark" +msgstr "&Lägg till bokmärke" + +#: ../src/gui/FrameMain.cpp:88 +msgid "&Bookmarks" +msgstr "&Bokmärken" + +#: ../src/gui/FrameMain.cpp:31 +msgid "&Connect...\tCtrl-T" +msgstr "&Anslut...\tCtrl-T" + +#: ../src/gui/FrameMain.cpp:96 +msgid "&Contents" +msgstr "&Innehåll" + +#: ../src/gui/FrameMain.cpp:86 +msgid "&Delete Bookmark" +msgstr "&Ta bort bokmärke" + +#: ../src/gui/FrameMain.cpp:35 +msgid "&Disconnect" +msgstr "&Koppla från" + +#: ../src/gui/FrameMain.cpp:84 +msgid "&Edit Bookmark" +msgstr "&Redigera bokmärke" + +#: ../src/gui/FrameMain.cpp:102 +msgid "&Help" +msgstr "&Hjälp" + +#: ../src/gui/FrameMain.cpp:33 +msgid "&Listen" +msgstr "&Lyssna" + +#: ../src/gui/FrameMain.cpp:52 +msgid "&Machine" +msgstr "&Maskin" + +#: ../src/gui/FrameMain.cpp:90 +msgid "&Share a Window" +msgstr "&Dela ett fönster" + +#: ../src/gui/FrameMain.cpp:79 +msgid "&View" +msgstr "&Visa" + +#: ../src/gui/MyFrameMain.cpp:890 +msgid "(Reverse Connection)" +msgstr "(Omvänd anslutning)" + +#: ../src/gui/MyFrameMain.cpp:1725 +msgid "A bookmark with this name already exists!" +msgstr "Det finns redan ett bokmärke med det här namnet!" + +#: ../src/gui/MyFrameMain.cpp:310 +msgid "Authentication canceled." +msgstr "Autentiseringen avbröts." + +#: ../src/gui/DialogSettings.cpp:102 +msgid "Autosave statistics on close" +msgstr "Spara statistik automatiskt vid avslut" + +#: ../src/gui/FrameMain.cpp:153 +msgid "Available VNC Servers" +msgstr "Tillgängliga VNC-servrar" + +#: ../src/gui/FrameMain.cpp:90 +msgid "Beam a window to the server." +msgstr "Stråla ett fönster till servern." + +#: ../src/gui/MyFrameMain.cpp:1187 +msgid "Bookmark" +msgstr "Bokmärk" + +#: ../src/gui/MyFrameMain.cpp:1991 +#, c-format +msgid "Bookmark %s" +msgstr "Bokmärk %s" + +#: ../src/gui/FrameMain.cpp:58 ../src/gui/FrameMain.cpp:158 +#: ../src/gui/MyFrameMain.cpp:1145 +msgid "Bookmarks" +msgstr "Bokmärken" + +#: ../src/gui/MyFrameMain.cpp:1822 +msgid "Built with" +msgstr "Byggt med" + +#: ../src/gui/MyFrameMain.cpp:769 ../src/gui/MyFrameMain.cpp:1405 +#: ../src/gui/MyFrameMain.cpp:1492 +msgid "CSV files|*.csv" +msgstr "CSV-filer|*.csv" + +#: ../src/gui/DialogLogin.cpp:37 +msgid "Cancel" +msgstr "Avbryt" + +#: ../src/gui/MyFrameMain.cpp:1713 +msgid "Cannot bookmark a reverse connection!" +msgstr "Kan inte bokmärka en omvänd anslutning!" + +#: ../src/gui/FrameMain.cpp:39 +msgid "Change preferences." +msgstr "Ändra inställningar." + +#: ../src/gui/DialogSettings.cpp:69 +msgid "CoRRE" +msgstr "CoRRE" + +#: ../src/gui/DialogSettings.cpp:87 +msgid "Compression level for 'Tight' and 'Zlib' encodings:" +msgstr "Komprimeringsnivå för Tight- och Zlib-kodningar:" + +#: ../src/gui/FrameMain.cpp:117 +msgid "Connect" +msgstr "Anslut" + +#: ../src/gui/FrameMain.cpp:31 ../src/gui/FrameMain.cpp:117 +#: ../src/gui/MyFrameMain.cpp:1215 +msgid "Connect to a specific host." +msgstr "Anslut en specifik värddator." + +#: ../src/gui/MyFrameMain.cpp:854 +#, c-format +msgid "Connecting to %s..." +msgstr "Ansluter %s..." + +#: ../src/gui/MyFrameMain.cpp:312 +msgid "Connection failed." +msgstr "Anslutningen misslyckades." + +#: ../src/gui/MyFrameMain.cpp:1055 +msgid "Connection terminated." +msgstr "Anslutningen avbröts." + +#: ../src/gui/MyFrameMain.cpp:377 +#, c-format +msgid "Connection to %s is now multicast." +msgstr "Anslutningen till %s är nu multicast." + +#: ../src/gui/MyFrameMain.cpp:388 +#, c-format +msgid "Connection to %s is now unicast." +msgstr "Anslutningen till %s är nu unicast." + +#: ../src/gui/MyFrameMain.cpp:386 +#, c-format +msgid "Connection to %s switched to unicast." +msgstr "Anslutningen till %s ändrades till unicast." + +#: ../src/gui/MyFrameMain.cpp:481 ../src/gui/MyFrameMain.cpp:491 +#, c-format +msgid "Connection to %s:%s terminated." +msgstr "Anslutningen till %s:%s avbröts." + +#: ../src/gui/DialogSettings.cpp:27 +msgid "Connections" +msgstr "Anslutningar" + +#: ../src/gui/DialogSettings.cpp:36 +msgid "" +"Continously ask the server for updates instead of just asking after each " +"received server message. Use this on high latency links." +msgstr "" +"Fråga kontinuerligt servern om uppdateringar i stället för att bara fråga " +"efter varje mottaget servermeddelande. Använd detta på länkar med hög " +"latenstid." + +#: ../src/gui/DialogSettings.cpp:33 +msgid "Continously request updates at the specified milisecond interval:" +msgstr "Begär kontinuerliga uppdateringar med angivet milisekundintervall:" + +#: ../src/gui/DialogSettings.cpp:63 +msgid "CopyRect" +msgstr "CopyRect" + +#: ../src/gui/MyFrameMain.cpp:994 +msgid "Could not autosave statistics!" +msgstr "Kunde inte spara statistik automatiskt!" + +#: ../src/VNCConn.cpp:1114 +msgid "Could not create VNC listener thread!" +msgstr "Kunde inte skapa VNC-lyssnartråd!" + +#: ../src/VNCConn.cpp:1178 +msgid "Could not create VNC thread!" +msgstr "Kunde inte skapa VNC-tråd!" + +#: ../src/MultiVNCApp.cpp:112 +msgid "Could not open config file!" +msgstr "Kunde öppna config-fil!" + +#: ../src/gui/MyFrameMain.cpp:1504 +msgid "Could not open file!" +msgstr "Kunde öppna fil!" + +#: ../src/gui/MyFrameMain.cpp:626 ../src/gui/MyFrameMain.cpp:627 +msgid "Could not run window share helper." +msgstr "Kunde inte köra fönsterdelningshjälpen." + +#: ../src/MultiVNCApp.cpp:162 +msgid "Could not save config file!" +msgstr "Kunde inte spara config-fil!" + +#: ../src/gui/MyFrameMain.cpp:781 ../src/gui/MyFrameMain.cpp:1416 +msgid "Could not save file!" +msgstr "Kunde inte spara fil!" + +#: ../src/gui/MyFrameMain.cpp:2107 +msgid "Could not share window, external program execution failed." +msgstr "Kunde inte dela fönster, extern programstart misslyckades." + +#: ../src/VNCConn.cpp:1122 +msgid "Could not start VNC listener thread!" +msgstr "Kunde inte starta VNC-lyssnartråd!" + +#: ../src/VNCConn.cpp:1186 +msgid "Could not start VNC thread!" +msgstr "Kunde inte starta VNC-tråd!" + +#: ../src/gui/MyFrameMain.cpp:718 ../src/gui/MyFrameMain.cpp:727 +msgid "Credentials required..." +msgstr "Autentiseringsuppgifter krävs..." + +#: ../src/gui/MyFrameMain.cpp:1259 +msgid "Detailed VNC Log" +msgstr "Detaljerad VNC-logg" + +#: ../src/gui/FrameMain.cpp:71 +msgid "Disabled" +msgstr "Inaktiverad" + +#: ../src/gui/FrameMain.cpp:122 +msgid "Disconnect" +msgstr "Koppla från" + +#: ../src/gui/FrameMain.cpp:56 +msgid "Discovered Servers" +msgstr "Identifierade servrar" + +#: ../src/gui/FrameMain.cpp:65 +msgid "East" +msgstr "Öst" + +#: ../src/gui/FrameMain.cpp:73 +msgid "Edge Connector" +msgstr "Edge Connector" + +#: ../src/gui/MyFrameMain.cpp:1764 +msgid "Edit bookmark" +msgstr "Redigera bokmärke" + +#: ../src/gui/ViewerWindow.cpp:436 +msgid "Eff. KB/s:" +msgstr "Eff. KB/s:" + +#: ../src/gui/DialogSettings.cpp:40 +msgid "Enable Expedited Forwarding tagging for sent data" +msgstr "Aktivera Expedited Forwarding-taggning för skickad data" + +#: ../src/gui/DialogSettings.cpp:31 +msgid "Enable FastRequest" +msgstr "Aktivera FastRequest" + +#: ../src/gui/DialogSettings.cpp:44 +msgid "Enable MulticastVNC" +msgstr "Aktivera MulticastVNC" + +#: ../src/gui/DialogSettings.cpp:59 +msgid "Enabled Encodings" +msgstr "Aktiverade kodningar" + +#: ../src/gui/DialogSettings.cpp:57 +msgid "Encodings" +msgstr "Kodningar" + +#: ../src/gui/MyFrameMain.cpp:1717 +msgid "Enter bookmark name:" +msgstr "Ange bokmärkesnamn:" + +#: ../src/gui/MyFrameMain.cpp:1214 +msgid "Enter host to connect to:" +msgstr "Ange värddator att ansluta:" + +#: ../src/gui/MyFrameMain.cpp:2075 +msgid "Enter name of window to share:" +msgstr "Ange namn för fönster att dela:" + +#: ../src/gui/MyFrameMain.cpp:697 +msgid "Enter password:" +msgstr "Ange lösenord:" + +#: ../src/gui/MyFrameMain.cpp:1160 +#, c-format +msgid "Error reading hostname of bookmark '%s'!" +msgstr "Fel vid inläsning av värdnamn för bokmärket %s!" + +#: ../src/gui/MyFrameMain.cpp:1167 +#, c-format +msgid "Error reading port of bookmark '%s'!" +msgstr "Fel vid inläsning av port för bokmärket %s!" + +#: ../src/gui/FrameMain.cpp:50 +msgid "Exit MultiVNC." +msgstr "Avsluta MultiVNC." + +#: ../src/gui/MyFrameMain.cpp:1739 +msgid "Failed to save credentials to the system secret store." +msgstr "Kunde inte spara autentiseringsuppgifter i systemets hemliga arkiv." + +#: ../src/VNCConn.cpp:294 +#, c-format +msgid "Failure connecting to server at %s:%d!" +msgstr "Kunde inte ansluta till servern på %s:%d!" + +#: ../src/VNCConn.cpp:257 +msgid "Failure setting up framebuffer: wrong BPP!" +msgstr "Fel i inställningen av rambuffert: Fel BPP!" + +#: ../src/gui/DialogSettings.cpp:29 +msgid "FastRequest" +msgstr "FastRequest" + +#: ../src/gui/MyFrameMain.cpp:1428 +msgid "" +"From now on, all your mouse and keyboard input will be recorded. Click the " +"stop button to finish recording and save your input." +msgstr "" +"Från och med nu kommer alla dina mus- och tangentbordsinmatningar att " +"registreras. Klicka på stoppknappen för att avsluta inspelningen och spara " +"din inmatning." + +#: ../src/gui/FrameMain.cpp:134 +msgid "Fullscreen" +msgstr "Fullskärm" + +#: ../src/gui/FrameMain.cpp:75 +msgid "Fullscreen\tF11" +msgstr "Fullskärm\tF11" + +#: ../src/MultiVNCApp.cpp:178 +msgid "GAAH! Got an unhandled exception! This should not happen." +msgstr "GAAH! Vi fick ett obehandlat undantag! Detta borde inte inträffa." + +#: ../src/gui/FrameMain.cpp:124 +msgid "Grab Keyboard" +msgstr "Ta över tangentbordet" + +#: ../src/gui/DialogSettings.cpp:65 +msgid "Hextile" +msgstr "Hextile" + +#: ../src/gui/MyFrameMain.cpp:1863 +msgid "" +"If there are VNC servers advertising themselves via ZeroConf, you can select " +"a host in the'Available VNC Servers' list. Otherwise, use the 'Connect' " +"button or menu item.\n" +"\n" +"When connected, a blue or green icon on the tab label shows if you are " +"running in unicast or multicast mode." +msgstr "" +"Om det finns VNC-servrar som annonserar sig själva via ZeroConf kan du välja " +"en värddator i listan \"Tillgängliga VNC-servrar\". Annars kan du använda " +"knappen \"Anslut\" eller menyalternativet.\n" +"\n" +"När du är ansluten visar en blå eller grön ikon på fliketiketten om du kör i " +"unicast- eller multicast-läge." + +#: ../src/gui/MyFrameMain.cpp:514 +msgid "Incoming Connection." +msgstr "Inkommande anslutning." + +#: ../src/gui/FrameMain.cpp:124 +msgid "" +"Intercept all keyboard input. Allows you to use special keys that would " +"otherwise be interpreted by the local computer." +msgstr "" +"Uppfattar all tangentbordsinmatning. Gör det möjligt att använda speciella " +"tangenter som annars skulle tolkas av den lokala datorn." + +#: ../src/gui/ViewerWindow.cpp:440 +msgid "Latency ms:" +msgstr "Latens ms:" + +#: ../src/gui/FrameMain.cpp:119 +msgid "Listen" +msgstr "Lyssna" + +#: ../src/gui/FrameMain.cpp:33 ../src/gui/FrameMain.cpp:119 +msgid "Listen for an incoming connection." +msgstr "Lyssnar efter inkommande anslutning." + +#: ../src/gui/MyFrameMain.cpp:848 ../src/gui/MyFrameMain.cpp:926 +msgid "Listening on port" +msgstr "Lyssnar på port" + +#: ../src/gui/MyFrameMain.cpp:1488 +msgid "Load recorded input..." +msgstr "Läs in inspelad indata..." + +#: ../src/gui/DialogSettings.cpp:98 +msgid "Logging" +msgstr "Loggning" + +#: ../src/gui/DialogLogin.cpp:39 +msgid "Login" +msgstr "Inloggning" + +#: ../src/gui/MyFrameMain.cpp:1903 +msgid "Looking up host address..." +msgstr "Söker upp värdadress..." + +#: ../src/gui/ViewerWindow.cpp:442 +msgid "Loss Ratio:" +msgstr "Förlustkvot:" + +#: ../src/gui/DialogSettings.cpp:85 +msgid "Lossy Encodings Settings" +msgstr "Inställningar för förstörande kodningar" + +#: ../src/gui/FrameMain.cpp:24 +msgid "MultiVNC" +msgstr "MultiVNC" + +#: ../src/MultiVNCApp.cpp:186 +msgid "MultiVNC crashed" +msgstr "MultiVNC kraschade" + +#: ../src/gui/MyFrameMain.cpp:1820 +msgid "MultiVNC is a cross-platform Multicast-enabled VNC client." +msgstr "MultiVNC är en plattformsöverskridande Multicast-aktiverad VNC-klient." + +#: ../src/gui/DialogSettings.cpp:42 +msgid "MulticastVNC" +msgstr "MulticastVNC" + +#: ../src/gui/MyFrameMain.cpp:1763 +msgid "New bookmark name:" +msgstr "Nytt bokmärkesnamn:" + +#: ../src/gui/MyFrameMain.cpp:1759 ../src/gui/MyFrameMain.cpp:1788 +msgid "No bookmark selected!" +msgstr "Inget bokmärke valt!" + +#: ../src/gui/MyFrameMain.cpp:1794 +msgid "No bookmark with this name!" +msgstr "Det finns inget bokmärke med det namnet!" + +#: ../src/gui/FrameMain.cpp:63 +msgid "North" +msgstr "Norr" + +#: ../src/gui/MyFrameMain.cpp:753 ../src/gui/MyFrameMain.cpp:1390 +msgid "Nothing to save!" +msgstr "Inget att spara!" + +#: ../src/MultiVNCApp.cpp:186 +msgid "" +"Ouch! MultiVNC crashed. This should not happen. Do you want to generate a " +"bug report?" +msgstr "" +"Ajaj! MultiVNC kraschade. Detta borde inte hända. Vill du generera en " +"felrapport?" + +#: ../src/gui/MyFrameMain.cpp:1324 +msgid "PNG files|*.png" +msgstr "PNG-filer|*.png" + +#: ../src/gui/MyFrameMain.cpp:697 +msgid "Password required!" +msgstr "Lösenord krävs!" + +#: ../src/gui/DialogLogin.cpp:23 +msgid "Password:" +msgstr "Lösenord:" + +#: ../src/gui/MyFrameMain.cpp:717 +#, c-format +msgid "Please enter password for user '%s'" +msgstr "Ange lösenord för användare '%s'" + +#: ../src/gui/MyFrameMain.cpp:1272 +msgid "Preferences" +msgstr "Inställningar" + +#: ../src/gui/DialogSettings.cpp:92 +msgid "Quality level for 'Tight' and 'ZYWRLE' encoding:" +msgstr "Kvalitetsnivå för Tight- och ZYWRLE-kodning:" + +#: ../src/gui/DialogSettings.cpp:38 +msgid "Quality of Service" +msgstr "Tjänstens kvalitet" + +#: ../src/gui/DialogSettings.cpp:67 +msgid "RRE" +msgstr "RRE" + +#: ../src/gui/ViewerWindow.cpp:444 +msgid "Rcv Buffer:" +msgstr "Rcv-buffert:" + +#: ../src/gui/DialogSettings.cpp:51 +msgid "Receive Buffer Size (kB):" +msgstr "Storlek på mottagningsbuffert (kB):" + +#: ../src/gui/FrameMain.cpp:45 ../src/gui/FrameMain.cpp:129 +#: ../src/gui/MyFrameMain.cpp:1374 ../src/gui/MyFrameMain.cpp:1382 +msgid "Record Input" +msgstr "Spela in indata" + +#: ../src/gui/FrameMain.cpp:129 +msgid "Record mouse and keyboard input for later replay as a macro." +msgstr "" +"Spelar in mus- och tangentbordsinmatning för senare uppspelning som ett " +"makro." + +#: ../src/gui/MyFrameMain.cpp:1440 +msgid "Recording user input..." +msgstr "Spelar in användarindata..." + +#: ../src/gui/FrameMain.cpp:47 ../src/gui/FrameMain.cpp:131 +#: ../src/gui/MyFrameMain.cpp:414 ../src/gui/MyFrameMain.cpp:418 +#: ../src/gui/MyFrameMain.cpp:1470 ../src/gui/MyFrameMain.cpp:1474 +msgid "Replay Input" +msgstr "Återupprepa indata" + +#: ../src/gui/FrameMain.cpp:131 +msgid "" +"Replay a recorded mouse and keyboard input macro. If is held down " +"while clicking this button, the macro is replayed in a loop." +msgstr "" +"Spelar om ett inspelat inmatningsmakro för mus och tangentbord. Om " +"hålls nedtryckt medan du klickar på den här knappen, spelas makrot upp i en " +"loop." + +#: ../src/gui/MyFrameMain.cpp:425 ../src/gui/MyFrameMain.cpp:426 +msgid "Replay finished!" +msgstr "Återupprepning slutförd!" + +#: ../src/gui/MyFrameMain.cpp:1528 +msgid "Replaying user input in loop..." +msgstr "Återupprepar användarinmatning i loop..." + +#: ../src/gui/MyFrameMain.cpp:1530 +msgid "Replaying user input..." +msgstr "Återupprepar användarinmatning..." + +#: ../src/gui/FrameMain.cpp:98 +msgid "Request a Feature / Report a Bug" +msgstr "Begär en funktion / Rapportera ett fel" + +#: ../src/gui/MyFrameMain.cpp:483 ../src/gui/MyFrameMain.cpp:493 +msgid "Reverse connection terminated." +msgstr "Omvänd anslutning avslutad." + +#: ../src/gui/FrameMain.cpp:92 +msgid "S&top Sharing Window" +msgstr "Stoppa fönsterdelning" + +#: ../src/gui/FrameMain.cpp:43 +msgid "Save Statistics..." +msgstr "Spara statistik..." + +#: ../src/gui/MyFrameLog.cpp:93 +msgid "Save log as..." +msgstr "Spara logg som..." + +#: ../src/gui/MyFrameMain.cpp:1401 +msgid "Save recorded input..." +msgstr "Spara inspelad indata..." + +#: ../src/gui/MyFrameMain.cpp:1320 +msgid "Save screenshot..." +msgstr "Spara skärmklipp..." + +#: ../src/gui/MyFrameMain.cpp:765 +#, c-format +msgid "Saving %s statistics..." +msgstr "Sparar %s statistik..." + +#: ../src/gui/MyFrameMain.cpp:1718 +msgid "Saving bookmark" +msgstr "Sparar bokmärke" + +#: ../src/gui/DialogSettings.cpp:54 +msgid "" +"Set the multicast receive buffer size. Increasing the value may help against " +"packet loss. The size of this buffer is independent of the operating system." +msgstr "" +"Ställ in storleken på bufferten för multicast-mottagning. Om du ökar värdet " +"kan det hjälpa mot paketförlust. Storleken på denna buffert är oberoende av " +"operativsystemet." + +#: ../src/gui/DialogSettings.cpp:49 +msgid "" +"Set the multicast socket receive buffer size. Increasing the value may help " +"against packet loss. Note that the maximum value is operating system " +"dependent." +msgstr "" +"Ställ in storleken på multicast mottagningsbuffert för socket. Om du ökar " +"värdet kan det hjälpa mot paketförlust. Observera att det maximala värdet " +"beror på operativsystemet." + +#: ../src/gui/MyFrameMain.cpp:2075 ../src/gui/MyFrameMain.cpp:2080 +msgid "Share a Window" +msgstr "Dela ett fönster" + +#: ../src/gui/MyFrameMain.cpp:2115 +#, c-format +msgid "Sharing window with %s" +msgstr "Delar fönster med %s" + +#: ../src/gui/FrameMain.cpp:37 +msgid "Show &Log" +msgstr "Visa &loggen" + +#: ../src/gui/FrameMain.cpp:96 +msgid "Show Help." +msgstr "Visa hjälpen." + +#: ../src/gui/FrameMain.cpp:37 +msgid "Show detailed log." +msgstr "Visa detaljerad logg." + +#: ../src/gui/DialogSettings.cpp:46 +msgid "Socket Receive Buffer Size (kB):" +msgstr "Storlek på mottagningsbuffert för socket (kB):" + +#: ../src/gui/FrameMain.cpp:69 +msgid "South" +msgstr "Syd" + +#: ../src/gui/FrameMain.cpp:60 ../src/gui/ViewerWindow.cpp:431 +msgid "Statistics" +msgstr "Statistik" + +#: ../src/gui/FrameMain.cpp:110 +msgid "Status" +msgstr "Status" + +#: ../src/gui/MyFrameMain.cpp:1437 ../src/gui/MyFrameMain.cpp:1524 +msgid "Stop" +msgstr "Stopp" + +#: ../src/gui/MyFrameMain.cpp:1433 +msgid "Stop Recording" +msgstr "Stoppa inspelning" + +#: ../src/gui/MyFrameMain.cpp:1520 +msgid "Stop Replaying" +msgstr "Stoppa återuppspelning" + +#: ../src/gui/FrameMain.cpp:92 +msgid "Stop Window Sharing." +msgstr "Stoppa fönsterdelning." + +#: ../src/gui/MyFrameMain.cpp:1381 +msgid "Stopped recording user input!" +msgstr "Stoppade inspelning av användarindata!" + +#: ../src/gui/MyFrameMain.cpp:1477 +msgid "Stopped replaying user input!" +msgstr "Stoppade återuppspelning av användarindata!" + +#: ../src/gui/MyFrameMain.cpp:2156 +#, c-format +msgid "Stopped sharing window with %s" +msgstr "Stoppade fönsterdelning med %s" + +#: ../src/gui/MyFrameMain.cpp:1834 +msgid "Supported Encodings:" +msgstr "Kodningar som stöds:" + +#: ../src/gui/MyFrameMain.cpp:1824 +msgid "Supported Security Types:" +msgstr "Säkerhetstyper som stöds:" + +#: ../src/gui/FrameMain.cpp:41 ../src/gui/FrameMain.cpp:126 +msgid "Take Screenshot" +msgstr "Ta ett skärmklipp" + +#: ../src/gui/FrameMain.cpp:126 +msgid "Take a screenshot of the current connection." +msgstr "Tar en skärmdump av den aktuella anslutningen." + +#: ../src/gui/FrameMain.cpp:35 ../src/gui/FrameMain.cpp:122 +msgid "Terminate connection." +msgstr "Avbryt anslutningen." + +#: ../src/gui/MyFrameLog.cpp:94 +msgid "Text files (*.txt)|*.txt" +msgstr "Textfiler (*.txt)|*.txt" + +#: ../src/gui/MyFrameMain.cpp:2079 +msgid "" +"The MultiVNC window will be minimized and a cross-shaped cursor will appear. " +"Use it to select the window you want to share." +msgstr "" +"MultiVNC-fönstret minimeras och en korsformad markör visas. Använd den för " +"att välja det fönster du vill dela." + +#: ../src/gui/DialogSettings.cpp:81 +msgid "Tight" +msgstr "Tight" + +#: ../src/gui/MyFrameMain.cpp:1957 ../src/gui/MyFrameMain.cpp:1958 +msgid "Timeout looking up IP address." +msgstr "Tidsgränsen för IP-uppslagning överskreds." + +#: ../src/gui/MyFrameMain.cpp:1918 ../src/gui/MyFrameMain.cpp:1919 +msgid "Timeout looking up hostname." +msgstr "Tidsgränsen för värdnamnsuppslagning överskreds." + +#: ../src/gui/FrameMain.cpp:136 +msgid "Toggle 1:1 view, disabling all scaling." +msgstr "Växla mellan 1:1-vy och skalad vy." + +#: ../src/gui/FrameMain.cpp:134 +msgid "Toggle fullscreen view." +msgstr "Växlar helskärmsvy." + +#: ../src/gui/FrameMain.cpp:54 +msgid "Toolbar" +msgstr "Verktygsfält" + +#: ../src/gui/DialogSettings.cpp:79 +msgid "Ultra" +msgstr "Ultra" + +#: ../src/gui/ViewerWindow.cpp:438 +msgid "Updates/s:" +msgstr "Uppdateringar/s:" + +#: ../src/gui/DialogSettings.cpp:90 +msgid "" +"Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. " +"Level 1 uses minimum of CPU time and achieves weak compression ratios, while " +"level 9 offers best compression but is slow in terms of CPU time consumption " +"on the server side. Use high levels with very slow network connections, and " +"low levels when working over high-speed LANs." +msgstr "" +"Använd angiven komprimeringsnivå (0..9) för Tight- och Zlib-kodningar. Nivå " +"1 använder minsta möjliga CPU-tid och uppnår svaga kompressionsförhållanden, " +"medan nivå 9 ger bästa komprimering men är långsam när det gäller CPU-tid på " +"serversidan. Använd höga nivåer vid mycket långsamma nätverksanslutningar " +"och låga nivåer när du arbetar över höghastighets-LAN." + +#: ../src/gui/DialogSettings.cpp:95 +msgid "" +"Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' " +"encodings. Quality level 0 denotes bad image quality but very impressive " +"compression ratios, while level 9 offers very good image quality at lower " +"compression ratios. Note that the \"tight\" encoder uses JPEG to encode only " +"those screen areas that look suitable for lossy compression, so quality " +"level 0 does not always mean unacceptable image quality." +msgstr "" +"Använd angiven kvalitetsnivå (0..9) för kodningarna Tight och ZYWRLE. " +"Kvalitetsnivå 0 innebär dålig bildkvalitet men mycket imponerande " +"kompressionsförhållanden, medan nivå 9 ger mycket god bildkvalitet vid lägre " +"kompressionsförhållanden. Observera att tight-kodaren använder JPEG för att " +"koda endast de skärmområden som ser lämpliga ut för förlustkomprimering, så " +"kvalitetsnivå 0 innebär inte alltid oacceptabel bildkvalitet." + +#: ../src/gui/DialogLogin.cpp:58 +msgid "Username or password must not empty" +msgstr "Användarnamn och lösenord kan inte vara tomt" + +#: ../src/gui/DialogLogin.cpp:15 +msgid "Username:" +msgstr "Användarnamn:" + +#: ../src/gui/MyFrameMain.cpp:1826 +msgid "VNC Authentication" +msgstr "VNC-autentisering" + +#: ../src/gui/MyFrameMain.cpp:1890 +msgid "VNC Server" +msgstr "VNC-server" + +#: ../src/gui/FrameMain.cpp:77 ../src/gui/FrameMain.cpp:136 +msgid "View 1:1" +msgstr "Vy 1:1" + +#: ../src/gui/DialogLogin.cpp:58 +msgid "Warning!" +msgstr "Varning!" + +#: ../src/gui/FrameMain.cpp:67 +msgid "West" +msgstr "Väst" + +#: ../src/gui/FrameMain.cpp:94 ../src/gui/MyFrameMain.cpp:134 +#: ../src/gui/MyFrameMain.cpp:139 +msgid "Window &Sharing" +msgstr "&Fönsterdelning" + +#: ../src/gui/MyFrameMain.cpp:611 +msgid "" +"Window share helper exited without an associated connection. That should not " +"happen." +msgstr "" +"Hjälparen för fönsterdelning avslutades utan associerad anslutning. Det " +"borde inte hända." + +#: ../src/gui/MyFrameMain.cpp:2106 +msgid "Window sharing helper execution failed." +msgstr "Exekvering av fönsterdelningshjälpen misslyckades." + +#: ../src/gui/MyFrameMain.cpp:623 +msgid "Window sharing stopped. Shared window was closed." +msgstr "Fönsterdelningen har stoppats. Det delade fönstret stängdes." + +#: ../src/gui/MyFrameMain.cpp:617 +#, c-format +msgid "" +"Window sharing with %s stopped. Either the other side does not support " +"receiving windows or the window was closed there." +msgstr "" +"Fönsterdelning med %s har stoppats. Antingen har den andra sidan inte stöd " +"för att ta emot fönster eller också stängdes fönstret där." + +#: ../src/gui/DialogSettings.cpp:100 +msgid "Write VNC log to logfile (MultiVNC.log)" +msgstr "Skriv VNC-logg till loggfil (MultiVNC.log)" + +#: ../src/gui/DialogSettings.cpp:75 +msgid "ZRLE" +msgstr "ZRLE" + +#: ../src/gui/DialogSettings.cpp:77 +msgid "ZYWRLE" +msgstr "ZYWRLE" + +#: ../src/gui/DialogSettings.cpp:71 +msgid "Zlib" +msgstr "Zlib" + +#: ../src/gui/DialogSettings.cpp:73 +msgid "ZlibHex" +msgstr "ZlibHex" + +msgid "translator-credits" +msgstr "Åke Engelbrektson " diff --git a/prepareLibreSSL.sh b/prepareLibreSSL.sh index a89a3ce2..2b9c722e 100755 --- a/prepareLibreSSL.sh +++ b/prepareLibreSSL.sh @@ -1,17 +1,17 @@ -#!/bin/sh - -READLINK="readlink" -if [ "$(uname)" = "Darwin" ] -then - if [ -z "$(which greadlink)" ] - then - echo "On MacOS, make sure you have GNU coreutils installed. Bailing out." - exit 1 - fi - READLINK="greadlink" -fi - -MYDIR=$(dirname $($READLINK -f "$0")) - -cd "$MYDIR"/libressl +#!/bin/sh + +READLINK="readlink" +if [ "$(uname)" = "Darwin" ] +then + if [ -z "$(which greadlink)" ] + then + echo "On MacOS, make sure you have GNU coreutils installed. Bailing out." + exit 1 + fi + READLINK="greadlink" +fi + +MYDIR=$(dirname $($READLINK -f "$0")) + +cd "$MYDIR"/libressl ./autogen.sh \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d487937e..52bba62c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,116 +1,116 @@ -# -# dependencies -# -find_package(wxWidgets 3.1.6 REQUIRED core base qa) -include(${wxWidgets_USE_FILE}) -include_directories( - ${CMAKE_BINARY_DIR} - ${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/libwxservdisc/src - ${CMAKE_SOURCE_DIR}/libvncserver/include - ${CMAKE_BINARY_DIR}/libvncserver/include -) - - -# -# source and targets -# -add_subdirectory(gui) - -set(executable_name multivnc) - -SET(multivnc_SRCS - MultiVNCApp.cpp - MultiVNCApp.h - VNCConn.cpp - VNCConn.h - DebugReportEmail.cpp - DebugReportEmail.h - dfltcfg.h -) - -if(APPLE) - set(executable_name MultiVNC) - set(MACOSX_BUNDLE_BUNDLE_NAME "MultiVNC") - set(MACOSX_BUNDLE_GUI_IDENTIFIER "net.christianbeier.MultiVNC") - set(MACOSX_BUNDLE_COPYRIGHT "© Christian Beier (multivnc@christianbeier.net), 2009-2024") - set(MACOSX_BUNDLE_BUNDLE_VERSION 2) - set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) - set(MACOSX_BUNDLE_ICON_FILE icon.icns) - set(multivnc_SRCS ${multivnc_SRCS} icon.icns) - set_source_files_properties(icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) -endif(APPLE) - -add_executable(${executable_name} WIN32 MACOSX_BUNDLE ${multivnc_SRCS}) - -set_target_properties(${executable_name} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in) - -target_link_libraries(${executable_name} MultiVNCgui ${wxWidgets_LIBRARIES} wxservdisc vncclient) - - - - - - -#original Makefile.am contents follow: - -### Process this file with automake to produce Makefile.in -# -# -# -#SUBDIRS = \ -# wxServDisc/src\ -# libvncclient \ -# gui -# -# -#AM_CPPFLAGS = \ -# -IwxServDisc/src \ -# $(WX_CXXFLAGS) \ -# -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ -# -DPACKAGE_SRC_DIR=\""$(srcdir)"\" \ -# -DPACKAGE_DATA_DIR=\""$(datadir)"\" $(MULTIVNC_CFLAGS) -# -#AM_CFLAGS =\ -# -Wall \ -# -g \ -# $(WX_CPPFLAGS) -# -#bin_PROGRAMS = multivnc -# -#multivnc_SOURCES = \ -# MultiVNCApp.cpp \ -# MultiVNCApp.h \ -# VNCConn.cpp \ -# VNCConn.h \ -# dfltcfg.h \ -# msgqueue.h -# -#multivnc_LDADD = \ -# gui/libMultiVNCgui.a \ -# wxServDisc/src/libwxservdisc.la \ -# libvncclient/libvncclient.a \ -# $(GTK_LIBS) \ -# $(WX_LIBS) -# -#if MINGW -#multivnc_SOURCES += winres.rc -#multivnc_LDADD += -lws2_32 -# -#.rc.o: -# $(WX_RESCOMP) -o $@ $< -#endif -# -#if HAVE_GNUTLS -#multivnc_LDADD += @GNUTLS_LIBS@ -#else -#if HAVE_LIBSSL -#multivnc_LDADD += @SSL_LIBS@ @CRYPT_LIBS@ -#endif -#endif -# -# -#EXTRA_DIST = \ -# winres.rc\ -# icon.ico +# +# dependencies +# +find_package(wxWidgets 3.1.6 REQUIRED core base qa) +include(${wxWidgets_USE_FILE}) +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/src/ + ${CMAKE_SOURCE_DIR}/libwxservdisc/src + ${CMAKE_SOURCE_DIR}/libvncserver/include + ${CMAKE_BINARY_DIR}/libvncserver/include +) + + +# +# source and targets +# +add_subdirectory(gui) + +set(executable_name multivnc) + +SET(multivnc_SRCS + MultiVNCApp.cpp + MultiVNCApp.h + VNCConn.cpp + VNCConn.h + DebugReportEmail.cpp + DebugReportEmail.h + dfltcfg.h +) + +if(APPLE) + set(executable_name MultiVNC) + set(MACOSX_BUNDLE_BUNDLE_NAME "MultiVNC") + set(MACOSX_BUNDLE_GUI_IDENTIFIER "net.christianbeier.MultiVNC") + set(MACOSX_BUNDLE_COPYRIGHT "© Christian Beier (multivnc@christianbeier.net), 2009-2024") + set(MACOSX_BUNDLE_BUNDLE_VERSION 2) + set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}) + set(MACOSX_BUNDLE_ICON_FILE icon.icns) + set(multivnc_SRCS ${multivnc_SRCS} icon.icns) + set_source_files_properties(icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) +endif(APPLE) + +add_executable(${executable_name} WIN32 MACOSX_BUNDLE ${multivnc_SRCS}) + +set_target_properties(${executable_name} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in) + +target_link_libraries(${executable_name} MultiVNCgui ${wxWidgets_LIBRARIES} wxservdisc vncclient) + + + + + + +#original Makefile.am contents follow: + +### Process this file with automake to produce Makefile.in +# +# +# +#SUBDIRS = \ +# wxServDisc/src\ +# libvncclient \ +# gui +# +# +#AM_CPPFLAGS = \ +# -IwxServDisc/src \ +# $(WX_CXXFLAGS) \ +# -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ +# -DPACKAGE_SRC_DIR=\""$(srcdir)"\" \ +# -DPACKAGE_DATA_DIR=\""$(datadir)"\" $(MULTIVNC_CFLAGS) +# +#AM_CFLAGS =\ +# -Wall \ +# -g \ +# $(WX_CPPFLAGS) +# +#bin_PROGRAMS = multivnc +# +#multivnc_SOURCES = \ +# MultiVNCApp.cpp \ +# MultiVNCApp.h \ +# VNCConn.cpp \ +# VNCConn.h \ +# dfltcfg.h \ +# msgqueue.h +# +#multivnc_LDADD = \ +# gui/libMultiVNCgui.a \ +# wxServDisc/src/libwxservdisc.la \ +# libvncclient/libvncclient.a \ +# $(GTK_LIBS) \ +# $(WX_LIBS) +# +#if MINGW +#multivnc_SOURCES += winres.rc +#multivnc_LDADD += -lws2_32 +# +#.rc.o: +# $(WX_RESCOMP) -o $@ $< +#endif +# +#if HAVE_GNUTLS +#multivnc_LDADD += @GNUTLS_LIBS@ +#else +#if HAVE_LIBSSL +#multivnc_LDADD += @SSL_LIBS@ @CRYPT_LIBS@ +#endif +#endif +# +# +#EXTRA_DIST = \ +# winres.rc\ +# icon.ico diff --git a/src/DebugReportEmail.cpp b/src/DebugReportEmail.cpp index 7f4f1f37..d2c6255d 100644 --- a/src/DebugReportEmail.cpp +++ b/src/DebugReportEmail.cpp @@ -1,66 +1,66 @@ - -#include "DebugReportEmail.h" - -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -// https://stackoverflow.com/a/17708801/361413 -// TODO convert to wx-only -static string url_encode(const string &value) { - ostringstream escaped; - escaped.fill('0'); - escaped << hex; - - for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) { - string::value_type c = (*i); - - // Keep alphanumeric and other accepted characters intact - if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { - escaped << c; - continue; - } - - // Any other characters are percent-encoded - escaped << uppercase; - escaped << '%' << setw(2) << int((unsigned char) c); - escaped << nouppercase; - } - - return escaped.str(); -} - -bool DebugReportEmail::DoProcess() -{ - wxString body; - for (size_t i = GetFilesCount()-1; ; --i) { - // read in each file's contents - wxString fileName; - GetFile(i, &fileName, NULL); - wxString filePath = GetDirectory() + wxFILE_SEP_PATH + fileName; - wxString fileContent; - wxFileInputStream input(filePath); - wxTextInputStream text(input, wxT(" \t"), wxConvUTF8); - while (input.IsOk() && !input.Eof()) { - fileContent += text.ReadLine() + "\n"; - } - - // append to message body string - body += "-------" + fileName + "-------\n" + fileContent + "\n"; - - // can't use for loop's check as size_t wraps around - if(i == 0) - break; - } - - return wxLaunchDefaultBrowser("mailto:" + m_toEmail + - "?subject=" + url_encode(m_subject.ToStdString()) + - "&body=" + url_encode(body.ToStdString())); -} - + +#include "DebugReportEmail.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// https://stackoverflow.com/a/17708801/361413 +// TODO convert to wx-only +static string url_encode(const string &value) { + ostringstream escaped; + escaped.fill('0'); + escaped << hex; + + for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) { + string::value_type c = (*i); + + // Keep alphanumeric and other accepted characters intact + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + escaped << c; + continue; + } + + // Any other characters are percent-encoded + escaped << uppercase; + escaped << '%' << setw(2) << int((unsigned char) c); + escaped << nouppercase; + } + + return escaped.str(); +} + +bool DebugReportEmail::DoProcess() +{ + wxString body; + for (size_t i = GetFilesCount()-1; ; --i) { + // read in each file's contents + wxString fileName; + GetFile(i, &fileName, NULL); + wxString filePath = GetDirectory() + wxFILE_SEP_PATH + fileName; + wxString fileContent; + wxFileInputStream input(filePath); + wxTextInputStream text(input, wxT(" \t"), wxConvUTF8); + while (input.IsOk() && !input.Eof()) { + fileContent += text.ReadLine() + "\n"; + } + + // append to message body string + body += "-------" + fileName + "-------\n" + fileContent + "\n"; + + // can't use for loop's check as size_t wraps around + if(i == 0) + break; + } + + return wxLaunchDefaultBrowser("mailto:" + m_toEmail + + "?subject=" + url_encode(m_subject.ToStdString()) + + "&body=" + url_encode(body.ToStdString())); +} + diff --git a/src/DebugReportEmail.h b/src/DebugReportEmail.h index bd709563..be4c26be 100644 --- a/src/DebugReportEmail.h +++ b/src/DebugReportEmail.h @@ -1,22 +1,22 @@ - -#ifndef DEBUG_REPORT_EMAIL_H -#define DEBUG_REPORT_EMAIL_H - -#include - -/** - DebugReportEmail is a wxDebugReport which opens the user's default mail - user agent with the report's files in the message body. - */ -class DebugReportEmail: public wxDebugReport -{ - public: - DebugReportEmail(const wxString &toEmail, const wxString &subject) - : m_toEmail(toEmail), m_subject(subject) {}; - protected: - virtual bool DoProcess() override; - private: - wxString m_toEmail, m_subject; -}; - -#endif + +#ifndef DEBUG_REPORT_EMAIL_H +#define DEBUG_REPORT_EMAIL_H + +#include + +/** + DebugReportEmail is a wxDebugReport which opens the user's default mail + user agent with the report's files in the message body. + */ +class DebugReportEmail: public wxDebugReport +{ + public: + DebugReportEmail(const wxString &toEmail, const wxString &subject) + : m_toEmail(toEmail), m_subject(subject) {}; + protected: + virtual bool DoProcess() override; + private: + wxString m_toEmail, m_subject; +}; + +#endif diff --git a/src/Info.plist.in b/src/Info.plist.in index ec1ad723..ea12d299 100644 --- a/src/Info.plist.in +++ b/src/Info.plist.in @@ -1,46 +1,46 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - ${MACOSX_BUNDLE_EXECUTABLE_NAME} - CFBundleGetInfoString - ${MACOSX_BUNDLE_INFO_STRING} - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} - CFBundleIdentifier - ${MACOSX_BUNDLE_GUI_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLongVersionString - ${MACOSX_BUNDLE_LONG_VERSION_STRING} - CFBundleName - ${MACOSX_BUNDLE_BUNDLE_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - ${MACOSX_BUNDLE_SHORT_VERSION_STRING} - CFBundleSignature - ???? - CFBundleVersion - ${MACOSX_BUNDLE_BUNDLE_VERSION} - CSResourcesFileMapped - - NSHumanReadableCopyright - ${MACOSX_BUNDLE_COPYRIGHT} - NSPrincipalClass - NSApplication - LSApplicationCategoryType - public.app-category.productivity - LSMinimumSystemVersion - ${CMAKE_OSX_DEPLOYMENT_TARGET} - NSLocalNetworkUsageDescription - MultiVNC requires access to your local network to be able to discover and connect to VNC servers for remote desktop control. - NSBonjourServices - - _rfb._tcp - - - + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + LSApplicationCategoryType + public.app-category.productivity + LSMinimumSystemVersion + ${CMAKE_OSX_DEPLOYMENT_TARGET} + NSLocalNetworkUsageDescription + MultiVNC requires access to your local network to be able to discover and connect to VNC servers for remote desktop control. + NSBonjourServices + + _rfb._tcp + + + diff --git a/src/MultiVNCApp.cpp b/src/MultiVNCApp.cpp index ebc88319..a670b622 100644 --- a/src/MultiVNCApp.cpp +++ b/src/MultiVNCApp.cpp @@ -1,223 +1,223 @@ -/* - MultiVNCApp.cpp: MultiVNC main app implementation. - - This file is part of MultiVNC, a multicast-enabled crossplatform - VNC viewer. - - Copyright (C) 2009, 2010 Christian Beier - - MultiVNC is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MultiVNC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - - -#include -#include -#include -#ifdef __WXMAC__ -#include -#endif -#include -#include "MultiVNCApp.h" -#include "DebugReportEmail.h" -#include "gui/MyFrameMain.h" - - -using namespace std; - - - -static void handle_sig(int s) -{ - switch(s) - { - case SIGINT: - wxGetApp().nr_sigints++; - cerr << "Got SIGINT, shutting down\n"; - wxGetApp().ExitMainLoop(); - if(wxGetApp().nr_sigints >= 3) - { - cerr << "Got 3 SIGINTs, killing myse...\n"; -#ifndef __WXMSW__ - raise(SIGKILL); -#else - raise(SIGTERM); -#endif - } - break; - } -} - - - -// this also sets up main() -IMPLEMENT_APP(MultiVNCApp); - - - -bool MultiVNCApp::OnInit() -{ - locale = 0; - - setLocale(wxLANGUAGE_DEFAULT); - - // setup signal handlers - nr_sigints = 0; - signal(SIGINT, handle_sig); -#if !defined __WXMSW__ || defined __BORLANDC__ || defined _MSC_VER // on win32, does only work with borland c++ or visual c++ - wxHandleFatalExceptions(true); // enable ::OnFatalException() which handles fatal signals -#endif - - // wxConfig: - // application and vendor name are used by wxConfig to construct the name - // of the config file/registry key and must be set before the first call - // to Get() if you want to override the default values (the application - // name is the name of the executable and the vendor name is the same) - SetVendorName(_T("MultiVNC")); - - // https://forums.wxwidgets.org/viewtopic.php?t=42605 - SetAppDisplayName("MultiVNC"); - - // if built as a portable edition, use file config always! -#ifdef PORTABLE_EDITION - const char* name = CFGFILE; - wxFile cfgfile; - - if(!wxFile::Exists(wxString(name, wxConvUTF8))) - cfgfile.Create(wxString(name, wxConvUTF8)); - - cfgfile.Open(wxT(CFGFILE), wxFile::read); - - wxFileInputStream sin(cfgfile); - if(sin.Ok()) - { - wxFileConfig* cfg = new wxFileConfig(sin); - wxConfigBase::Set(cfg); - } - else - { - wxLogError(_("Could not open config file!")); - } -#endif - - // use XDG layout if running as flatpak or there is no old-style config file already - if(wxGetEnv("FLATPAK_ID", NULL) || !wxFile::Exists(wxFileName::GetHomeDir() + wxFileName::GetPathSeparator() + ".multivnc")) - wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); - - // greetings to anyone who made it... - cout << "\n::: this is MultiVNC :::\n\n"; - cout << COPYRIGHT << ".\n"; - cout << "MultiVNC is free software, licensed unter the GPL.\n\n"; - - - // wx stuff - wxInitAllImageHandlers(); - - // the main frame - MyFrameMain* frame_main = new MyFrameMain(NULL, wxID_ANY, wxEmptyString); - - for(int i=1; i < wxApp::argc; ++i) - { - wxString arg(wxApp::argv[i]); - frame_main->cmdline_connect(arg); - } - - SetTopWindow(frame_main); - frame_main->Show(); - - return true; -} - - -int MultiVNCApp::OnExit() -{ - // if built as a portable edition, use file config always! -#ifdef PORTABLE_EDITION - wxFile cfgfile(wxT(CFGFILE), wxFile::write); - wxFileOutputStream sout(cfgfile); - wxFileConfig* cfg = (wxFileConfig*)wxConfigBase::Get(); - if(!cfg->Save(sout)) - wxLogError(_("Could not save config file!")); -#endif - - // clean up: Set() returns the active config object as Get() does, but unlike - // Get() it doesn't try to create one if there is none (definitely not what - // we want here!) - delete wxConfigBase::Set((wxConfigBase *) NULL); - - return 0; -} - - - -void MultiVNCApp::OnUnhandledException() -{ - //this way it should work under both normal and debug builds - wxString msg = _("GAAH! Got an unhandled exception! This should not happen."); - wxLogError(msg); - wxLogDebug(msg); -} - - -void MultiVNCApp::OnFatalException() -{ -#ifndef __WXMAC__ // handled by CrashReporter on MacOS - int answer = wxMessageBox(_("Ouch! MultiVNC crashed. This should not happen. Do you want to generate a bug report?"), _("MultiVNC crashed"), - wxICON_ERROR | wxYES_NO, NULL); - if(answer == wxYES) - genDebugReport(wxDebugReport::Context_Exception); -#endif -} - - -void MultiVNCApp::genDebugReport(wxDebugReport::Context ctx) -{ - DebugReportEmail report("multivnc@christianbeier.net", "MultiVNC Bug Report"); - - // add all standard files: currently this means just a minidump and an - // XML file with system info and stack trace - report.AddAll(ctx); - - // calling Show() is not mandatory, but is more polite - if(wxDebugReportPreviewStd().Show(report)) - report.Process(); -} - - - - -bool MultiVNCApp::setLocale(int language) -{ - delete locale; - - locale = new wxLocale; - - // don't use wxLOCALE_LOAD_DEFAULT flag so that Init() doesn't return - // false just because it failed to load wxstd catalog - if(! locale->Init(language) ) - return false; - - // normally this wouldn't be necessary as the catalog files would be found - // in the default locations, but when the program is not installed the - // catalogs are in the build directory where we wouldn't find them by - // default - wxLocale::AddCatalogLookupPathPrefix(wxT(".")); - - // Initialize the catalogs we'll be using - locale->AddCatalog(wxT("multivnc")); - - return true; -} - - +/* + MultiVNCApp.cpp: MultiVNC main app implementation. + + This file is part of MultiVNC, a multicast-enabled crossplatform + VNC viewer. + + Copyright (C) 2009, 2010 Christian Beier + + MultiVNC is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + MultiVNC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#include +#include +#include +#ifdef __WXMAC__ +#include +#endif +#include +#include "MultiVNCApp.h" +#include "DebugReportEmail.h" +#include "gui/MyFrameMain.h" + + +using namespace std; + + + +static void handle_sig(int s) +{ + switch(s) + { + case SIGINT: + wxGetApp().nr_sigints++; + cerr << "Got SIGINT, shutting down\n"; + wxGetApp().ExitMainLoop(); + if(wxGetApp().nr_sigints >= 3) + { + cerr << "Got 3 SIGINTs, killing myse...\n"; +#ifndef __WXMSW__ + raise(SIGKILL); +#else + raise(SIGTERM); +#endif + } + break; + } +} + + + +// this also sets up main() +IMPLEMENT_APP(MultiVNCApp); + + + +bool MultiVNCApp::OnInit() +{ + locale = 0; + + setLocale(wxLANGUAGE_DEFAULT); + + // setup signal handlers + nr_sigints = 0; + signal(SIGINT, handle_sig); +#if !defined __WXMSW__ || defined __BORLANDC__ || defined _MSC_VER // on win32, does only work with borland c++ or visual c++ + wxHandleFatalExceptions(true); // enable ::OnFatalException() which handles fatal signals +#endif + + // wxConfig: + // application and vendor name are used by wxConfig to construct the name + // of the config file/registry key and must be set before the first call + // to Get() if you want to override the default values (the application + // name is the name of the executable and the vendor name is the same) + SetVendorName(_T("MultiVNC")); + + // https://forums.wxwidgets.org/viewtopic.php?t=42605 + SetAppDisplayName("MultiVNC"); + + // if built as a portable edition, use file config always! +#ifdef PORTABLE_EDITION + const char* name = CFGFILE; + wxFile cfgfile; + + if(!wxFile::Exists(wxString(name, wxConvUTF8))) + cfgfile.Create(wxString(name, wxConvUTF8)); + + cfgfile.Open(wxT(CFGFILE), wxFile::read); + + wxFileInputStream sin(cfgfile); + if(sin.Ok()) + { + wxFileConfig* cfg = new wxFileConfig(sin); + wxConfigBase::Set(cfg); + } + else + { + wxLogError(_("Could not open config file!")); + } +#endif + + // use XDG layout if running as flatpak or there is no old-style config file already + if(wxGetEnv("FLATPAK_ID", NULL) || !wxFile::Exists(wxFileName::GetHomeDir() + wxFileName::GetPathSeparator() + ".multivnc")) + wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); + + // greetings to anyone who made it... + cout << "\n::: this is MultiVNC :::\n\n"; + cout << COPYRIGHT << ".\n"; + cout << "MultiVNC is free software, licensed unter the GPL.\n\n"; + + + // wx stuff + wxInitAllImageHandlers(); + + // the main frame + MyFrameMain* frame_main = new MyFrameMain(NULL, wxID_ANY, wxEmptyString); + + for(int i=1; i < wxApp::argc; ++i) + { + wxString arg(wxApp::argv[i]); + frame_main->cmdline_connect(arg); + } + + SetTopWindow(frame_main); + frame_main->Show(); + + return true; +} + + +int MultiVNCApp::OnExit() +{ + // if built as a portable edition, use file config always! +#ifdef PORTABLE_EDITION + wxFile cfgfile(wxT(CFGFILE), wxFile::write); + wxFileOutputStream sout(cfgfile); + wxFileConfig* cfg = (wxFileConfig*)wxConfigBase::Get(); + if(!cfg->Save(sout)) + wxLogError(_("Could not save config file!")); +#endif + + // clean up: Set() returns the active config object as Get() does, but unlike + // Get() it doesn't try to create one if there is none (definitely not what + // we want here!) + delete wxConfigBase::Set((wxConfigBase *) NULL); + + return 0; +} + + + +void MultiVNCApp::OnUnhandledException() +{ + //this way it should work under both normal and debug builds + wxString msg = _("GAAH! Got an unhandled exception! This should not happen."); + wxLogError(msg); + wxLogDebug(msg); +} + + +void MultiVNCApp::OnFatalException() +{ +#ifndef __WXMAC__ // handled by CrashReporter on MacOS + int answer = wxMessageBox(_("Ouch! MultiVNC crashed. This should not happen. Do you want to generate a bug report?"), _("MultiVNC crashed"), + wxICON_ERROR | wxYES_NO, NULL); + if(answer == wxYES) + genDebugReport(wxDebugReport::Context_Exception); +#endif +} + + +void MultiVNCApp::genDebugReport(wxDebugReport::Context ctx) +{ + DebugReportEmail report("multivnc@christianbeier.net", "MultiVNC Bug Report"); + + // add all standard files: currently this means just a minidump and an + // XML file with system info and stack trace + report.AddAll(ctx); + + // calling Show() is not mandatory, but is more polite + if(wxDebugReportPreviewStd().Show(report)) + report.Process(); +} + + + + +bool MultiVNCApp::setLocale(int language) +{ + delete locale; + + locale = new wxLocale; + + // don't use wxLOCALE_LOAD_DEFAULT flag so that Init() doesn't return + // false just because it failed to load wxstd catalog + if(! locale->Init(language) ) + return false; + + // normally this wouldn't be necessary as the catalog files would be found + // in the default locations, but when the program is not installed the + // catalogs are in the build directory where we wouldn't find them by + // default + wxLocale::AddCatalogLookupPathPrefix(wxT(".")); + + // Initialize the catalogs we'll be using + locale->AddCatalog(wxT("multivnc")); + + return true; +} + + diff --git a/src/MultiVNCApp.h b/src/MultiVNCApp.h index 0697caee..87c11ee1 100644 --- a/src/MultiVNCApp.h +++ b/src/MultiVNCApp.h @@ -1,71 +1,71 @@ -// -*- C++ -*- -/* - MultiVNCApp.h: MultiVNC main app header. - - This file is part of MultiVNC, a multicast-enabled crossplatform - VNC viewer. - - Copyright (C) 2009, 2010 Christian Beier - - MultiVNC is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MultiVNC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#ifndef MULTIVNCAPP_H -#define MULTIVNCAPP_H - - -#include -#ifndef WX_PRECOMP -#include "wx/wx.h" -#endif -#include "wx/debugrpt.h" -#include "wx/config.h" -#include "config.h" - -// in case we have an old autoconf... -#ifndef PACKAGE_URL -#define PACKAGE_URL "http://multivnc.sf.net" -#endif - -#define COPYRIGHT "Copyright (C) 2009-2024 Christian Beier " -#define CFGFILE "multivnc.cfg" - -class MultiVNCApp: public wxApp -{ - wxLocale *locale; - -public: - - virtual bool OnInit(); - virtual int OnExit(); - virtual void OnUnhandledException(); - virtual void OnFatalException(); - size_t nr_sigints; - - // this is where we really generate the debug report - void genDebugReport(wxDebugReport::Context ctx); - - bool setLocale(int language); - -}; - - -DECLARE_APP(MultiVNCApp); - - - - - -#endif // MULTIVNCAPP_H +// -*- C++ -*- +/* + MultiVNCApp.h: MultiVNC main app header. + + This file is part of MultiVNC, a multicast-enabled crossplatform + VNC viewer. + + Copyright (C) 2009, 2010 Christian Beier + + MultiVNC is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + MultiVNC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef MULTIVNCAPP_H +#define MULTIVNCAPP_H + + +#include +#ifndef WX_PRECOMP +#include "wx/wx.h" +#endif +#include "wx/debugrpt.h" +#include "wx/config.h" +#include "config.h" + +// in case we have an old autoconf... +#ifndef PACKAGE_URL +#define PACKAGE_URL "http://multivnc.sf.net" +#endif + +#define COPYRIGHT "Copyright (C) 2009-2024 Christian Beier " +#define CFGFILE "multivnc.cfg" + +class MultiVNCApp: public wxApp +{ + wxLocale *locale; + +public: + + virtual bool OnInit(); + virtual int OnExit(); + virtual void OnUnhandledException(); + virtual void OnFatalException(); + size_t nr_sigints; + + // this is where we really generate the debug report + void genDebugReport(wxDebugReport::Context ctx); + + bool setLocale(int language); + +}; + + +DECLARE_APP(MultiVNCApp); + + + + + +#endif // MULTIVNCAPP_H diff --git a/src/README.md b/src/README.md index 26c76b37..b51f7cbb 100644 --- a/src/README.md +++ b/src/README.md @@ -1,12 +1,12 @@ - -# Release Checklist - -* [ ] Increment version in CMakeLists.txt -* [ ] Update CHANGELOG. -* [ ] Update Copyright year in About dialog. -* [ ] Update authors in About dialog and AUTHORS file. -* [ ] Update AppStream. -* [ ] Fill out GitHub release. -* [ ] Make Flathub release. -* [ ] Make App Store release. -* [ ] Announce on social. + +# Release Checklist + +* [ ] Increment version in CMakeLists.txt +* [ ] Update CHANGELOG. +* [ ] Update Copyright year in About dialog. +* [ ] Update authors in About dialog and AUTHORS file. +* [ ] Update AppStream. +* [ ] Fill out GitHub release. +* [ ] Make Flathub release. +* [ ] Make App Store release. +* [ ] Announce on social. diff --git a/src/VNCConn.cpp b/src/VNCConn.cpp index 43789b7f..73d53497 100644 --- a/src/VNCConn.cpp +++ b/src/VNCConn.cpp @@ -1,1884 +1,1884 @@ -/* - VNCConn.cpp: VNC connection class implementation. - - This file is part of MultiVNC, a multicast-enabled crossplatform - VNC viewer. - - Copyright (C) 2009, 2010 Christian Beier - - MultiVNC is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MultiVNC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - - -#include -#include -#include -#include -#include -#ifdef __WIN32__ -#include -#else -#include -#endif -#include "VNCConn.h" - - -// use some global address -#define VNCCONN_OBJ_ID (void*)VNCConn::thread_got_update - -// logfile name -#define LOGFILE _T("MultiVNC.log") - - -// pixelformat defaults -// seems 8,3,4 and 5,3,2 are possible with rfbGetClient() -#define BITSPERSAMPLE 8 -#define SAMPLESPERPIXEL 3 -#define BYTESPERPIXEL 4 - - -// define our new notify events! -DEFINE_EVENT_TYPE(VNCConnListenNOTIFY) -DEFINE_EVENT_TYPE(VNCConnInitNOTIFY) -wxDEFINE_EVENT(VNCConnGetPasswordNOTIFY, wxCommandEvent); -wxDEFINE_EVENT(VNCConnGetCredentialsNOTIFY, wxCommandEvent); -DEFINE_EVENT_TYPE(VNCConnIncomingConnectionNOTIFY) -DEFINE_EVENT_TYPE(VNCConnDisconnectNOTIFY) -DEFINE_EVENT_TYPE(VNCConnUpdateNOTIFY) -DEFINE_EVENT_TYPE(VNCConnFBResizeNOTIFY) -DEFINE_EVENT_TYPE(VNCConnCuttextNOTIFY) -DEFINE_EVENT_TYPE(VNCConnBellNOTIFY) -DEFINE_EVENT_TYPE(VNCConnUniMultiChangedNOTIFY); -DEFINE_EVENT_TYPE(VNCConnReplayFinishedNOTIFY); - - -BEGIN_EVENT_TABLE(VNCConn, wxEvtHandler) - EVT_TIMER (wxID_ANY, VNCConn::on_stats_timer) -END_EVENT_TABLE(); - - -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS -bool VNCConn::TLS_threading_initialized; -extern "C" -{ -#include -#include - /* - * gcrypt thread option wx implementation - */ - static int gcry_wx_mutex_init( void **p ) - { - *p = new wxMutex(); - return 0; - } - - static int gcry_wx_mutex_destroy( void **p ) - { - delete (wxMutex*)*p; - return 0; - } - - static int gcry_wx_mutex_lock( void **p ) - { - if(((wxMutex*)(*p))->Lock() == wxMUTEX_NO_ERROR) - return 0; - else - return 1; - } - - static int gcry_wx_mutex_unlock( void **p ) - { - if(((wxMutex*)(*p))->Unlock() == wxMUTEX_NO_ERROR) - return 0; - else - return 1; - } - - static const struct gcry_thread_cbs gcry_threads_wx = - { - GCRY_THREAD_OPTION_USER, - NULL, - gcry_wx_mutex_init, - gcry_wx_mutex_destroy, - gcry_wx_mutex_lock, - gcry_wx_mutex_unlock - }; -} -#endif - - - - -/* - constructor/destructor -*/ - - -VNCConn::VNCConn(void* p) : condition_auth(mutex_auth) -{ - // save our caller - parent = p; - - cl = 0; - multicastStatus = 0; - latency = -1; - - rfbClientLog = rfbClientErr = thread_logger; - -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - /* we're using threads in here, tell libgcrypt before TLS - gets initialized by libvncclient! */ - if(! TLS_threading_initialized) - { - wxLogDebug(wxT("Initialized libgcrypt threading.")); - gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_wx); - gcry_check_version (NULL); - gcry_control (GCRYCTL_DISABLE_SECMEM, 0); - gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); - TLS_threading_initialized = true; - } -#endif - - // statistics stuff - do_stats = false; - upd_bytes = 0; - upd_bytes_inflated = 0; - upd_count = 0; - stats_timer.SetOwner(this); - - // fastrequest stuff - fastrequest_interval = 0; - - // record/replay stuff - recording = replaying = false; - - require_auth = false; -} - - - - -VNCConn::~VNCConn() -{ - Shutdown(); - wxLogDebug(wxT("VNCConn %p: I'm dead!"), this); -} - - - -/* - private members -*/ - - -void VNCConn::on_stats_timer(wxTimerEvent& event) -{ - if(do_stats) - { - wxCriticalSectionLocker lock(mutex_stats); - - if(statistics.IsEmpty()) - statistics.Add(wxString() - + wxT("UTC time,") - + wxT("conn time,") - + wxT("rcvd bytes,") - + wxT("rcvd bytes inflated,") - + wxT("upd count,") - + wxT("latency,") - + wxT("nack rate,") - + wxT("loss rate,") - + wxT("buf size,") - + wxT("buf fill,")); - - wxString sample; - - sample += (wxString() << (int)wxGetUTCTime()); // global UTC time - sample += wxT(","); - sample += (wxString() << (int)conn_stopwatch.Time()); // connection time - sample += wxT(","); - sample += (wxString() << upd_bytes); // rcvd bytes sampling - sample += wxT(","); - sample += (wxString() << upd_bytes_inflated); // rcvd bytes inflated sampling - sample += wxT(","); - sample += (wxString() << upd_count); // number of updates sampling - sample += wxT(","); - sample += (wxString() << latency); // latency sampling - sample += wxT(","); - wxString nackrate_str = wxString::Format(wxT("%.4f"), getMCNACKedRatio()); - nackrate_str.Replace(wxT(","), wxT(".")); - sample += nackrate_str; // nack rate sampling - sample += wxT(","); - wxString lossrate_str = wxString::Format(wxT("%.4f"), getMCLossRatio()); - lossrate_str.Replace(wxT(","), wxT(".")); - sample += lossrate_str; // loss rate sampling - sample += wxT(","); - sample += (wxString() << getMCBufSize()); // buffer size sampling - sample += wxT(","); - sample += (wxString() << getMCBufFill()); // buffer fill sampling - - // add the sample - statistics.Add(sample); - - // reset these - upd_bytes = 0; - upd_bytes_inflated = 0; - upd_count = 0; - latency = -1; - - - latency_test_trigger = true; - } -} - - - -rfbBool VNCConn::thread_alloc_framebuffer(rfbClient* client) -{ - // get VNCConn object belonging to this client - VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); - - wxLogDebug(wxT("VNCConn %p: alloc'ing framebuffer w:%i, h:%i"), conn, client->width, client->height); - - // assert 32bpp, as requested with GetClient() in Init() - if(client->format.bitsPerPixel != 32) - { - conn->err.Printf(_("Failure setting up framebuffer: wrong BPP!")); - return false; - } - - // ensure that we get the whole framebuffer in case of a resize! - client->updateRect.x = client->updateRect.y = 0; - client->updateRect.w = client->width; client->updateRect.h = client->height; - - // free - if(client->frameBuffer) - free(client->frameBuffer); - - // alloc, zeroed - client->frameBuffer = (uint8_t*)calloc(1, client->width*client->height*client->format.bitsPerPixel/8); - - // notify our parent - conn->thread_post_fbresize_notify(); - - return client->frameBuffer ? TRUE : FALSE; -} - - - - -wxThread::ExitCode VNCConn::Entry() -{ - // init connection before going into main loop if this is not a listening one - if (!thread_listenmode) { - rfbClientLog("About to connect to '%s', port %d\n", cl->serverHost, cl->serverPort); - - // save these for the error case - wxString host = wxString(cl->serverHost); - int port = cl->serverPort; - if (!rfbInitClient(cl, 0, NULL)) { - // rfbInitClient() calls rfbClientCleanup() on failure, but - // this does not zero the ptr - cl = 0; - err.Printf(_("Failure connecting to server at %s:%d!"), host, port); - wxLogDebug("VNCConn %p: rfbInitClient() failed. Cleanup by library.", this); - thread_post_init_notify(1); // TODO add more error codes - wxLogDebug("VNCConn %p: vncthread done", this); - return 0; - } - - // set the client sock to blocking again until libvncclient is fixed -#ifdef WIN32 - unsigned long block = 0; - if (ioctlsocket(cl->sock, FIONBIO, &block) == SOCKET_ERROR) { - errno = WSAGetLastError(); -#else - int flags = fcntl(cl->sock, F_GETFL); - if (flags < 0 || fcntl(cl->sock, F_SETFL, flags & ~O_NONBLOCK) < 0) { -#endif - rfbClientErr("Setting socket to blocking failed: %s\n", strerror(errno)); - } - - // if there was an error in alloc_framebuffer(), catch that here - // err is set by alloc_framebuffer() - if (!cl->frameBuffer) { - thread_post_init_notify(1); // TODO add more error codes - wxLogDebug("VNCConn %p: vncthread done", this); - return 0; - } - // connect succesful - thread_post_init_notify(0); - } - - int i=0; - - pointerEvent pe; - keyEvent ke = {0, 0}; - - bool listen_outcome_posted = false; - - while(! GetThread()->TestDestroy()) - { - if(thread_listenmode) - { - i=listenForIncomingConnectionsNoFork(cl, 100000); // 100 ms - if (i == 0) { - // just notify about success once - if (!listen_outcome_posted) { - thread_post_listen_notify(0); - listen_outcome_posted = true; - } - } - if(i<0) - { - if(errno==EINTR) - continue; - wxLogDebug(wxT("VNCConn %p: vncthread listen() failed"), this); - thread_post_listen_notify(1); //TODO add more error codes - break; - } - if(i) - { - // have this here in case of immediate connection - // but just notify about success once - if (!listen_outcome_posted) { - thread_post_listen_notify(0); - listen_outcome_posted = true; - } - thread_post_incomingconnection_notify(); - break; - } - } - else - { - // userinput replay here - { - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(replaying) - { - if(userinput_pos < userinput.GetCount()) // still recorded input there - { - wxString ui_now = userinput[userinput_pos]; - // get timestamp and strip it from string - long ts = wxAtol(ui_now.BeforeFirst(',')); - ui_now = ui_now.AfterFirst(','); - - if(ts <= recordreplay_stopwatch.Time()) // past or now, process it - { - // get type - wxString type = ui_now.BeforeFirst(','); - ui_now = ui_now.AfterFirst(','); - - if(type == wxT("p")) - { - // get pointer x,y, buttmask - int x = wxAtoi(ui_now.BeforeFirst(',')); - ui_now = ui_now.AfterFirst(','); - int y = wxAtoi(ui_now.BeforeFirst(',')); - ui_now = ui_now.AfterFirst(','); - int bmask = wxAtoi(ui_now); - - // and send - SendPointerEvent(cl, x, y, bmask); - } - - if(type == wxT("k")) - { - // get keysym - rfbKeySym keysym = wxAtoi(ui_now.BeforeFirst(',')); - ui_now = ui_now.AfterFirst(','); - bool down = wxAtoi(ui_now); - - // and send - SendKeyEvent(cl, keysym, down); - } - - // advance to next input - ++userinput_pos; - } - } - else if (replay_loop) - { - userinput_pos = 0; // rewind - recordreplay_stopwatch.Start(); // restart - } - else - { - replaying = false; // all done - thread_post_replayfinished_notify(); - } - - } - } - - // send everything that's inside the input queues - while(pointer_event_q.ReceiveTimeout(0, pe) != wxMSGQUEUE_TIMEOUT) // timeout == empty - thread_send_pointer_event(pe); - while(key_event_q.ReceiveTimeout(0, ke) != wxMSGQUEUE_TIMEOUT) // timeout == empty - thread_send_key_event(ke); - - { - wxCriticalSectionLocker lock(mutex_stats); - if(latency_test_trigger) - { - latency_test_trigger = false; - thread_send_latency_probe(); - } - } - - if(fastrequest_interval && (size_t)fastrequest_stopwatch.Time() > fastrequest_interval) - { - if(isMulticast()) - SendMulticastFramebufferUpdateRequest(cl, TRUE); - else - SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, TRUE); - - fastrequest_stopwatch.Start(); // restart - } - - - // request update and handle response - if(!rfbProcessServerMessage(cl, 500)) - { - if(errno == EINTR) - continue; - wxLogDebug(wxT("VNCConn %p: vncthread rfbProcessServerMessage() failed"), this); - thread_post_disconnect_notify(); - break; - } - - - /* - Compute nacked/loss ratio: We take a ratio sample every second and put it into a sample queue - of size N. Action is taken when the average sample value of the whole buffer exceeds a per-action - limit. This has advantages over taking a sample every N seconds: First, it's able to catch say a 5sec burst - that could be missed by two adjacent 10sec samples (one catches 2sec, the next one 3sec - no action triggered - although condition present). Second, this way we're able to show a value to the user every second independent - of the sample time frame. - */ - if(isMulticast() && multicastratio_stopwatch.Time() >= 1000) - { - // restart - multicastratio_stopwatch.Start(); - - /* - take sample - */ - { - // the fifos are read by the GUI thread as well! - wxCriticalSectionLocker lock(mutex_multicastratio); - - if(multicastNACKedRatios.size() >= MULTICAST_RATIO_SAMPLES) // make room if size exceeded - multicastNACKedRatios.pop_front(); - if(cl->multicastPktsRcvd + cl->multicastPktsNACKed > 0) - multicastNACKedRatios.push_back(cl->multicastPktsNACKed/(double)(cl->multicastPktsRcvd + cl->multicastPktsNACKed)); - else - multicastNACKedRatios.push_back(-1); // nothing to measure, add invalid marker - - if(multicastLossRatios.size() >= MULTICAST_RATIO_SAMPLES) // make room if size exceeded - multicastLossRatios.pop_front(); - if(cl->multicastPktsRcvd + cl->multicastPktsLost > 0) - multicastLossRatios.push_back(cl->multicastPktsLost/(double)(cl->multicastPktsRcvd + cl->multicastPktsLost)); - else - multicastLossRatios.push_back(-1); // nothing to measure, add invalid marker - - // reset the values we sample - cl->multicastPktsRcvd = cl->multicastPktsNACKed = cl->multicastPktsLost = 0; - } - - /* - And act accordingly, but only after the ratio deques are at least half full. - When a client joins a multicast group with heavy traffic going on, it will lose - a lot of packets in the very beginning because there is a considerable time - amount between it's multicast socket creation and the first read. Thus, the socket - buffer is likely to overflow in this start situation, resulting in packet loss. - */ - if(multicastLossRatios.size() >= MULTICAST_RATIO_SAMPLES/2) - { - if(getMCLossRatio() > 0.5) - { - rfbClientLog("MultiVNC: loss ratio > 0.5, falling back to unicast\n"); - wxLogDebug(wxT("VNCConn %p: multicast loss ratio > 0.5, falling back to unicast"), this); - cl->multicastDisabled = TRUE; - SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, FALSE); - } - else if(getMCLossRatio() > 0.2) - { - rfbClientLog("MultiVNC: loss ratio > 0.2, requesting a full multicast framebuffer update\n"); - SendMulticastFramebufferUpdateRequest(cl, FALSE); - cl->multicastPktsLost /= 2; - } - } - } - - - - int now = isMulticast(); - if(now != multicastStatus) - { - multicastStatus = now; - thread_post_unimultichanged_notify(); - } - } - } - - wxLogDebug("VNCConn %p: vncthread done", this); - return 0; -} - - - - - -bool VNCConn::thread_send_pointer_event(pointerEvent &event) -{ - int buttonmask = 0; - - if(event.LeftIsDown()) - buttonmask |= rfbButton1Mask; - - if(event.MiddleIsDown()) - buttonmask |= rfbButton2Mask; - - if(event.RightIsDown()) - buttonmask |= rfbButton3Mask; - - if(event.GetWheelRotation() > 0) - buttonmask |= rfbWheelUpMask; - - if(event.GetWheelRotation() < 0) - buttonmask |= rfbWheelDownMask; - - if(event.Entering() && ! cuttext.IsEmpty()) - { - wxCriticalSectionLocker lock(mutex_cuttext); // since cuttext can be set from the main thread - - // first, try sending UTF-8 - if(SendClientCutTextUTF8(cl, const_cast(cuttext.utf8_str().data()), strlen(cuttext.utf8_str().data()))) { - wxLogDebug(wxT("VNCConn %p: sent UTF-8 cuttext: '%s'"), this, cuttext.utf8_str()); - } else { - // server does not support Extended Clipboard, try Latin-1. - // if encoding fails, length() returns 0 - if(cuttext.mb_str(wxCSConv("iso-8859-1")).length()) - { - char* encoded_text = strdup(cuttext.mb_str(wxCSConv(wxT("iso-8859-1")))); - SendClientCutText(cl, encoded_text, strlen(encoded_text)); - wxLogDebug(wxT("VNCConn %p: sent Latin-1 cuttext: '%s'"), this, encoded_text); - free(encoded_text); - } - else - wxLogDebug(wxT("VNCConn %p: sending Latin-1 cuttext FAILED, could not convert '%s' to ISO-8859-1"), this, cuttext.c_str()); - } - } - - // record here - { - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(recording) - { - wxString ui_now; - ui_now += (wxString() << (int)recordreplay_stopwatch.Time()); - ui_now += wxT(","); - ui_now += wxT("p"); // is pointer event - ui_now += wxT(","); - ui_now += (wxString() << event.m_x); - ui_now += wxT(","); - ui_now += (wxString() << event.m_y); - ui_now += wxT(","); - ui_now += (wxString() << buttonmask); - - userinput.Add(ui_now); - } - } - - wxLogDebug(wxT("VNCConn %p: sending pointer event at (%d,%d), buttonmask %d"), this, event.m_x, event.m_y, buttonmask); - return SendPointerEvent(cl, event.m_x, event.m_y, buttonmask); -} - - - - -bool VNCConn::thread_send_key_event(keyEvent &event) -{ - // record here - { - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(recording) - { - wxString ui_now; - ui_now += (wxString() << (int)recordreplay_stopwatch.Time()); - ui_now += wxT(","); - ui_now += wxT("k"); // is key event - ui_now += wxT(","); - ui_now += (wxString() << event.keysym); - ui_now += wxT(","); - ui_now += (wxString() << event.down); - - userinput.Add(ui_now); - } - } - - return SendKeyEvent(cl, event.keysym, event.down); -} - - - -bool VNCConn::thread_send_latency_probe() -{ - bool result = TRUE; - // latency check start - if(SupportsClient2Server(cl, rfbXvp)) // favor xvp over the rect check - { - if(!latency_test_xvpmsg_sent) - { - result = SendXvpMsg(cl, LATENCY_TEST_XVP_VER, 2); - latency_test_xvpmsg_sent = true; - latency_stopwatch.Start(); - wxLogDebug(wxT("VNCConn %p: xvp message sent to test latency"), this); - } - } - else // check using special rect - { - if(!latency_test_rect_sent) - { - result = SendFramebufferUpdateRequest(cl, LATENCY_TEST_RECT, FALSE); - latency_test_rect_sent = true; - latency_stopwatch.Start(); - wxLogDebug(wxT("VNCConn %p: fb update request sent to test latency"), this); - } - } - return result; -} - -void VNCConn::thread_post_listen_notify(int error) { - wxLogDebug(wxT("VNCConn %p: post_listen_notify(%d)"), this, error); - wxCommandEvent event(VNCConnListenNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - event.SetInt(error); - wxPostEvent((wxEvtHandler*)parent, event); -} - -void VNCConn::thread_post_init_notify(int error) { - wxLogDebug(wxT("VNCConn %p: post_init_notify(%d)"), this, error); - wxCommandEvent event(VNCConnInitNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - event.SetInt(error); - wxPostEvent((wxEvtHandler*)parent, event); -} - -void VNCConn::thread_post_getpasswd_notify() { - wxLogDebug(wxT("VNCConn %p: post_getpasswd_notify()"), this); - wxCommandEvent event(VNCConnGetPasswordNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - wxPostEvent((wxEvtHandler*)parent, event); -} - -void VNCConn::thread_post_getcreds_notify(bool withUserPrompt) { - wxLogDebug(wxT("VNCConn %p: post_getcreds_notify()"), this); - wxCommandEvent event(VNCConnGetCredentialsNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - event.SetInt(withUserPrompt); - wxPostEvent((wxEvtHandler*)parent, event); -} - - -void VNCConn::thread_post_incomingconnection_notify() -{ - wxLogDebug(wxT("VNCConn %p: post_incomingconnection_notify()"), this); - - // new NOTIFY event, we got no window id - wxCommandEvent event(VNCConnIncomingConnectionNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - -void VNCConn::thread_post_disconnect_notify() -{ - wxLogDebug(wxT("VNCConn %p: post_disconnect_notify()"), this); - - // new NOTIFY event, we got no window id - wxCommandEvent event(VNCConnDisconnectNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - -void VNCConn::thread_post_update_notify(int x, int y, int w, int h) -{ - VNCConnUpdateNotifyEvent event(VNCConnUpdateNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // set info about what was updated - event.rect = wxRect(x, y, w, h); - wxLogDebug(wxT("VNCConn %p: post_update_notify(%i,%i,%i,%i)"), this, - event.rect.x, - event.rect.y, - event.rect.width, - event.rect.height); - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - -void VNCConn::thread_post_fbresize_notify() -{ - wxLogDebug(wxT("VNCConn %p: post_fbresize_notify() (%i, %i)"), - this, - getFrameBufferWidth(), - getFrameBufferHeight()); - - // new NOTIFY event, we got no window id - wxCommandEvent event(VNCConnFBResizeNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - - -void VNCConn::thread_post_cuttext_notify() -{ - // new NOTIFY event, we got no window id - wxCommandEvent event(VNCConnCuttextNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - - -void VNCConn::thread_post_bell_notify() -{ - // new NOTIFY event, we got no window id - wxCommandEvent event(VNCConnBellNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - - -void VNCConn::thread_post_unimultichanged_notify() -{ - wxCommandEvent event(VNCConnUniMultiChangedNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - -void VNCConn::thread_post_replayfinished_notify() -{ - wxCommandEvent event(VNCConnReplayFinishedNOTIFY, wxID_ANY); - event.SetEventObject(this); // set sender - - // Send it - wxPostEvent((wxEvtHandler*)parent, event); -} - - -char* VNCConn::thread_getpasswd(rfbClient *client) { - VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); - - conn->require_auth = true; - -#if wxUSE_SECRETSTORE - if (!conn->getPassword().IsOk()) { -#else - if (conn->getPassword().IsEmpty()) { -#endif - // get password from user - conn->thread_post_getpasswd_notify(); - // wxMutexes are not recursive under Unix, so test first - if (conn->mutex_auth.TryLock() == wxMUTEX_NO_ERROR) { - conn->mutex_auth.Lock(); - } - wxLogDebug("VNCConn %p: vncthread waiting for password", conn); - conn->condition_auth.Wait(); - wxLogDebug("VNCConn %p: vncthread done waiting for password", conn); - // we get here once setPassword() was called - } -#if wxUSE_SECRETSTORE - return strdup(conn->getPassword().GetAsString().char_str()); -#else - return strdup(conn->getPassword().char_str()); -#endif -}; - - -rfbCredential* VNCConn::thread_getcreds(rfbClient *client, int type) { - VNCConn *conn = VNCConn::getVNCConnFromRfbClient(client); - - conn->require_auth = true; - - if(type == rfbCredentialTypeUser) { - - if(conn->getUserName().IsEmpty() -#if wxUSE_SECRETSTORE - || !conn->getPassword().IsOk()) { -#else - || conn->getPassword().IsEmpty()) { -#endif - // username and/or password needed - conn->thread_post_getcreds_notify(conn->getUserName().IsEmpty()); - // wxMutexes are not recursive under Unix, so test first - if (conn->mutex_auth.TryLock() == wxMUTEX_NO_ERROR) { - conn->mutex_auth.Lock(); - } - wxLogDebug("VNCConn %p: vncthread waiting for credentials", conn); - conn->condition_auth.Wait(); - wxLogDebug("VNCConn %p: vncthread done waiting for credentials", - conn); - // we get here once setPassword() was called - } - - rfbCredential *c = (rfbCredential *)calloc(1, sizeof(rfbCredential)); - c->userCredential.username = strdup(conn->getUserName().char_str()); -#if wxUSE_SECRETSTORE - c->userCredential.password = strdup(conn->getPassword().GetAsString().char_str()); -#else - c->userCredential.password = strdup(conn->getPassword().char_str()); -#endif - return c; - } - - return NULL; -}; - - - -void VNCConn::thread_got_update(rfbClient* client,int x,int y,int w,int h) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); - if(! conn->GetThread()->TestDestroy()) - { - conn->updated_rect.Union(wxRect(x, y, w, h)); - - // single (partial) multicast updates are small, so when a big region is updated, - // the update notify receiver gets flooded, resulting in way too much cpu load. - // thus, when multicasting, we only notify for logic (whole) framebuffer updates. - if(!conn->isMulticast()) - conn->thread_post_update_notify(x, y, w, h); - - if(conn->do_stats) - { - wxCriticalSectionLocker lock(conn->mutex_stats); - - wxRect this_update_rect = wxRect(x,y,w,h); - - // compressed bytes - conn->upd_bytes += conn->cl->bytesRcvd; - conn->upd_bytes += conn->cl->multicastBytesRcvd; - conn->cl->bytesRcvd = conn->cl->multicastBytesRcvd = 0; - - // uncompressed bytes - conn->upd_bytes_inflated += w*h*BYTESPERPIXEL; - - // latency check, rect case - if(conn->latency_test_rect_sent && this_update_rect.Contains(wxRect(LATENCY_TEST_RECT))) - { - conn->latency_stopwatch.Pause(); - conn->latency = conn->latency_stopwatch.Time(); - conn->latency_test_rect_sent = false; - - wxLogDebug(wxT("VNCConn %p: got update containing latency test rect, took %ims"), conn, conn->latency_stopwatch.Time()); - } - } - } -} - - - - -void VNCConn::thread_update_finished(rfbClient* client) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); - if(! conn->GetThread()->TestDestroy()) - { - // single (partial) multicast updates are small, so when a big region is updated, - // the update notify receiver gets flooded, resulting in way too much cpu load. - // thus, when multicasting, we only notify for logic (whole) framebuffer updates. - if(conn->isMulticast() && !conn->updated_rect.IsEmpty()) - conn->thread_post_update_notify(conn->updated_rect.x, conn->updated_rect.y, conn->updated_rect.width, conn->updated_rect.height); - - conn->updated_rect = wxRect(); - - if(conn->do_stats) - { - wxCriticalSectionLocker lock(conn->mutex_stats); - conn->upd_count++; - } - } -} - - - -void VNCConn::thread_kbd_leds(rfbClient* cl, int value, int pad) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - wxLogDebug(wxT("VNCConn %p: Led State= 0x%02X"), conn, value); -} - - -void VNCConn::thread_textchat(rfbClient* cl, int value, char *text) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - switch(value) - { - case (int)rfbTextChatOpen: - wxLogDebug(wxT("VNCConn %p: got textchat open\n"), conn); - break; - case (int)rfbTextChatClose: - wxLogDebug(wxT("VNCConn %p: got textchat close\n"), conn); - break; - case (int)rfbTextChatFinished: - wxLogDebug(wxT("VNCConn %p: got textchat finish\n"), conn); - break; - default: - wxLogDebug(wxT("VNCConn %p: got textchat text: '%s'\n"), conn, text); - } -} - - -void VNCConn::thread_got_cuttext(rfbClient *cl, const char *text, int len) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - - wxLogDebug(wxT("VNCConn %p: got Latin1 cuttext: '%s'"), conn, wxString(text, wxCSConv(wxT("iso-8859-1"))).c_str()); - - wxCriticalSectionLocker lock(conn->mutex_cuttext); // since cuttext can also be set from the main thread - conn->cuttext = wxString(text, wxCSConv(wxT("iso-8859-1"))); - conn->thread_post_cuttext_notify(); -} - - -void VNCConn::thread_got_cuttext_utf8(rfbClient *cl, const char *text, int len) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - - wxLogDebug(wxT("VNCConn %p: got UTF-8 cuttext: '%s'"), conn, wxString(text, wxConvUTF8).c_str()); - - wxCriticalSectionLocker lock(conn->mutex_cuttext); // since cuttext can also be set from the main thread - conn->cuttext = wxString(text, wxConvUTF8); - conn->thread_post_cuttext_notify(); -} - - -void VNCConn::thread_bell(rfbClient *cl) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - wxLogDebug(wxT("VNCConn %p: bell"), conn); - conn->thread_post_bell_notify(); -} - - -void VNCConn::thread_handle_xvp(rfbClient *cl, uint8_t ver, uint8_t code) -{ - VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - wxLogDebug(wxT("VNCConn %p: handling xvp msg version %d code %d"), conn, ver, code); - - if(conn->latency_test_xvpmsg_sent && ver == LATENCY_TEST_XVP_VER && code == rfbXvp_Fail) - { - wxCriticalSectionLocker lock(conn->mutex_stats); - conn->latency_stopwatch.Pause(); - conn->latency = conn->latency_stopwatch.Time(); - conn->latency_test_xvpmsg_sent = false; - - wxLogDebug(wxT("VNCConn %p: got latency test xvp message back, took %ims"), conn, conn->latency_stopwatch.Time()); - } -} - - - - -// there's no per-connection log since we cannot find out which client -// called the logger function :-( -wxArrayString VNCConn::log; -wxCriticalSection VNCConn::mutex_log; -bool VNCConn::do_logfile; - -void VNCConn::thread_logger(const char *format, ...) -{ - if(!rfbEnableClientLogging) - return; - - // since we're accessing some global things here from different threads - wxCriticalSectionLocker lock(mutex_log); - - wxChar timebuf[256]; - time_t log_clock; - time(&log_clock); - wxStrftime(timebuf, WXSIZEOF(timebuf), _T("%d/%m/%Y %X "), localtime(&log_clock)); - - // global log string array - va_list args; - wxString wx_format(format, wxConvUTF8); - va_start(args, format); - char msg[1024]; - vsnprintf(msg, 1024, format, args); - log.Add( wxString(timebuf) + wxString(msg, wxConvUTF8)); - va_end(args); - - // global log file - if(do_logfile) - { - FILE* logfile; - wxString logfile_str = LOGFILE; - // delete logfile on program startup - static bool firstrun = 1; - if(firstrun) - { - remove(logfile_str.char_str()); - firstrun = 0; - } - - logfile=fopen(logfile_str.char_str(),"a"); - - va_start(args, format); - fprintf(logfile, "%s", (const char*)wxString(timebuf).mb_str()); - vfprintf(logfile, format, args); - va_end(args); - - fclose(logfile); - } - - // and stderr - va_start(args, format); - fprintf(stderr, "%s", (const char*)wxString(timebuf).mb_str()); - vfprintf(stderr, format, args); - va_end(args); -} - - - - - -/* - public members -*/ - -bool VNCConn::setupClient() -{ - wxLogDebug(wxT("VNCConn %p: setupClient()"), this); - - if(cl) // already set up - { - wxLogDebug(wxT("VNCConn %p: setupClient() already done"), this); - return false; - } - - // this takes (int bitsPerSample,int samplesPerPixel, int bytesPerPixel) - // 5,3,2 and 8,3,4 seem possible - cl=rfbGetClient(BITSPERSAMPLE, SAMPLESPERPIXEL, BYTESPERPIXEL); - - rfbClientSetClientData(cl, VNCCONN_OBJ_ID, this); - - // callbacks - cl->MallocFrameBuffer = thread_alloc_framebuffer; - cl->GotFrameBufferUpdate = thread_got_update; - cl->FinishedFrameBufferUpdate = thread_update_finished; - cl->GetPassword = thread_getpasswd; - cl->GetCredential = thread_getcreds; - cl->HandleKeyboardLedState = thread_kbd_leds; - cl->HandleTextChat = thread_textchat; - cl->GotXCutText = thread_got_cuttext; - cl->GotXCutTextUTF8 = thread_got_cuttext_utf8; - cl->Bell = thread_bell; - cl->HandleXvpMsg = thread_handle_xvp; - - cl->canHandleNewFBSize = TRUE; - cl->connectTimeout = 5; - - return true; -} - - - -void VNCConn::Listen(int port) -{ - wxLogDebug(wxT("VNCConn %p: Listen() port %d"), this, port); - - if(!cl) - setupClient(); - - cl->listenPort = cl->listen6Port = port; - - thread_listenmode = true; - - if( CreateThread() != wxTHREAD_NO_ERROR ) - { - err.Printf(_("Could not create VNC listener thread!")); - Shutdown(); - thread_post_listen_notify(1); //TODO add more error codes - return; - } - - if( GetThread()->Run() != wxTHREAD_NO_ERROR ) - { - err.Printf(_("Could not start VNC listener thread!")); - Shutdown(); - thread_post_listen_notify(1); //TODO add more error codes - return; - } -} - - -void VNCConn::Init(const wxString& host, const wxString& username, -#if wxUSE_SECRETSTORE - const wxSecretValue& password, -#endif - const wxString& encodings, int compresslevel, int quality, bool multicast, int multicast_socketrecvbuf, int multicast_recvbuf) -{ - wxLogDebug("VNCConn %p: Init() host '%s'", this, host); - - if(!cl) - setupClient(); - - if(cl->frameBuffer || (GetThread() && GetThread()->IsRunning())) - { - wxLogDebug(wxT("VNCConn %p: Init() already done. Call Shutdown() first!"), this); - thread_post_init_notify(1); // TODO add more error codes - return; - } - - // reset stats before doing new connection - resetStats(); - - cl->programName = "VNCConn"; - parseHostString(host.mb_str(), 5900, &cl->serverHost, &cl->serverPort); - this->username = username; -#if wxUSE_SECRETSTORE - this->password = password; -#endif - // Support short-form (:0, :1) - if(cl->serverPort < 100) - cl->serverPort += 5900; - - cl->appData.compressLevel = compresslevel; - cl->appData.qualityLevel = quality; - cl->appData.encodingsString = strdup(encodings.mb_str()); - if(multicast) - { - cl->canHandleMulticastVNC = TRUE; - cl->multicastSocketRcvBufSize = multicast_socketrecvbuf*1024; - cl->multicastRcvBufSize = multicast_recvbuf*1024; - multicastratio_stopwatch.Start(); - } - else - cl->canHandleMulticastVNC = FALSE; - - // this is like our main loop - thread_listenmode = false; - if( CreateThread() != wxTHREAD_NO_ERROR ) - { - err.Printf(_("Could not create VNC thread!")); - Shutdown(); - thread_post_init_notify(1); // TODO add more error codes - return; - } - - if( GetThread()->Run() != wxTHREAD_NO_ERROR ) - { - err.Printf(_("Could not start VNC thread!")); - Shutdown(); - thread_post_init_notify(1); // TODO add more error codes - return; - } - - conn_stopwatch.Start(); -} - - - - -void VNCConn::Shutdown() -{ - wxLogDebug(wxT("VNCConn %p: Shutdown()"), this); - - conn_stopwatch.Pause(); - - mutex_auth.Unlock(); - - if(GetThread() && GetThread()->IsRunning()) - { - wxLogDebug(wxT( "VNCConn %p: Shutdown() before vncthread delete"), this); - - GetThread()->Delete(); // this blocks if thread is joinable, i.e. on stack - wxLogDebug(wxT("VNCConn %p: Shutdown() after vncthread delete"), this); - } - - if(cl) - { - wxLogDebug(wxT( "VNCConn %p: Shutdown() closing connection"), this); -#ifdef __WIN32__ - closesocket(cl->sock); -#else - close(cl->sock); -#endif - // this one was strdup'ed before - if(!cl->listenSpecified) - free((void*)cl->appData.encodingsString); - - // in case we called listen, canceled that, and now want to connect to some - // host via Init() - cl->listenSpecified = FALSE; - - if(cl->frameBuffer) - { - free(cl->frameBuffer); - cl->frameBuffer = 0; - } - - rfbClientCleanup(cl); - cl = 0; - } -} - - -void VNCConn::setFastRequest(size_t interval) -{ - fastrequest_interval = interval; -} - -bool VNCConn::setDSCP(uint8_t dscp) -{ - if(cl && cl->sock >= 0) - return SetDSCP(cl->sock, dscp); - else - return false; -} - - -// this simply posts the mouse event into the worker thread's input queue -void VNCConn::sendPointerEvent(wxMouseEvent &event) -{ - if(replaying) - return; - - if(GetThread() && GetThread()->IsRunning()) - pointer_event_q.Post(event); -} - - -// because of the possible wxKeyEvent.Skip(), this posts the found keysym + down -bool VNCConn::sendKeyEvent(wxKeyEvent &event, bool down, bool isChar) -{ - if(replaying) - return false; - - keyEvent kev = {0,0}; - - if(isChar) - { - wxLogDebug(wxT("VNCConn %p: got CHAR key event"), this); - wxLogDebug(wxT("VNCConn %p: wxkeycode: %d"), this, event.GetKeyCode()); - wxLogDebug(wxT("VNCConn %p: wxkeycode char: %c"), this, event.GetKeyCode()); - wxLogDebug(wxT("VNCConn %p: unicode: %d"), this, event.GetUnicodeKey()); - wxLogDebug(wxT("VNCConn %p: unicode char: %c"), this, event.GetUnicodeKey()); - - // get tranlated char - kev.keysym = event.GetKeyCode(); - // if we got ne keycode, try unicodekey - if(kev.keysym==0) - kev.keysym = event.GetUnicodeKey(); - - // if wxwidgets translates a key combination into a - // value below 32, revert this here. - // we dont't send ASCII 0x03, but ctrl and then a 'c'! - if(kev.keysym <= 32) - { - kev.keysym += 96; - wxLogDebug(wxT("VNCConn %p: translating key to: %d"), this, kev.keysym); - } - - wxLogDebug(wxT("VNCConn %p: sending rfbkeysym: 0x%.3x down"), this, kev.keysym); - wxLogDebug(wxT("VNCConn %p: sending rfbkeysym: 0x%.3x up"), this, kev.keysym); - - // down, then up - if(GetThread() && GetThread()->IsRunning()) - { - kev.down = true; - key_event_q.Post(kev); - kev.down = false; - key_event_q.Post(kev); - } - return true; - } - else - { - // lookup keysym - switch(event.GetKeyCode()) - { - case WXK_BACK: kev.keysym = XK_BackSpace; break; - case WXK_TAB: kev.keysym = XK_Tab; break; - case WXK_CLEAR: kev.keysym = XK_Clear; break; - case WXK_RETURN: kev.keysym = XK_Return; break; - case WXK_PAUSE: kev.keysym = XK_Pause; break; - case WXK_ESCAPE: kev.keysym = XK_Escape; break; - case WXK_SPACE: kev.keysym = XK_space; break; - case WXK_DELETE: kev.keysym = XK_Delete; break; - case WXK_NUMPAD0: kev.keysym = XK_KP_0; break; - case WXK_NUMPAD1: kev.keysym = XK_KP_1; break; - case WXK_NUMPAD2: kev.keysym = XK_KP_2; break; - case WXK_NUMPAD3: kev.keysym = XK_KP_3; break; - case WXK_NUMPAD4: kev.keysym = XK_KP_4; break; - case WXK_NUMPAD5: kev.keysym = XK_KP_5; break; - case WXK_NUMPAD6: kev.keysym = XK_KP_6; break; - case WXK_NUMPAD7: kev.keysym = XK_KP_7; break; - case WXK_NUMPAD8: kev.keysym = XK_KP_8; break; - case WXK_NUMPAD9: kev.keysym = XK_KP_9; break; - case WXK_NUMPAD_DECIMAL: kev.keysym = XK_KP_Decimal; break; - case WXK_NUMPAD_DIVIDE: kev.keysym = XK_KP_Divide; break; - case WXK_NUMPAD_MULTIPLY: kev.keysym = XK_KP_Multiply; break; - case WXK_NUMPAD_SUBTRACT: kev.keysym = XK_KP_Subtract; break; - case WXK_NUMPAD_ADD: kev.keysym = XK_KP_Add; break; - case WXK_NUMPAD_ENTER: kev.keysym = XK_KP_Enter; break; - case WXK_NUMPAD_EQUAL: kev.keysym = XK_KP_Equal; break; - case WXK_UP: kev.keysym = XK_Up; break; - case WXK_DOWN: kev.keysym = XK_Down; break; - case WXK_RIGHT: kev.keysym = XK_Right; break; - case WXK_LEFT: kev.keysym = XK_Left; break; - case WXK_INSERT: kev.keysym = XK_Insert; break; - case WXK_HOME: kev.keysym = XK_Home; break; - case WXK_END: kev.keysym = XK_End; break; - case WXK_PAGEUP: kev.keysym = XK_Page_Up; break; - case WXK_PAGEDOWN: kev.keysym = XK_Page_Down; break; - case WXK_F1: kev.keysym = XK_F1; break; - case WXK_F2: kev.keysym = XK_F2; break; - case WXK_F3: kev.keysym = XK_F3; break; - case WXK_F4: kev.keysym = XK_F4; break; - case WXK_F5: kev.keysym = XK_F5; break; - case WXK_F6: kev.keysym = XK_F6; break; - case WXK_F7: kev.keysym = XK_F7; break; - case WXK_F8: kev.keysym = XK_F8; break; - case WXK_F9: kev.keysym = XK_F9; break; - case WXK_F10: kev.keysym = XK_F10; break; - case WXK_F11: kev.keysym = XK_F11; break; - case WXK_F12: kev.keysym = XK_F12; break; - case WXK_F13: kev.keysym = XK_F13; break; - case WXK_F14: kev.keysym = XK_F14; break; - case WXK_F15: kev.keysym = XK_F15; break; - case WXK_NUMLOCK: kev.keysym = XK_Num_Lock; break; - case WXK_CAPITAL: kev.keysym = XK_Caps_Lock; break; - case WXK_SCROLL: kev.keysym = XK_Scroll_Lock; break; - //case WXK_RSHIFT: kev.keysym = XK_Shift_R; break; - case WXK_SHIFT: kev.keysym = XK_Shift_L; break; - //case WXK_RCTRL: kev.keysym = XK_Control_R; break; - case WXK_CONTROL: kev.keysym = XK_Control_L; break; - // case WXK_RALT: kev.keysym = XK_Alt_R; break; - case WXK_ALT: kev.keysym = XK_Alt_L; break; - // case WXK_RMETA: kev.keysym = XK_Meta_R; break; - // case WXK_META: kev.keysym = XK_Meta_L; break; - case WXK_WINDOWS_LEFT: kev.keysym = XK_Super_L; break; - case WXK_WINDOWS_RIGHT: kev.keysym = XK_Super_R; break; - //case WXK_COMPOSE: kev.keysym = XK_Compose; break; - //case WXK_MODE: kev.keysym = XK_Mode_switch; break; - case WXK_HELP: kev.keysym = XK_Help; break; - case WXK_PRINT: kev.keysym = XK_Print; break; - //case WXK_SYSREQ: kev.keysym = XK_Sys_Req; break; - case WXK_CANCEL: kev.keysym = XK_Break; break; - default: break; - } - - if(kev.keysym) - { - wxLogDebug(wxT("VNCConn %p: got key %s event:"), this, down ? wxT("down") : wxT("up")); - wxLogDebug(wxT("VNCConn %p: wxkeycode: %d"), this, event.GetKeyCode()); - wxLogDebug(wxT("VNCConn %p: wxkeycode char: %c"), this, event.GetKeyCode()); - wxLogDebug(wxT("VNCConn %p: unicode: %d"), this, event.GetUnicodeKey()); - wxLogDebug(wxT("VNCConn %p: unicode char: %c"), this, event.GetUnicodeKey()); - wxLogDebug(wxT("VNCConn %p: sending rfbkeysym: 0x%.3x %s"), this, kev.keysym, down ? wxT("down") : wxT("up")); - - kev.down = down; - if(GetThread() && GetThread()->IsRunning()) - key_event_q.Post(kev); - return true; - } - else - { - // nothing of the above? - // then propagate this event through the EVT_CHAR handler - event.Skip(); - return false; - } - } - - wxLogDebug(wxT("VNCConn %p: no matching keysym found"), this); - return false; - -} - - - - - -void VNCConn::doStats(bool yesno) -{ - do_stats = yesno; - wxCriticalSectionLocker lock(mutex_stats); - if(do_stats) - { - stats_timer.Start(1000); - latency_test_rect_sent = latency_test_xvpmsg_sent = false; // to start sending one - } - else - stats_timer.Stop(); -} - - -void VNCConn::resetStats() -{ - wxCriticalSectionLocker lock(mutex_stats); - statistics.Clear(); -} - - - - -/* - user input record/replay stuff - */ - bool VNCConn::replayUserInputStart(wxArrayString src, bool loop) -{ - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(!recording) - { - recordreplay_stopwatch.Start(); - userinput_pos = 0; - userinput = src; - replay_loop = loop; - replaying = true; - return true; - } - return false; -} - - -bool VNCConn::replayUserInputStop() -{ - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(replaying) - { - recordreplay_stopwatch.Pause(); - userinput.Clear(); - replaying = false; - return true; - } - return false; -} - - -bool VNCConn::recordUserInputStart() -{ - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(!replaying) - { - recordreplay_stopwatch.Start(); - userinput_pos = 0; - userinput.Clear(); - recording = true; - return true; - } - return false; -} - - -bool VNCConn::recordUserInputStop(wxArrayString &dst) -{ - wxCriticalSectionLocker lock(mutex_recordreplay); - - if(recording) - { - recordreplay_stopwatch.Pause(); - recording = false; - dst = userinput; // copy over - return true; - } - return false; -} - - - - -/* - we could use a wxBitmap directly as the framebuffer, thus being more efficient - BUT: - - win32 expects DIB data in BGRA, not RGBA (can be solved by requesting another pixel format) - - win32 DIBs expect data from bottom to top (this means we HAVE to use a self made - framebuffer -> wxBitmap function instead of GetSubBitmap().) - - direct data access of a wxBitmap on Mac OS X does not seem to work - INSTEAD: - - we have an ordinary char array as framebuffer and copy the requested content into a - wxBitmap (the return of its copy is cheap cause wxBitmaps use copy-on-write) - */ -wxBitmap VNCConn::getFrameBufferRegion(const wxRect& rect) const -{ - // sanity check requested region - if(rect.x < 0 || rect.x + rect.width > getFrameBufferWidth() - || rect.y < 0 || rect.y + rect.height > getFrameBufferHeight()) - return wxBitmap(); - - /* - copy directly from framebuffer into a new bitmap - */ - - wxBitmap region(rect.width, rect.height, cl->format.bitsPerPixel); - wxAlphaPixelData region_data(region); - wxAlphaPixelData::Iterator region_it(region_data); - - int bytesPerPixel = cl->format.bitsPerPixel/8; - uint8_t *fbsub_it = cl->frameBuffer + rect.y*cl->width*bytesPerPixel + rect.x*bytesPerPixel; - - for( int y = 0; y < rect.height; ++y ) - { - wxAlphaPixelData::Iterator region_it_rowStart = region_it; - uint8_t *fbsub_it_rowStart = fbsub_it; - - for( int x = 0; x < rect.width; ++x, ++region_it, fbsub_it += bytesPerPixel) - { - region_it.Red() = *(fbsub_it+0); - region_it.Green() = *(fbsub_it+1); - region_it.Blue() = *(fbsub_it+2); - region_it.Alpha() = 255; // 255 is opaque, libvncclient always sets this byte to 0 - } - - // CR - region_it = region_it_rowStart; - fbsub_it = fbsub_it_rowStart; - - // LF - region_it.OffsetY(region_data, 1); - fbsub_it += cl->width * bytesPerPixel; - } - - return region; -} - - - - -bool VNCConn::getFrameBufferRegion(const wxRect& rect, wxBitmap& dst) const -{ - // sanity check requested region against framebuffer - if(rect.x < 0 || rect.x + rect.width > getFrameBufferWidth() - || rect.y < 0 || rect.y + rect.height > getFrameBufferHeight()) - return false; - - // check dst against framebuffer - if(dst.GetWidth() != getFrameBufferWidth() || dst.GetHeight() != getFrameBufferHeight()) - return false; - - /* - copy directly from framebuffer into the destination bitmap - */ - wxAlphaPixelData dst_data(dst); - wxAlphaPixelData::Iterator dst_it(dst_data); - dst_it.Offset(dst_data, rect.x, rect.y); - - int bytesPerPixel = cl->format.bitsPerPixel/8; - uint8_t *fbsub_it = cl->frameBuffer + rect.y*cl->width*bytesPerPixel + rect.x*bytesPerPixel; - - for( int y = 0; y < rect.height; ++y ) - { - wxAlphaPixelData::Iterator dst_it_rowStart = dst_it; - uint8_t *fbsub_it_rowStart = fbsub_it; - - for( int x = 0; x < rect.width; ++x, ++dst_it, fbsub_it += bytesPerPixel) - { - dst_it.Red() = *(fbsub_it+0); - dst_it.Green() = *(fbsub_it+1); - dst_it.Blue() = *(fbsub_it+2); - dst_it.Alpha() = 255; // 255 is opaque, libvncclient always sets this byte to 0 - } - - // CR - dst_it = dst_it_rowStart; - fbsub_it = fbsub_it_rowStart; - - // LF - dst_it.OffsetY(dst_data, 1); - fbsub_it += cl->width * bytesPerPixel; - } - - return true; -} - - - -int VNCConn::getFrameBufferWidth() const -{ - if(cl) - return cl->width; - else - return 0; -} - - -int VNCConn::getFrameBufferHeight() const -{ - if(cl) - return cl->height; - else - return 0; -} - - -int VNCConn::getFrameBufferDepth() const -{ - if(cl) - return cl->format.bitsPerPixel; - else - return 0; -} - - - - -wxString VNCConn::getDesktopName() const -{ - if(cl) - return wxString(cl->desktopName, wxConvUTF8); - else - return wxEmptyString; -} - - -const wxString& VNCConn::getUserName() const { - return username; -} - -void VNCConn::setUserName(const wxString& username) { - this->username = username; -} - -#if wxUSE_SECRETSTORE -const wxSecretValue& VNCConn::getPassword() const { -#else -const wxString& VNCConn::getPassword() const { -#endif - return password; -} - -#if wxUSE_SECRETSTORE -void VNCConn::setPassword(const wxSecretValue& password) { -#else -void VNCConn::setPassword(const wxString& password) { -#endif - this->password = password; - // tell worker thread to go on - condition_auth.Signal(); -} - - -const bool VNCConn::getRequireAuth() const { - return require_auth; -} - - -wxString VNCConn::getServerHost() const -{ - if(cl) - { - if(cl->listenSpecified) - return wxEmptyString; - else - return wxString(cl->serverHost, wxConvUTF8); - } - else - return wxEmptyString; -} - - - -wxString VNCConn::getServerPort() const -{ - if(cl) - return wxString() << cl->serverPort; - else - return wxEmptyString; -} - - - -wxString VNCConn::getListenPort() const -{ - if(cl && cl->listenSpecified) - return wxString() << cl->listenPort; - else - return wxEmptyString; -} - - - -bool VNCConn::isMulticast() const -{ - if(cl && cl->multicastSock >= 0 && !cl->multicastDisabled) - return true; - else - return false; -} - - -double VNCConn::getMCNACKedRatio() -{ - wxCriticalSectionLocker lock(mutex_multicastratio); - double retval = 0; - int samples = 0; - std::deque::const_iterator it; - for(it = multicastNACKedRatios.begin(); it != multicastNACKedRatios.end(); ++it) - if(*it >= 0) // valid value - { - retval += *it; - ++samples; - } - - if(samples) - return retval/samples; - else - return -1; -} - - -double VNCConn::getMCLossRatio() -{ - wxCriticalSectionLocker lock(mutex_multicastratio); - double retval = 0; - int samples = 0; - std::deque::const_iterator it; - for(it = multicastLossRatios.begin(); it != multicastLossRatios.end(); ++it) - if(*it >= 0) // valid value - { - retval += *it; - ++samples; - } - - if(samples) - return retval/samples; - else - return -1; -} - - -void VNCConn::clearLog() -{ - // since we're accessing some global things here from different threads - wxCriticalSectionLocker lock(mutex_log); - log.Clear(); -} - - - -// get the OS-dependent socket receive buffer size in KByte. -// max returned value is 32MB, negative values on error -int VNCConn::getMaxSocketRecvBufSize() -{ - int sock; - -#ifdef WIN32 - WSADATA trash; - WSAStartup(MAKEWORD(2,0),&trash); -#endif - - // create the test socket - if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) - return -1; - - // try with some high value and see what we get - int recv_buf_try = 33554432; // 32 MB - // This is needed on Linux to see what the maximum value is but errors on MacOS, so we - // simply treat an error here as non-fatal. - setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recv_buf_try, sizeof(recv_buf_try)); - - int recv_buf_got = -1; - socklen_t recv_buf_got_len = sizeof(recv_buf_got); - if(getsockopt(sock, SOL_SOCKET, SO_RCVBUF,(char*)&recv_buf_got, &recv_buf_got_len) <0) - { - rfbCloseSocket(sock); - return -2; - } - - rfbCloseSocket(sock); - - return recv_buf_got/1024; -} - - -/* - parse ipv4 or ipv6 address string. - taken from remmina, thanks! -*/ -void VNCConn::parseHostString(const char *server, int defaultport, char **host, int *port) -{ - char *str, *ptr, *ptr2; - - str = strdup(server); - - /* [server]:port format */ - ptr = strchr(str, '['); - if (ptr) - { - ptr++; - ptr2 = strchr(ptr, ']'); - if (ptr2) - *ptr2++ = '\0'; - if (*ptr2 == ':') - defaultport = atoi(ptr2 + 1); - if (host) - *host = strdup(ptr); - if (port) - *port = defaultport; - free(str); - return; - } - - /* server:port format, IPv6 cannot use this format */ - ptr = strchr(str, ':'); - if (ptr) - { - ptr2 = strchr(ptr + 1, ':'); - if (ptr2 == NULL) - { - *ptr++ = '\0'; - defaultport = atoi(ptr); - } - /* More than one ':' means this is IPv6 address. Treat it as a whole address */ - } - if (host) - *host = str; - if (port) - *port = defaultport; -} - - -VNCConn* VNCConn::getVNCConnFromRfbClient(rfbClient *cl) { - - return (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); - -} +/* + VNCConn.cpp: VNC connection class implementation. + + This file is part of MultiVNC, a multicast-enabled crossplatform + VNC viewer. + + Copyright (C) 2009, 2010 Christian Beier + + MultiVNC is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + MultiVNC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#include +#include +#include +#include +#include +#ifdef __WIN32__ +#include +#else +#include +#endif +#include "VNCConn.h" + + +// use some global address +#define VNCCONN_OBJ_ID (void*)VNCConn::thread_got_update + +// logfile name +#define LOGFILE _T("MultiVNC.log") + + +// pixelformat defaults +// seems 8,3,4 and 5,3,2 are possible with rfbGetClient() +#define BITSPERSAMPLE 8 +#define SAMPLESPERPIXEL 3 +#define BYTESPERPIXEL 4 + + +// define our new notify events! +DEFINE_EVENT_TYPE(VNCConnListenNOTIFY) +DEFINE_EVENT_TYPE(VNCConnInitNOTIFY) +wxDEFINE_EVENT(VNCConnGetPasswordNOTIFY, wxCommandEvent); +wxDEFINE_EVENT(VNCConnGetCredentialsNOTIFY, wxCommandEvent); +DEFINE_EVENT_TYPE(VNCConnIncomingConnectionNOTIFY) +DEFINE_EVENT_TYPE(VNCConnDisconnectNOTIFY) +DEFINE_EVENT_TYPE(VNCConnUpdateNOTIFY) +DEFINE_EVENT_TYPE(VNCConnFBResizeNOTIFY) +DEFINE_EVENT_TYPE(VNCConnCuttextNOTIFY) +DEFINE_EVENT_TYPE(VNCConnBellNOTIFY) +DEFINE_EVENT_TYPE(VNCConnUniMultiChangedNOTIFY); +DEFINE_EVENT_TYPE(VNCConnReplayFinishedNOTIFY); + + +BEGIN_EVENT_TABLE(VNCConn, wxEvtHandler) + EVT_TIMER (wxID_ANY, VNCConn::on_stats_timer) +END_EVENT_TABLE(); + + +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS +bool VNCConn::TLS_threading_initialized; +extern "C" +{ +#include +#include + /* + * gcrypt thread option wx implementation + */ + static int gcry_wx_mutex_init( void **p ) + { + *p = new wxMutex(); + return 0; + } + + static int gcry_wx_mutex_destroy( void **p ) + { + delete (wxMutex*)*p; + return 0; + } + + static int gcry_wx_mutex_lock( void **p ) + { + if(((wxMutex*)(*p))->Lock() == wxMUTEX_NO_ERROR) + return 0; + else + return 1; + } + + static int gcry_wx_mutex_unlock( void **p ) + { + if(((wxMutex*)(*p))->Unlock() == wxMUTEX_NO_ERROR) + return 0; + else + return 1; + } + + static const struct gcry_thread_cbs gcry_threads_wx = + { + GCRY_THREAD_OPTION_USER, + NULL, + gcry_wx_mutex_init, + gcry_wx_mutex_destroy, + gcry_wx_mutex_lock, + gcry_wx_mutex_unlock + }; +} +#endif + + + + +/* + constructor/destructor +*/ + + +VNCConn::VNCConn(void* p) : condition_auth(mutex_auth) +{ + // save our caller + parent = p; + + cl = 0; + multicastStatus = 0; + latency = -1; + + rfbClientLog = rfbClientErr = thread_logger; + +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + /* we're using threads in here, tell libgcrypt before TLS + gets initialized by libvncclient! */ + if(! TLS_threading_initialized) + { + wxLogDebug(wxT("Initialized libgcrypt threading.")); + gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_wx); + gcry_check_version (NULL); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + TLS_threading_initialized = true; + } +#endif + + // statistics stuff + do_stats = false; + upd_bytes = 0; + upd_bytes_inflated = 0; + upd_count = 0; + stats_timer.SetOwner(this); + + // fastrequest stuff + fastrequest_interval = 0; + + // record/replay stuff + recording = replaying = false; + + require_auth = false; +} + + + + +VNCConn::~VNCConn() +{ + Shutdown(); + wxLogDebug(wxT("VNCConn %p: I'm dead!"), this); +} + + + +/* + private members +*/ + + +void VNCConn::on_stats_timer(wxTimerEvent& event) +{ + if(do_stats) + { + wxCriticalSectionLocker lock(mutex_stats); + + if(statistics.IsEmpty()) + statistics.Add(wxString() + + wxT("UTC time,") + + wxT("conn time,") + + wxT("rcvd bytes,") + + wxT("rcvd bytes inflated,") + + wxT("upd count,") + + wxT("latency,") + + wxT("nack rate,") + + wxT("loss rate,") + + wxT("buf size,") + + wxT("buf fill,")); + + wxString sample; + + sample += (wxString() << (int)wxGetUTCTime()); // global UTC time + sample += wxT(","); + sample += (wxString() << (int)conn_stopwatch.Time()); // connection time + sample += wxT(","); + sample += (wxString() << upd_bytes); // rcvd bytes sampling + sample += wxT(","); + sample += (wxString() << upd_bytes_inflated); // rcvd bytes inflated sampling + sample += wxT(","); + sample += (wxString() << upd_count); // number of updates sampling + sample += wxT(","); + sample += (wxString() << latency); // latency sampling + sample += wxT(","); + wxString nackrate_str = wxString::Format(wxT("%.4f"), getMCNACKedRatio()); + nackrate_str.Replace(wxT(","), wxT(".")); + sample += nackrate_str; // nack rate sampling + sample += wxT(","); + wxString lossrate_str = wxString::Format(wxT("%.4f"), getMCLossRatio()); + lossrate_str.Replace(wxT(","), wxT(".")); + sample += lossrate_str; // loss rate sampling + sample += wxT(","); + sample += (wxString() << getMCBufSize()); // buffer size sampling + sample += wxT(","); + sample += (wxString() << getMCBufFill()); // buffer fill sampling + + // add the sample + statistics.Add(sample); + + // reset these + upd_bytes = 0; + upd_bytes_inflated = 0; + upd_count = 0; + latency = -1; + + + latency_test_trigger = true; + } +} + + + +rfbBool VNCConn::thread_alloc_framebuffer(rfbClient* client) +{ + // get VNCConn object belonging to this client + VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); + + wxLogDebug(wxT("VNCConn %p: alloc'ing framebuffer w:%i, h:%i"), conn, client->width, client->height); + + // assert 32bpp, as requested with GetClient() in Init() + if(client->format.bitsPerPixel != 32) + { + conn->err.Printf(_("Failure setting up framebuffer: wrong BPP!")); + return false; + } + + // ensure that we get the whole framebuffer in case of a resize! + client->updateRect.x = client->updateRect.y = 0; + client->updateRect.w = client->width; client->updateRect.h = client->height; + + // free + if(client->frameBuffer) + free(client->frameBuffer); + + // alloc, zeroed + client->frameBuffer = (uint8_t*)calloc(1, client->width*client->height*client->format.bitsPerPixel/8); + + // notify our parent + conn->thread_post_fbresize_notify(); + + return client->frameBuffer ? TRUE : FALSE; +} + + + + +wxThread::ExitCode VNCConn::Entry() +{ + // init connection before going into main loop if this is not a listening one + if (!thread_listenmode) { + rfbClientLog("About to connect to '%s', port %d\n", cl->serverHost, cl->serverPort); + + // save these for the error case + wxString host = wxString(cl->serverHost); + int port = cl->serverPort; + if (!rfbInitClient(cl, 0, NULL)) { + // rfbInitClient() calls rfbClientCleanup() on failure, but + // this does not zero the ptr + cl = 0; + err.Printf(_("Failure connecting to server at %s:%d!"), host, port); + wxLogDebug("VNCConn %p: rfbInitClient() failed. Cleanup by library.", this); + thread_post_init_notify(1); // TODO add more error codes + wxLogDebug("VNCConn %p: vncthread done", this); + return 0; + } + + // set the client sock to blocking again until libvncclient is fixed +#ifdef WIN32 + unsigned long block = 0; + if (ioctlsocket(cl->sock, FIONBIO, &block) == SOCKET_ERROR) { + errno = WSAGetLastError(); +#else + int flags = fcntl(cl->sock, F_GETFL); + if (flags < 0 || fcntl(cl->sock, F_SETFL, flags & ~O_NONBLOCK) < 0) { +#endif + rfbClientErr("Setting socket to blocking failed: %s\n", strerror(errno)); + } + + // if there was an error in alloc_framebuffer(), catch that here + // err is set by alloc_framebuffer() + if (!cl->frameBuffer) { + thread_post_init_notify(1); // TODO add more error codes + wxLogDebug("VNCConn %p: vncthread done", this); + return 0; + } + // connect succesful + thread_post_init_notify(0); + } + + int i=0; + + pointerEvent pe; + keyEvent ke = {0, 0}; + + bool listen_outcome_posted = false; + + while(! GetThread()->TestDestroy()) + { + if(thread_listenmode) + { + i=listenForIncomingConnectionsNoFork(cl, 100000); // 100 ms + if (i == 0) { + // just notify about success once + if (!listen_outcome_posted) { + thread_post_listen_notify(0); + listen_outcome_posted = true; + } + } + if(i<0) + { + if(errno==EINTR) + continue; + wxLogDebug(wxT("VNCConn %p: vncthread listen() failed"), this); + thread_post_listen_notify(1); //TODO add more error codes + break; + } + if(i) + { + // have this here in case of immediate connection + // but just notify about success once + if (!listen_outcome_posted) { + thread_post_listen_notify(0); + listen_outcome_posted = true; + } + thread_post_incomingconnection_notify(); + break; + } + } + else + { + // userinput replay here + { + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(replaying) + { + if(userinput_pos < userinput.GetCount()) // still recorded input there + { + wxString ui_now = userinput[userinput_pos]; + // get timestamp and strip it from string + long ts = wxAtol(ui_now.BeforeFirst(',')); + ui_now = ui_now.AfterFirst(','); + + if(ts <= recordreplay_stopwatch.Time()) // past or now, process it + { + // get type + wxString type = ui_now.BeforeFirst(','); + ui_now = ui_now.AfterFirst(','); + + if(type == wxT("p")) + { + // get pointer x,y, buttmask + int x = wxAtoi(ui_now.BeforeFirst(',')); + ui_now = ui_now.AfterFirst(','); + int y = wxAtoi(ui_now.BeforeFirst(',')); + ui_now = ui_now.AfterFirst(','); + int bmask = wxAtoi(ui_now); + + // and send + SendPointerEvent(cl, x, y, bmask); + } + + if(type == wxT("k")) + { + // get keysym + rfbKeySym keysym = wxAtoi(ui_now.BeforeFirst(',')); + ui_now = ui_now.AfterFirst(','); + bool down = wxAtoi(ui_now); + + // and send + SendKeyEvent(cl, keysym, down); + } + + // advance to next input + ++userinput_pos; + } + } + else if (replay_loop) + { + userinput_pos = 0; // rewind + recordreplay_stopwatch.Start(); // restart + } + else + { + replaying = false; // all done + thread_post_replayfinished_notify(); + } + + } + } + + // send everything that's inside the input queues + while(pointer_event_q.ReceiveTimeout(0, pe) != wxMSGQUEUE_TIMEOUT) // timeout == empty + thread_send_pointer_event(pe); + while(key_event_q.ReceiveTimeout(0, ke) != wxMSGQUEUE_TIMEOUT) // timeout == empty + thread_send_key_event(ke); + + { + wxCriticalSectionLocker lock(mutex_stats); + if(latency_test_trigger) + { + latency_test_trigger = false; + thread_send_latency_probe(); + } + } + + if(fastrequest_interval && (size_t)fastrequest_stopwatch.Time() > fastrequest_interval) + { + if(isMulticast()) + SendMulticastFramebufferUpdateRequest(cl, TRUE); + else + SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, TRUE); + + fastrequest_stopwatch.Start(); // restart + } + + + // request update and handle response + if(!rfbProcessServerMessage(cl, 500)) + { + if(errno == EINTR) + continue; + wxLogDebug(wxT("VNCConn %p: vncthread rfbProcessServerMessage() failed"), this); + thread_post_disconnect_notify(); + break; + } + + + /* + Compute nacked/loss ratio: We take a ratio sample every second and put it into a sample queue + of size N. Action is taken when the average sample value of the whole buffer exceeds a per-action + limit. This has advantages over taking a sample every N seconds: First, it's able to catch say a 5sec burst + that could be missed by two adjacent 10sec samples (one catches 2sec, the next one 3sec - no action triggered + although condition present). Second, this way we're able to show a value to the user every second independent + of the sample time frame. + */ + if(isMulticast() && multicastratio_stopwatch.Time() >= 1000) + { + // restart + multicastratio_stopwatch.Start(); + + /* + take sample + */ + { + // the fifos are read by the GUI thread as well! + wxCriticalSectionLocker lock(mutex_multicastratio); + + if(multicastNACKedRatios.size() >= MULTICAST_RATIO_SAMPLES) // make room if size exceeded + multicastNACKedRatios.pop_front(); + if(cl->multicastPktsRcvd + cl->multicastPktsNACKed > 0) + multicastNACKedRatios.push_back(cl->multicastPktsNACKed/(double)(cl->multicastPktsRcvd + cl->multicastPktsNACKed)); + else + multicastNACKedRatios.push_back(-1); // nothing to measure, add invalid marker + + if(multicastLossRatios.size() >= MULTICAST_RATIO_SAMPLES) // make room if size exceeded + multicastLossRatios.pop_front(); + if(cl->multicastPktsRcvd + cl->multicastPktsLost > 0) + multicastLossRatios.push_back(cl->multicastPktsLost/(double)(cl->multicastPktsRcvd + cl->multicastPktsLost)); + else + multicastLossRatios.push_back(-1); // nothing to measure, add invalid marker + + // reset the values we sample + cl->multicastPktsRcvd = cl->multicastPktsNACKed = cl->multicastPktsLost = 0; + } + + /* + And act accordingly, but only after the ratio deques are at least half full. + When a client joins a multicast group with heavy traffic going on, it will lose + a lot of packets in the very beginning because there is a considerable time + amount between it's multicast socket creation and the first read. Thus, the socket + buffer is likely to overflow in this start situation, resulting in packet loss. + */ + if(multicastLossRatios.size() >= MULTICAST_RATIO_SAMPLES/2) + { + if(getMCLossRatio() > 0.5) + { + rfbClientLog("MultiVNC: loss ratio > 0.5, falling back to unicast\n"); + wxLogDebug(wxT("VNCConn %p: multicast loss ratio > 0.5, falling back to unicast"), this); + cl->multicastDisabled = TRUE; + SendFramebufferUpdateRequest(cl, 0, 0, cl->width, cl->height, FALSE); + } + else if(getMCLossRatio() > 0.2) + { + rfbClientLog("MultiVNC: loss ratio > 0.2, requesting a full multicast framebuffer update\n"); + SendMulticastFramebufferUpdateRequest(cl, FALSE); + cl->multicastPktsLost /= 2; + } + } + } + + + + int now = isMulticast(); + if(now != multicastStatus) + { + multicastStatus = now; + thread_post_unimultichanged_notify(); + } + } + } + + wxLogDebug("VNCConn %p: vncthread done", this); + return 0; +} + + + + + +bool VNCConn::thread_send_pointer_event(pointerEvent &event) +{ + int buttonmask = 0; + + if(event.LeftIsDown()) + buttonmask |= rfbButton1Mask; + + if(event.MiddleIsDown()) + buttonmask |= rfbButton2Mask; + + if(event.RightIsDown()) + buttonmask |= rfbButton3Mask; + + if(event.GetWheelRotation() > 0) + buttonmask |= rfbWheelUpMask; + + if(event.GetWheelRotation() < 0) + buttonmask |= rfbWheelDownMask; + + if(event.Entering() && ! cuttext.IsEmpty()) + { + wxCriticalSectionLocker lock(mutex_cuttext); // since cuttext can be set from the main thread + + // first, try sending UTF-8 + if(SendClientCutTextUTF8(cl, const_cast(cuttext.utf8_str().data()), strlen(cuttext.utf8_str().data()))) { + wxLogDebug(wxT("VNCConn %p: sent UTF-8 cuttext: '%s'"), this, cuttext.utf8_str()); + } else { + // server does not support Extended Clipboard, try Latin-1. + // if encoding fails, length() returns 0 + if(cuttext.mb_str(wxCSConv("iso-8859-1")).length()) + { + char* encoded_text = strdup(cuttext.mb_str(wxCSConv(wxT("iso-8859-1")))); + SendClientCutText(cl, encoded_text, strlen(encoded_text)); + wxLogDebug(wxT("VNCConn %p: sent Latin-1 cuttext: '%s'"), this, encoded_text); + free(encoded_text); + } + else + wxLogDebug(wxT("VNCConn %p: sending Latin-1 cuttext FAILED, could not convert '%s' to ISO-8859-1"), this, cuttext.c_str()); + } + } + + // record here + { + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(recording) + { + wxString ui_now; + ui_now += (wxString() << (int)recordreplay_stopwatch.Time()); + ui_now += wxT(","); + ui_now += wxT("p"); // is pointer event + ui_now += wxT(","); + ui_now += (wxString() << event.m_x); + ui_now += wxT(","); + ui_now += (wxString() << event.m_y); + ui_now += wxT(","); + ui_now += (wxString() << buttonmask); + + userinput.Add(ui_now); + } + } + + wxLogDebug(wxT("VNCConn %p: sending pointer event at (%d,%d), buttonmask %d"), this, event.m_x, event.m_y, buttonmask); + return SendPointerEvent(cl, event.m_x, event.m_y, buttonmask); +} + + + + +bool VNCConn::thread_send_key_event(keyEvent &event) +{ + // record here + { + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(recording) + { + wxString ui_now; + ui_now += (wxString() << (int)recordreplay_stopwatch.Time()); + ui_now += wxT(","); + ui_now += wxT("k"); // is key event + ui_now += wxT(","); + ui_now += (wxString() << event.keysym); + ui_now += wxT(","); + ui_now += (wxString() << event.down); + + userinput.Add(ui_now); + } + } + + return SendKeyEvent(cl, event.keysym, event.down); +} + + + +bool VNCConn::thread_send_latency_probe() +{ + bool result = TRUE; + // latency check start + if(SupportsClient2Server(cl, rfbXvp)) // favor xvp over the rect check + { + if(!latency_test_xvpmsg_sent) + { + result = SendXvpMsg(cl, LATENCY_TEST_XVP_VER, 2); + latency_test_xvpmsg_sent = true; + latency_stopwatch.Start(); + wxLogDebug(wxT("VNCConn %p: xvp message sent to test latency"), this); + } + } + else // check using special rect + { + if(!latency_test_rect_sent) + { + result = SendFramebufferUpdateRequest(cl, LATENCY_TEST_RECT, FALSE); + latency_test_rect_sent = true; + latency_stopwatch.Start(); + wxLogDebug(wxT("VNCConn %p: fb update request sent to test latency"), this); + } + } + return result; +} + +void VNCConn::thread_post_listen_notify(int error) { + wxLogDebug(wxT("VNCConn %p: post_listen_notify(%d)"), this, error); + wxCommandEvent event(VNCConnListenNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + event.SetInt(error); + wxPostEvent((wxEvtHandler*)parent, event); +} + +void VNCConn::thread_post_init_notify(int error) { + wxLogDebug(wxT("VNCConn %p: post_init_notify(%d)"), this, error); + wxCommandEvent event(VNCConnInitNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + event.SetInt(error); + wxPostEvent((wxEvtHandler*)parent, event); +} + +void VNCConn::thread_post_getpasswd_notify() { + wxLogDebug(wxT("VNCConn %p: post_getpasswd_notify()"), this); + wxCommandEvent event(VNCConnGetPasswordNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + wxPostEvent((wxEvtHandler*)parent, event); +} + +void VNCConn::thread_post_getcreds_notify(bool withUserPrompt) { + wxLogDebug(wxT("VNCConn %p: post_getcreds_notify()"), this); + wxCommandEvent event(VNCConnGetCredentialsNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + event.SetInt(withUserPrompt); + wxPostEvent((wxEvtHandler*)parent, event); +} + + +void VNCConn::thread_post_incomingconnection_notify() +{ + wxLogDebug(wxT("VNCConn %p: post_incomingconnection_notify()"), this); + + // new NOTIFY event, we got no window id + wxCommandEvent event(VNCConnIncomingConnectionNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + +void VNCConn::thread_post_disconnect_notify() +{ + wxLogDebug(wxT("VNCConn %p: post_disconnect_notify()"), this); + + // new NOTIFY event, we got no window id + wxCommandEvent event(VNCConnDisconnectNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + +void VNCConn::thread_post_update_notify(int x, int y, int w, int h) +{ + VNCConnUpdateNotifyEvent event(VNCConnUpdateNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // set info about what was updated + event.rect = wxRect(x, y, w, h); + wxLogDebug(wxT("VNCConn %p: post_update_notify(%i,%i,%i,%i)"), this, + event.rect.x, + event.rect.y, + event.rect.width, + event.rect.height); + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + +void VNCConn::thread_post_fbresize_notify() +{ + wxLogDebug(wxT("VNCConn %p: post_fbresize_notify() (%i, %i)"), + this, + getFrameBufferWidth(), + getFrameBufferHeight()); + + // new NOTIFY event, we got no window id + wxCommandEvent event(VNCConnFBResizeNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + + +void VNCConn::thread_post_cuttext_notify() +{ + // new NOTIFY event, we got no window id + wxCommandEvent event(VNCConnCuttextNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + + +void VNCConn::thread_post_bell_notify() +{ + // new NOTIFY event, we got no window id + wxCommandEvent event(VNCConnBellNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + + +void VNCConn::thread_post_unimultichanged_notify() +{ + wxCommandEvent event(VNCConnUniMultiChangedNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + +void VNCConn::thread_post_replayfinished_notify() +{ + wxCommandEvent event(VNCConnReplayFinishedNOTIFY, wxID_ANY); + event.SetEventObject(this); // set sender + + // Send it + wxPostEvent((wxEvtHandler*)parent, event); +} + + +char* VNCConn::thread_getpasswd(rfbClient *client) { + VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); + + conn->require_auth = true; + +#if wxUSE_SECRETSTORE + if (!conn->getPassword().IsOk()) { +#else + if (conn->getPassword().IsEmpty()) { +#endif + // get password from user + conn->thread_post_getpasswd_notify(); + // wxMutexes are not recursive under Unix, so test first + if (conn->mutex_auth.TryLock() == wxMUTEX_NO_ERROR) { + conn->mutex_auth.Lock(); + } + wxLogDebug("VNCConn %p: vncthread waiting for password", conn); + conn->condition_auth.Wait(); + wxLogDebug("VNCConn %p: vncthread done waiting for password", conn); + // we get here once setPassword() was called + } +#if wxUSE_SECRETSTORE + return strdup(conn->getPassword().GetAsString().char_str()); +#else + return strdup(conn->getPassword().char_str()); +#endif +}; + + +rfbCredential* VNCConn::thread_getcreds(rfbClient *client, int type) { + VNCConn *conn = VNCConn::getVNCConnFromRfbClient(client); + + conn->require_auth = true; + + if(type == rfbCredentialTypeUser) { + + if(conn->getUserName().IsEmpty() +#if wxUSE_SECRETSTORE + || !conn->getPassword().IsOk()) { +#else + || conn->getPassword().IsEmpty()) { +#endif + // username and/or password needed + conn->thread_post_getcreds_notify(conn->getUserName().IsEmpty()); + // wxMutexes are not recursive under Unix, so test first + if (conn->mutex_auth.TryLock() == wxMUTEX_NO_ERROR) { + conn->mutex_auth.Lock(); + } + wxLogDebug("VNCConn %p: vncthread waiting for credentials", conn); + conn->condition_auth.Wait(); + wxLogDebug("VNCConn %p: vncthread done waiting for credentials", + conn); + // we get here once setPassword() was called + } + + rfbCredential *c = (rfbCredential *)calloc(1, sizeof(rfbCredential)); + c->userCredential.username = strdup(conn->getUserName().char_str()); +#if wxUSE_SECRETSTORE + c->userCredential.password = strdup(conn->getPassword().GetAsString().char_str()); +#else + c->userCredential.password = strdup(conn->getPassword().char_str()); +#endif + return c; + } + + return NULL; +}; + + + +void VNCConn::thread_got_update(rfbClient* client,int x,int y,int w,int h) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); + if(! conn->GetThread()->TestDestroy()) + { + conn->updated_rect.Union(wxRect(x, y, w, h)); + + // single (partial) multicast updates are small, so when a big region is updated, + // the update notify receiver gets flooded, resulting in way too much cpu load. + // thus, when multicasting, we only notify for logic (whole) framebuffer updates. + if(!conn->isMulticast()) + conn->thread_post_update_notify(x, y, w, h); + + if(conn->do_stats) + { + wxCriticalSectionLocker lock(conn->mutex_stats); + + wxRect this_update_rect = wxRect(x,y,w,h); + + // compressed bytes + conn->upd_bytes += conn->cl->bytesRcvd; + conn->upd_bytes += conn->cl->multicastBytesRcvd; + conn->cl->bytesRcvd = conn->cl->multicastBytesRcvd = 0; + + // uncompressed bytes + conn->upd_bytes_inflated += w*h*BYTESPERPIXEL; + + // latency check, rect case + if(conn->latency_test_rect_sent && this_update_rect.Contains(wxRect(LATENCY_TEST_RECT))) + { + conn->latency_stopwatch.Pause(); + conn->latency = conn->latency_stopwatch.Time(); + conn->latency_test_rect_sent = false; + + wxLogDebug(wxT("VNCConn %p: got update containing latency test rect, took %ims"), conn, conn->latency_stopwatch.Time()); + } + } + } +} + + + + +void VNCConn::thread_update_finished(rfbClient* client) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(client, VNCCONN_OBJ_ID); + if(! conn->GetThread()->TestDestroy()) + { + // single (partial) multicast updates are small, so when a big region is updated, + // the update notify receiver gets flooded, resulting in way too much cpu load. + // thus, when multicasting, we only notify for logic (whole) framebuffer updates. + if(conn->isMulticast() && !conn->updated_rect.IsEmpty()) + conn->thread_post_update_notify(conn->updated_rect.x, conn->updated_rect.y, conn->updated_rect.width, conn->updated_rect.height); + + conn->updated_rect = wxRect(); + + if(conn->do_stats) + { + wxCriticalSectionLocker lock(conn->mutex_stats); + conn->upd_count++; + } + } +} + + + +void VNCConn::thread_kbd_leds(rfbClient* cl, int value, int pad) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + wxLogDebug(wxT("VNCConn %p: Led State= 0x%02X"), conn, value); +} + + +void VNCConn::thread_textchat(rfbClient* cl, int value, char *text) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + switch(value) + { + case (int)rfbTextChatOpen: + wxLogDebug(wxT("VNCConn %p: got textchat open\n"), conn); + break; + case (int)rfbTextChatClose: + wxLogDebug(wxT("VNCConn %p: got textchat close\n"), conn); + break; + case (int)rfbTextChatFinished: + wxLogDebug(wxT("VNCConn %p: got textchat finish\n"), conn); + break; + default: + wxLogDebug(wxT("VNCConn %p: got textchat text: '%s'\n"), conn, text); + } +} + + +void VNCConn::thread_got_cuttext(rfbClient *cl, const char *text, int len) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + + wxLogDebug(wxT("VNCConn %p: got Latin1 cuttext: '%s'"), conn, wxString(text, wxCSConv(wxT("iso-8859-1"))).c_str()); + + wxCriticalSectionLocker lock(conn->mutex_cuttext); // since cuttext can also be set from the main thread + conn->cuttext = wxString(text, wxCSConv(wxT("iso-8859-1"))); + conn->thread_post_cuttext_notify(); +} + + +void VNCConn::thread_got_cuttext_utf8(rfbClient *cl, const char *text, int len) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + + wxLogDebug(wxT("VNCConn %p: got UTF-8 cuttext: '%s'"), conn, wxString(text, wxConvUTF8).c_str()); + + wxCriticalSectionLocker lock(conn->mutex_cuttext); // since cuttext can also be set from the main thread + conn->cuttext = wxString(text, wxConvUTF8); + conn->thread_post_cuttext_notify(); +} + + +void VNCConn::thread_bell(rfbClient *cl) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + wxLogDebug(wxT("VNCConn %p: bell"), conn); + conn->thread_post_bell_notify(); +} + + +void VNCConn::thread_handle_xvp(rfbClient *cl, uint8_t ver, uint8_t code) +{ + VNCConn* conn = (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + wxLogDebug(wxT("VNCConn %p: handling xvp msg version %d code %d"), conn, ver, code); + + if(conn->latency_test_xvpmsg_sent && ver == LATENCY_TEST_XVP_VER && code == rfbXvp_Fail) + { + wxCriticalSectionLocker lock(conn->mutex_stats); + conn->latency_stopwatch.Pause(); + conn->latency = conn->latency_stopwatch.Time(); + conn->latency_test_xvpmsg_sent = false; + + wxLogDebug(wxT("VNCConn %p: got latency test xvp message back, took %ims"), conn, conn->latency_stopwatch.Time()); + } +} + + + + +// there's no per-connection log since we cannot find out which client +// called the logger function :-( +wxArrayString VNCConn::log; +wxCriticalSection VNCConn::mutex_log; +bool VNCConn::do_logfile; + +void VNCConn::thread_logger(const char *format, ...) +{ + if(!rfbEnableClientLogging) + return; + + // since we're accessing some global things here from different threads + wxCriticalSectionLocker lock(mutex_log); + + wxChar timebuf[256]; + time_t log_clock; + time(&log_clock); + wxStrftime(timebuf, WXSIZEOF(timebuf), _T("%d/%m/%Y %X "), localtime(&log_clock)); + + // global log string array + va_list args; + wxString wx_format(format, wxConvUTF8); + va_start(args, format); + char msg[1024]; + vsnprintf(msg, 1024, format, args); + log.Add( wxString(timebuf) + wxString(msg, wxConvUTF8)); + va_end(args); + + // global log file + if(do_logfile) + { + FILE* logfile; + wxString logfile_str = LOGFILE; + // delete logfile on program startup + static bool firstrun = 1; + if(firstrun) + { + remove(logfile_str.char_str()); + firstrun = 0; + } + + logfile=fopen(logfile_str.char_str(),"a"); + + va_start(args, format); + fprintf(logfile, "%s", (const char*)wxString(timebuf).mb_str()); + vfprintf(logfile, format, args); + va_end(args); + + fclose(logfile); + } + + // and stderr + va_start(args, format); + fprintf(stderr, "%s", (const char*)wxString(timebuf).mb_str()); + vfprintf(stderr, format, args); + va_end(args); +} + + + + + +/* + public members +*/ + +bool VNCConn::setupClient() +{ + wxLogDebug(wxT("VNCConn %p: setupClient()"), this); + + if(cl) // already set up + { + wxLogDebug(wxT("VNCConn %p: setupClient() already done"), this); + return false; + } + + // this takes (int bitsPerSample,int samplesPerPixel, int bytesPerPixel) + // 5,3,2 and 8,3,4 seem possible + cl=rfbGetClient(BITSPERSAMPLE, SAMPLESPERPIXEL, BYTESPERPIXEL); + + rfbClientSetClientData(cl, VNCCONN_OBJ_ID, this); + + // callbacks + cl->MallocFrameBuffer = thread_alloc_framebuffer; + cl->GotFrameBufferUpdate = thread_got_update; + cl->FinishedFrameBufferUpdate = thread_update_finished; + cl->GetPassword = thread_getpasswd; + cl->GetCredential = thread_getcreds; + cl->HandleKeyboardLedState = thread_kbd_leds; + cl->HandleTextChat = thread_textchat; + cl->GotXCutText = thread_got_cuttext; + cl->GotXCutTextUTF8 = thread_got_cuttext_utf8; + cl->Bell = thread_bell; + cl->HandleXvpMsg = thread_handle_xvp; + + cl->canHandleNewFBSize = TRUE; + cl->connectTimeout = 5; + + return true; +} + + + +void VNCConn::Listen(int port) +{ + wxLogDebug(wxT("VNCConn %p: Listen() port %d"), this, port); + + if(!cl) + setupClient(); + + cl->listenPort = cl->listen6Port = port; + + thread_listenmode = true; + + if( CreateThread() != wxTHREAD_NO_ERROR ) + { + err.Printf(_("Could not create VNC listener thread!")); + Shutdown(); + thread_post_listen_notify(1); //TODO add more error codes + return; + } + + if( GetThread()->Run() != wxTHREAD_NO_ERROR ) + { + err.Printf(_("Could not start VNC listener thread!")); + Shutdown(); + thread_post_listen_notify(1); //TODO add more error codes + return; + } +} + + +void VNCConn::Init(const wxString& host, const wxString& username, +#if wxUSE_SECRETSTORE + const wxSecretValue& password, +#endif + const wxString& encodings, int compresslevel, int quality, bool multicast, int multicast_socketrecvbuf, int multicast_recvbuf) +{ + wxLogDebug("VNCConn %p: Init() host '%s'", this, host); + + if(!cl) + setupClient(); + + if(cl->frameBuffer || (GetThread() && GetThread()->IsRunning())) + { + wxLogDebug(wxT("VNCConn %p: Init() already done. Call Shutdown() first!"), this); + thread_post_init_notify(1); // TODO add more error codes + return; + } + + // reset stats before doing new connection + resetStats(); + + cl->programName = "VNCConn"; + parseHostString(host.mb_str(), 5900, &cl->serverHost, &cl->serverPort); + this->username = username; +#if wxUSE_SECRETSTORE + this->password = password; +#endif + // Support short-form (:0, :1) + if(cl->serverPort < 100) + cl->serverPort += 5900; + + cl->appData.compressLevel = compresslevel; + cl->appData.qualityLevel = quality; + cl->appData.encodingsString = strdup(encodings.mb_str()); + if(multicast) + { + cl->canHandleMulticastVNC = TRUE; + cl->multicastSocketRcvBufSize = multicast_socketrecvbuf*1024; + cl->multicastRcvBufSize = multicast_recvbuf*1024; + multicastratio_stopwatch.Start(); + } + else + cl->canHandleMulticastVNC = FALSE; + + // this is like our main loop + thread_listenmode = false; + if( CreateThread() != wxTHREAD_NO_ERROR ) + { + err.Printf(_("Could not create VNC thread!")); + Shutdown(); + thread_post_init_notify(1); // TODO add more error codes + return; + } + + if( GetThread()->Run() != wxTHREAD_NO_ERROR ) + { + err.Printf(_("Could not start VNC thread!")); + Shutdown(); + thread_post_init_notify(1); // TODO add more error codes + return; + } + + conn_stopwatch.Start(); +} + + + + +void VNCConn::Shutdown() +{ + wxLogDebug(wxT("VNCConn %p: Shutdown()"), this); + + conn_stopwatch.Pause(); + + mutex_auth.Unlock(); + + if(GetThread() && GetThread()->IsRunning()) + { + wxLogDebug(wxT( "VNCConn %p: Shutdown() before vncthread delete"), this); + + GetThread()->Delete(); // this blocks if thread is joinable, i.e. on stack + wxLogDebug(wxT("VNCConn %p: Shutdown() after vncthread delete"), this); + } + + if(cl) + { + wxLogDebug(wxT( "VNCConn %p: Shutdown() closing connection"), this); +#ifdef __WIN32__ + closesocket(cl->sock); +#else + close(cl->sock); +#endif + // this one was strdup'ed before + if(!cl->listenSpecified) + free((void*)cl->appData.encodingsString); + + // in case we called listen, canceled that, and now want to connect to some + // host via Init() + cl->listenSpecified = FALSE; + + if(cl->frameBuffer) + { + free(cl->frameBuffer); + cl->frameBuffer = 0; + } + + rfbClientCleanup(cl); + cl = 0; + } +} + + +void VNCConn::setFastRequest(size_t interval) +{ + fastrequest_interval = interval; +} + +bool VNCConn::setDSCP(uint8_t dscp) +{ + if(cl && cl->sock >= 0) + return SetDSCP(cl->sock, dscp); + else + return false; +} + + +// this simply posts the mouse event into the worker thread's input queue +void VNCConn::sendPointerEvent(wxMouseEvent &event) +{ + if(replaying) + return; + + if(GetThread() && GetThread()->IsRunning()) + pointer_event_q.Post(event); +} + + +// because of the possible wxKeyEvent.Skip(), this posts the found keysym + down +bool VNCConn::sendKeyEvent(wxKeyEvent &event, bool down, bool isChar) +{ + if(replaying) + return false; + + keyEvent kev = {0,0}; + + if(isChar) + { + wxLogDebug(wxT("VNCConn %p: got CHAR key event"), this); + wxLogDebug(wxT("VNCConn %p: wxkeycode: %d"), this, event.GetKeyCode()); + wxLogDebug(wxT("VNCConn %p: wxkeycode char: %c"), this, event.GetKeyCode()); + wxLogDebug(wxT("VNCConn %p: unicode: %d"), this, event.GetUnicodeKey()); + wxLogDebug(wxT("VNCConn %p: unicode char: %c"), this, event.GetUnicodeKey()); + + // get tranlated char + kev.keysym = event.GetKeyCode(); + // if we got ne keycode, try unicodekey + if(kev.keysym==0) + kev.keysym = event.GetUnicodeKey(); + + // if wxwidgets translates a key combination into a + // value below 32, revert this here. + // we dont't send ASCII 0x03, but ctrl and then a 'c'! + if(kev.keysym <= 32) + { + kev.keysym += 96; + wxLogDebug(wxT("VNCConn %p: translating key to: %d"), this, kev.keysym); + } + + wxLogDebug(wxT("VNCConn %p: sending rfbkeysym: 0x%.3x down"), this, kev.keysym); + wxLogDebug(wxT("VNCConn %p: sending rfbkeysym: 0x%.3x up"), this, kev.keysym); + + // down, then up + if(GetThread() && GetThread()->IsRunning()) + { + kev.down = true; + key_event_q.Post(kev); + kev.down = false; + key_event_q.Post(kev); + } + return true; + } + else + { + // lookup keysym + switch(event.GetKeyCode()) + { + case WXK_BACK: kev.keysym = XK_BackSpace; break; + case WXK_TAB: kev.keysym = XK_Tab; break; + case WXK_CLEAR: kev.keysym = XK_Clear; break; + case WXK_RETURN: kev.keysym = XK_Return; break; + case WXK_PAUSE: kev.keysym = XK_Pause; break; + case WXK_ESCAPE: kev.keysym = XK_Escape; break; + case WXK_SPACE: kev.keysym = XK_space; break; + case WXK_DELETE: kev.keysym = XK_Delete; break; + case WXK_NUMPAD0: kev.keysym = XK_KP_0; break; + case WXK_NUMPAD1: kev.keysym = XK_KP_1; break; + case WXK_NUMPAD2: kev.keysym = XK_KP_2; break; + case WXK_NUMPAD3: kev.keysym = XK_KP_3; break; + case WXK_NUMPAD4: kev.keysym = XK_KP_4; break; + case WXK_NUMPAD5: kev.keysym = XK_KP_5; break; + case WXK_NUMPAD6: kev.keysym = XK_KP_6; break; + case WXK_NUMPAD7: kev.keysym = XK_KP_7; break; + case WXK_NUMPAD8: kev.keysym = XK_KP_8; break; + case WXK_NUMPAD9: kev.keysym = XK_KP_9; break; + case WXK_NUMPAD_DECIMAL: kev.keysym = XK_KP_Decimal; break; + case WXK_NUMPAD_DIVIDE: kev.keysym = XK_KP_Divide; break; + case WXK_NUMPAD_MULTIPLY: kev.keysym = XK_KP_Multiply; break; + case WXK_NUMPAD_SUBTRACT: kev.keysym = XK_KP_Subtract; break; + case WXK_NUMPAD_ADD: kev.keysym = XK_KP_Add; break; + case WXK_NUMPAD_ENTER: kev.keysym = XK_KP_Enter; break; + case WXK_NUMPAD_EQUAL: kev.keysym = XK_KP_Equal; break; + case WXK_UP: kev.keysym = XK_Up; break; + case WXK_DOWN: kev.keysym = XK_Down; break; + case WXK_RIGHT: kev.keysym = XK_Right; break; + case WXK_LEFT: kev.keysym = XK_Left; break; + case WXK_INSERT: kev.keysym = XK_Insert; break; + case WXK_HOME: kev.keysym = XK_Home; break; + case WXK_END: kev.keysym = XK_End; break; + case WXK_PAGEUP: kev.keysym = XK_Page_Up; break; + case WXK_PAGEDOWN: kev.keysym = XK_Page_Down; break; + case WXK_F1: kev.keysym = XK_F1; break; + case WXK_F2: kev.keysym = XK_F2; break; + case WXK_F3: kev.keysym = XK_F3; break; + case WXK_F4: kev.keysym = XK_F4; break; + case WXK_F5: kev.keysym = XK_F5; break; + case WXK_F6: kev.keysym = XK_F6; break; + case WXK_F7: kev.keysym = XK_F7; break; + case WXK_F8: kev.keysym = XK_F8; break; + case WXK_F9: kev.keysym = XK_F9; break; + case WXK_F10: kev.keysym = XK_F10; break; + case WXK_F11: kev.keysym = XK_F11; break; + case WXK_F12: kev.keysym = XK_F12; break; + case WXK_F13: kev.keysym = XK_F13; break; + case WXK_F14: kev.keysym = XK_F14; break; + case WXK_F15: kev.keysym = XK_F15; break; + case WXK_NUMLOCK: kev.keysym = XK_Num_Lock; break; + case WXK_CAPITAL: kev.keysym = XK_Caps_Lock; break; + case WXK_SCROLL: kev.keysym = XK_Scroll_Lock; break; + //case WXK_RSHIFT: kev.keysym = XK_Shift_R; break; + case WXK_SHIFT: kev.keysym = XK_Shift_L; break; + //case WXK_RCTRL: kev.keysym = XK_Control_R; break; + case WXK_CONTROL: kev.keysym = XK_Control_L; break; + // case WXK_RALT: kev.keysym = XK_Alt_R; break; + case WXK_ALT: kev.keysym = XK_Alt_L; break; + // case WXK_RMETA: kev.keysym = XK_Meta_R; break; + // case WXK_META: kev.keysym = XK_Meta_L; break; + case WXK_WINDOWS_LEFT: kev.keysym = XK_Super_L; break; + case WXK_WINDOWS_RIGHT: kev.keysym = XK_Super_R; break; + //case WXK_COMPOSE: kev.keysym = XK_Compose; break; + //case WXK_MODE: kev.keysym = XK_Mode_switch; break; + case WXK_HELP: kev.keysym = XK_Help; break; + case WXK_PRINT: kev.keysym = XK_Print; break; + //case WXK_SYSREQ: kev.keysym = XK_Sys_Req; break; + case WXK_CANCEL: kev.keysym = XK_Break; break; + default: break; + } + + if(kev.keysym) + { + wxLogDebug(wxT("VNCConn %p: got key %s event:"), this, down ? wxT("down") : wxT("up")); + wxLogDebug(wxT("VNCConn %p: wxkeycode: %d"), this, event.GetKeyCode()); + wxLogDebug(wxT("VNCConn %p: wxkeycode char: %c"), this, event.GetKeyCode()); + wxLogDebug(wxT("VNCConn %p: unicode: %d"), this, event.GetUnicodeKey()); + wxLogDebug(wxT("VNCConn %p: unicode char: %c"), this, event.GetUnicodeKey()); + wxLogDebug(wxT("VNCConn %p: sending rfbkeysym: 0x%.3x %s"), this, kev.keysym, down ? wxT("down") : wxT("up")); + + kev.down = down; + if(GetThread() && GetThread()->IsRunning()) + key_event_q.Post(kev); + return true; + } + else + { + // nothing of the above? + // then propagate this event through the EVT_CHAR handler + event.Skip(); + return false; + } + } + + wxLogDebug(wxT("VNCConn %p: no matching keysym found"), this); + return false; + +} + + + + + +void VNCConn::doStats(bool yesno) +{ + do_stats = yesno; + wxCriticalSectionLocker lock(mutex_stats); + if(do_stats) + { + stats_timer.Start(1000); + latency_test_rect_sent = latency_test_xvpmsg_sent = false; // to start sending one + } + else + stats_timer.Stop(); +} + + +void VNCConn::resetStats() +{ + wxCriticalSectionLocker lock(mutex_stats); + statistics.Clear(); +} + + + + +/* + user input record/replay stuff + */ + bool VNCConn::replayUserInputStart(wxArrayString src, bool loop) +{ + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(!recording) + { + recordreplay_stopwatch.Start(); + userinput_pos = 0; + userinput = src; + replay_loop = loop; + replaying = true; + return true; + } + return false; +} + + +bool VNCConn::replayUserInputStop() +{ + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(replaying) + { + recordreplay_stopwatch.Pause(); + userinput.Clear(); + replaying = false; + return true; + } + return false; +} + + +bool VNCConn::recordUserInputStart() +{ + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(!replaying) + { + recordreplay_stopwatch.Start(); + userinput_pos = 0; + userinput.Clear(); + recording = true; + return true; + } + return false; +} + + +bool VNCConn::recordUserInputStop(wxArrayString &dst) +{ + wxCriticalSectionLocker lock(mutex_recordreplay); + + if(recording) + { + recordreplay_stopwatch.Pause(); + recording = false; + dst = userinput; // copy over + return true; + } + return false; +} + + + + +/* + we could use a wxBitmap directly as the framebuffer, thus being more efficient + BUT: + - win32 expects DIB data in BGRA, not RGBA (can be solved by requesting another pixel format) + - win32 DIBs expect data from bottom to top (this means we HAVE to use a self made + framebuffer -> wxBitmap function instead of GetSubBitmap().) + - direct data access of a wxBitmap on Mac OS X does not seem to work + INSTEAD: + - we have an ordinary char array as framebuffer and copy the requested content into a + wxBitmap (the return of its copy is cheap cause wxBitmaps use copy-on-write) + */ +wxBitmap VNCConn::getFrameBufferRegion(const wxRect& rect) const +{ + // sanity check requested region + if(rect.x < 0 || rect.x + rect.width > getFrameBufferWidth() + || rect.y < 0 || rect.y + rect.height > getFrameBufferHeight()) + return wxBitmap(); + + /* + copy directly from framebuffer into a new bitmap + */ + + wxBitmap region(rect.width, rect.height, cl->format.bitsPerPixel); + wxAlphaPixelData region_data(region); + wxAlphaPixelData::Iterator region_it(region_data); + + int bytesPerPixel = cl->format.bitsPerPixel/8; + uint8_t *fbsub_it = cl->frameBuffer + rect.y*cl->width*bytesPerPixel + rect.x*bytesPerPixel; + + for( int y = 0; y < rect.height; ++y ) + { + wxAlphaPixelData::Iterator region_it_rowStart = region_it; + uint8_t *fbsub_it_rowStart = fbsub_it; + + for( int x = 0; x < rect.width; ++x, ++region_it, fbsub_it += bytesPerPixel) + { + region_it.Red() = *(fbsub_it+0); + region_it.Green() = *(fbsub_it+1); + region_it.Blue() = *(fbsub_it+2); + region_it.Alpha() = 255; // 255 is opaque, libvncclient always sets this byte to 0 + } + + // CR + region_it = region_it_rowStart; + fbsub_it = fbsub_it_rowStart; + + // LF + region_it.OffsetY(region_data, 1); + fbsub_it += cl->width * bytesPerPixel; + } + + return region; +} + + + + +bool VNCConn::getFrameBufferRegion(const wxRect& rect, wxBitmap& dst) const +{ + // sanity check requested region against framebuffer + if(rect.x < 0 || rect.x + rect.width > getFrameBufferWidth() + || rect.y < 0 || rect.y + rect.height > getFrameBufferHeight()) + return false; + + // check dst against framebuffer + if(dst.GetWidth() != getFrameBufferWidth() || dst.GetHeight() != getFrameBufferHeight()) + return false; + + /* + copy directly from framebuffer into the destination bitmap + */ + wxAlphaPixelData dst_data(dst); + wxAlphaPixelData::Iterator dst_it(dst_data); + dst_it.Offset(dst_data, rect.x, rect.y); + + int bytesPerPixel = cl->format.bitsPerPixel/8; + uint8_t *fbsub_it = cl->frameBuffer + rect.y*cl->width*bytesPerPixel + rect.x*bytesPerPixel; + + for( int y = 0; y < rect.height; ++y ) + { + wxAlphaPixelData::Iterator dst_it_rowStart = dst_it; + uint8_t *fbsub_it_rowStart = fbsub_it; + + for( int x = 0; x < rect.width; ++x, ++dst_it, fbsub_it += bytesPerPixel) + { + dst_it.Red() = *(fbsub_it+0); + dst_it.Green() = *(fbsub_it+1); + dst_it.Blue() = *(fbsub_it+2); + dst_it.Alpha() = 255; // 255 is opaque, libvncclient always sets this byte to 0 + } + + // CR + dst_it = dst_it_rowStart; + fbsub_it = fbsub_it_rowStart; + + // LF + dst_it.OffsetY(dst_data, 1); + fbsub_it += cl->width * bytesPerPixel; + } + + return true; +} + + + +int VNCConn::getFrameBufferWidth() const +{ + if(cl) + return cl->width; + else + return 0; +} + + +int VNCConn::getFrameBufferHeight() const +{ + if(cl) + return cl->height; + else + return 0; +} + + +int VNCConn::getFrameBufferDepth() const +{ + if(cl) + return cl->format.bitsPerPixel; + else + return 0; +} + + + + +wxString VNCConn::getDesktopName() const +{ + if(cl) + return wxString(cl->desktopName, wxConvUTF8); + else + return wxEmptyString; +} + + +const wxString& VNCConn::getUserName() const { + return username; +} + +void VNCConn::setUserName(const wxString& username) { + this->username = username; +} + +#if wxUSE_SECRETSTORE +const wxSecretValue& VNCConn::getPassword() const { +#else +const wxString& VNCConn::getPassword() const { +#endif + return password; +} + +#if wxUSE_SECRETSTORE +void VNCConn::setPassword(const wxSecretValue& password) { +#else +void VNCConn::setPassword(const wxString& password) { +#endif + this->password = password; + // tell worker thread to go on + condition_auth.Signal(); +} + + +const bool VNCConn::getRequireAuth() const { + return require_auth; +} + + +wxString VNCConn::getServerHost() const +{ + if(cl) + { + if(cl->listenSpecified) + return wxEmptyString; + else + return wxString(cl->serverHost, wxConvUTF8); + } + else + return wxEmptyString; +} + + + +wxString VNCConn::getServerPort() const +{ + if(cl) + return wxString() << cl->serverPort; + else + return wxEmptyString; +} + + + +wxString VNCConn::getListenPort() const +{ + if(cl && cl->listenSpecified) + return wxString() << cl->listenPort; + else + return wxEmptyString; +} + + + +bool VNCConn::isMulticast() const +{ + if(cl && cl->multicastSock >= 0 && !cl->multicastDisabled) + return true; + else + return false; +} + + +double VNCConn::getMCNACKedRatio() +{ + wxCriticalSectionLocker lock(mutex_multicastratio); + double retval = 0; + int samples = 0; + std::deque::const_iterator it; + for(it = multicastNACKedRatios.begin(); it != multicastNACKedRatios.end(); ++it) + if(*it >= 0) // valid value + { + retval += *it; + ++samples; + } + + if(samples) + return retval/samples; + else + return -1; +} + + +double VNCConn::getMCLossRatio() +{ + wxCriticalSectionLocker lock(mutex_multicastratio); + double retval = 0; + int samples = 0; + std::deque::const_iterator it; + for(it = multicastLossRatios.begin(); it != multicastLossRatios.end(); ++it) + if(*it >= 0) // valid value + { + retval += *it; + ++samples; + } + + if(samples) + return retval/samples; + else + return -1; +} + + +void VNCConn::clearLog() +{ + // since we're accessing some global things here from different threads + wxCriticalSectionLocker lock(mutex_log); + log.Clear(); +} + + + +// get the OS-dependent socket receive buffer size in KByte. +// max returned value is 32MB, negative values on error +int VNCConn::getMaxSocketRecvBufSize() +{ + int sock; + +#ifdef WIN32 + WSADATA trash; + WSAStartup(MAKEWORD(2,0),&trash); +#endif + + // create the test socket + if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return -1; + + // try with some high value and see what we get + int recv_buf_try = 33554432; // 32 MB + // This is needed on Linux to see what the maximum value is but errors on MacOS, so we + // simply treat an error here as non-fatal. + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&recv_buf_try, sizeof(recv_buf_try)); + + int recv_buf_got = -1; + socklen_t recv_buf_got_len = sizeof(recv_buf_got); + if(getsockopt(sock, SOL_SOCKET, SO_RCVBUF,(char*)&recv_buf_got, &recv_buf_got_len) <0) + { + rfbCloseSocket(sock); + return -2; + } + + rfbCloseSocket(sock); + + return recv_buf_got/1024; +} + + +/* + parse ipv4 or ipv6 address string. + taken from remmina, thanks! +*/ +void VNCConn::parseHostString(const char *server, int defaultport, char **host, int *port) +{ + char *str, *ptr, *ptr2; + + str = strdup(server); + + /* [server]:port format */ + ptr = strchr(str, '['); + if (ptr) + { + ptr++; + ptr2 = strchr(ptr, ']'); + if (ptr2) + *ptr2++ = '\0'; + if (*ptr2 == ':') + defaultport = atoi(ptr2 + 1); + if (host) + *host = strdup(ptr); + if (port) + *port = defaultport; + free(str); + return; + } + + /* server:port format, IPv6 cannot use this format */ + ptr = strchr(str, ':'); + if (ptr) + { + ptr2 = strchr(ptr + 1, ':'); + if (ptr2 == NULL) + { + *ptr++ = '\0'; + defaultport = atoi(ptr); + } + /* More than one ':' means this is IPv6 address. Treat it as a whole address */ + } + if (host) + *host = str; + if (port) + *port = defaultport; +} + + +VNCConn* VNCConn::getVNCConnFromRfbClient(rfbClient *cl) { + + return (VNCConn*) rfbClientGetClientData(cl, VNCCONN_OBJ_ID); + +} diff --git a/src/VNCConn.h b/src/VNCConn.h index 606c1126..7d116a5f 100644 --- a/src/VNCConn.h +++ b/src/VNCConn.h @@ -1,364 +1,364 @@ -// -*- C++ -*- -/* - VNCConn.h: VNC connection class API definition. - - This file is part of MultiVNC, a multicast-enabled crossplatform - VNC viewer. - - Copyright (C) 2009, 2010 Christian Beier - - MultiVNC is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MultiVNC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - -#ifndef VNCCONN_H -#define VNCCONN_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "rfb/rfbclient.h" - - - -/* - custom events -*/ -/// Sent when Listen() completes with success(listening, m_commandInt==0) or failure (m_commandInt!=0) -DECLARE_EVENT_TYPE(VNCConnListenNOTIFY, -1) -/// Sent when Init() completes with success(connection established, m_commandInt==0) or failure (m_commandInt!=0) -DECLARE_EVENT_TYPE(VNCConnInitNOTIFY, -1) -/// Sent when this VNCConn wants a password. This blocks the VNCConn's internal worker thread until SetPassword() is called! -wxDECLARE_EVENT(VNCConnGetPasswordNOTIFY, wxCommandEvent); -/// Sent when this VNCConn wants a username and password. This blocks the VNCConn's internal worker thread until SetPassword() is called! -wxDECLARE_EVENT(VNCConnGetCredentialsNOTIFY, wxCommandEvent); -// sent when an incoming connection is available -DECLARE_EVENT_TYPE(VNCConnIncomingConnectionNOTIFY, -1) -// sent on disconnect -DECLARE_EVENT_TYPE(VNCConnDisconnectNOTIFY, -1) -// sent on framebuffer resize, get new size via getFrameBufferWidth/Height() -DECLARE_EVENT_TYPE(VNCConnFBResizeNOTIFY, -1) -// sent when new cuttext is available -DECLARE_EVENT_TYPE(VNCConnCuttextNOTIFY, -1) -// sent when bell message received -DECLARE_EVENT_TYPE(VNCConnBellNOTIFY, -1) -// sent framebuffer update, event's rect is set to region -DECLARE_EVENT_TYPE(VNCConnUpdateNOTIFY, -1) -// sent when status changes from/to uni/-multicast. -// get current state via isMulticast() -DECLARE_EVENT_TYPE(VNCConnUniMultiChangedNOTIFY, -1) -// sent when userinput replay finished -DECLARE_EVENT_TYPE(VNCConnReplayFinishedNOTIFY, -1) - - -/** - To make a listening connection, call Listen(). - You'll be informed by a VNCConnListenNOTIFY event about the outcome. - You'll get a VNCConnIncomingConnectionNOTIFY event when a connection is made from the outside; - in this case, call Init() with empty host and port to finalise the connection to the remote. - - To make an outgoing connection, call Init(). - You'll be informed by a VNCConnInitNOTIFY event about the outcome. - You'll get a VNCConnDisconnectNOTIFY when the connection is unexpectedly terminated. - - To shut down a connection, call Shutdown(). - */ -class VNCConn: public wxEvtHandler, public wxThreadHelper -{ -public: - VNCConn(void *parent); - ~VNCConn(); - - void Listen(int port); - void Init(const wxString& host, const wxString& username, -#if wxUSE_SECRETSTORE - const wxSecretValue& password, -#endif - const wxString& encodings, int compresslevel = 1, int quality = 5, bool multicast = true, int multicastSocketRecvBuf = 5120, int multicastRecvBuf = 5120); - void Shutdown(); - - - /* - This is for usage on high latency links: Keep asking for framebuffer - updates every 'interval' ms instead of asking after every received - server message. - 0 to disable. default: disabled - */ - void setFastRequest(size_t interval); - - /* - enables marking the DSCP/Traffic Class of outgoing IP/IPv6 packets - */ - bool setDSCP(uint8_t dscp); - - // get kind of VNCConn - bool isReverse() const { return cl ? cl->listenSpecified : false; }; - bool isMulticast() const; - - // send events - void sendPointerEvent(wxMouseEvent &event); - bool sendKeyEvent(wxKeyEvent &event, bool down, bool isChar); - - // toggle statistics, default is off - void doStats(bool yesno); - // this clears internal statistics - void resetStats(); - // get stats, format is described in first line - const wxArrayString& getStats() const { const wxArrayString& ref = statistics; return ref; }; - - /* - replay/record user interaction - */ - bool replayUserInputStart(wxArrayString src, bool loop); // copies in src and plays it - bool replayUserInputStop(); - bool recordUserInputStart(); - bool recordUserInputStop(wxArrayString& dst); // if ok, copies recorded input to dst - bool isReplaying() { return replaying; }; - bool isRecording() { return recording; }; - - - // cuttext - const wxString& getCuttext() const { const wxString& ref = cuttext; return ref; }; - void setCuttext(const wxString& text) { wxCriticalSectionLocker lock(mutex_cuttext); cuttext = text; }; - - // returns a wxBitmap (this uses COW, so is okay) - wxBitmap getFrameBufferRegion(const wxRect& region) const; - // writes requested region directly into dst bitmap which must have the same dimensions as the framebuffer - bool getFrameBufferRegion(const wxRect& rect, wxBitmap& dst) const; - int getFrameBufferWidth() const; - int getFrameBufferHeight() const; - int getFrameBufferDepth() const; - - wxString getDesktopName() const; - wxString getServerHost() const; - wxString getServerPort() const; - wxString getListenPort() const; - - const wxString& getUserName() const; - void setUserName(const wxString& username); -#if wxUSE_SECRETSTORE - const wxSecretValue& getPassword() const; - void setPassword(const wxSecretValue& password); -#else - const wxString& getPassword() const; - void setPassword(const wxString& password); -#endif - const bool getRequireAuth() const; - - - // get current multicast receive buf state - int getMCBufSize() const { if(cl) return cl->multicastRcvBufSize; else return 0; }; - int getMCBufFill() const { if(cl) return cl->multicastRcvBufLen; else return 0; }; - // returns average (over last few seconds) NACKed ratio or -1 if there was nothing to be measured - double getMCNACKedRatio(); - // returns average (over last few seconds) loss ratio or -1 if there was nothing to be measured - double getMCLossRatio(); - - // get error string - const wxString& getErr() const { const wxString& ref = err; return ref; }; - // get global log string - static const wxArrayString& getLog() { const wxArrayString& ref = log; return ref; }; - static void clearLog(); - static void doLogfile(bool yesno) { do_logfile = yesno; }; - - static int getMaxSocketRecvBufSize(); - - /** - Returns the VNCConn handling the given rfbClient. - */ - static VNCConn* getVNCConnFromRfbClient(rfbClient *cl); - -protected: - // thread execution starts here - virtual wxThread::ExitCode Entry(); - - DECLARE_EVENT_TABLE(); - -private: - void *parent; - - rfbClient* cl; - bool setupClient(); - - wxRect updated_rect; - - int multicastStatus; - std::deque multicastNACKedRatios; - std::deque multicastLossRatios; -#define MULTICAST_RATIO_SAMPLES 10 // we are averaging over this many seconds - wxCriticalSection mutex_multicastratio; // the fifos above are read by both the VNC and the GUI thread - wxStopWatch multicastratio_stopwatch; - - -#ifdef LIBVNCSERVER_WITH_CLIENT_TLS - static bool TLS_threading_initialized; -#endif - - // this counts the ms since Init() - wxStopWatch conn_stopwatch; - - // fastrequest stuff - size_t fastrequest_interval; - wxStopWatch fastrequest_stopwatch; - - // this contains cuttext we received or should send - wxString cuttext; - wxCriticalSection mutex_cuttext; - - // credentials - wxString username; -#if wxUSE_SECRETSTORE - wxSecretValue password; -#else - wxString password; -#endif - bool require_auth; - wxMutex mutex_auth; - wxCondition condition_auth; - - // statistics - bool do_stats; - wxTimer stats_timer; // a timer that samples statistics every second - void on_stats_timer(wxTimerEvent& event); - wxCriticalSection mutex_stats; - wxArrayString statistics; - // counts received (probably compressed) bytes of updates - int upd_bytes; - // counts uncompressed bytes of updates - int upd_bytes_inflated; - // counts updates - int upd_count; - // check latency by isueing an xvp request with some unsupported version -#define LATENCY_TEST_XVP_VER 42 - bool latency_test_xvpmsg_sent; - // when xvp is not available, check latency by requesting a certain test rect as non-incremental -#define LATENCY_TEST_RECT 0,0,1,1 - bool latency_test_rect_sent; - bool latency_test_trigger; - wxStopWatch latency_stopwatch; - int latency; - - // record/replay stuff - wxArrayString userinput; - size_t userinput_pos; - wxStopWatch recordreplay_stopwatch; - bool replaying; - bool replay_loop; - bool recording; - wxCriticalSection mutex_recordreplay; - - // per-connection error string - wxString err; - - // global libvcnclient log stuff - // there's no per-connection log since we cannot find out which client - // called the logger function :-( - static wxArrayString log; - static wxCriticalSection mutex_log; - static bool do_logfile; - - // utility functions - static void parseHostString(const char *server, int defaultport, char **host, int *port); - - // messagequeues for posting events to the worker thread - typedef wxMouseEvent pointerEvent; - struct keyEvent - { - rfbKeySym keysym; - bool down; - }; - wxMessageQueue pointer_event_q; - wxMessageQueue key_event_q; - - bool thread_listenmode; - bool thread_send_pointer_event(pointerEvent &event); - bool thread_send_key_event(keyEvent &event); - bool thread_send_latency_probe(); - - - // event dispatchers - void thread_post_listen_notify(int error); - void thread_post_init_notify(int error); - void thread_post_getpasswd_notify(); - void thread_post_getcreds_notify(bool withUserPrompt); - void thread_post_incomingconnection_notify(); - void thread_post_disconnect_notify(); - void thread_post_update_notify(int x, int y, int w, int h); - void thread_post_fbresize_notify(); - void thread_post_cuttext_notify(); - void thread_post_bell_notify(); - void thread_post_unimultichanged_notify(); - void thread_post_replayfinished_notify(); - - // libvncclient callbacks - static rfbBool thread_alloc_framebuffer(rfbClient* client); - static void thread_got_update(rfbClient* cl,int x,int y,int w,int h); - static void thread_update_finished(rfbClient* client); - static void thread_kbd_leds(rfbClient* cl, int value, int pad); - static void thread_textchat(rfbClient* cl, int value, char *text); - static void thread_got_cuttext(rfbClient *cl, const char *text, int len); - static void thread_got_cuttext_utf8(rfbClient *cl, const char *text, int len); - static void thread_bell(rfbClient *cl); - static void thread_handle_xvp(rfbClient *cl, uint8_t ver, uint8_t code); - static void thread_logger(const char *format, ...); - static char* thread_getpasswd(rfbClient* client); - static rfbCredential* thread_getcreds(rfbClient* client, int type); -}; - - - - -// the custom VNCConnUpdateNotifyEvent -class VNCConnUpdateNotifyEvent: public wxCommandEvent -{ -public: - wxRect rect; - - VNCConnUpdateNotifyEvent(wxEventType commandType = VNCConnUpdateNOTIFY, int id = 0 ) - : wxCommandEvent(commandType, id) { } - - // You *must* copy here the data to be transported - VNCConnUpdateNotifyEvent( const VNCConnUpdateNotifyEvent &event ) - : wxCommandEvent(event) { this->rect = event.rect; } - - // Required for sending with wxPostEvent() - wxEvent* Clone() const { return new VNCConnUpdateNotifyEvent(*this); } - }; - - -// This #define simplifies the one below, and makes the syntax less -// ugly if you want to use Connect() instead of an event table. -typedef void (wxEvtHandler::*VNCConnUpdateNotifyEventFunction)(VNCConnUpdateNotifyEvent &); -#define VNCConnUpdateNotifyEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) \ - wxStaticCastEvent(VNCConnUpdateNotifyEventFunction, &func) - -// Define the event table entry. Yes, it really *does* end in a comma. -#define EVT_VNCCONNUPDATENOTIFY(id, fn) \ - DECLARE_EVENT_TABLE_ENTRY(VNCConnUpdateNOTIFY, id, wxID_ANY, \ - (wxObjectEventFunction)(wxEventFunction) \ - (wxCommandEventFunction) \ - wxStaticCastEvent(VNCConnUpdateNotifyEventFunction, &fn ), (wxObject*) NULL ), - - - - -#endif +// -*- C++ -*- +/* + VNCConn.h: VNC connection class API definition. + + This file is part of MultiVNC, a multicast-enabled crossplatform + VNC viewer. + + Copyright (C) 2009, 2010 Christian Beier + + MultiVNC is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + MultiVNC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef VNCCONN_H +#define VNCCONN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rfb/rfbclient.h" + + + +/* + custom events +*/ +/// Sent when Listen() completes with success(listening, m_commandInt==0) or failure (m_commandInt!=0) +DECLARE_EVENT_TYPE(VNCConnListenNOTIFY, -1) +/// Sent when Init() completes with success(connection established, m_commandInt==0) or failure (m_commandInt!=0) +DECLARE_EVENT_TYPE(VNCConnInitNOTIFY, -1) +/// Sent when this VNCConn wants a password. This blocks the VNCConn's internal worker thread until SetPassword() is called! +wxDECLARE_EVENT(VNCConnGetPasswordNOTIFY, wxCommandEvent); +/// Sent when this VNCConn wants a username and password. This blocks the VNCConn's internal worker thread until SetPassword() is called! +wxDECLARE_EVENT(VNCConnGetCredentialsNOTIFY, wxCommandEvent); +// sent when an incoming connection is available +DECLARE_EVENT_TYPE(VNCConnIncomingConnectionNOTIFY, -1) +// sent on disconnect +DECLARE_EVENT_TYPE(VNCConnDisconnectNOTIFY, -1) +// sent on framebuffer resize, get new size via getFrameBufferWidth/Height() +DECLARE_EVENT_TYPE(VNCConnFBResizeNOTIFY, -1) +// sent when new cuttext is available +DECLARE_EVENT_TYPE(VNCConnCuttextNOTIFY, -1) +// sent when bell message received +DECLARE_EVENT_TYPE(VNCConnBellNOTIFY, -1) +// sent framebuffer update, event's rect is set to region +DECLARE_EVENT_TYPE(VNCConnUpdateNOTIFY, -1) +// sent when status changes from/to uni/-multicast. +// get current state via isMulticast() +DECLARE_EVENT_TYPE(VNCConnUniMultiChangedNOTIFY, -1) +// sent when userinput replay finished +DECLARE_EVENT_TYPE(VNCConnReplayFinishedNOTIFY, -1) + + +/** + To make a listening connection, call Listen(). + You'll be informed by a VNCConnListenNOTIFY event about the outcome. + You'll get a VNCConnIncomingConnectionNOTIFY event when a connection is made from the outside; + in this case, call Init() with empty host and port to finalise the connection to the remote. + + To make an outgoing connection, call Init(). + You'll be informed by a VNCConnInitNOTIFY event about the outcome. + You'll get a VNCConnDisconnectNOTIFY when the connection is unexpectedly terminated. + + To shut down a connection, call Shutdown(). + */ +class VNCConn: public wxEvtHandler, public wxThreadHelper +{ +public: + VNCConn(void *parent); + ~VNCConn(); + + void Listen(int port); + void Init(const wxString& host, const wxString& username, +#if wxUSE_SECRETSTORE + const wxSecretValue& password, +#endif + const wxString& encodings, int compresslevel = 1, int quality = 5, bool multicast = true, int multicastSocketRecvBuf = 5120, int multicastRecvBuf = 5120); + void Shutdown(); + + + /* + This is for usage on high latency links: Keep asking for framebuffer + updates every 'interval' ms instead of asking after every received + server message. + 0 to disable. default: disabled + */ + void setFastRequest(size_t interval); + + /* + enables marking the DSCP/Traffic Class of outgoing IP/IPv6 packets + */ + bool setDSCP(uint8_t dscp); + + // get kind of VNCConn + bool isReverse() const { return cl ? cl->listenSpecified : false; }; + bool isMulticast() const; + + // send events + void sendPointerEvent(wxMouseEvent &event); + bool sendKeyEvent(wxKeyEvent &event, bool down, bool isChar); + + // toggle statistics, default is off + void doStats(bool yesno); + // this clears internal statistics + void resetStats(); + // get stats, format is described in first line + const wxArrayString& getStats() const { const wxArrayString& ref = statistics; return ref; }; + + /* + replay/record user interaction + */ + bool replayUserInputStart(wxArrayString src, bool loop); // copies in src and plays it + bool replayUserInputStop(); + bool recordUserInputStart(); + bool recordUserInputStop(wxArrayString& dst); // if ok, copies recorded input to dst + bool isReplaying() { return replaying; }; + bool isRecording() { return recording; }; + + + // cuttext + const wxString& getCuttext() const { const wxString& ref = cuttext; return ref; }; + void setCuttext(const wxString& text) { wxCriticalSectionLocker lock(mutex_cuttext); cuttext = text; }; + + // returns a wxBitmap (this uses COW, so is okay) + wxBitmap getFrameBufferRegion(const wxRect& region) const; + // writes requested region directly into dst bitmap which must have the same dimensions as the framebuffer + bool getFrameBufferRegion(const wxRect& rect, wxBitmap& dst) const; + int getFrameBufferWidth() const; + int getFrameBufferHeight() const; + int getFrameBufferDepth() const; + + wxString getDesktopName() const; + wxString getServerHost() const; + wxString getServerPort() const; + wxString getListenPort() const; + + const wxString& getUserName() const; + void setUserName(const wxString& username); +#if wxUSE_SECRETSTORE + const wxSecretValue& getPassword() const; + void setPassword(const wxSecretValue& password); +#else + const wxString& getPassword() const; + void setPassword(const wxString& password); +#endif + const bool getRequireAuth() const; + + + // get current multicast receive buf state + int getMCBufSize() const { if(cl) return cl->multicastRcvBufSize; else return 0; }; + int getMCBufFill() const { if(cl) return cl->multicastRcvBufLen; else return 0; }; + // returns average (over last few seconds) NACKed ratio or -1 if there was nothing to be measured + double getMCNACKedRatio(); + // returns average (over last few seconds) loss ratio or -1 if there was nothing to be measured + double getMCLossRatio(); + + // get error string + const wxString& getErr() const { const wxString& ref = err; return ref; }; + // get global log string + static const wxArrayString& getLog() { const wxArrayString& ref = log; return ref; }; + static void clearLog(); + static void doLogfile(bool yesno) { do_logfile = yesno; }; + + static int getMaxSocketRecvBufSize(); + + /** + Returns the VNCConn handling the given rfbClient. + */ + static VNCConn* getVNCConnFromRfbClient(rfbClient *cl); + +protected: + // thread execution starts here + virtual wxThread::ExitCode Entry(); + + DECLARE_EVENT_TABLE(); + +private: + void *parent; + + rfbClient* cl; + bool setupClient(); + + wxRect updated_rect; + + int multicastStatus; + std::deque multicastNACKedRatios; + std::deque multicastLossRatios; +#define MULTICAST_RATIO_SAMPLES 10 // we are averaging over this many seconds + wxCriticalSection mutex_multicastratio; // the fifos above are read by both the VNC and the GUI thread + wxStopWatch multicastratio_stopwatch; + + +#ifdef LIBVNCSERVER_WITH_CLIENT_TLS + static bool TLS_threading_initialized; +#endif + + // this counts the ms since Init() + wxStopWatch conn_stopwatch; + + // fastrequest stuff + size_t fastrequest_interval; + wxStopWatch fastrequest_stopwatch; + + // this contains cuttext we received or should send + wxString cuttext; + wxCriticalSection mutex_cuttext; + + // credentials + wxString username; +#if wxUSE_SECRETSTORE + wxSecretValue password; +#else + wxString password; +#endif + bool require_auth; + wxMutex mutex_auth; + wxCondition condition_auth; + + // statistics + bool do_stats; + wxTimer stats_timer; // a timer that samples statistics every second + void on_stats_timer(wxTimerEvent& event); + wxCriticalSection mutex_stats; + wxArrayString statistics; + // counts received (probably compressed) bytes of updates + int upd_bytes; + // counts uncompressed bytes of updates + int upd_bytes_inflated; + // counts updates + int upd_count; + // check latency by isueing an xvp request with some unsupported version +#define LATENCY_TEST_XVP_VER 42 + bool latency_test_xvpmsg_sent; + // when xvp is not available, check latency by requesting a certain test rect as non-incremental +#define LATENCY_TEST_RECT 0,0,1,1 + bool latency_test_rect_sent; + bool latency_test_trigger; + wxStopWatch latency_stopwatch; + int latency; + + // record/replay stuff + wxArrayString userinput; + size_t userinput_pos; + wxStopWatch recordreplay_stopwatch; + bool replaying; + bool replay_loop; + bool recording; + wxCriticalSection mutex_recordreplay; + + // per-connection error string + wxString err; + + // global libvcnclient log stuff + // there's no per-connection log since we cannot find out which client + // called the logger function :-( + static wxArrayString log; + static wxCriticalSection mutex_log; + static bool do_logfile; + + // utility functions + static void parseHostString(const char *server, int defaultport, char **host, int *port); + + // messagequeues for posting events to the worker thread + typedef wxMouseEvent pointerEvent; + struct keyEvent + { + rfbKeySym keysym; + bool down; + }; + wxMessageQueue pointer_event_q; + wxMessageQueue key_event_q; + + bool thread_listenmode; + bool thread_send_pointer_event(pointerEvent &event); + bool thread_send_key_event(keyEvent &event); + bool thread_send_latency_probe(); + + + // event dispatchers + void thread_post_listen_notify(int error); + void thread_post_init_notify(int error); + void thread_post_getpasswd_notify(); + void thread_post_getcreds_notify(bool withUserPrompt); + void thread_post_incomingconnection_notify(); + void thread_post_disconnect_notify(); + void thread_post_update_notify(int x, int y, int w, int h); + void thread_post_fbresize_notify(); + void thread_post_cuttext_notify(); + void thread_post_bell_notify(); + void thread_post_unimultichanged_notify(); + void thread_post_replayfinished_notify(); + + // libvncclient callbacks + static rfbBool thread_alloc_framebuffer(rfbClient* client); + static void thread_got_update(rfbClient* cl,int x,int y,int w,int h); + static void thread_update_finished(rfbClient* client); + static void thread_kbd_leds(rfbClient* cl, int value, int pad); + static void thread_textchat(rfbClient* cl, int value, char *text); + static void thread_got_cuttext(rfbClient *cl, const char *text, int len); + static void thread_got_cuttext_utf8(rfbClient *cl, const char *text, int len); + static void thread_bell(rfbClient *cl); + static void thread_handle_xvp(rfbClient *cl, uint8_t ver, uint8_t code); + static void thread_logger(const char *format, ...); + static char* thread_getpasswd(rfbClient* client); + static rfbCredential* thread_getcreds(rfbClient* client, int type); +}; + + + + +// the custom VNCConnUpdateNotifyEvent +class VNCConnUpdateNotifyEvent: public wxCommandEvent +{ +public: + wxRect rect; + + VNCConnUpdateNotifyEvent(wxEventType commandType = VNCConnUpdateNOTIFY, int id = 0 ) + : wxCommandEvent(commandType, id) { } + + // You *must* copy here the data to be transported + VNCConnUpdateNotifyEvent( const VNCConnUpdateNotifyEvent &event ) + : wxCommandEvent(event) { this->rect = event.rect; } + + // Required for sending with wxPostEvent() + wxEvent* Clone() const { return new VNCConnUpdateNotifyEvent(*this); } + }; + + +// This #define simplifies the one below, and makes the syntax less +// ugly if you want to use Connect() instead of an event table. +typedef void (wxEvtHandler::*VNCConnUpdateNotifyEventFunction)(VNCConnUpdateNotifyEvent &); +#define VNCConnUpdateNotifyEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) \ + wxStaticCastEvent(VNCConnUpdateNotifyEventFunction, &func) + +// Define the event table entry. Yes, it really *does* end in a comma. +#define EVT_VNCCONNUPDATENOTIFY(id, fn) \ + DECLARE_EVENT_TABLE_ENTRY(VNCConnUpdateNOTIFY, id, wxID_ANY, \ + (wxObjectEventFunction)(wxEventFunction) \ + (wxCommandEventFunction) \ + wxStaticCastEvent(VNCConnUpdateNotifyEventFunction, &fn ), (wxObject*) NULL ), + + + + +#endif diff --git a/src/dfltcfg.h b/src/dfltcfg.h index dfed27e3..06badeb0 100644 --- a/src/dfltcfg.h +++ b/src/dfltcfg.h @@ -1,113 +1,113 @@ -/* - dftlcfg.h: applicationwide defines for config keys and values. - - This file is part of MultiVNC, a multicast-enabled crossplatform - VNC viewer. - - Copyright (C) 2009, 2010 Christian Beier - - MultiVNC is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MultiVNC is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ - - -#ifndef DFLTCFG_H -#define DFLTCFG_H - - -// gui stuff -#define K_SHOWTOOLBAR _T("ShowToolbar") -#define V_SHOWTOOLBAR true -#define K_SHOWDISCOVERED _T("ShowZeroConf") -#define V_SHOWDISCOVERED true -#define K_SHOWBOOKMARKS _T("ShowBookmarks") -#define V_SHOWBOOKMARKS true -#define K_SHOWSTATS _T("ShowStats") -#define V_SHOWSTATS false -#define K_SHOWSEAMLESS _T("ShowSeamless") -#define V_SHOWSEAMLESS EDGE_NONE -#define K_SHOW1TO1 "Show1To1" -#define V_SHOW1TO1 false -#define K_SIZE_X _T("SizeX") -#define V_SIZE_X 1024 -#define K_SIZE_Y _T("SizeY") -#define V_SIZE_Y 768 - -// connection settings -#define K_MULTICAST wxT("MulticastVNC") -#define V_MULTICAST true -#define K_MULTICASTSOCKETRECVBUF wxT("MulticastSocketRecvBufSize") -#define V_MULTICASTSOCKETRECVBUF 5120 -#define K_MULTICASTRECVBUF wxT("MulticastRecvBufSize") -#define V_MULTICASTRECVBUF 5120 -#define K_FASTREQUEST wxT("FastRequest") -#define V_FASTREQUEST true -#define K_FASTREQUESTINTERVAL wxT("FastRequestInterval") -#define V_FASTREQUESTINTERVAL 30 -#define K_QOS_EF wxT("QOS_EF") -#define V_QOS_EF true -#define K_GRABKEYBOARD wxT("GrabKeyboard") -#define V_GRABKEYBOARD false -#define K_LASTHOST "LastHost" - -// encodings settings -#define K_ENC_COPYRECT wxT("EncodingCopyRect") -#define V_ENC_COPYRECT true -#define K_ENC_RRE wxT("EncodingRRE") -#define V_ENC_RRE true -#define K_ENC_HEXTILE wxT("EncodingHextile") -#define V_ENC_HEXTILE true -#define K_ENC_CORRE wxT("EncodingCorre") -#define V_ENC_CORRE true -#define K_ENC_ZLIB wxT("EncodingZlib") -#define V_ENC_ZLIB true -#define K_ENC_ZLIBHEX wxT("EncodingZlibhex") -#define V_ENC_ZLIBHEX true -#define K_ENC_ZRLE wxT("EncodingZRLE") -#define V_ENC_ZRLE true -#define K_ENC_ZYWRLE wxT("EncodingZYWRLE") -#define V_ENC_ZYWRLE true -#define K_ENC_ULTRA wxT("EncodingUltra") -#define V_ENC_ULTRA true -#define K_ENC_TIGHT wxT("EncodingTight") -#define V_ENC_TIGHT true -#define K_COMPRESSLEVEL wxT("CompressLevel") -#define V_COMPRESSLEVEL 1 -#define K_QUALITY wxT("Quality") -#define V_QUALITY 5 - -// logging -#define K_LOGSAVETOFILE wxT("LogFile") -#define V_LOGSAVETOFILE false - -// stats settings -#define K_STATSAUTOSAVE wxT("StatsAutosave") -#define V_STATSAUTOSAVE false - -//bookmarks -#define G_BOOKMARKS wxT("/Bookmarks/") -#define K_BOOKMARKS_HOST wxT("Host") -#define K_BOOKMARKS_PORT wxT("Port") -#define K_BOOKMARKS_USER "User" - -// collab features -#define K_WINDOWSHARE _T("WindowShareCmd") -#ifdef __WIN32__ -#define V_DFLTWINDOWSHARE _T("windowshare.exe -oneshot -sharewindow \"%w\" -connect %a") -#else -#define V_DFLTWINDOWSHARE _T("x11vnc -repeat -viewonly -id pick -xrandr -connect_or_exit %a") -#endif - - -#endif // DFLTCFG_H +/* + dftlcfg.h: applicationwide defines for config keys and values. + + This file is part of MultiVNC, a multicast-enabled crossplatform + VNC viewer. + + Copyright (C) 2009, 2010 Christian Beier + + MultiVNC is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + MultiVNC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + + +#ifndef DFLTCFG_H +#define DFLTCFG_H + + +// gui stuff +#define K_SHOWTOOLBAR _T("ShowToolbar") +#define V_SHOWTOOLBAR true +#define K_SHOWDISCOVERED _T("ShowZeroConf") +#define V_SHOWDISCOVERED true +#define K_SHOWBOOKMARKS _T("ShowBookmarks") +#define V_SHOWBOOKMARKS true +#define K_SHOWSTATS _T("ShowStats") +#define V_SHOWSTATS false +#define K_SHOWSEAMLESS _T("ShowSeamless") +#define V_SHOWSEAMLESS EDGE_NONE +#define K_SHOW1TO1 "Show1To1" +#define V_SHOW1TO1 false +#define K_SIZE_X _T("SizeX") +#define V_SIZE_X 1024 +#define K_SIZE_Y _T("SizeY") +#define V_SIZE_Y 768 + +// connection settings +#define K_MULTICAST wxT("MulticastVNC") +#define V_MULTICAST true +#define K_MULTICASTSOCKETRECVBUF wxT("MulticastSocketRecvBufSize") +#define V_MULTICASTSOCKETRECVBUF 5120 +#define K_MULTICASTRECVBUF wxT("MulticastRecvBufSize") +#define V_MULTICASTRECVBUF 5120 +#define K_FASTREQUEST wxT("FastRequest") +#define V_FASTREQUEST true +#define K_FASTREQUESTINTERVAL wxT("FastRequestInterval") +#define V_FASTREQUESTINTERVAL 30 +#define K_QOS_EF wxT("QOS_EF") +#define V_QOS_EF true +#define K_GRABKEYBOARD wxT("GrabKeyboard") +#define V_GRABKEYBOARD false +#define K_LASTHOST "LastHost" + +// encodings settings +#define K_ENC_COPYRECT wxT("EncodingCopyRect") +#define V_ENC_COPYRECT true +#define K_ENC_RRE wxT("EncodingRRE") +#define V_ENC_RRE true +#define K_ENC_HEXTILE wxT("EncodingHextile") +#define V_ENC_HEXTILE true +#define K_ENC_CORRE wxT("EncodingCorre") +#define V_ENC_CORRE true +#define K_ENC_ZLIB wxT("EncodingZlib") +#define V_ENC_ZLIB true +#define K_ENC_ZLIBHEX wxT("EncodingZlibhex") +#define V_ENC_ZLIBHEX true +#define K_ENC_ZRLE wxT("EncodingZRLE") +#define V_ENC_ZRLE true +#define K_ENC_ZYWRLE wxT("EncodingZYWRLE") +#define V_ENC_ZYWRLE true +#define K_ENC_ULTRA wxT("EncodingUltra") +#define V_ENC_ULTRA true +#define K_ENC_TIGHT wxT("EncodingTight") +#define V_ENC_TIGHT true +#define K_COMPRESSLEVEL wxT("CompressLevel") +#define V_COMPRESSLEVEL 1 +#define K_QUALITY wxT("Quality") +#define V_QUALITY 5 + +// logging +#define K_LOGSAVETOFILE wxT("LogFile") +#define V_LOGSAVETOFILE false + +// stats settings +#define K_STATSAUTOSAVE wxT("StatsAutosave") +#define V_STATSAUTOSAVE false + +//bookmarks +#define G_BOOKMARKS wxT("/Bookmarks/") +#define K_BOOKMARKS_HOST wxT("Host") +#define K_BOOKMARKS_PORT wxT("Port") +#define K_BOOKMARKS_USER "User" + +// collab features +#define K_WINDOWSHARE _T("WindowShareCmd") +#ifdef __WIN32__ +#define V_DFLTWINDOWSHARE _T("windowshare.exe -oneshot -sharewindow \"%w\" -connect %a") +#else +#define V_DFLTWINDOWSHARE _T("x11vnc -repeat -viewonly -id pick -xrandr -connect_or_exit %a") +#endif + + +#endif // DFLTCFG_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 9d4f8a6c..f8fdc7cf 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,99 +1,99 @@ - - -# -# source and targets -# - -SET(MultiVNCgui_SRCS - FrameMain.cpp - FrameMain.h - MyFrameMain.cpp - MyFrameMain.h - FrameLog.cpp - FrameLog.h - MyFrameLog.cpp - MyFrameLog.h - ViewerWindow.cpp - ViewerWindow.h - VNCSeamlessConnector.cpp - VNCSeamlessConnector.h - DialogSettings.cpp - DialogSettings.h - MyDialogSettings.cpp - MyDialogSettings.h - DialogLogin.cpp - DialogLogin.h - evtids.h - bitmapFromMem.h - res/multivnc.xpm - res/vnccursor-mask.xbm - res/vnccursor.xbm -) - -add_library(MultiVNCgui STATIC ${MultiVNCgui_SRCS}) - - -# -# dependencies -# -FIND_PACKAGE(PkgConfig) -PKG_CHECK_MODULES(GTK3 gtk+-3.0) -include_directories(${GTK3_INCLUDE_DIRS}) -target_link_directories(MultiVNCgui INTERFACE ${GTK3_LIBRARY_DIRS}) -target_link_libraries(MultiVNCgui ${GTK3_LIBRARIES}) - - - -#original Makefile.am contents follow: - -### Process this file with automake to produce Makefile.in -# -#noinst_LIBRARIES = libMultiVNCgui.a -#libMultiVNCgui_a_SOURCES = \ -# FrameMain.cpp \ -# FrameMain.h \ -# MyFrameMain.cpp \ -# MyFrameMain.h \ -# FrameLog.cpp \ -# FrameLog.h \ -# MyFrameLog.cpp \ -# MyFrameLog.h \ -# ViewerWindow.cpp \ -# ViewerWindow.h \ -# VNCSeamlessConnector.cpp \ -# VNCSeamlessConnector.h \ -# DialogSettings.cpp \ -# DialogSettings.h \ -# MyDialogSettings.cpp \ -# MyDialogSettings.h \ -# DialogLogin.cpp \ -# DialogLogin.h \ -# evtids.h\ -# bitmapFromMem.h\ -# res/about.png.h \ -# res/connect.png.h \ -# res/listen.png.h \ -# res/disconnect.png.h \ -# res/fullscreen.png.h \ -# res/screenshot.png.h \ -# res/unicast.png.h \ -# res/multicast.png.h \ -# res/multivnc.xpm \ -# res/vnccursor-mask.xbm \ -# res/vnccursor.xbm -# -# -#libMultiVNCgui_a_CPPFLAGS = @WX_CPPFLAGS@ @GTK_CFLAGS@ -I.. -I../wxServDisc/src -# -# -#EXTRA_DIST = \ -# res/about.png \ -# res/connect.png \ -# res/listen.png \ -# res/disconnect.png \ -# res/fullscreen.png \ -# res/screenshot.png \ -# res/unicast.png \ -# res/multicast.png -# -# + + +# +# source and targets +# + +SET(MultiVNCgui_SRCS + FrameMain.cpp + FrameMain.h + MyFrameMain.cpp + MyFrameMain.h + FrameLog.cpp + FrameLog.h + MyFrameLog.cpp + MyFrameLog.h + ViewerWindow.cpp + ViewerWindow.h + VNCSeamlessConnector.cpp + VNCSeamlessConnector.h + DialogSettings.cpp + DialogSettings.h + MyDialogSettings.cpp + MyDialogSettings.h + DialogLogin.cpp + DialogLogin.h + evtids.h + bitmapFromMem.h + res/multivnc.xpm + res/vnccursor-mask.xbm + res/vnccursor.xbm +) + +add_library(MultiVNCgui STATIC ${MultiVNCgui_SRCS}) + + +# +# dependencies +# +FIND_PACKAGE(PkgConfig) +PKG_CHECK_MODULES(GTK3 gtk+-3.0) +include_directories(${GTK3_INCLUDE_DIRS}) +target_link_directories(MultiVNCgui INTERFACE ${GTK3_LIBRARY_DIRS}) +target_link_libraries(MultiVNCgui ${GTK3_LIBRARIES}) + + + +#original Makefile.am contents follow: + +### Process this file with automake to produce Makefile.in +# +#noinst_LIBRARIES = libMultiVNCgui.a +#libMultiVNCgui_a_SOURCES = \ +# FrameMain.cpp \ +# FrameMain.h \ +# MyFrameMain.cpp \ +# MyFrameMain.h \ +# FrameLog.cpp \ +# FrameLog.h \ +# MyFrameLog.cpp \ +# MyFrameLog.h \ +# ViewerWindow.cpp \ +# ViewerWindow.h \ +# VNCSeamlessConnector.cpp \ +# VNCSeamlessConnector.h \ +# DialogSettings.cpp \ +# DialogSettings.h \ +# MyDialogSettings.cpp \ +# MyDialogSettings.h \ +# DialogLogin.cpp \ +# DialogLogin.h \ +# evtids.h\ +# bitmapFromMem.h\ +# res/about.png.h \ +# res/connect.png.h \ +# res/listen.png.h \ +# res/disconnect.png.h \ +# res/fullscreen.png.h \ +# res/screenshot.png.h \ +# res/unicast.png.h \ +# res/multicast.png.h \ +# res/multivnc.xpm \ +# res/vnccursor-mask.xbm \ +# res/vnccursor.xbm +# +# +#libMultiVNCgui_a_CPPFLAGS = @WX_CPPFLAGS@ @GTK_CFLAGS@ -I.. -I../wxServDisc/src +# +# +#EXTRA_DIST = \ +# res/about.png \ +# res/connect.png \ +# res/listen.png \ +# res/disconnect.png \ +# res/fullscreen.png \ +# res/screenshot.png \ +# res/unicast.png \ +# res/multicast.png +# +# diff --git a/src/gui/DialogLogin.cpp b/src/gui/DialogLogin.cpp index cfb7aa6f..6f9d7ae6 100644 --- a/src/gui/DialogLogin.cpp +++ b/src/gui/DialogLogin.cpp @@ -1,71 +1,71 @@ -#include "DialogLogin.h" -#include -#include - -/* - adopted from http://imron02.wordpress.com/2014/09/26/c-simple-form-login-using-wxwidgets/, thanks! - */ - -DialogLogin::DialogLogin(wxFrame *parent, wxWindowID id, const wxString &title ) -: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); - - wxBoxSizer *hbox1 = new wxBoxSizer(wxHORIZONTAL); - m_usernameLabel = new wxStaticText(this, wxID_ANY, _("Username:") + " ", wxDefaultPosition, wxSize(110, -1)); - hbox1->Add(m_usernameLabel, 0); - - m_usernameEntry = new wxTextCtrl(this, wxID_ANY); - hbox1->Add(m_usernameEntry, 1); - vbox->Add(hbox1, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10); - - wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL); - m_passwordLabel = new wxStaticText(this, wxID_ANY, _("Password:") + " ", wxDefaultPosition, wxSize(110, -1)); - hbox2->Add(m_passwordLabel, 0); - - m_passwordEntry = new wxTextCtrl(this, BUTTON_Login, wxString(""), - wxDefaultPosition, wxSize(220, -1), wxTE_PASSWORD|wxTE_PROCESS_ENTER); - hbox2->Add(m_passwordEntry, 1); - vbox->Add(hbox2, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 10); - - // divider - wxStaticLine *divider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); - vbox->Add(divider, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10); - - // buttons - wxBoxSizer *hbox3 = new wxBoxSizer(wxHORIZONTAL); - m_buttonCancel = new wxButton(this, wxID_CANCEL, _("Cancel")); - hbox3->Add(m_buttonCancel, 1, wxEXPAND | wxALL, 3); - m_buttonLogin = new wxButton(this, BUTTON_Login, _("Login")); - hbox3->Add(m_buttonLogin, 1, wxEXPAND | wxALL, 3); - vbox->Add(hbox3, 0, wxEXPAND | wxALL, 10); - - SetSizerAndFit(vbox); - -} - -void DialogLogin::OnCancel(wxCommandEvent& event) -{ - EndModal(wxID_CANCEL); -} - -void DialogLogin::OnLogin(wxCommandEvent& event) -{ - wxString username = m_usernameEntry->GetValue(); - wxString password = m_passwordEntry->GetValue(); - - if (username.empty() || password.empty()) { - wxMessageBox(_("Username or password must not empty"), _("Warning!"), wxICON_WARNING); - } - else - EndModal(wxID_OK); - -} - -DialogLogin::~DialogLogin() {} - -BEGIN_EVENT_TABLE(DialogLogin, wxDialog) -EVT_BUTTON(wxID_CANCEL, DialogLogin::OnCancel) -EVT_BUTTON(BUTTON_Login, DialogLogin::OnLogin) -EVT_TEXT_ENTER(BUTTON_Login, DialogLogin::OnLogin) -END_EVENT_TABLE() +#include "DialogLogin.h" +#include +#include + +/* + adopted from http://imron02.wordpress.com/2014/09/26/c-simple-form-login-using-wxwidgets/, thanks! + */ + +DialogLogin::DialogLogin(wxFrame *parent, wxWindowID id, const wxString &title ) +: wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); + + wxBoxSizer *hbox1 = new wxBoxSizer(wxHORIZONTAL); + m_usernameLabel = new wxStaticText(this, wxID_ANY, _("Username:") + " ", wxDefaultPosition, wxSize(110, -1)); + hbox1->Add(m_usernameLabel, 0); + + m_usernameEntry = new wxTextCtrl(this, wxID_ANY); + hbox1->Add(m_usernameEntry, 1); + vbox->Add(hbox1, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10); + + wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL); + m_passwordLabel = new wxStaticText(this, wxID_ANY, _("Password:") + " ", wxDefaultPosition, wxSize(110, -1)); + hbox2->Add(m_passwordLabel, 0); + + m_passwordEntry = new wxTextCtrl(this, BUTTON_Login, wxString(""), + wxDefaultPosition, wxSize(220, -1), wxTE_PASSWORD|wxTE_PROCESS_ENTER); + hbox2->Add(m_passwordEntry, 1); + vbox->Add(hbox2, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 10); + + // divider + wxStaticLine *divider = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL); + vbox->Add(divider, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 10); + + // buttons + wxBoxSizer *hbox3 = new wxBoxSizer(wxHORIZONTAL); + m_buttonCancel = new wxButton(this, wxID_CANCEL, _("Cancel")); + hbox3->Add(m_buttonCancel, 1, wxEXPAND | wxALL, 3); + m_buttonLogin = new wxButton(this, BUTTON_Login, _("Login")); + hbox3->Add(m_buttonLogin, 1, wxEXPAND | wxALL, 3); + vbox->Add(hbox3, 0, wxEXPAND | wxALL, 10); + + SetSizerAndFit(vbox); + +} + +void DialogLogin::OnCancel(wxCommandEvent& event) +{ + EndModal(wxID_CANCEL); +} + +void DialogLogin::OnLogin(wxCommandEvent& event) +{ + wxString username = m_usernameEntry->GetValue(); + wxString password = m_passwordEntry->GetValue(); + + if (username.empty() || password.empty()) { + wxMessageBox(_("Username or password must not empty"), _("Warning!"), wxICON_WARNING); + } + else + EndModal(wxID_OK); + +} + +DialogLogin::~DialogLogin() {} + +BEGIN_EVENT_TABLE(DialogLogin, wxDialog) +EVT_BUTTON(wxID_CANCEL, DialogLogin::OnCancel) +EVT_BUTTON(BUTTON_Login, DialogLogin::OnLogin) +EVT_TEXT_ENTER(BUTTON_Login, DialogLogin::OnLogin) +END_EVENT_TABLE() diff --git a/src/gui/DialogSettings.cpp b/src/gui/DialogSettings.cpp index d7aec378..dc913986 100644 --- a/src/gui/DialogSettings.cpp +++ b/src/gui/DialogSettings.cpp @@ -1,113 +1,113 @@ -// -*- C++ -*- -// -// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 -// -// Example for compiling a single file project under Linux using g++: -// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp -// -// Example for compiling a multi file project under Linux using g++: -// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp -// - -#include -#include "DialogSettings.h" - -// begin wxGlade: ::extracode -// end wxGlade - - -DialogSettings::DialogSettings(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): - wxDialog(parent, id, title, pos, size, wxDEFAULT_DIALOG_STYLE) -{ - // begin wxGlade: DialogSettings::DialogSettings - wxBoxSizer* sizer_top = new wxBoxSizer(wxVERTICAL); - notebook_settings = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); - sizer_top->Add(notebook_settings, 0, wxALL|wxEXPAND, 3); - notebook_settings_pane_conn = new wxPanel(notebook_settings, wxID_ANY); - notebook_settings->AddPage(notebook_settings_pane_conn, _("Connections")); - wxBoxSizer* sizer_conn = new wxBoxSizer(wxVERTICAL); - wxStaticBoxSizer* sizer_fastrequest = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_conn, wxID_ANY, _("FastRequest")), wxVERTICAL); - sizer_conn->Add(sizer_fastrequest, 0, wxALL|wxEXPAND, 3); - checkbox_fastrequest = new wxCheckBox(notebook_settings_pane_conn, wxID_ANY, _("Enable FastRequest")); - sizer_fastrequest->Add(checkbox_fastrequest, 0, wxALL|wxEXPAND, 3); - label_fastrequest = new wxStaticText(notebook_settings_pane_conn, wxID_ANY, _("Continously request updates at the specified milisecond interval:")); - sizer_fastrequest->Add(label_fastrequest, 0, wxALL, 3); - slider_fastrequest = new wxSlider(notebook_settings_pane_conn, wxID_ANY, 1, 1, 100, wxDefaultPosition, wxDefaultSize, wxSL_AUTOTICKS|wxSL_HORIZONTAL|wxSL_LABELS); - slider_fastrequest->SetToolTip(_("Continously ask the server for updates instead of just asking after each received server message. Use this on high latency links.")); - sizer_fastrequest->Add(slider_fastrequest, 0, wxALL|wxEXPAND, 3); - wxStaticBoxSizer* sizer_qos = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_conn, wxID_ANY, _("Quality of Service")), wxHORIZONTAL); - sizer_conn->Add(sizer_qos, 0, wxALL|wxEXPAND, 3); - checkbox_qos_ef = new wxCheckBox(notebook_settings_pane_conn, wxID_ANY, _("Enable Expedited Forwarding tagging for sent data")); - sizer_qos->Add(checkbox_qos_ef, 0, wxALL|wxEXPAND, 3); - wxStaticBoxSizer* sizer_multicast = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_conn, wxID_ANY, _("MulticastVNC")), wxVERTICAL); - sizer_conn->Add(sizer_multicast, 0, wxALL|wxEXPAND, 3); - checkbox_multicast = new wxCheckBox(notebook_settings_pane_conn, wxID_ANY, _("Enable MulticastVNC")); - sizer_multicast->Add(checkbox_multicast, 0, wxALL|wxEXPAND, 3); - label_socketrecvbuf = new wxStaticText(notebook_settings_pane_conn, wxID_ANY, _("Socket Receive Buffer Size (kB):")); - sizer_multicast->Add(label_socketrecvbuf, 0, wxALL, 3); - slider_socketrecvbuf = new wxSlider(notebook_settings_pane_conn, wxID_ANY, 65, 65, 9750, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_LABELS); - slider_socketrecvbuf->SetToolTip(_("Set the multicast socket receive buffer size. Increasing the value may help against packet loss. Note that the maximum value is operating system dependent.")); - sizer_multicast->Add(slider_socketrecvbuf, 0, wxALL|wxEXPAND, 3); - label_recvbuf = new wxStaticText(notebook_settings_pane_conn, wxID_ANY, _("Receive Buffer Size (kB):")); - sizer_multicast->Add(label_recvbuf, 0, wxALL, 3); - slider_recvbuf = new wxSlider(notebook_settings_pane_conn, wxID_ANY, 65, 65, 9750, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_LABELS); - slider_recvbuf->SetToolTip(_("Set the multicast receive buffer size. Increasing the value may help against packet loss. The size of this buffer is independent of the operating system.")); - sizer_multicast->Add(slider_recvbuf, 0, wxALL|wxEXPAND, 3); - notebook_settings_pane_encodings = new wxPanel(notebook_settings, wxID_ANY); - notebook_settings->AddPage(notebook_settings_pane_encodings, _("Encodings")); - wxBoxSizer* sizer_encodings = new wxBoxSizer(wxVERTICAL); - wxStaticBoxSizer* sizer_encbox = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_encodings, wxID_ANY, _("Enabled Encodings")), wxHORIZONTAL); - sizer_encodings->Add(sizer_encbox, 0, wxALL|wxEXPAND, 3); - wxGridSizer* grid_sizer_encodings = new wxGridSizer(4, 3, 0, 0); - sizer_encbox->Add(grid_sizer_encodings, 1, 0, 3); - checkbox_enc_copyrect = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("CopyRect")); - grid_sizer_encodings->Add(checkbox_enc_copyrect, 0, wxALL, 3); - checkbox_enc_hextile = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Hextile")); - grid_sizer_encodings->Add(checkbox_enc_hextile, 0, wxALL, 3); - checkbox_enc_rre = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("RRE")); - grid_sizer_encodings->Add(checkbox_enc_rre, 0, wxALL, 3); - checkbox_enc_corre = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("CoRRE")); - grid_sizer_encodings->Add(checkbox_enc_corre, 0, wxALL, 3); - checkbox_enc_zlib = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Zlib")); - grid_sizer_encodings->Add(checkbox_enc_zlib, 0, wxALL, 3); - checkbox_enc_zlibhex = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("ZlibHex")); - grid_sizer_encodings->Add(checkbox_enc_zlibhex, 0, wxALL, 3); - checkbox_enc_zrle = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("ZRLE")); - grid_sizer_encodings->Add(checkbox_enc_zrle, 0, wxALL, 3); - checkbox_enc_zywrle = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("ZYWRLE")); - grid_sizer_encodings->Add(checkbox_enc_zywrle, 0, wxALL, 3); - checkbox_enc_ultra = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Ultra")); - grid_sizer_encodings->Add(checkbox_enc_ultra, 0, wxALL, 3); - checkbox_enc_tight = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Tight")); - grid_sizer_encodings->Add(checkbox_enc_tight, 0, wxALL, 3); - grid_sizer_encodings->Add(0, 0, 0, 0, 0); - grid_sizer_encodings->Add(0, 0, 0, 0, 0); - wxStaticBoxSizer* sizer_tightsettings = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_encodings, wxID_ANY, _("Lossy Encodings Settings")), wxVERTICAL); - sizer_encodings->Add(sizer_tightsettings, 0, wxALL|wxEXPAND, 3); - label_compresslevel = new wxStaticText(notebook_settings_pane_encodings, wxID_ANY, _("Compression level for 'Tight' and 'Zlib' encodings:")); - sizer_tightsettings->Add(label_compresslevel, 0, wxALL, 3); - slider_compresslevel = new wxSlider(notebook_settings_pane_encodings, wxID_ANY, 0, 0, 9, wxDefaultPosition, wxDefaultSize, wxSL_AUTOTICKS|wxSL_HORIZONTAL|wxSL_LABELS); - slider_compresslevel->SetToolTip(_("Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. Level 1 uses minimum of CPU time and achieves weak compression ratios, while level 9 offers best compression but is slow in terms of CPU time consumption on the server side. Use high levels with very slow network connections, and low levels when working over high-speed LANs.")); - sizer_tightsettings->Add(slider_compresslevel, 0, wxALL|wxEXPAND, 3); - label_quality = new wxStaticText(notebook_settings_pane_encodings, wxID_ANY, _("Quality level for 'Tight' and 'ZYWRLE' encoding:")); - sizer_tightsettings->Add(label_quality, 0, wxALL, 3); - slider_quality = new wxSlider(notebook_settings_pane_encodings, wxID_ANY, 0, 0, 9, wxDefaultPosition, wxDefaultSize, wxSL_AUTOTICKS|wxSL_HORIZONTAL|wxSL_LABELS); - slider_quality->SetToolTip(_("Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' encodings. Quality level 0 denotes bad image quality but very impressive compression ratios, while level 9 offers very good image quality at lower compression ratios. Note that the \"tight\" encoder uses JPEG to encode only those screen areas that look suitable for lossy compression, so quality level 0 does not always mean unacceptable image quality.")); - sizer_tightsettings->Add(slider_quality, 0, wxALL|wxEXPAND, 3); - notebook_settings_pane_logging = new wxPanel(notebook_settings, wxID_ANY); - notebook_settings->AddPage(notebook_settings_pane_logging, _("Logging")); - wxBoxSizer* sizer_logging = new wxBoxSizer(wxVERTICAL); - checkbox_logfile = new wxCheckBox(notebook_settings_pane_logging, wxID_ANY, _("Write VNC log to logfile (MultiVNC.log)")); - sizer_logging->Add(checkbox_logfile, 0, wxALL, 6); - checkbox_stats_save = new wxCheckBox(notebook_settings_pane_logging, wxID_ANY, _("Autosave statistics on close")); - sizer_logging->Add(checkbox_stats_save, 0, wxALL, 6); - - notebook_settings_pane_logging->SetSizer(sizer_logging); - notebook_settings_pane_encodings->SetSizer(sizer_encodings); - notebook_settings_pane_conn->SetSizer(sizer_conn); - SetSizer(sizer_top); - sizer_top->Fit(this); - Layout(); - // end wxGlade -} - +// -*- C++ -*- +// +// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 +// +// Example for compiling a single file project under Linux using g++: +// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp +// +// Example for compiling a multi file project under Linux using g++: +// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp +// + +#include +#include "DialogSettings.h" + +// begin wxGlade: ::extracode +// end wxGlade + + +DialogSettings::DialogSettings(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): + wxDialog(parent, id, title, pos, size, wxDEFAULT_DIALOG_STYLE) +{ + // begin wxGlade: DialogSettings::DialogSettings + wxBoxSizer* sizer_top = new wxBoxSizer(wxVERTICAL); + notebook_settings = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + sizer_top->Add(notebook_settings, 0, wxALL|wxEXPAND, 3); + notebook_settings_pane_conn = new wxPanel(notebook_settings, wxID_ANY); + notebook_settings->AddPage(notebook_settings_pane_conn, _("Connections")); + wxBoxSizer* sizer_conn = new wxBoxSizer(wxVERTICAL); + wxStaticBoxSizer* sizer_fastrequest = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_conn, wxID_ANY, _("FastRequest")), wxVERTICAL); + sizer_conn->Add(sizer_fastrequest, 0, wxALL|wxEXPAND, 3); + checkbox_fastrequest = new wxCheckBox(notebook_settings_pane_conn, wxID_ANY, _("Enable FastRequest")); + sizer_fastrequest->Add(checkbox_fastrequest, 0, wxALL|wxEXPAND, 3); + label_fastrequest = new wxStaticText(notebook_settings_pane_conn, wxID_ANY, _("Continously request updates at the specified milisecond interval:")); + sizer_fastrequest->Add(label_fastrequest, 0, wxALL, 3); + slider_fastrequest = new wxSlider(notebook_settings_pane_conn, wxID_ANY, 1, 1, 100, wxDefaultPosition, wxDefaultSize, wxSL_AUTOTICKS|wxSL_HORIZONTAL|wxSL_LABELS); + slider_fastrequest->SetToolTip(_("Continously ask the server for updates instead of just asking after each received server message. Use this on high latency links.")); + sizer_fastrequest->Add(slider_fastrequest, 0, wxALL|wxEXPAND, 3); + wxStaticBoxSizer* sizer_qos = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_conn, wxID_ANY, _("Quality of Service")), wxHORIZONTAL); + sizer_conn->Add(sizer_qos, 0, wxALL|wxEXPAND, 3); + checkbox_qos_ef = new wxCheckBox(notebook_settings_pane_conn, wxID_ANY, _("Enable Expedited Forwarding tagging for sent data")); + sizer_qos->Add(checkbox_qos_ef, 0, wxALL|wxEXPAND, 3); + wxStaticBoxSizer* sizer_multicast = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_conn, wxID_ANY, _("MulticastVNC")), wxVERTICAL); + sizer_conn->Add(sizer_multicast, 0, wxALL|wxEXPAND, 3); + checkbox_multicast = new wxCheckBox(notebook_settings_pane_conn, wxID_ANY, _("Enable MulticastVNC")); + sizer_multicast->Add(checkbox_multicast, 0, wxALL|wxEXPAND, 3); + label_socketrecvbuf = new wxStaticText(notebook_settings_pane_conn, wxID_ANY, _("Socket Receive Buffer Size (kB):")); + sizer_multicast->Add(label_socketrecvbuf, 0, wxALL, 3); + slider_socketrecvbuf = new wxSlider(notebook_settings_pane_conn, wxID_ANY, 65, 65, 9750, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_LABELS); + slider_socketrecvbuf->SetToolTip(_("Set the multicast socket receive buffer size. Increasing the value may help against packet loss. Note that the maximum value is operating system dependent.")); + sizer_multicast->Add(slider_socketrecvbuf, 0, wxALL|wxEXPAND, 3); + label_recvbuf = new wxStaticText(notebook_settings_pane_conn, wxID_ANY, _("Receive Buffer Size (kB):")); + sizer_multicast->Add(label_recvbuf, 0, wxALL, 3); + slider_recvbuf = new wxSlider(notebook_settings_pane_conn, wxID_ANY, 65, 65, 9750, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL|wxSL_LABELS); + slider_recvbuf->SetToolTip(_("Set the multicast receive buffer size. Increasing the value may help against packet loss. The size of this buffer is independent of the operating system.")); + sizer_multicast->Add(slider_recvbuf, 0, wxALL|wxEXPAND, 3); + notebook_settings_pane_encodings = new wxPanel(notebook_settings, wxID_ANY); + notebook_settings->AddPage(notebook_settings_pane_encodings, _("Encodings")); + wxBoxSizer* sizer_encodings = new wxBoxSizer(wxVERTICAL); + wxStaticBoxSizer* sizer_encbox = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_encodings, wxID_ANY, _("Enabled Encodings")), wxHORIZONTAL); + sizer_encodings->Add(sizer_encbox, 0, wxALL|wxEXPAND, 3); + wxGridSizer* grid_sizer_encodings = new wxGridSizer(4, 3, 0, 0); + sizer_encbox->Add(grid_sizer_encodings, 1, 0, 3); + checkbox_enc_copyrect = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("CopyRect")); + grid_sizer_encodings->Add(checkbox_enc_copyrect, 0, wxALL, 3); + checkbox_enc_hextile = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Hextile")); + grid_sizer_encodings->Add(checkbox_enc_hextile, 0, wxALL, 3); + checkbox_enc_rre = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("RRE")); + grid_sizer_encodings->Add(checkbox_enc_rre, 0, wxALL, 3); + checkbox_enc_corre = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("CoRRE")); + grid_sizer_encodings->Add(checkbox_enc_corre, 0, wxALL, 3); + checkbox_enc_zlib = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Zlib")); + grid_sizer_encodings->Add(checkbox_enc_zlib, 0, wxALL, 3); + checkbox_enc_zlibhex = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("ZlibHex")); + grid_sizer_encodings->Add(checkbox_enc_zlibhex, 0, wxALL, 3); + checkbox_enc_zrle = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("ZRLE")); + grid_sizer_encodings->Add(checkbox_enc_zrle, 0, wxALL, 3); + checkbox_enc_zywrle = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("ZYWRLE")); + grid_sizer_encodings->Add(checkbox_enc_zywrle, 0, wxALL, 3); + checkbox_enc_ultra = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Ultra")); + grid_sizer_encodings->Add(checkbox_enc_ultra, 0, wxALL, 3); + checkbox_enc_tight = new wxCheckBox(notebook_settings_pane_encodings, wxID_ANY, _("Tight")); + grid_sizer_encodings->Add(checkbox_enc_tight, 0, wxALL, 3); + grid_sizer_encodings->Add(0, 0, 0, 0, 0); + grid_sizer_encodings->Add(0, 0, 0, 0, 0); + wxStaticBoxSizer* sizer_tightsettings = new wxStaticBoxSizer(new wxStaticBox(notebook_settings_pane_encodings, wxID_ANY, _("Lossy Encodings Settings")), wxVERTICAL); + sizer_encodings->Add(sizer_tightsettings, 0, wxALL|wxEXPAND, 3); + label_compresslevel = new wxStaticText(notebook_settings_pane_encodings, wxID_ANY, _("Compression level for 'Tight' and 'Zlib' encodings:")); + sizer_tightsettings->Add(label_compresslevel, 0, wxALL, 3); + slider_compresslevel = new wxSlider(notebook_settings_pane_encodings, wxID_ANY, 0, 0, 9, wxDefaultPosition, wxDefaultSize, wxSL_AUTOTICKS|wxSL_HORIZONTAL|wxSL_LABELS); + slider_compresslevel->SetToolTip(_("Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. Level 1 uses minimum of CPU time and achieves weak compression ratios, while level 9 offers best compression but is slow in terms of CPU time consumption on the server side. Use high levels with very slow network connections, and low levels when working over high-speed LANs.")); + sizer_tightsettings->Add(slider_compresslevel, 0, wxALL|wxEXPAND, 3); + label_quality = new wxStaticText(notebook_settings_pane_encodings, wxID_ANY, _("Quality level for 'Tight' and 'ZYWRLE' encoding:")); + sizer_tightsettings->Add(label_quality, 0, wxALL, 3); + slider_quality = new wxSlider(notebook_settings_pane_encodings, wxID_ANY, 0, 0, 9, wxDefaultPosition, wxDefaultSize, wxSL_AUTOTICKS|wxSL_HORIZONTAL|wxSL_LABELS); + slider_quality->SetToolTip(_("Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' encodings. Quality level 0 denotes bad image quality but very impressive compression ratios, while level 9 offers very good image quality at lower compression ratios. Note that the \"tight\" encoder uses JPEG to encode only those screen areas that look suitable for lossy compression, so quality level 0 does not always mean unacceptable image quality.")); + sizer_tightsettings->Add(slider_quality, 0, wxALL|wxEXPAND, 3); + notebook_settings_pane_logging = new wxPanel(notebook_settings, wxID_ANY); + notebook_settings->AddPage(notebook_settings_pane_logging, _("Logging")); + wxBoxSizer* sizer_logging = new wxBoxSizer(wxVERTICAL); + checkbox_logfile = new wxCheckBox(notebook_settings_pane_logging, wxID_ANY, _("Write VNC log to logfile (MultiVNC.log)")); + sizer_logging->Add(checkbox_logfile, 0, wxALL, 6); + checkbox_stats_save = new wxCheckBox(notebook_settings_pane_logging, wxID_ANY, _("Autosave statistics on close")); + sizer_logging->Add(checkbox_stats_save, 0, wxALL, 6); + + notebook_settings_pane_logging->SetSizer(sizer_logging); + notebook_settings_pane_encodings->SetSizer(sizer_encodings); + notebook_settings_pane_conn->SetSizer(sizer_conn); + SetSizer(sizer_top); + sizer_top->Fit(this); + Layout(); + // end wxGlade +} + diff --git a/src/gui/DialogSettings.h b/src/gui/DialogSettings.h index 57e06dba..947effc6 100644 --- a/src/gui/DialogSettings.h +++ b/src/gui/DialogSettings.h @@ -1,76 +1,76 @@ -// -*- C++ -*- -// -// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 -// -// Example for compiling a single file project under Linux using g++: -// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp -// -// Example for compiling a multi file project under Linux using g++: -// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp -// - -#ifndef DIALOGSETTINGS_H -#define DIALOGSETTINGS_H - -#include -#include -#include - -#ifndef APP_CATALOG -#define APP_CATALOG "app" // replace with the appropriate catalog name -#endif - - -// begin wxGlade: ::dependencies -#include -// end wxGlade - -// begin wxGlade: ::extracode -// end wxGlade - - -class DialogSettings: public wxDialog { -public: - // begin wxGlade: DialogSettings::ids - // end wxGlade - - DialogSettings(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE); - -private: - -protected: - // begin wxGlade: DialogSettings::attributes - wxNotebook* notebook_settings; - wxPanel* notebook_settings_pane_conn; - wxCheckBox* checkbox_fastrequest; - wxStaticText* label_fastrequest; - wxSlider* slider_fastrequest; - wxCheckBox* checkbox_qos_ef; - wxCheckBox* checkbox_multicast; - wxStaticText* label_socketrecvbuf; - wxSlider* slider_socketrecvbuf; - wxStaticText* label_recvbuf; - wxSlider* slider_recvbuf; - wxPanel* notebook_settings_pane_encodings; - wxCheckBox* checkbox_enc_copyrect; - wxCheckBox* checkbox_enc_hextile; - wxCheckBox* checkbox_enc_rre; - wxCheckBox* checkbox_enc_corre; - wxCheckBox* checkbox_enc_zlib; - wxCheckBox* checkbox_enc_zlibhex; - wxCheckBox* checkbox_enc_zrle; - wxCheckBox* checkbox_enc_zywrle; - wxCheckBox* checkbox_enc_ultra; - wxCheckBox* checkbox_enc_tight; - wxStaticText* label_compresslevel; - wxSlider* slider_compresslevel; - wxStaticText* label_quality; - wxSlider* slider_quality; - wxPanel* notebook_settings_pane_logging; - wxCheckBox* checkbox_logfile; - wxCheckBox* checkbox_stats_save; - // end wxGlade -}; // wxGlade: end class - - -#endif // DIALOGSETTINGS_H +// -*- C++ -*- +// +// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 +// +// Example for compiling a single file project under Linux using g++: +// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp +// +// Example for compiling a multi file project under Linux using g++: +// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp +// + +#ifndef DIALOGSETTINGS_H +#define DIALOGSETTINGS_H + +#include +#include +#include + +#ifndef APP_CATALOG +#define APP_CATALOG "app" // replace with the appropriate catalog name +#endif + + +// begin wxGlade: ::dependencies +#include +// end wxGlade + +// begin wxGlade: ::extracode +// end wxGlade + + +class DialogSettings: public wxDialog { +public: + // begin wxGlade: DialogSettings::ids + // end wxGlade + + DialogSettings(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE); + +private: + +protected: + // begin wxGlade: DialogSettings::attributes + wxNotebook* notebook_settings; + wxPanel* notebook_settings_pane_conn; + wxCheckBox* checkbox_fastrequest; + wxStaticText* label_fastrequest; + wxSlider* slider_fastrequest; + wxCheckBox* checkbox_qos_ef; + wxCheckBox* checkbox_multicast; + wxStaticText* label_socketrecvbuf; + wxSlider* slider_socketrecvbuf; + wxStaticText* label_recvbuf; + wxSlider* slider_recvbuf; + wxPanel* notebook_settings_pane_encodings; + wxCheckBox* checkbox_enc_copyrect; + wxCheckBox* checkbox_enc_hextile; + wxCheckBox* checkbox_enc_rre; + wxCheckBox* checkbox_enc_corre; + wxCheckBox* checkbox_enc_zlib; + wxCheckBox* checkbox_enc_zlibhex; + wxCheckBox* checkbox_enc_zrle; + wxCheckBox* checkbox_enc_zywrle; + wxCheckBox* checkbox_enc_ultra; + wxCheckBox* checkbox_enc_tight; + wxStaticText* label_compresslevel; + wxSlider* slider_compresslevel; + wxStaticText* label_quality; + wxSlider* slider_quality; + wxPanel* notebook_settings_pane_logging; + wxCheckBox* checkbox_logfile; + wxCheckBox* checkbox_stats_save; + // end wxGlade +}; // wxGlade: end class + + +#endif // DIALOGSETTINGS_H diff --git a/src/gui/FrameLog.cpp b/src/gui/FrameLog.cpp index 876872b7..dad1354f 100644 --- a/src/gui/FrameLog.cpp +++ b/src/gui/FrameLog.cpp @@ -1,80 +1,80 @@ -// -*- C++ -*- -// -// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 -// -// Example for compiling a single file project under Linux using g++: -// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp -// -// Example for compiling a multi file project under Linux using g++: -// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp -// - -#include -#include "FrameLog.h" - -// begin wxGlade: ::extracode -// end wxGlade - - -FrameLog::FrameLog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): - wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE) -{ - // begin wxGlade: FrameLog::FrameLog - wxBoxSizer* sizer_top = new wxBoxSizer(wxVERTICAL); - panel_top = new wxPanel(this, wxID_ANY); - panel_top->SetMinSize(wxSize(800, 600)); - sizer_top->Add(panel_top, 2, wxEXPAND, 0); - wxBoxSizer* sizer_log = new wxBoxSizer(wxVERTICAL); - text_ctrl_log = new wxTextCtrl(panel_top, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxTE_MULTILINE|wxTE_READONLY); - sizer_log->Add(text_ctrl_log, 1, wxALL|wxEXPAND, 3); - wxBoxSizer* sizer_buttons = new wxBoxSizer(wxHORIZONTAL); - sizer_log->Add(sizer_buttons, 0, wxALIGN_RIGHT, 0); - button_clear = new wxButton(panel_top, wxID_CLEAR, wxEmptyString); - sizer_buttons->Add(button_clear, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 3); - button_save = new wxButton(panel_top, wxID_SAVEAS, wxEmptyString); - sizer_buttons->Add(button_save, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 3); - button_close = new wxButton(panel_top, wxID_CLOSE, wxEmptyString); - button_close->SetDefault(); - sizer_buttons->Add(button_close, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 3); - - panel_top->SetSizer(sizer_log); - SetSizer(sizer_top); - sizer_top->Fit(this); - Layout(); - // end wxGlade -} - - -BEGIN_EVENT_TABLE(FrameLog, wxFrame) - // begin wxGlade: FrameLog::event_table - EVT_BUTTON(wxID_CLEAR, FrameLog::log_clear) - EVT_BUTTON(wxID_SAVEAS, FrameLog::log_saveas) - EVT_BUTTON(wxID_CLOSE, FrameLog::log_close) - // end wxGlade -END_EVENT_TABLE(); - - -void FrameLog::log_clear(wxCommandEvent &event) // wxGlade: FrameLog. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameLog::log_clear) not implemented yet")); -} - -void FrameLog::log_saveas(wxCommandEvent &event) // wxGlade: FrameLog. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameLog::log_saveas) not implemented yet")); -} - -void FrameLog::log_close(wxCommandEvent &event) // wxGlade: FrameLog. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameLog::log_close) not implemented yet")); -} - - -// wxGlade: add FrameLog event handlers - +// -*- C++ -*- +// +// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 +// +// Example for compiling a single file project under Linux using g++: +// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp +// +// Example for compiling a multi file project under Linux using g++: +// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp +// + +#include +#include "FrameLog.h" + +// begin wxGlade: ::extracode +// end wxGlade + + +FrameLog::FrameLog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): + wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE) +{ + // begin wxGlade: FrameLog::FrameLog + wxBoxSizer* sizer_top = new wxBoxSizer(wxVERTICAL); + panel_top = new wxPanel(this, wxID_ANY); + panel_top->SetMinSize(wxSize(800, 600)); + sizer_top->Add(panel_top, 2, wxEXPAND, 0); + wxBoxSizer* sizer_log = new wxBoxSizer(wxVERTICAL); + text_ctrl_log = new wxTextCtrl(panel_top, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxTE_MULTILINE|wxTE_READONLY); + sizer_log->Add(text_ctrl_log, 1, wxALL|wxEXPAND, 3); + wxBoxSizer* sizer_buttons = new wxBoxSizer(wxHORIZONTAL); + sizer_log->Add(sizer_buttons, 0, wxALIGN_RIGHT, 0); + button_clear = new wxButton(panel_top, wxID_CLEAR, wxEmptyString); + sizer_buttons->Add(button_clear, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 3); + button_save = new wxButton(panel_top, wxID_SAVEAS, wxEmptyString); + sizer_buttons->Add(button_save, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 3); + button_close = new wxButton(panel_top, wxID_CLOSE, wxEmptyString); + button_close->SetDefault(); + sizer_buttons->Add(button_close, 0, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 3); + + panel_top->SetSizer(sizer_log); + SetSizer(sizer_top); + sizer_top->Fit(this); + Layout(); + // end wxGlade +} + + +BEGIN_EVENT_TABLE(FrameLog, wxFrame) + // begin wxGlade: FrameLog::event_table + EVT_BUTTON(wxID_CLEAR, FrameLog::log_clear) + EVT_BUTTON(wxID_SAVEAS, FrameLog::log_saveas) + EVT_BUTTON(wxID_CLOSE, FrameLog::log_close) + // end wxGlade +END_EVENT_TABLE(); + + +void FrameLog::log_clear(wxCommandEvent &event) // wxGlade: FrameLog. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameLog::log_clear) not implemented yet")); +} + +void FrameLog::log_saveas(wxCommandEvent &event) // wxGlade: FrameLog. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameLog::log_saveas) not implemented yet")); +} + +void FrameLog::log_close(wxCommandEvent &event) // wxGlade: FrameLog. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameLog::log_close) not implemented yet")); +} + + +// wxGlade: add FrameLog event handlers + diff --git a/src/gui/FrameLog.h b/src/gui/FrameLog.h index 95c217bb..92160b2f 100644 --- a/src/gui/FrameLog.h +++ b/src/gui/FrameLog.h @@ -1,58 +1,58 @@ -// -*- C++ -*- -// -// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 -// -// Example for compiling a single file project under Linux using g++: -// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp -// -// Example for compiling a multi file project under Linux using g++: -// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp -// - -#ifndef FRAMELOG_H -#define FRAMELOG_H - -#include -#include -#include - -#ifndef APP_CATALOG -#define APP_CATALOG "app" // replace with the appropriate catalog name -#endif - - -// begin wxGlade: ::dependencies -// end wxGlade - -// begin wxGlade: ::extracode -// end wxGlade - - -class FrameLog: public wxFrame { -public: - // begin wxGlade: FrameLog::ids - // end wxGlade - - FrameLog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE); - -private: - -protected: - // begin wxGlade: FrameLog::attributes - wxPanel* panel_top; - wxTextCtrl* text_ctrl_log; - wxButton* button_clear; - wxButton* button_save; - wxButton* button_close; - // end wxGlade - - DECLARE_EVENT_TABLE(); - -public: - virtual void log_clear(wxCommandEvent &event); // wxGlade: - virtual void log_saveas(wxCommandEvent &event); // wxGlade: - virtual void log_close(wxCommandEvent &event); // wxGlade: -}; // wxGlade: end class - - -#endif // FRAMELOG_H +// -*- C++ -*- +// +// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 +// +// Example for compiling a single file project under Linux using g++: +// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp +// +// Example for compiling a multi file project under Linux using g++: +// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp +// + +#ifndef FRAMELOG_H +#define FRAMELOG_H + +#include +#include +#include + +#ifndef APP_CATALOG +#define APP_CATALOG "app" // replace with the appropriate catalog name +#endif + + +// begin wxGlade: ::dependencies +// end wxGlade + +// begin wxGlade: ::extracode +// end wxGlade + + +class FrameLog: public wxFrame { +public: + // begin wxGlade: FrameLog::ids + // end wxGlade + + FrameLog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE); + +private: + +protected: + // begin wxGlade: FrameLog::attributes + wxPanel* panel_top; + wxTextCtrl* text_ctrl_log; + wxButton* button_clear; + wxButton* button_save; + wxButton* button_close; + // end wxGlade + + DECLARE_EVENT_TABLE(); + +public: + virtual void log_clear(wxCommandEvent &event); // wxGlade: + virtual void log_saveas(wxCommandEvent &event); // wxGlade: + virtual void log_close(wxCommandEvent &event); // wxGlade: +}; // wxGlade: end class + + +#endif // FRAMELOG_H diff --git a/src/gui/FrameMain.cpp b/src/gui/FrameMain.cpp index 93fb833d..6ab7bc17 100644 --- a/src/gui/FrameMain.cpp +++ b/src/gui/FrameMain.cpp @@ -1,391 +1,391 @@ -// -*- C++ -*- -// -// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 -// -// Example for compiling a single file project under Linux using g++: -// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp -// -// Example for compiling a multi file project under Linux using g++: -// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp -// - -#include -#include "FrameMain.h" - -// begin wxGlade: ::extracode -// end wxGlade - - -FrameMain::FrameMain(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): - wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE) -{ - // begin wxGlade: FrameMain::FrameMain - SetSize(wxSize(1089, 1030)); - SetTitle(_("MultiVNC")); - wxIcon _icon; - _icon.CopyFromBitmap(wxICON(icon)); - SetIcon(_icon); - frame_main_menubar = new wxMenuBar(); - wxMenu *wxglade_tmp_menu; - wxglade_tmp_menu = new wxMenu(); - wxglade_tmp_menu->Append(wxID_YES, _("&Connect...\tCtrl-T"), _("Connect to a specific host.")); - Bind(wxEVT_MENU, &FrameMain::machine_connect, this, wxID_YES); - wxglade_tmp_menu->Append(wxID_REDO, _("&Listen"), _("Listen for an incoming connection.")); - Bind(wxEVT_MENU, &FrameMain::machine_listen, this, wxID_REDO); - wxglade_tmp_menu->Append(wxID_STOP, _("&Disconnect"), _("Terminate connection.")); - Bind(wxEVT_MENU, &FrameMain::machine_disconnect, this, wxID_STOP); - wxglade_tmp_menu->Append(wxID_FILE, _("Show &Log"), _("Show detailed log.")); - Bind(wxEVT_MENU, &FrameMain::machine_showlog, this, wxID_FILE); - wxglade_tmp_menu->Append(wxID_PREFERENCES, wxEmptyString, _("Change preferences.")); - Bind(wxEVT_MENU, &FrameMain::machine_preferences, this, wxID_PREFERENCES); - wxglade_tmp_menu->Append(wxID_SAVE, _("Take Screenshot"), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_screenshot, this, wxID_SAVE); - wxglade_tmp_menu->Append(ID_STATS_SAVE, _("Save Statistics..."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_save_stats, this, ID_STATS_SAVE); - wxglade_tmp_menu->Append(ID_INPUT_RECORD, _("Record Input"), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_input_record, this, ID_INPUT_RECORD); - wxglade_tmp_menu->Append(ID_INPUT_REPLAY, _("Replay Input"), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_input_replay, this, ID_INPUT_REPLAY); - wxglade_tmp_menu->AppendSeparator(); - wxglade_tmp_menu->Append(wxID_EXIT, wxEmptyString, _("Exit MultiVNC.")); - Bind(wxEVT_MENU, &FrameMain::machine_exit, this, wxID_EXIT); - frame_main_menubar->Append(wxglade_tmp_menu, _("&Machine")); - wxglade_tmp_menu = new wxMenu(); - wxglade_tmp_menu->Append(ID_TOOLBAR, _("Toolbar"), wxEmptyString, wxITEM_CHECK); - Bind(wxEVT_MENU, &FrameMain::view_toggletoolbar, this, ID_TOOLBAR); - wxglade_tmp_menu->Append(ID_DISCOVERED, _("Discovered Servers"), wxEmptyString, wxITEM_CHECK); - Bind(wxEVT_MENU, &FrameMain::view_togglediscovered, this, ID_DISCOVERED); - wxglade_tmp_menu->Append(ID_BOOKMARKS, _("Bookmarks"), wxEmptyString, wxITEM_CHECK); - Bind(wxEVT_MENU, &FrameMain::view_togglebookmarks, this, ID_BOOKMARKS); - wxglade_tmp_menu->Append(ID_STATISTICS, _("Statistics"), wxEmptyString, wxITEM_CHECK); - Bind(wxEVT_MENU, &FrameMain::view_togglestatistics, this, ID_STATISTICS); - wxMenu* wxglade_tmp_menu_sub = new wxMenu(); - wxglade_tmp_menu_sub->Append(ID_SEAMLESS_NORTH, _("North"), wxEmptyString, wxITEM_RADIO); - Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_NORTH); - wxglade_tmp_menu_sub->Append(ID_SEAMLESS_EAST, _("East"), wxEmptyString, wxITEM_RADIO); - Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_EAST); - wxglade_tmp_menu_sub->Append(ID_SEAMLESS_WEST, _("West"), wxEmptyString, wxITEM_RADIO); - Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_WEST); - wxglade_tmp_menu_sub->Append(ID_SEAMLESS_SOUTH, _("South"), wxEmptyString, wxITEM_RADIO); - Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_SOUTH); - wxglade_tmp_menu_sub->Append(ID_SEAMLESS_DISABLED, _("Disabled"), wxEmptyString, wxITEM_RADIO); - Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_DISABLED); - wxglade_tmp_menu->Append(ID_SEAMLESS, _("Edge Connector"), wxglade_tmp_menu_sub, wxEmptyString); - wxglade_tmp_menu->AppendSeparator(); - wxglade_tmp_menu->Append(ID_FULLSCREEN, _("Fullscreen\tF11"), wxEmptyString, wxITEM_CHECK); - Bind(wxEVT_MENU, &FrameMain::view_togglefullscreen, this, ID_FULLSCREEN); - wxglade_tmp_menu->Append(ID_ONE_TO_ONE, _("View 1:1"), wxEmptyString, wxITEM_CHECK); - Bind(wxEVT_MENU, &FrameMain::view_toggle1to1, this, ID_ONE_TO_ONE); - frame_main_menubar->Append(wxglade_tmp_menu, _("&View")); - wxglade_tmp_menu = new wxMenu(); - wxglade_tmp_menu->Append(wxID_ADD, _("&Add Bookmark"), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::bookmarks_add, this, wxID_ADD); - frame_main_menubar->Append(wxglade_tmp_menu, _("&Bookmarks")); - wxglade_tmp_menu = new wxMenu(); - wxglade_tmp_menu->Append(wxID_UP, _("&Share a Window"), _("Beam a window to the server.")); - Bind(wxEVT_MENU, &FrameMain::windowshare_start, this, wxID_UP); - wxglade_tmp_menu->Append(wxID_CANCEL, _("S&top Sharing Window"), _("Stop Window Sharing.")); - Bind(wxEVT_MENU, &FrameMain::windowshare_stop, this, wxID_CANCEL); - frame_main_menubar->Append(wxglade_tmp_menu, _("Window &Sharing")); - wxglade_tmp_menu = new wxMenu(); - wxglade_tmp_menu->Append(wxID_HELP, _("&Contents"), _("Show Help.")); - Bind(wxEVT_MENU, &FrameMain::help_contents, this, wxID_HELP); - wxglade_tmp_menu->Append(ID_ISSUE_LIST, _("Request a Feature / Report a Bug"), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::help_issue_list, this, ID_ISSUE_LIST); - wxglade_tmp_menu->Append(wxID_ABOUT, wxEmptyString, wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::help_about, this, wxID_ABOUT); - frame_main_menubar->Append(wxglade_tmp_menu, _("&Help")); - SetMenuBar(frame_main_menubar); - frame_main_statusbar = CreateStatusBar(1); - int frame_main_statusbar_widths[] = { -1 }; - frame_main_statusbar->SetStatusWidths(1, frame_main_statusbar_widths); - - // statusbar fields - const wxString frame_main_statusbar_fields[] = { - _("Status"), - }; - for(int i = 0; i < frame_main_statusbar->GetFieldsCount(); ++i) { - frame_main_statusbar->SetStatusText(frame_main_statusbar_fields[i], i); - } - frame_main_toolbar = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL|wxTB_TEXT|wxTB_NODIVIDER); - SetToolBar(frame_main_toolbar); - frame_main_toolbar->AddTool(wxID_YES, _("Connect"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "connect"), wxNullBitmap, wxITEM_NORMAL, _("Connect to a specific host."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_connect, this, wxID_YES); - frame_main_toolbar->AddTool(wxID_REDO, _("Listen"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "listen"), wxNullBitmap, wxITEM_NORMAL, _("Listen for an incoming connection."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_listen, this, wxID_REDO); - frame_main_toolbar->AddSeparator(); - frame_main_toolbar->AddTool(wxID_STOP, _("Disconnect"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "disconnect"), wxNullBitmap, wxITEM_NORMAL, _("Terminate connection."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_disconnect, this, wxID_STOP); - frame_main_toolbar->AddTool(ID_GRABKEYBOARD, _("Grab Keyboard"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "toggle-keyboard-grab"), wxNullBitmap, wxITEM_CHECK, _("Intercept all keyboard input. Allows you to use special keys that would otherwise be interpreted by the local computer."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_grabkeyboard, this, ID_GRABKEYBOARD); - frame_main_toolbar->AddTool(wxID_SAVE, _("Take Screenshot"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "screenshot"), wxNullBitmap, wxITEM_NORMAL, _("Take a screenshot of the current connection."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_screenshot, this, wxID_SAVE); - frame_main_toolbar->AddSeparator(); - frame_main_toolbar->AddTool(ID_INPUT_RECORD, _("Record Input"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "record"), wxNullBitmap, wxITEM_NORMAL, _("Record mouse and keyboard input for later replay as a macro."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_input_record, this, ID_INPUT_RECORD); - frame_main_toolbar->AddTool(ID_INPUT_REPLAY, _("Replay Input"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "replay"), wxNullBitmap, wxITEM_NORMAL, _("Replay a recorded mouse and keyboard input macro. If is held down while clicking this button, the macro is replayed in a loop."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::machine_input_replay, this, ID_INPUT_REPLAY); - frame_main_toolbar->AddSeparator(); - frame_main_toolbar->AddTool(ID_ONE_TO_ONE, _("View 1:1"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "one-to-one"), wxNullBitmap, wxITEM_CHECK, _("Toggle 1:1 view, disabling all scaling."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::view_toggle1to1, this, ID_ONE_TO_ONE); - frame_main_toolbar->AddTool(ID_FULLSCREEN, _("Fullscreen"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "fullscreen"), wxNullBitmap, wxITEM_NORMAL, _("Toggle fullscreen view."), wxEmptyString); - Bind(wxEVT_MENU, &FrameMain::view_togglefullscreen, this, ID_FULLSCREEN); - frame_main_toolbar->Realize(); - wxBoxSizer* sizer_top = new wxBoxSizer(wxHORIZONTAL); - panel_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_STATIC|wxTAB_TRAVERSAL); - sizer_top->Add(panel_top, 1, wxEXPAND, 0); - wxBoxSizer* sizer_splitwinmain = new wxBoxSizer(wxHORIZONTAL); - splitwin_main = new wxSplitterWindow(panel_top, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE); - splitwin_main->SetMinimumPaneSize(20); - sizer_splitwinmain->Add(splitwin_main, 1, wxALL|wxEXPAND, 3); - splitwin_main_pane_1 = new wxPanel(splitwin_main, wxID_ANY); - splitwin_main_pane_1->SetMinSize(wxSize(200, -1)); - wxBoxSizer* sizer_2 = new wxBoxSizer(wxHORIZONTAL); - splitwin_left = new wxSplitterWindow(splitwin_main_pane_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE); - splitwin_left->SetMinimumPaneSize(20); - sizer_2->Add(splitwin_left, 1, wxALL|wxEXPAND, 3); - splitwin_left_pane_1 = new wxPanel(splitwin_left, wxID_ANY); - wxStaticBoxSizer* sizer_services = new wxStaticBoxSizer(new wxStaticBox(splitwin_left_pane_1, wxID_ANY, _("Available VNC Servers")), wxHORIZONTAL); - const wxString *list_box_services_choices = NULL; - list_box_services = new wxListBox(splitwin_left_pane_1, ID_LISTBOX_SERVICES, wxDefaultPosition, wxDefaultSize, 0, list_box_services_choices, wxLB_HSCROLL|wxLB_NEEDED_SB|wxLB_SINGLE); - sizer_services->Add(list_box_services, 1, wxALL|wxEXPAND, 3); - splitwin_left_pane_2 = new wxPanel(splitwin_left, wxID_ANY); - wxStaticBoxSizer* sizer_bookmarks = new wxStaticBoxSizer(new wxStaticBox(splitwin_left_pane_2, wxID_ANY, _("Bookmarks")), wxHORIZONTAL); - const wxString *list_box_bookmarks_choices = NULL; - list_box_bookmarks = new wxListBox(splitwin_left_pane_2, ID_LISTBOX_BOOKMARKS, wxDefaultPosition, wxDefaultSize, 0, list_box_bookmarks_choices, wxLB_HSCROLL|wxLB_NEEDED_SB|wxLB_SINGLE); - sizer_bookmarks->Add(list_box_bookmarks, 1, wxALL|wxEXPAND, 3); - splitwin_main_pane_2 = new wxPanel(splitwin_main, wxID_ANY); - wxBoxSizer* sizer_notebook = new wxBoxSizer(wxHORIZONTAL); - notebook_connections = new wxNotebook(splitwin_main_pane_2, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); - sizer_notebook->Add(notebook_connections, 1, wxALL|wxEXPAND, 3); - - splitwin_main_pane_2->SetSizer(sizer_notebook); - splitwin_left_pane_2->SetSizer(sizer_bookmarks); - splitwin_left_pane_1->SetSizer(sizer_services); - splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); - splitwin_main_pane_1->SetSizer(sizer_2); - splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2, 31); - panel_top->SetSizer(sizer_splitwinmain); - SetSizer(sizer_top); - Layout(); - // end wxGlade -} - - -BEGIN_EVENT_TABLE(FrameMain, wxFrame) - // begin wxGlade: FrameMain::event_table - EVT_LISTBOX(ID_LISTBOX_SERVICES, FrameMain::listbox_services_select) - EVT_LISTBOX_DCLICK(ID_LISTBOX_SERVICES, FrameMain::listbox_services_dclick) - EVT_LISTBOX(ID_LISTBOX_BOOKMARKS, FrameMain::listbox_bookmarks_select) - EVT_LISTBOX_DCLICK(ID_LISTBOX_BOOKMARKS, FrameMain::listbox_bookmarks_dclick) - EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, FrameMain::notebook_connections_pagechanged) - // end wxGlade -END_EVENT_TABLE(); - - -void FrameMain::machine_connect(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_connect) not implemented yet")); -} - -void FrameMain::machine_listen(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_listen) not implemented yet")); -} - -void FrameMain::machine_disconnect(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_disconnect) not implemented yet")); -} - -void FrameMain::machine_showlog(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_showlog) not implemented yet")); -} - -void FrameMain::machine_preferences(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_preferences) not implemented yet")); -} - -void FrameMain::machine_screenshot(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_screenshot) not implemented yet")); -} - -void FrameMain::machine_save_stats(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_save_stats) not implemented yet")); -} - -void FrameMain::machine_input_record(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_input_record) not implemented yet")); -} - -void FrameMain::machine_input_replay(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_input_replay) not implemented yet")); -} - -void FrameMain::machine_exit(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_exit) not implemented yet")); -} - -void FrameMain::view_toggletoolbar(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_toggletoolbar) not implemented yet")); -} - -void FrameMain::view_togglediscovered(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_togglediscovered) not implemented yet")); -} - -void FrameMain::view_togglebookmarks(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_togglebookmarks) not implemented yet")); -} - -void FrameMain::view_togglestatistics(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_togglestatistics) not implemented yet")); -} - -void FrameMain::view_seamless(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_seamless) not implemented yet")); -} - -void FrameMain::view_togglefullscreen(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_togglefullscreen) not implemented yet")); -} - -void FrameMain::view_toggle1to1(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::view_toggle1to1) not implemented yet")); -} - -void FrameMain::bookmarks_add(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::bookmarks_add) not implemented yet")); -} - -void FrameMain::windowshare_start(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::windowshare_start) not implemented yet")); -} - -void FrameMain::windowshare_stop(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::windowshare_stop) not implemented yet")); -} - -void FrameMain::help_contents(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::help_contents) not implemented yet")); -} - -void FrameMain::help_issue_list(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::help_issue_list) not implemented yet")); -} - -void FrameMain::help_about(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::help_about) not implemented yet")); -} - -void FrameMain::machine_grabkeyboard(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::machine_grabkeyboard) not implemented yet")); -} - -void FrameMain::listbox_services_select(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::listbox_services_select) not implemented yet")); -} - -void FrameMain::listbox_services_dclick(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::listbox_services_dclick) not implemented yet")); -} - -void FrameMain::listbox_bookmarks_select(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::listbox_bookmarks_select) not implemented yet")); -} - -void FrameMain::listbox_bookmarks_dclick(wxCommandEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::listbox_bookmarks_dclick) not implemented yet")); -} - -void FrameMain::notebook_connections_pagechanged(wxBookCtrlEvent &event) // wxGlade: FrameMain. -{ - event.Skip(); - // notify the user that he hasn't implemented the event handler yet - wxLogDebug(wxT("Event handler (FrameMain::notebook_connections_pagechanged) not implemented yet")); -} - - -// wxGlade: add FrameMain event handlers - +// -*- C++ -*- +// +// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 +// +// Example for compiling a single file project under Linux using g++: +// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp +// +// Example for compiling a multi file project under Linux using g++: +// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp +// + +#include +#include "FrameMain.h" + +// begin wxGlade: ::extracode +// end wxGlade + + +FrameMain::FrameMain(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): + wxFrame(parent, id, title, pos, size, wxDEFAULT_FRAME_STYLE) +{ + // begin wxGlade: FrameMain::FrameMain + SetSize(wxSize(1089, 1030)); + SetTitle(_("MultiVNC")); + wxIcon _icon; + _icon.CopyFromBitmap(wxICON(icon)); + SetIcon(_icon); + frame_main_menubar = new wxMenuBar(); + wxMenu *wxglade_tmp_menu; + wxglade_tmp_menu = new wxMenu(); + wxglade_tmp_menu->Append(wxID_YES, _("&Connect...\tCtrl-T"), _("Connect to a specific host.")); + Bind(wxEVT_MENU, &FrameMain::machine_connect, this, wxID_YES); + wxglade_tmp_menu->Append(wxID_REDO, _("&Listen"), _("Listen for an incoming connection.")); + Bind(wxEVT_MENU, &FrameMain::machine_listen, this, wxID_REDO); + wxglade_tmp_menu->Append(wxID_STOP, _("&Disconnect"), _("Terminate connection.")); + Bind(wxEVT_MENU, &FrameMain::machine_disconnect, this, wxID_STOP); + wxglade_tmp_menu->Append(wxID_FILE, _("Show &Log"), _("Show detailed log.")); + Bind(wxEVT_MENU, &FrameMain::machine_showlog, this, wxID_FILE); + wxglade_tmp_menu->Append(wxID_PREFERENCES, wxEmptyString, _("Change preferences.")); + Bind(wxEVT_MENU, &FrameMain::machine_preferences, this, wxID_PREFERENCES); + wxglade_tmp_menu->Append(wxID_SAVE, _("Take Screenshot"), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_screenshot, this, wxID_SAVE); + wxglade_tmp_menu->Append(ID_STATS_SAVE, _("Save Statistics..."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_save_stats, this, ID_STATS_SAVE); + wxglade_tmp_menu->Append(ID_INPUT_RECORD, _("Record Input"), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_input_record, this, ID_INPUT_RECORD); + wxglade_tmp_menu->Append(ID_INPUT_REPLAY, _("Replay Input"), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_input_replay, this, ID_INPUT_REPLAY); + wxglade_tmp_menu->AppendSeparator(); + wxglade_tmp_menu->Append(wxID_EXIT, wxEmptyString, _("Exit MultiVNC.")); + Bind(wxEVT_MENU, &FrameMain::machine_exit, this, wxID_EXIT); + frame_main_menubar->Append(wxglade_tmp_menu, _("&Machine")); + wxglade_tmp_menu = new wxMenu(); + wxglade_tmp_menu->Append(ID_TOOLBAR, _("Toolbar"), wxEmptyString, wxITEM_CHECK); + Bind(wxEVT_MENU, &FrameMain::view_toggletoolbar, this, ID_TOOLBAR); + wxglade_tmp_menu->Append(ID_DISCOVERED, _("Discovered Servers"), wxEmptyString, wxITEM_CHECK); + Bind(wxEVT_MENU, &FrameMain::view_togglediscovered, this, ID_DISCOVERED); + wxglade_tmp_menu->Append(ID_BOOKMARKS, _("Bookmarks"), wxEmptyString, wxITEM_CHECK); + Bind(wxEVT_MENU, &FrameMain::view_togglebookmarks, this, ID_BOOKMARKS); + wxglade_tmp_menu->Append(ID_STATISTICS, _("Statistics"), wxEmptyString, wxITEM_CHECK); + Bind(wxEVT_MENU, &FrameMain::view_togglestatistics, this, ID_STATISTICS); + wxMenu* wxglade_tmp_menu_sub = new wxMenu(); + wxglade_tmp_menu_sub->Append(ID_SEAMLESS_NORTH, _("North"), wxEmptyString, wxITEM_RADIO); + Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_NORTH); + wxglade_tmp_menu_sub->Append(ID_SEAMLESS_EAST, _("East"), wxEmptyString, wxITEM_RADIO); + Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_EAST); + wxglade_tmp_menu_sub->Append(ID_SEAMLESS_WEST, _("West"), wxEmptyString, wxITEM_RADIO); + Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_WEST); + wxglade_tmp_menu_sub->Append(ID_SEAMLESS_SOUTH, _("South"), wxEmptyString, wxITEM_RADIO); + Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_SOUTH); + wxglade_tmp_menu_sub->Append(ID_SEAMLESS_DISABLED, _("Disabled"), wxEmptyString, wxITEM_RADIO); + Bind(wxEVT_MENU, &FrameMain::view_seamless, this, ID_SEAMLESS_DISABLED); + wxglade_tmp_menu->Append(ID_SEAMLESS, _("Edge Connector"), wxglade_tmp_menu_sub, wxEmptyString); + wxglade_tmp_menu->AppendSeparator(); + wxglade_tmp_menu->Append(ID_FULLSCREEN, _("Fullscreen\tF11"), wxEmptyString, wxITEM_CHECK); + Bind(wxEVT_MENU, &FrameMain::view_togglefullscreen, this, ID_FULLSCREEN); + wxglade_tmp_menu->Append(ID_ONE_TO_ONE, _("View 1:1"), wxEmptyString, wxITEM_CHECK); + Bind(wxEVT_MENU, &FrameMain::view_toggle1to1, this, ID_ONE_TO_ONE); + frame_main_menubar->Append(wxglade_tmp_menu, _("&View")); + wxglade_tmp_menu = new wxMenu(); + wxglade_tmp_menu->Append(wxID_ADD, _("&Add Bookmark"), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::bookmarks_add, this, wxID_ADD); + frame_main_menubar->Append(wxglade_tmp_menu, _("&Bookmarks")); + wxglade_tmp_menu = new wxMenu(); + wxglade_tmp_menu->Append(wxID_UP, _("&Share a Window"), _("Beam a window to the server.")); + Bind(wxEVT_MENU, &FrameMain::windowshare_start, this, wxID_UP); + wxglade_tmp_menu->Append(wxID_CANCEL, _("S&top Sharing Window"), _("Stop Window Sharing.")); + Bind(wxEVT_MENU, &FrameMain::windowshare_stop, this, wxID_CANCEL); + frame_main_menubar->Append(wxglade_tmp_menu, _("Window &Sharing")); + wxglade_tmp_menu = new wxMenu(); + wxglade_tmp_menu->Append(wxID_HELP, _("&Contents"), _("Show Help.")); + Bind(wxEVT_MENU, &FrameMain::help_contents, this, wxID_HELP); + wxglade_tmp_menu->Append(ID_ISSUE_LIST, _("Request a Feature / Report a Bug"), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::help_issue_list, this, ID_ISSUE_LIST); + wxglade_tmp_menu->Append(wxID_ABOUT, wxEmptyString, wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::help_about, this, wxID_ABOUT); + frame_main_menubar->Append(wxglade_tmp_menu, _("&Help")); + SetMenuBar(frame_main_menubar); + frame_main_statusbar = CreateStatusBar(1); + int frame_main_statusbar_widths[] = { -1 }; + frame_main_statusbar->SetStatusWidths(1, frame_main_statusbar_widths); + + // statusbar fields + const wxString frame_main_statusbar_fields[] = { + _("Status"), + }; + for(int i = 0; i < frame_main_statusbar->GetFieldsCount(); ++i) { + frame_main_statusbar->SetStatusText(frame_main_statusbar_fields[i], i); + } + frame_main_toolbar = new wxToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL|wxTB_TEXT|wxTB_NODIVIDER); + SetToolBar(frame_main_toolbar); + frame_main_toolbar->AddTool(wxID_YES, _("Connect"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "connect"), wxNullBitmap, wxITEM_NORMAL, _("Connect to a specific host."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_connect, this, wxID_YES); + frame_main_toolbar->AddTool(wxID_REDO, _("Listen"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "listen"), wxNullBitmap, wxITEM_NORMAL, _("Listen for an incoming connection."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_listen, this, wxID_REDO); + frame_main_toolbar->AddSeparator(); + frame_main_toolbar->AddTool(wxID_STOP, _("Disconnect"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "disconnect"), wxNullBitmap, wxITEM_NORMAL, _("Terminate connection."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_disconnect, this, wxID_STOP); + frame_main_toolbar->AddTool(ID_GRABKEYBOARD, _("Grab Keyboard"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "toggle-keyboard-grab"), wxNullBitmap, wxITEM_CHECK, _("Intercept all keyboard input. Allows you to use special keys that would otherwise be interpreted by the local computer."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_grabkeyboard, this, ID_GRABKEYBOARD); + frame_main_toolbar->AddTool(wxID_SAVE, _("Take Screenshot"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "screenshot"), wxNullBitmap, wxITEM_NORMAL, _("Take a screenshot of the current connection."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_screenshot, this, wxID_SAVE); + frame_main_toolbar->AddSeparator(); + frame_main_toolbar->AddTool(ID_INPUT_RECORD, _("Record Input"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "record"), wxNullBitmap, wxITEM_NORMAL, _("Record mouse and keyboard input for later replay as a macro."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_input_record, this, ID_INPUT_RECORD); + frame_main_toolbar->AddTool(ID_INPUT_REPLAY, _("Replay Input"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "replay"), wxNullBitmap, wxITEM_NORMAL, _("Replay a recorded mouse and keyboard input macro. If is held down while clicking this button, the macro is replayed in a loop."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::machine_input_replay, this, ID_INPUT_REPLAY); + frame_main_toolbar->AddSeparator(); + frame_main_toolbar->AddTool(ID_ONE_TO_ONE, _("View 1:1"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "one-to-one"), wxNullBitmap, wxITEM_CHECK, _("Toggle 1:1 view, disabling all scaling."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::view_toggle1to1, this, ID_ONE_TO_ONE); + frame_main_toolbar->AddTool(ID_FULLSCREEN, _("Fullscreen"), bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "fullscreen"), wxNullBitmap, wxITEM_NORMAL, _("Toggle fullscreen view."), wxEmptyString); + Bind(wxEVT_MENU, &FrameMain::view_togglefullscreen, this, ID_FULLSCREEN); + frame_main_toolbar->Realize(); + wxBoxSizer* sizer_top = new wxBoxSizer(wxHORIZONTAL); + panel_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_STATIC|wxTAB_TRAVERSAL); + sizer_top->Add(panel_top, 1, wxEXPAND, 0); + wxBoxSizer* sizer_splitwinmain = new wxBoxSizer(wxHORIZONTAL); + splitwin_main = new wxSplitterWindow(panel_top, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE); + splitwin_main->SetMinimumPaneSize(20); + sizer_splitwinmain->Add(splitwin_main, 1, wxALL|wxEXPAND, 3); + splitwin_main_pane_1 = new wxPanel(splitwin_main, wxID_ANY); + splitwin_main_pane_1->SetMinSize(wxSize(200, -1)); + wxBoxSizer* sizer_2 = new wxBoxSizer(wxHORIZONTAL); + splitwin_left = new wxSplitterWindow(splitwin_main_pane_1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_LIVE_UPDATE); + splitwin_left->SetMinimumPaneSize(20); + sizer_2->Add(splitwin_left, 1, wxALL|wxEXPAND, 3); + splitwin_left_pane_1 = new wxPanel(splitwin_left, wxID_ANY); + wxStaticBoxSizer* sizer_services = new wxStaticBoxSizer(new wxStaticBox(splitwin_left_pane_1, wxID_ANY, _("Available VNC Servers")), wxHORIZONTAL); + const wxString *list_box_services_choices = NULL; + list_box_services = new wxListBox(splitwin_left_pane_1, ID_LISTBOX_SERVICES, wxDefaultPosition, wxDefaultSize, 0, list_box_services_choices, wxLB_HSCROLL|wxLB_NEEDED_SB|wxLB_SINGLE); + sizer_services->Add(list_box_services, 1, wxALL|wxEXPAND, 3); + splitwin_left_pane_2 = new wxPanel(splitwin_left, wxID_ANY); + wxStaticBoxSizer* sizer_bookmarks = new wxStaticBoxSizer(new wxStaticBox(splitwin_left_pane_2, wxID_ANY, _("Bookmarks")), wxHORIZONTAL); + const wxString *list_box_bookmarks_choices = NULL; + list_box_bookmarks = new wxListBox(splitwin_left_pane_2, ID_LISTBOX_BOOKMARKS, wxDefaultPosition, wxDefaultSize, 0, list_box_bookmarks_choices, wxLB_HSCROLL|wxLB_NEEDED_SB|wxLB_SINGLE); + sizer_bookmarks->Add(list_box_bookmarks, 1, wxALL|wxEXPAND, 3); + splitwin_main_pane_2 = new wxPanel(splitwin_main, wxID_ANY); + wxBoxSizer* sizer_notebook = new wxBoxSizer(wxHORIZONTAL); + notebook_connections = new wxNotebook(splitwin_main_pane_2, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + sizer_notebook->Add(notebook_connections, 1, wxALL|wxEXPAND, 3); + + splitwin_main_pane_2->SetSizer(sizer_notebook); + splitwin_left_pane_2->SetSizer(sizer_bookmarks); + splitwin_left_pane_1->SetSizer(sizer_services); + splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); + splitwin_main_pane_1->SetSizer(sizer_2); + splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2, 31); + panel_top->SetSizer(sizer_splitwinmain); + SetSizer(sizer_top); + Layout(); + // end wxGlade +} + + +BEGIN_EVENT_TABLE(FrameMain, wxFrame) + // begin wxGlade: FrameMain::event_table + EVT_LISTBOX(ID_LISTBOX_SERVICES, FrameMain::listbox_services_select) + EVT_LISTBOX_DCLICK(ID_LISTBOX_SERVICES, FrameMain::listbox_services_dclick) + EVT_LISTBOX(ID_LISTBOX_BOOKMARKS, FrameMain::listbox_bookmarks_select) + EVT_LISTBOX_DCLICK(ID_LISTBOX_BOOKMARKS, FrameMain::listbox_bookmarks_dclick) + EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, FrameMain::notebook_connections_pagechanged) + // end wxGlade +END_EVENT_TABLE(); + + +void FrameMain::machine_connect(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_connect) not implemented yet")); +} + +void FrameMain::machine_listen(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_listen) not implemented yet")); +} + +void FrameMain::machine_disconnect(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_disconnect) not implemented yet")); +} + +void FrameMain::machine_showlog(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_showlog) not implemented yet")); +} + +void FrameMain::machine_preferences(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_preferences) not implemented yet")); +} + +void FrameMain::machine_screenshot(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_screenshot) not implemented yet")); +} + +void FrameMain::machine_save_stats(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_save_stats) not implemented yet")); +} + +void FrameMain::machine_input_record(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_input_record) not implemented yet")); +} + +void FrameMain::machine_input_replay(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_input_replay) not implemented yet")); +} + +void FrameMain::machine_exit(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_exit) not implemented yet")); +} + +void FrameMain::view_toggletoolbar(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_toggletoolbar) not implemented yet")); +} + +void FrameMain::view_togglediscovered(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_togglediscovered) not implemented yet")); +} + +void FrameMain::view_togglebookmarks(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_togglebookmarks) not implemented yet")); +} + +void FrameMain::view_togglestatistics(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_togglestatistics) not implemented yet")); +} + +void FrameMain::view_seamless(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_seamless) not implemented yet")); +} + +void FrameMain::view_togglefullscreen(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_togglefullscreen) not implemented yet")); +} + +void FrameMain::view_toggle1to1(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::view_toggle1to1) not implemented yet")); +} + +void FrameMain::bookmarks_add(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::bookmarks_add) not implemented yet")); +} + +void FrameMain::windowshare_start(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::windowshare_start) not implemented yet")); +} + +void FrameMain::windowshare_stop(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::windowshare_stop) not implemented yet")); +} + +void FrameMain::help_contents(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::help_contents) not implemented yet")); +} + +void FrameMain::help_issue_list(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::help_issue_list) not implemented yet")); +} + +void FrameMain::help_about(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::help_about) not implemented yet")); +} + +void FrameMain::machine_grabkeyboard(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::machine_grabkeyboard) not implemented yet")); +} + +void FrameMain::listbox_services_select(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::listbox_services_select) not implemented yet")); +} + +void FrameMain::listbox_services_dclick(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::listbox_services_dclick) not implemented yet")); +} + +void FrameMain::listbox_bookmarks_select(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::listbox_bookmarks_select) not implemented yet")); +} + +void FrameMain::listbox_bookmarks_dclick(wxCommandEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::listbox_bookmarks_dclick) not implemented yet")); +} + +void FrameMain::notebook_connections_pagechanged(wxBookCtrlEvent &event) // wxGlade: FrameMain. +{ + event.Skip(); + // notify the user that he hasn't implemented the event handler yet + wxLogDebug(wxT("Event handler (FrameMain::notebook_connections_pagechanged) not implemented yet")); +} + + +// wxGlade: add FrameMain event handlers + diff --git a/src/gui/FrameMain.h b/src/gui/FrameMain.h index d04ad3f7..3e87fedc 100644 --- a/src/gui/FrameMain.h +++ b/src/gui/FrameMain.h @@ -1,101 +1,101 @@ -// -*- C++ -*- -// -// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 -// -// Example for compiling a single file project under Linux using g++: -// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp -// -// Example for compiling a multi file project under Linux using g++: -// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp -// - -#ifndef FRAMEMAIN_H -#define FRAMEMAIN_H - -#include -#include -#include - -#ifndef APP_CATALOG -#define APP_CATALOG "app" // replace with the appropriate catalog name -#endif - - -// begin wxGlade: ::dependencies -#include -#include -// end wxGlade - -// begin wxGlade: ::extracode -#include "bitmapFromMem.h" -#ifndef ICON_XPM -#define ICON_XPM -#include "res/multivnc.xpm" -#endif -#include "evtids.h" -#include "evtids.h" -// end wxGlade - - -class FrameMain: public wxFrame { -public: - // begin wxGlade: FrameMain::ids - // end wxGlade - - FrameMain(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE); - -private: - -protected: - // begin wxGlade: FrameMain::attributes - wxMenuBar* frame_main_menubar; - wxStatusBar* frame_main_statusbar; - wxToolBar* frame_main_toolbar; - wxPanel* panel_top; - wxSplitterWindow* splitwin_main; - wxPanel* splitwin_main_pane_1; - wxSplitterWindow* splitwin_left; - wxPanel* splitwin_left_pane_1; - wxListBox* list_box_services; - wxPanel* splitwin_left_pane_2; - wxListBox* list_box_bookmarks; - wxPanel* splitwin_main_pane_2; - wxNotebook* notebook_connections; - // end wxGlade - - DECLARE_EVENT_TABLE(); - -public: - virtual void machine_connect(wxCommandEvent &event); // wxGlade: - virtual void machine_listen(wxCommandEvent &event); // wxGlade: - virtual void machine_disconnect(wxCommandEvent &event); // wxGlade: - virtual void machine_showlog(wxCommandEvent &event); // wxGlade: - virtual void machine_preferences(wxCommandEvent &event); // wxGlade: - virtual void machine_screenshot(wxCommandEvent &event); // wxGlade: - virtual void machine_save_stats(wxCommandEvent &event); // wxGlade: - virtual void machine_input_record(wxCommandEvent &event); // wxGlade: - virtual void machine_input_replay(wxCommandEvent &event); // wxGlade: - virtual void machine_exit(wxCommandEvent &event); // wxGlade: - virtual void view_toggletoolbar(wxCommandEvent &event); // wxGlade: - virtual void view_togglediscovered(wxCommandEvent &event); // wxGlade: - virtual void view_togglebookmarks(wxCommandEvent &event); // wxGlade: - virtual void view_togglestatistics(wxCommandEvent &event); // wxGlade: - virtual void view_seamless(wxCommandEvent &event); // wxGlade: - virtual void view_togglefullscreen(wxCommandEvent &event); // wxGlade: - virtual void view_toggle1to1(wxCommandEvent &event); // wxGlade: - virtual void bookmarks_add(wxCommandEvent &event); // wxGlade: - virtual void windowshare_start(wxCommandEvent &event); // wxGlade: - virtual void windowshare_stop(wxCommandEvent &event); // wxGlade: - virtual void help_contents(wxCommandEvent &event); // wxGlade: - virtual void help_issue_list(wxCommandEvent &event); // wxGlade: - virtual void help_about(wxCommandEvent &event); // wxGlade: - virtual void machine_grabkeyboard(wxCommandEvent &event); // wxGlade: - virtual void listbox_services_select(wxCommandEvent &event); // wxGlade: - virtual void listbox_services_dclick(wxCommandEvent &event); // wxGlade: - virtual void listbox_bookmarks_select(wxCommandEvent &event); // wxGlade: - virtual void listbox_bookmarks_dclick(wxCommandEvent &event); // wxGlade: - virtual void notebook_connections_pagechanged(wxBookCtrlEvent &event); // wxGlade: -}; // wxGlade: end class - - -#endif // FRAMEMAIN_H +// -*- C++ -*- +// +// generated by wxGlade 1.0.4 on Sat Dec 28 19:37:55 2024 +// +// Example for compiling a single file project under Linux using g++: +// g++ MyApp.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp +// +// Example for compiling a multi file project under Linux using g++: +// g++ main.cpp $(wx-config --libs) $(wx-config --cxxflags) -o MyApp Dialog1.cpp Frame1.cpp +// + +#ifndef FRAMEMAIN_H +#define FRAMEMAIN_H + +#include +#include +#include + +#ifndef APP_CATALOG +#define APP_CATALOG "app" // replace with the appropriate catalog name +#endif + + +// begin wxGlade: ::dependencies +#include +#include +// end wxGlade + +// begin wxGlade: ::extracode +#include "bitmapFromMem.h" +#ifndef ICON_XPM +#define ICON_XPM +#include "res/multivnc.xpm" +#endif +#include "evtids.h" +#include "evtids.h" +// end wxGlade + + +class FrameMain: public wxFrame { +public: + // begin wxGlade: FrameMain::ids + // end wxGlade + + FrameMain(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos=wxDefaultPosition, const wxSize& size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE); + +private: + +protected: + // begin wxGlade: FrameMain::attributes + wxMenuBar* frame_main_menubar; + wxStatusBar* frame_main_statusbar; + wxToolBar* frame_main_toolbar; + wxPanel* panel_top; + wxSplitterWindow* splitwin_main; + wxPanel* splitwin_main_pane_1; + wxSplitterWindow* splitwin_left; + wxPanel* splitwin_left_pane_1; + wxListBox* list_box_services; + wxPanel* splitwin_left_pane_2; + wxListBox* list_box_bookmarks; + wxPanel* splitwin_main_pane_2; + wxNotebook* notebook_connections; + // end wxGlade + + DECLARE_EVENT_TABLE(); + +public: + virtual void machine_connect(wxCommandEvent &event); // wxGlade: + virtual void machine_listen(wxCommandEvent &event); // wxGlade: + virtual void machine_disconnect(wxCommandEvent &event); // wxGlade: + virtual void machine_showlog(wxCommandEvent &event); // wxGlade: + virtual void machine_preferences(wxCommandEvent &event); // wxGlade: + virtual void machine_screenshot(wxCommandEvent &event); // wxGlade: + virtual void machine_save_stats(wxCommandEvent &event); // wxGlade: + virtual void machine_input_record(wxCommandEvent &event); // wxGlade: + virtual void machine_input_replay(wxCommandEvent &event); // wxGlade: + virtual void machine_exit(wxCommandEvent &event); // wxGlade: + virtual void view_toggletoolbar(wxCommandEvent &event); // wxGlade: + virtual void view_togglediscovered(wxCommandEvent &event); // wxGlade: + virtual void view_togglebookmarks(wxCommandEvent &event); // wxGlade: + virtual void view_togglestatistics(wxCommandEvent &event); // wxGlade: + virtual void view_seamless(wxCommandEvent &event); // wxGlade: + virtual void view_togglefullscreen(wxCommandEvent &event); // wxGlade: + virtual void view_toggle1to1(wxCommandEvent &event); // wxGlade: + virtual void bookmarks_add(wxCommandEvent &event); // wxGlade: + virtual void windowshare_start(wxCommandEvent &event); // wxGlade: + virtual void windowshare_stop(wxCommandEvent &event); // wxGlade: + virtual void help_contents(wxCommandEvent &event); // wxGlade: + virtual void help_issue_list(wxCommandEvent &event); // wxGlade: + virtual void help_about(wxCommandEvent &event); // wxGlade: + virtual void machine_grabkeyboard(wxCommandEvent &event); // wxGlade: + virtual void listbox_services_select(wxCommandEvent &event); // wxGlade: + virtual void listbox_services_dclick(wxCommandEvent &event); // wxGlade: + virtual void listbox_bookmarks_select(wxCommandEvent &event); // wxGlade: + virtual void listbox_bookmarks_dclick(wxCommandEvent &event); // wxGlade: + virtual void notebook_connections_pagechanged(wxBookCtrlEvent &event); // wxGlade: +}; // wxGlade: end class + + +#endif // FRAMEMAIN_H diff --git a/src/gui/MyDialogSettings.cpp b/src/gui/MyDialogSettings.cpp index 42829080..8926d7fa 100644 --- a/src/gui/MyDialogSettings.cpp +++ b/src/gui/MyDialogSettings.cpp @@ -1,103 +1,103 @@ - -#include -#include "MyDialogSettings.h" -#include "VNCConn.h" -#include "dfltcfg.h" - - - - -MyDialogSettings::MyDialogSettings(wxWindow* parent, int id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): - DialogSettings(parent, id, title, pos, size, style) -{ - // this creates standard buttons in platform specific order - wxSizer* std_buttonsizer = CreateButtonSizer(wxOK | wxCANCEL); - // Add it to the dialog's main sizer - GetSizer()->Add(std_buttonsizer, 0, wxEXPAND | wxALL, 3); - // and resize to fit - Fit(); - - // get current settings - int value; - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Read(K_STATSAUTOSAVE, &value, V_STATSAUTOSAVE); - checkbox_stats_save->SetValue(value); - pConfig->Read(K_LOGSAVETOFILE, &value, V_LOGSAVETOFILE); - checkbox_logfile->SetValue(value); - pConfig->Read(K_MULTICAST, &value, V_MULTICAST); - checkbox_multicast->SetValue(value); - pConfig->Read(K_MULTICASTSOCKETRECVBUF, &value, V_MULTICASTSOCKETRECVBUF); - slider_socketrecvbuf->SetValue(value); - pConfig->Read(K_MULTICASTRECVBUF, &value, V_MULTICASTRECVBUF); - slider_recvbuf->SetValue(value); - pConfig->Read(K_FASTREQUEST, &value, V_FASTREQUEST); - checkbox_fastrequest->SetValue(value); - pConfig->Read(K_FASTREQUESTINTERVAL, &value, V_FASTREQUESTINTERVAL); - slider_fastrequest->SetValue(value); - pConfig->Read(K_QOS_EF, &value, V_QOS_EF); - checkbox_qos_ef->SetValue(value); - - // adopt socket recv buf slider to OS-dependent max - int os_dependent_max = VNCConn::getMaxSocketRecvBufSize(); - if(slider_socketrecvbuf->GetValue() > os_dependent_max) - slider_socketrecvbuf->SetValue(os_dependent_max); // needed on MacOS at least - if(slider_socketrecvbuf->GetMax() > os_dependent_max) - slider_socketrecvbuf->SetRange(slider_socketrecvbuf->GetMin(), os_dependent_max); - - // read in encodings settings - pConfig->Read(K_ENC_COPYRECT, &value, V_ENC_COPYRECT); - checkbox_enc_copyrect->SetValue(value); - pConfig->Read(K_ENC_RRE, &value, V_ENC_RRE); - checkbox_enc_rre->SetValue(value); - pConfig->Read(K_ENC_CORRE, &value, V_ENC_CORRE); - checkbox_enc_corre->SetValue(value); - pConfig->Read(K_ENC_ZRLE, &value, V_ENC_ZRLE); - checkbox_enc_zrle->SetValue(value); - pConfig->Read(K_ENC_ZYWRLE, &value, V_ENC_ZYWRLE); - checkbox_enc_zywrle->SetValue(value); - pConfig->Read(K_ENC_HEXTILE, &value, V_ENC_HEXTILE); - checkbox_enc_hextile->SetValue(value); - pConfig->Read(K_ENC_ZLIB, &value, V_ENC_ZLIB); - checkbox_enc_zlib->SetValue(value); - pConfig->Read(K_ENC_ZLIBHEX, &value, V_ENC_ZLIBHEX); - checkbox_enc_zlibhex->SetValue(value); - pConfig->Read(K_ENC_ULTRA, &value, V_ENC_ULTRA); - checkbox_enc_ultra->SetValue(value); - pConfig->Read(K_ENC_TIGHT, &value, V_ENC_TIGHT); - checkbox_enc_tight->SetValue(value); - - // but only show encodings we support in this build -#ifndef LIBVNCSERVER_HAVE_LIBZ - checkbox_enc_zrle->Disable(); - checkbox_enc_zrle->SetValue(false); - checkbox_enc_zywrle->Disable(); - checkbox_enc_zywrle->SetValue(false); - checkbox_enc_zlib->Disable(); - checkbox_enc_zlib->SetValue(false); - checkbox_enc_zlibhex->Disable(); - checkbox_enc_zlibhex->SetValue(false); - checkbox_enc_tight->Disable(); - checkbox_enc_tight->SetValue(false); -#endif -#ifndef LIBVNCSERVER_HAVE_LIBJPEG - checkbox_enc_tight->Disable(); - checkbox_enc_tight->SetValue(false); -#endif - - - pConfig->Read(K_COMPRESSLEVEL, &value, V_COMPRESSLEVEL); - slider_compresslevel->SetValue(value); - pConfig->Read(K_QUALITY, &value, V_QUALITY); - slider_quality->SetValue(value); -} - - -MyDialogSettings::~MyDialogSettings() -{ - - -} - - - - + +#include +#include "MyDialogSettings.h" +#include "VNCConn.h" +#include "dfltcfg.h" + + + + +MyDialogSettings::MyDialogSettings(wxWindow* parent, int id, const wxString& title, const wxPoint& pos, const wxSize& size, long style): + DialogSettings(parent, id, title, pos, size, style) +{ + // this creates standard buttons in platform specific order + wxSizer* std_buttonsizer = CreateButtonSizer(wxOK | wxCANCEL); + // Add it to the dialog's main sizer + GetSizer()->Add(std_buttonsizer, 0, wxEXPAND | wxALL, 3); + // and resize to fit + Fit(); + + // get current settings + int value; + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Read(K_STATSAUTOSAVE, &value, V_STATSAUTOSAVE); + checkbox_stats_save->SetValue(value); + pConfig->Read(K_LOGSAVETOFILE, &value, V_LOGSAVETOFILE); + checkbox_logfile->SetValue(value); + pConfig->Read(K_MULTICAST, &value, V_MULTICAST); + checkbox_multicast->SetValue(value); + pConfig->Read(K_MULTICASTSOCKETRECVBUF, &value, V_MULTICASTSOCKETRECVBUF); + slider_socketrecvbuf->SetValue(value); + pConfig->Read(K_MULTICASTRECVBUF, &value, V_MULTICASTRECVBUF); + slider_recvbuf->SetValue(value); + pConfig->Read(K_FASTREQUEST, &value, V_FASTREQUEST); + checkbox_fastrequest->SetValue(value); + pConfig->Read(K_FASTREQUESTINTERVAL, &value, V_FASTREQUESTINTERVAL); + slider_fastrequest->SetValue(value); + pConfig->Read(K_QOS_EF, &value, V_QOS_EF); + checkbox_qos_ef->SetValue(value); + + // adopt socket recv buf slider to OS-dependent max + int os_dependent_max = VNCConn::getMaxSocketRecvBufSize(); + if(slider_socketrecvbuf->GetValue() > os_dependent_max) + slider_socketrecvbuf->SetValue(os_dependent_max); // needed on MacOS at least + if(slider_socketrecvbuf->GetMax() > os_dependent_max) + slider_socketrecvbuf->SetRange(slider_socketrecvbuf->GetMin(), os_dependent_max); + + // read in encodings settings + pConfig->Read(K_ENC_COPYRECT, &value, V_ENC_COPYRECT); + checkbox_enc_copyrect->SetValue(value); + pConfig->Read(K_ENC_RRE, &value, V_ENC_RRE); + checkbox_enc_rre->SetValue(value); + pConfig->Read(K_ENC_CORRE, &value, V_ENC_CORRE); + checkbox_enc_corre->SetValue(value); + pConfig->Read(K_ENC_ZRLE, &value, V_ENC_ZRLE); + checkbox_enc_zrle->SetValue(value); + pConfig->Read(K_ENC_ZYWRLE, &value, V_ENC_ZYWRLE); + checkbox_enc_zywrle->SetValue(value); + pConfig->Read(K_ENC_HEXTILE, &value, V_ENC_HEXTILE); + checkbox_enc_hextile->SetValue(value); + pConfig->Read(K_ENC_ZLIB, &value, V_ENC_ZLIB); + checkbox_enc_zlib->SetValue(value); + pConfig->Read(K_ENC_ZLIBHEX, &value, V_ENC_ZLIBHEX); + checkbox_enc_zlibhex->SetValue(value); + pConfig->Read(K_ENC_ULTRA, &value, V_ENC_ULTRA); + checkbox_enc_ultra->SetValue(value); + pConfig->Read(K_ENC_TIGHT, &value, V_ENC_TIGHT); + checkbox_enc_tight->SetValue(value); + + // but only show encodings we support in this build +#ifndef LIBVNCSERVER_HAVE_LIBZ + checkbox_enc_zrle->Disable(); + checkbox_enc_zrle->SetValue(false); + checkbox_enc_zywrle->Disable(); + checkbox_enc_zywrle->SetValue(false); + checkbox_enc_zlib->Disable(); + checkbox_enc_zlib->SetValue(false); + checkbox_enc_zlibhex->Disable(); + checkbox_enc_zlibhex->SetValue(false); + checkbox_enc_tight->Disable(); + checkbox_enc_tight->SetValue(false); +#endif +#ifndef LIBVNCSERVER_HAVE_LIBJPEG + checkbox_enc_tight->Disable(); + checkbox_enc_tight->SetValue(false); +#endif + + + pConfig->Read(K_COMPRESSLEVEL, &value, V_COMPRESSLEVEL); + slider_compresslevel->SetValue(value); + pConfig->Read(K_QUALITY, &value, V_QUALITY); + slider_quality->SetValue(value); +} + + +MyDialogSettings::~MyDialogSettings() +{ + + +} + + + + diff --git a/src/gui/MyFrameMain.cpp b/src/gui/MyFrameMain.cpp index 53f25195..21a45ee3 100644 --- a/src/gui/MyFrameMain.cpp +++ b/src/gui/MyFrameMain.cpp @@ -1,2159 +1,2159 @@ - -#include "gui/bitmapFromMem.h" -#include "gui/evtids.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include "MyFrameMain.h" -#include "MyDialogSettings.h" -#include "DialogLogin.h" -#include "../dfltcfg.h" -#include "../MultiVNCApp.h" - - -using namespace std; - -#ifdef __WXGTK__ // only GTK supports this atm -#define MULTIVNC_GRABKEYBOARD -#endif - -#ifndef EVT_FULLSCREEN -// workaround for missing EVT_FULLSCREEN -#define wxFullScreenEventHandler(func) \ - wxEVENT_HANDLER_CAST(wxFullScreenEventFunction, func) -typedef void (wxEvtHandler::*wxFullScreenEventFunction)(wxFullScreenEvent&); -#define EVT_FULLSCREEN(func) wx__DECLARE_EVT0(wxEVT_FULLSCREEN, wxFullScreenEventHandler(func)) -#endif - -// map recv of custom events to handler methods -BEGIN_EVENT_TABLE(MyFrameMain, FrameMain) - EVT_COMMAND (wxID_ANY, MyFrameLogCloseNOTIFY, MyFrameMain::onMyFrameLogCloseNotify) - EVT_COMMAND (wxID_ANY, wxServDiscNOTIFY, MyFrameMain::onSDNotify) - EVT_COMMAND (wxID_ANY, VNCConnListenNOTIFY, MyFrameMain::onVNCConnListenNotify) - EVT_COMMAND (wxID_ANY, VNCConnInitNOTIFY, MyFrameMain::onVNCConnInitNotify) - EVT_COMMAND (wxID_ANY, VNCConnGetPasswordNOTIFY, MyFrameMain::onVNCConnGetPasswordNotify) - EVT_COMMAND (wxID_ANY, VNCConnGetCredentialsNOTIFY, MyFrameMain::onVNCConnGetCredentialsNotify) - EVT_VNCCONNUPDATENOTIFY (wxID_ANY, MyFrameMain::onVNCConnUpdateNotify) - EVT_COMMAND (wxID_ANY, VNCConnUniMultiChangedNOTIFY, MyFrameMain::onVNCConnUniMultiChangedNotify) - EVT_COMMAND (wxID_ANY, VNCConnReplayFinishedNOTIFY, MyFrameMain::onVNCConnReplayFinishedNotify) - EVT_COMMAND (wxID_ANY, VNCConnFBResizeNOTIFY, MyFrameMain::onVNCConnFBResizeNotify) - EVT_COMMAND (wxID_ANY, VNCConnCuttextNOTIFY, MyFrameMain::onVNCConnCuttextNotify) - EVT_COMMAND (wxID_ANY, VNCConnBellNOTIFY, MyFrameMain::onVNCConnBellNotify) - EVT_COMMAND (wxID_ANY, VNCConnDisconnectNOTIFY, MyFrameMain::onVNCConnDisconnectNotify) - EVT_COMMAND (wxID_ANY, VNCConnIncomingConnectionNOTIFY, MyFrameMain::onVNCConnIncomingConnectionNotify) - EVT_END_PROCESS (ID_WINDOWSHARE_PROC_END, MyFrameMain::onWindowshareTerminate) - EVT_FULLSCREEN (MyFrameMain::onFullScreenChanged) - EVT_SYS_COLOUR_CHANGED(MyFrameMain::onSysColourChanged) -END_EVENT_TABLE() - - -/* - constructor/destructor -*/ - -MyFrameMain::MyFrameMain(wxWindow* parent, int id, const wxString& title, - const wxPoint& pos, - const wxSize& size, - long style): - FrameMain(parent, id, title, pos, size, style) -{ - int x,y; - bool grab_keyboard; - // get default config object, created on demand if not exist - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Read(K_SHOWTOOLBAR, &show_toolbar, V_SHOWTOOLBAR); - pConfig->Read(K_SHOWDISCOVERED, &show_discovered, V_SHOWDISCOVERED); - pConfig->Read(K_SHOWBOOKMARKS, &show_bookmarks, V_SHOWBOOKMARKS); - pConfig->Read(K_SHOWSTATS, &show_stats, V_SHOWSTATS); - pConfig->Read(K_SHOWSEAMLESS, &show_seamless, V_SHOWSEAMLESS); - pConfig->Read(K_SHOW1TO1, &show_1to1, V_SHOW1TO1); - pConfig->Read(K_GRABKEYBOARD, &grab_keyboard, V_GRABKEYBOARD); - pConfig->Read(K_SIZE_X, &x, V_SIZE_X); - pConfig->Read(K_SIZE_Y, &y, V_SIZE_Y); - - bool do_log; - pConfig->Read(K_LOGSAVETOFILE, &do_log, V_LOGSAVETOFILE); - VNCConn::doLogfile(do_log); - - // windowshare template - windowshare_cmd_template = pConfig->Read(K_WINDOWSHARE, V_DFLTWINDOWSHARE); - - - // window size - show_fullscreen = false; - SetMinSize(wxSize(640, 480)); - splitwin_main->SetMinimumPaneSize(160); - splitwin_left->SetMinimumPaneSize(250); - SetSize(x, y); - EnableFullScreenView(); - - - // assign image list to notebook_connections - notebook_connections->AssignImageList(new wxImageList(24, 24)); - notebook_connections->GetImageList()->Add(bitmapBundleFromSVGResource("unicast").GetBitmapFor(this)); - notebook_connections->GetImageList()->Add(bitmapBundleFromSVGResource("multicast").GetBitmapFor(this)); - - - /* - setup menu items for the frame - */ - // "disconnect" - frame_main_menubar->Enable(wxID_STOP, false); - // "screenshot" - frame_main_menubar->Enable(wxID_SAVE, false); - // stats - frame_main_menubar->Enable(ID_STATS_SAVE, false); - // record/replay - frame_main_menubar->Enable(ID_INPUT_RECORD, false); - frame_main_menubar->Enable(ID_INPUT_REPLAY, false); - // bookmarks - frame_main_menubar->Enable(wxID_ADD, false); - // window sharing - frame_main_menubar->Enable(wxID_UP, false); - frame_main_menubar->Enable(wxID_CANCEL, false); -#ifdef __WXGTK__ - wxString sessionType, flatpakId; - wxGetEnv("XDG_SESSION_TYPE", &sessionType); - wxGetEnv("FLATPAK_ID", &flatpakId); - // don't show for flatpak and wayland - if(!flatpakId.IsEmpty() || !sessionType.IsSameAs("x11")) - frame_main_menubar->Remove(frame_main_menubar->FindMenu(_("Window &Sharing"))); -#elif defined __WXMSW__ - // always on -#else - // always off so far - frame_main_menubar->Remove(frame_main_menubar->FindMenu(_("Window &Sharing"))); -#endif - // edge connector - if(!VNCSeamlessConnector::isSupportedByCurrentPlatform()) - frame_main_menubar->FindItem(ID_SEAMLESS)->GetMenu()->Delete(ID_SEAMLESS); - - // toolbar setup -#ifdef MULTIVNC_GRABKEYBOARD - frame_main_toolbar->ToggleTool(ID_GRABKEYBOARD, grab_keyboard); -#else - frame_main_toolbar->DeleteTool(ID_GRABKEYBOARD); -#endif - - - if(show_toolbar) - { - frame_main_toolbar->EnableTool(wxID_STOP, false); // disconnect - frame_main_toolbar->EnableTool(wxID_SAVE, false); // screenshot - frame_main_toolbar->EnableTool(ID_INPUT_REPLAY, false); - frame_main_toolbar->EnableTool(ID_INPUT_RECORD, false); - - frame_main_menubar->Check(ID_TOOLBAR, true); - } - else - { - frame_main_toolbar->Show(false); - } - - - splitwinlayout(); - - loadbookmarks(); - - if(show_discovered) - frame_main_menubar->Check(ID_DISCOVERED, true); - if(show_bookmarks) - frame_main_menubar->Check(ID_BOOKMARKS, true); - if(show_stats) - frame_main_menubar->Check(ID_STATISTICS, true); - - switch(show_seamless) - { - case EDGE_NORTH: - frame_main_menubar->Check(ID_SEAMLESS_NORTH, true); - break; - case EDGE_EAST: - frame_main_menubar->Check(ID_SEAMLESS_EAST, true); - break; - case EDGE_WEST: - frame_main_menubar->Check(ID_SEAMLESS_WEST, true); - break; - case EDGE_SOUTH: - frame_main_menubar->Check(ID_SEAMLESS_SOUTH, true); - break; - default: - frame_main_menubar->Check(ID_SEAMLESS_DISABLED, true); - } - - if(show_1to1) { - frame_main_menubar->Check(ID_ONE_TO_ONE, true); - GetToolBar()->ToggleTool(ID_ONE_TO_ONE, true); - } - - // setup clipboard -#ifdef __WXGTK__ - // always use middle mouse button paste - if(wxTheClipboard->Open()) - { - wxTheClipboard->UsePrimarySelection(true); - wxTheClipboard->Close(); - } -#endif - - - // theres no log window at startup - logwindow = 0; - - // finally, our mdns service scanner - servscan = new wxServDisc(this, wxT("_rfb._tcp.local."), QTYPE_PTR); - - // right click handler for bookmarks - list_box_bookmarks->Bind(wxEVT_CONTEXT_MENU, &MyFrameMain::listbox_bookmarks_context, this); -} - - - - -MyFrameMain::~MyFrameMain() -{ - wxConfigBase *pConfig = wxConfigBase::Get(); - int x,y; - GetSize(&x, &y); - pConfig->Write(K_SIZE_X, x); - pConfig->Write(K_SIZE_Y, y); -#ifdef MULTIVNC_GRABKEYBOARD - pConfig->Write(K_GRABKEYBOARD, frame_main_toolbar->GetToolState(ID_GRABKEYBOARD)); -#endif - - // this has to be from end to start in order for stats autosave to assign right connection numbers! - for(int i = connections.size()-1; i >= 0; --i) - terminate_conn(i); - - delete servscan; - - - // since we use the userData parameter in Connect() in loadbookmarks() - // in a nonstandard way, we have to have this workaround here, otherwise - // the library would segfault while trying to delete the m_callbackUserData - // member of m_dynamicEvents - if (m_dynamicEvents) - for ( wxVector::iterator it = m_dynamicEvents->begin(), - end = m_dynamicEvents->end(); - it != end; - ++it ) - { -#if WXWIN_COMPATIBILITY_EVENT_TYPES - wxEventTableEntry *entry = (wxEventTableEntry*)*it; -#else - wxDynamicEventTableEntry *entry = (wxDynamicEventTableEntry*)*it; -#endif - entry->m_callbackUserData = 0; - } -} - - - - - -/* - private members -*/ - - -// handlers - - -void MyFrameMain::onMyFrameLogCloseNotify(wxCommandEvent& event) -{ - logwindow = 0; -} - - -void MyFrameMain::onVNCConnListenNotify(wxCommandEvent& event) -{ - VNCConn* c = static_cast(event.GetEventObject()); - if (event.GetInt() == 0) { - setup_conn(c); - } else { - // listen error - wxLogError(c->getErr()); - delete c; - } -} - -void MyFrameMain::onVNCConnInitNotify(wxCommandEvent& event) -{ - wxEndBusyCursor(); - - VNCConn* c = static_cast(event.GetEventObject()); - - if (event.GetInt() == 0) { - setup_conn(c); - } else { - // error. only show error if this was not a auth case with empty password, i.e. a canceled one - if (c->getRequireAuth() -#if wxUSE_SECRETSTORE - && !c->getPassword().IsOk()) { -#else - && c->getPassword().IsEmpty()) { -#endif - wxLogStatus(_("Authentication canceled.")); - } else { - wxLogStatus(_("Connection failed.")); - wxArrayString log = VNCConn::getLog(); - // show last 3 log strings - for (size_t i = log.GetCount() >= 3 ? log.GetCount() - 3 : 0; - i < log.GetCount(); ++i) - wxLogMessage(log[i]); - wxLogError(c->getErr()); - } - - // find out if we already setup this this connection. - // this happens if it was a listening one that failed it's Init() - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - if (index < connections.size()) { - // found it. terminate! - terminate_conn(index); - } else { - // not yet setup in UI, simply delete - delete c; - } - } -} - - - -void MyFrameMain::onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event) -{ - VNCConn* sending_conn = static_cast(event.GetEventObject()); - - // only draw something for the currently selected connection - int sel; - if((sel = notebook_connections->GetSelection()) != -1) - { - VNCConn* selected_conn = connections.at(sel).conn; - if(selected_conn == sending_conn) - wxPostEvent((wxEvtHandler*)connections.at(sel).viewerwindow, event); - } -} - - - -void MyFrameMain::onVNCConnUniMultiChangedNotify(wxCommandEvent& event) -{ - // get sender - VNCConn* c = static_cast(event.GetEventObject()); - - // find index of this connection - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - - if(index < connections.size()) // found - { - // update icon - if(c->isMulticast()) - { - wxLogStatus( _("Connection to %s is now multicast."), c->getServerHost().c_str()); - notebook_connections->SetPageImage(index, 1); - } - else - { - wxArrayString log = VNCConn::getLog(); - // show last 3 log strings - for(size_t i = log.GetCount() >= 3 ? log.GetCount()-3 : 0; i < log.GetCount(); ++i) - wxLogMessage(log[i]); - wxLogMessage( _("Connection to %s switched to unicast."), c->getServerHost().c_str()); - - wxLogStatus( _("Connection to %s is now unicast."), c->getServerHost().c_str()); - notebook_connections->SetPageImage(index, 0); - } - } -} - - - - -void MyFrameMain::onVNCConnReplayFinishedNotify(wxCommandEvent& event) -{ - // get sender - VNCConn* c = static_cast(event.GetEventObject()); - - // find index of this connection - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - - if(index < connections.size()) // found - { - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "replay")); - frame_main_toolbar->FindById(ID_INPUT_REPLAY)->SetLabel(_("Replay Input")); - frame_main_menubar->SetLabel(ID_INPUT_REPLAY, _("Replay Input")); - - // re-enable record buttons - GetToolBar()->EnableTool(ID_INPUT_RECORD, true); - frame_main_menubar->Enable(ID_INPUT_RECORD, true); - - wxLogMessage( _("Replay finished!")); - wxLogStatus(_("Replay finished!")); - } -} - - - -void MyFrameMain::onVNCConnFBResizeNotify(wxCommandEvent& event) -{ - // get sender - VNCConn* c = static_cast(event.GetEventObject()); - - // find index of this connection - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - - if(index < connections.size()) // found - connections.at(index).viewerwindow->adjustCanvasSize(); -} - - - -void MyFrameMain::onVNCConnCuttextNotify(wxCommandEvent& event) -{ - if(wxTheClipboard->Open()) - { - // get sender - VNCConn* c = (VNCConn*)event.GetEventObject(); - // these data objects are held by the clipboard, so do not delete them in the app. - wxTheClipboard->SetData(new wxTextDataObject(c->getCuttext())); - wxTheClipboard->Close(); - } -} - - - -void MyFrameMain::onVNCConnBellNotify(wxCommandEvent& event) -{ - wxBell(); -} - - - - - -void MyFrameMain::onVNCConnDisconnectNotify(wxCommandEvent& event) -{ - // get sender - VNCConn* c = static_cast(event.GetEventObject()); - - if (!c->getServerHost().IsEmpty()) { - wxLogStatus(_("Connection to %s:%s terminated."), c->getServerHost().c_str(), c->getServerPort().c_str()); - } else { - wxLogStatus(_("Reverse connection terminated.")); - } - - wxArrayString log = VNCConn::getLog(); - // show last 3 log strings - for(size_t i = log.GetCount() >= 3 ? log.GetCount()-3 : 0; i < log.GetCount(); ++i) - wxLogMessage(log[i]); - if (!c->getServerHost().IsEmpty()) { - wxLogMessage(_("Connection to %s:%s terminated."), c->getServerHost().c_str(), c->getServerPort().c_str()); - } else { - wxLogMessage(_("Reverse connection terminated.")); - } - - // find index of this connection - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - - if(index < connections.size()) - terminate_conn(index); -} - - - - -void MyFrameMain::onVNCConnIncomingConnectionNotify(wxCommandEvent& event) -{ - wxLogStatus(_("Incoming Connection.")); - - // get sender - VNCConn* c = static_cast(event.GetEventObject()); - - // get connection settings - int compresslevel, quality; - wxString encodings; - bool enc_enabled; - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Read(K_COMPRESSLEVEL, &compresslevel, V_COMPRESSLEVEL); - pConfig->Read(K_QUALITY, &quality, V_QUALITY); - - pConfig->Read(K_ENC_TIGHT, &enc_enabled, V_ENC_TIGHT); - if(enc_enabled) - encodings += wxT(" tight"); - pConfig->Read(K_ENC_ULTRA, &enc_enabled, V_ENC_ULTRA); - if(enc_enabled) - encodings += wxT(" ultra"); - pConfig->Read(K_ENC_ZLIBHEX, &enc_enabled, V_ENC_ZLIBHEX); - if(enc_enabled) - encodings += wxT(" zlibhex"); - pConfig->Read(K_ENC_ZLIB, &enc_enabled, V_ENC_ZLIB); - if(enc_enabled) - encodings += wxT(" zlib"); - pConfig->Read(K_ENC_ZYWRLE, &enc_enabled, V_ENC_ZYWRLE); - if(enc_enabled) - encodings += wxT(" zywrle"); - pConfig->Read(K_ENC_ZRLE, &enc_enabled, V_ENC_ZRLE); - if(enc_enabled) - encodings += wxT(" zrle"); - pConfig->Read(K_ENC_CORRE, &enc_enabled, V_ENC_CORRE); - if(enc_enabled) - encodings += wxT(" corre"); - pConfig->Read(K_ENC_RRE, &enc_enabled, V_ENC_RRE); - if(enc_enabled) - encodings += wxT(" rre"); - pConfig->Read(K_ENC_HEXTILE, &enc_enabled, V_ENC_HEXTILE); - if(enc_enabled) - encodings += wxT(" hextile"); - pConfig->Read(K_ENC_COPYRECT, &enc_enabled, V_ENC_COPYRECT); - if(enc_enabled) - encodings += wxT(" copyrect"); - // chomp leading space - encodings = encodings.AfterFirst(' '); - - - c->Init(wxEmptyString, wxEmptyString, -#if wxUSE_SECRETSTORE - wxSecretValue(), // Creates an empty secret value (not the same as an empty password). -#endif - encodings, compresslevel, quality); -} - - -void MyFrameMain::onSDNotify(wxCommandEvent& event) -{ - if(event.GetEventObject() == servscan) - { - wxArrayString items; - - // length of query plus leading dot - size_t qlen = servscan->getQuery().Len() + 1; - - vector entries = servscan->getResults(); - vector::const_iterator it; - for(it=entries.begin(); it != entries.end(); it++) - items.Add(it->name.Mid(0, it->name.Len() - qlen)); - - list_box_services->Set(items); - } -} - - - - -void MyFrameMain::onWindowshareTerminate(wxProcessEvent& event) -{ - int status = event.GetExitCode(); - int pid = event.GetPid(); - wxLogDebug(wxT("onWindowshareTerminate() called for %d with exit code %d."), pid, status); - - ConnBlob* cb; - // find index of this connection - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->windowshare_proc_pid != pid) - { - ++it; - ++index; - } - - //found? - if(index < connections.size()) - cb = &*it; - else - { - wxLogError(_("Window share helper exited without an associated connection. That should not happen.")); - return; - } - - if(status == 0) - { - wxString msg = wxString::Format(_("Window sharing with %s stopped. Either the other side does not support receiving windows or the window was closed there."), cb->conn->getDesktopName()); - wxLogMessage(msg); - SetStatusText(msg); - } - else - if(status == -1 || status == 1) - SetStatusText( _("Window sharing stopped. Shared window was closed.")); - else - { - SetStatusText(_("Could not run window share helper.")); - wxLogError(_("Could not run window share helper.")); - } - - delete cb->windowshare_proc; - cb->windowshare_proc = 0; - cb->windowshare_proc_pid = 0; - - // the window sharer of the currently selected session terminated, - // so update menu - if(notebook_connections->GetSelection() == (int)index) - { - // this is "share window" - frame_main_menubar->Enable(wxID_UP, true); - // "stop window share" - frame_main_menubar->Enable(wxID_CANCEL, false); - } -} - -void MyFrameMain::onFullScreenChanged(wxFullScreenEvent &event) { - wxLogDebug("onFullScreenChanged %d", event.IsFullScreen()); - // update this here as well as it might have been triggered from the WM buttons outside of our control - show_fullscreen = event.IsFullScreen(); - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - if (show_fullscreen) { - // tick menu item - frame_main_menubar->Check(ID_FULLSCREEN, true); - GetToolBar()->SetToolNormalBitmap(ID_FULLSCREEN, bitmapBundleFromSVGResource(prefix + "/" + "restore")); -#ifdef __WXMAC__ - // only disable affected view items - frame_main_menubar->Enable(ID_BOOKMARKS, false); - frame_main_menubar->Enable(ID_DISCOVERED, false); -#else - // hide whole menu - frame_main_menubar->Show(false); -#endif - // hide bookmarks and discovered servers - show_bookmarks = show_discovered = false; - splitwinlayout(); - // hide toolbar labels - GetToolBar()->SetWindowStyle(GetToolBar()->GetWindowStyle() & ~wxTB_TEXT); - // hide and unlink status bar - GetStatusBar()->Hide(); - SetStatusBar(nullptr); - } else { - // untick menu item - frame_main_menubar->Check(ID_FULLSCREEN, false); - GetToolBar()->SetToolNormalBitmap(ID_FULLSCREEN, bitmapBundleFromSVGResource(prefix + "/" + "fullscreen")); -#ifdef __WXMAC__ - // only enable affected view items - frame_main_menubar->Enable(ID_BOOKMARKS, true); - frame_main_menubar->Enable(ID_DISCOVERED, true); -#else - // show whole menu again - frame_main_menubar->Show(true); -#endif - // restore bookmarks and discovered servers to saved state - wxConfigBase::Get()->Read(K_SHOWDISCOVERED, &show_discovered, V_SHOWDISCOVERED); - wxConfigBase::Get()->Read(K_SHOWBOOKMARKS, &show_bookmarks, V_SHOWBOOKMARKS); - splitwinlayout(); - // show toolbar labels - GetToolBar()->SetWindowStyle(GetToolBar()->GetWindowStyle() | wxTB_TEXT); - // reattach and status bar - SetStatusBar(frame_main_statusbar); - GetStatusBar()->Show(); - } - // needed at least on MacOS to let the status bar re-appear correctly on restore - SendSizeEvent(); -} - - -void MyFrameMain::onSysColourChanged(wxSysColourChangedEvent& event) -{ - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - GetToolBar()->SetToolNormalBitmap(wxID_YES, bitmapBundleFromSVGResource(prefix + "/" + "connect")); - GetToolBar()->SetToolNormalBitmap(wxID_REDO, bitmapBundleFromSVGResource(prefix + "/" + "listen")); - GetToolBar()->SetToolNormalBitmap(wxID_STOP, bitmapBundleFromSVGResource(prefix + "/" + "disconnect")); - GetToolBar()->SetToolNormalBitmap(ID_GRABKEYBOARD, bitmapBundleFromSVGResource(prefix + "/" + "toggle-keyboard-grab")); - GetToolBar()->SetToolNormalBitmap(wxID_SAVE, bitmapBundleFromSVGResource(prefix + "/" + "screenshot")); - GetToolBar()->SetToolNormalBitmap(ID_INPUT_RECORD, bitmapBundleFromSVGResource(prefix + "/" + "record")); - GetToolBar()->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "replay")); - GetToolBar()->SetToolNormalBitmap(ID_FULLSCREEN, bitmapBundleFromSVGResource(prefix + "/" + (show_fullscreen ? "restore" : "fullscreen"))); - GetToolBar()->SetToolNormalBitmap(ID_ONE_TO_ONE, bitmapBundleFromSVGResource(prefix + "/" + "one-to-one")); -} - - - -void MyFrameMain::onVNCConnGetPasswordNotify(wxCommandEvent &event) -{ - // get sender - VNCConn *conn = static_cast(event.GetEventObject()); - - // Get password. We are only called if the password is needed! - wxString pass = wxGetPasswordFromUser(_("Enter password:"), _("Password required!")); - // And set password at conn. -#if wxUSE_SECRETSTORE - conn->setPassword(pass.IsEmpty() ? wxSecretValue() : wxSecretValue(pass)); -#else - conn->setPassword(pass); -#endif -} - - -void MyFrameMain::onVNCConnGetCredentialsNotify(wxCommandEvent &event) -{ - // get sender - VNCConn *conn = static_cast(event.GetEventObject()); - - // stop showing connection setup busy cursor when entering creds - wxEndBusyCursor(); - - if(!event.GetInt()) { - // without user prompt, get only password - wxString pass = wxGetPasswordFromUser(wxString::Format(_("Please enter password for user '%s'"), conn->getUserName()), - _("Credentials required...")); - // And set password at conn. -#if wxUSE_SECRETSTORE - conn->setPassword(wxSecretValue(pass)); -#else - conn->setPassword(pass); -#endif - } else { - // with user prompt - DialogLogin formLogin(0, wxID_ANY, _("Credentials required...")); - if (formLogin.ShowModal() == wxID_OK) { - conn->setUserName(formLogin.getUserName()); -#if wxUSE_SECRETSTORE - conn->setPassword(wxSecretValue(formLogin.getPassword())); -#else - conn->setPassword(formLogin.getPassword()); -#endif - } else { - // canceled -#if wxUSE_SECRETSTORE - conn->setPassword(wxSecretValue()); -#else - conn->setPassword(wxEmptyString); -#endif - } - } -} - - - -bool MyFrameMain::saveStats(VNCConn* c, int conn_index, const wxArrayString& stats, wxString desc, bool autosave) -{ - if(stats.IsEmpty()) - { - if(!autosave) - wxLogMessage(_("Nothing to save!")); - return true; - } - - wxString filename = wxGetHostName() + wxT(" to ") + c->getDesktopName() + wxString::Format(wxT("(%i)"), conn_index) + - wxT(" ") + desc + wxT(" stats on ") + wxNow() + wxT(".csv"); -#ifdef __WIN32__ - // windows doesn't like ':'s - filename.Replace(wxString(wxT(":")), wxString(wxT("-"))); -#endif - - if(!autosave) - filename = wxFileSelector(wxString::Format(_("Saving %s statistics..."), desc), - wxEmptyString, - filename, - wxT(".txt"), - _("CSV files|*.csv"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - - wxLogDebug(wxT("About to save stats to ") + filename); - - if(!filename.empty()) - { - wxBusyCursor busy; - - ofstream ostream(filename.char_str()); - if(! ostream) - { - wxLogError(_("Could not save file!")); - return false; - } - - for(size_t i=0; i < stats.GetCount(); ++i) - ostream << stats[i].char_str() << endl; - } - - return true; -} - - - - - -// connection initiation and shutdown -void MyFrameMain::spawn_conn(wxString service, int listenPort) -{ - // get connection settings - int compresslevel, quality, multicast_socketrecvbuf, multicast_recvbuf; - bool multicast, enc_enabled; - wxString encodings; - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Read(K_MULTICAST, &multicast, V_MULTICAST); - pConfig->Read(K_MULTICASTSOCKETRECVBUF, &multicast_socketrecvbuf, V_MULTICASTSOCKETRECVBUF); - pConfig->Read(K_MULTICASTRECVBUF, &multicast_recvbuf, V_MULTICASTRECVBUF); - - pConfig->Read(K_ENC_TIGHT, &enc_enabled, V_ENC_TIGHT); - if(enc_enabled) - encodings += wxT(" tight"); - pConfig->Read(K_ENC_ULTRA, &enc_enabled, V_ENC_ULTRA); - if(enc_enabled) - encodings += wxT(" ultra"); - pConfig->Read(K_ENC_ZLIBHEX, &enc_enabled, V_ENC_ZLIBHEX); - if(enc_enabled) - encodings += wxT(" zlibhex"); - pConfig->Read(K_ENC_ZLIB, &enc_enabled, V_ENC_ZLIB); - if(enc_enabled) - encodings += wxT(" zlib"); - pConfig->Read(K_ENC_ZYWRLE, &enc_enabled, V_ENC_ZYWRLE); - if(enc_enabled) - encodings += wxT(" zywrle"); - pConfig->Read(K_ENC_ZRLE, &enc_enabled, V_ENC_ZRLE); - if(enc_enabled) - encodings += wxT(" zrle"); - pConfig->Read(K_ENC_CORRE, &enc_enabled, V_ENC_CORRE); - if(enc_enabled) - encodings += wxT(" corre"); - pConfig->Read(K_ENC_RRE, &enc_enabled, V_ENC_RRE); - if(enc_enabled) - encodings += wxT(" rre"); - pConfig->Read(K_ENC_HEXTILE, &enc_enabled, V_ENC_HEXTILE); - if(enc_enabled) - encodings += wxT(" hextile"); - pConfig->Read(K_ENC_COPYRECT, &enc_enabled, V_ENC_COPYRECT); - if(enc_enabled) - encodings += wxT(" copyrect"); - // chomp leading space - encodings = encodings.AfterFirst(' '); - - pConfig->Read(K_COMPRESSLEVEL, &compresslevel, V_COMPRESSLEVEL); - pConfig->Read(K_QUALITY, &quality, V_QUALITY); - - VNCConn* c = new VNCConn(this); - - if(listenPort > 0) - { - wxLogStatus(_("Listening on port") + " " + (wxString() << listenPort) + wxT(" ...")); - c->Listen(listenPort); - } - else // normal init without previous listen - { - wxBeginBusyCursor(); - wxLogStatus(_("Connecting to %s..."), service); - - wxString user = service.Contains("@") ? service.BeforeFirst('@') : ""; - wxString host = service.Contains("@") ? service.AfterFirst('@') : service; -#if wxUSE_SECRETSTORE - wxSecretValue password; - wxSecretStore store = wxSecretStore::GetDefault(); - if (store.IsOk()) { - wxString username; // this will not be used - store.Load("MultiVNC/Bookmarks/" + service, username, - password); // if Load() fails, password will still be empty - } -#endif - c->Init(host, user, -#if wxUSE_SECRETSTORE - password, -#endif - encodings, compresslevel, quality, multicast, - multicast_socketrecvbuf, multicast_recvbuf); - } -} - - -void MyFrameMain::setup_conn(VNCConn *c) { - - // first, find out if we already setup this this connection. - // this happens if it was a listening one that's now connected. - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - if (index < connections.size()) { - // found it, just update the label and skip the rest we already did - notebook_connections->SetPageText(index, c->getDesktopName() + " " + _("(Reverse Connection)")); - return; - } - - // get more settings - int fastrequest_interval; - bool fastrequest, qos_ef; - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Read(K_FASTREQUEST, &fastrequest, V_FASTREQUEST); - pConfig->Read(K_FASTREQUESTINTERVAL, &fastrequest_interval, V_FASTREQUESTINTERVAL); - pConfig->Read(K_QOS_EF, &qos_ef, V_QOS_EF); - - if(show_stats) - c->doStats(true); - - ViewerWindow* win = new ViewerWindow(notebook_connections, c); - win->showStats(show_stats); - win->showOneToOne(show_1to1); -#ifdef MULTIVNC_GRABKEYBOARD - win->grabKeyboard(frame_main_toolbar->GetToolState(ID_GRABKEYBOARD)); -#endif - - VNCSeamlessConnector* sc = 0; - if(VNCSeamlessConnector::isSupportedByCurrentPlatform() && show_seamless != EDGE_NONE) - sc = new VNCSeamlessConnector(this, c, show_seamless); - - ConnBlob cb; - cb.conn = c; - cb.viewerwindow = win; - cb.seamlessconnector = sc; - cb.windowshare_proc = 0; - cb.windowshare_proc_pid = 0; - - connections.push_back(cb); - - if(!c->getListenPort().IsEmpty()) - notebook_connections->AddPage(win, _("Listening on port") + " " + c->getListenPort(), true); - else - notebook_connections->AddPage(win, c->getDesktopName() + wxT(" (") + c->getServerHost() + wxT(")") , true); - - if(c->isMulticast()) - notebook_connections->SetPageImage(notebook_connections->GetSelection(), 1); - else - notebook_connections->SetPageImage(notebook_connections->GetSelection(), 0); - - if(fastrequest) - c->setFastRequest(fastrequest_interval); - - if(qos_ef) - c->setDSCP(184); // 184 == 0xb8 == expedited forwarding - - - // "end connection" - frame_main_menubar->Enable(wxID_STOP, true); - // "screenshot" - frame_main_menubar->Enable(wxID_SAVE, true); - // stats - frame_main_menubar->Enable(ID_STATS_SAVE, true); - // record/replay - frame_main_menubar->Enable(ID_INPUT_RECORD, true); - frame_main_menubar->Enable(ID_INPUT_REPLAY, true); - // bookmarks - frame_main_menubar->Enable(wxID_ADD, true); - // window sharing - frame_main_menubar->Enable(wxID_UP, true); - frame_main_menubar->Enable(wxID_CANCEL, false); - - if(GetToolBar()) - { - GetToolBar()->EnableTool(wxID_STOP, true); // disconnect - GetToolBar()->EnableTool(wxID_SAVE, true); // screenshot - GetToolBar()->EnableTool(ID_INPUT_REPLAY, true); - GetToolBar()->EnableTool(ID_INPUT_RECORD, true); - } - -} - - -void MyFrameMain::terminate_conn(int which) -{ - if(which == wxNOT_FOUND) - return; - - ConnBlob* cb = &connections.at(which); - - wxConfigBase *pConfig = wxConfigBase::Get(); - bool autosave_stats; - pConfig->Read(K_STATSAUTOSAVE, &autosave_stats, V_STATSAUTOSAVE); - if(autosave_stats) - { - VNCConn* c = cb->conn; - - // find index of this connection - vector::iterator it = connections.begin(); - size_t index = 0; - while(it != connections.end() && it->conn != c) - { - ++it; - ++index; - } - - if(index < connections.size()) // found - { - if(!saveStats(c, index, c->getStats(), c->isMulticast() ? wxT("MulticastVNC") : wxT("VNC"), true)) - wxLogError(_("Could not autosave statistics!")); - } - } - - - if(cb->conn->isReverse()) - listen_ports.erase(wxAtoi(cb->conn->getServerPort())); - // this deletes the ViewerWindow plus canvas - notebook_connections->DeletePage(which); - // this deletes the seamless connector - if(cb->seamlessconnector) - delete cb->seamlessconnector; - // this deletes the VNCConn - delete cb->conn; - // this deletes the windowsharer, if there's any - if(cb->windowshare_proc) - { - wxLogDebug(wxT("terminate_conn(): trying to kill %d."), cb->windowshare_proc_pid); - if(!wxProcess::Exists(cb->windowshare_proc_pid)) - { - wxLogDebug(wxT("terminate_conn(): window sharing helper PID does not exist!")); - } - else - { - // avoid callback call, now obj pointed to by windowshare_proc deletes itself! - cb->windowshare_proc->Detach(); - if(wxKill(cb->windowshare_proc_pid, wxSIGTERM, NULL, wxKILL_CHILDREN) == 0) - wxLogDebug(wxT("terminate_conn(): successfully killed %d."), cb->windowshare_proc_pid); - else - wxLogDebug(wxT("terminate_conn(): could not kill %d."), cb->windowshare_proc_pid); - } - } - // erase the ConnBlob - connections.erase(connections.begin() + which); - - if(connections.size() == 0) // nothing to end - { - // "end connection" - frame_main_menubar->Enable(wxID_STOP, false); - // "screenshot" - frame_main_menubar->Enable(wxID_SAVE, false); - // stats - frame_main_menubar->Enable(ID_STATS_SAVE, false); - // record/replay - frame_main_menubar->Enable(ID_INPUT_RECORD, false); - frame_main_menubar->Enable(ID_INPUT_REPLAY, false); - // bookmarks - frame_main_menubar->Enable(wxID_ADD, false); - // window sharing - frame_main_menubar->Enable(wxID_UP, false); - frame_main_menubar->Enable(wxID_CANCEL, false); - - if(GetToolBar()) - { - GetToolBar()->EnableTool(wxID_STOP, false); // disconnect - GetToolBar()->EnableTool(wxID_SAVE, false); // screenshot - GetToolBar()->EnableTool(ID_INPUT_REPLAY, false); - GetToolBar()->EnableTool(ID_INPUT_RECORD, false); - } - } - - wxLogStatus( _("Connection terminated.")); -} - - - - - - -void MyFrameMain::splitwinlayout() -{ - // setup layout in respect to every possible combination - - if(!show_discovered && !show_bookmarks) // 00 - { - splitwin_main->Unsplit(splitwin_main_pane_1); - } - - if(!show_discovered && show_bookmarks) // 01 - { - splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2); - - splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); - splitwin_left->Unsplit(splitwin_left_pane_1); - } - - if(show_discovered && !show_bookmarks) // 10 - { - splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2); - - splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); - splitwin_left->Unsplit(splitwin_left_pane_2); - } - - if(show_discovered && show_bookmarks) // 11 - { - splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2); - - splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); - } - - - // and set proportions - int w,h; - GetSize(&w, &h); - - splitwin_main->SetSashPosition(w * 0.1); - splitwin_left->SetSashPosition(h * 0.4); -} - - -bool MyFrameMain::loadbookmarks() -{ - wxConfigBase *cfg = wxConfigBase::Get(); - - wxArrayString bookmarknames; - - // enumeration variables - wxString str; - long dummy; - - // first, get all bookmark names - cfg->SetPath(G_BOOKMARKS); - bool cont = cfg->GetFirstGroup(str, dummy); - while(cont) - { - bookmarknames.Add(str); - cont = cfg->GetNextGroup(str, dummy); - } - - - // clean up - bookmarks.Clear(); - wxMenu* bm_menu = frame_main_menubar->GetMenu(frame_main_menubar->FindMenu(_("Bookmarks"))); - for(int i = bm_menu->GetMenuItemCount()-1; i > 0; --i) - bm_menu->Destroy(bm_menu->FindItemByPosition(i)); - bm_menu->AppendSeparator(); - - - // then read in each bookmark value pair - for(size_t i=0; i < bookmarknames.GetCount(); ++i) - { - wxString host, port, user; - - cfg->SetPath(G_BOOKMARKS + bookmarknames[i]); - - if(!cfg->Read(K_BOOKMARKS_HOST, &host)) - { - wxLogError(_("Error reading hostname of bookmark '%s'!"), bookmarknames[i].c_str()); - cfg->SetPath(wxT("/")); - return false; - } - - if(!cfg->Read(K_BOOKMARKS_PORT, &port)) - { - wxLogError(_("Error reading port of bookmark '%s'!"), bookmarknames[i].c_str()); - cfg->SetPath(wxT("/")); - return false; - } - - // user is optional - cfg->Read(K_BOOKMARKS_USER, &user); - - // add brackets if host is an IPv6 address - if(host.Freq(':') > 0) - host = wxT("[") + host + wxT("]"); - - // all fine, add it - bookmarks.Add((user != wxEmptyString ? user + "@" : "") + host + wxT(":") + port); - - // and add to bookmarks menu - int id = NewControlId(); - wxString* index_str = new wxString; // pack i into a wxObject, we use wxString here - *index_str << i; - bm_menu->Append(id, bookmarknames[i]); - bm_menu->SetHelpString(id, _("Bookmark") + " " + host + wxT(":") + port); - Connect(id, wxEVT_COMMAND_MENU_SELECTED, - wxCommandEventHandler(FrameMain::listbox_bookmarks_dclick), (wxObject*)index_str); - } - - cfg->SetPath(wxT("/")); - - list_box_bookmarks->Set(bookmarknames); - - return true; -} - - - - - -/* - public members -*/ - - -void MyFrameMain::machine_connect(wxCommandEvent &event) -{ - wxConfigBase *pConfig = wxConfigBase::Get(); - wxString host; - pConfig->Read(K_LASTHOST, &host); - - wxString s = wxGetTextFromUser(_("Enter host to connect to:"), - _("Connect to a specific host."), - host); - - if (s != wxEmptyString) { - pConfig->Write(K_LASTHOST, s); - spawn_conn(s); - } -} - - - -void MyFrameMain::machine_listen(wxCommandEvent &event) -{ - // find a free port - set::iterator it; - bool foundfree = false; - int port = LISTEN_PORT_OFFSET; - while(!foundfree) - { - if(listen_ports.find(port) == listen_ports.end()) // not in set - foundfree = true; - else - ++port; - } - - listen_ports.insert(port); - spawn_conn(wxEmptyString, port); -} - - - -void MyFrameMain::machine_disconnect(wxCommandEvent &event) -{ - // terminate connection thats currently selected - terminate_conn(notebook_connections->GetSelection()); -} - - - - -void MyFrameMain::machine_showlog(wxCommandEvent &event) -{ - if(!logwindow) - { - logwindow = new MyFrameLog(this, wxID_ANY, _("Detailed VNC Log")); - logwindow->Show(); - } - else - logwindow->Raise(); -} - - - - - -void MyFrameMain::machine_preferences(wxCommandEvent &event) -{ - MyDialogSettings dialog_settings(this, wxID_ANY, _("Preferences")); - - if(dialog_settings.ShowModal() == wxID_OK) - { - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_STATSAUTOSAVE, dialog_settings.getStatsAutosave()); - pConfig->Write(K_LOGSAVETOFILE, dialog_settings.getLogSavetofile()); - pConfig->Write(K_MULTICAST, dialog_settings.getDoMulticast()); - pConfig->Write(K_MULTICASTSOCKETRECVBUF, dialog_settings.getMulticastSocketRecvBuf()); - pConfig->Write(K_MULTICASTRECVBUF, dialog_settings.getMulticastRecvBuf()); - pConfig->Write(K_FASTREQUEST, dialog_settings.getDoFastRequest()); - pConfig->Write(K_FASTREQUESTINTERVAL, dialog_settings.getFastRequestInterval()); - pConfig->Write(K_QOS_EF, dialog_settings.getQoS_EF()); - - pConfig->Write(K_ENC_COPYRECT, dialog_settings.getEncCopyRect()); - pConfig->Write(K_ENC_HEXTILE, dialog_settings.getEncHextile()); - pConfig->Write(K_ENC_RRE, dialog_settings.getEncRRE()); - pConfig->Write(K_ENC_CORRE, dialog_settings.getEncCoRRE()); - pConfig->Write(K_ENC_ZLIB, dialog_settings.getEncZlib()); - pConfig->Write(K_ENC_ZLIBHEX, dialog_settings.getEncZlibHex()); - pConfig->Write(K_ENC_ZRLE, dialog_settings.getEncZRLE()); - pConfig->Write(K_ENC_ZYWRLE, dialog_settings.getEncZYWRLE()); - pConfig->Write(K_ENC_ULTRA, dialog_settings.getEncUltra()); - pConfig->Write(K_ENC_TIGHT, dialog_settings.getEncTight()); - pConfig->Write(K_COMPRESSLEVEL, dialog_settings.getCompressLevel()); - pConfig->Write(K_QUALITY, dialog_settings.getQuality()); - - VNCConn::doLogfile(dialog_settings.getLogSavetofile()); - } -} - - -void MyFrameMain::machine_screenshot(wxCommandEvent &event) -{ - if(connections.size()) - { - VNCConn* c = connections.at(notebook_connections->GetSelection()).conn; - - wxRect rect(0, 0, c->getFrameBufferWidth(), c->getFrameBufferHeight()); - if(rect.IsEmpty()) - return; - wxBitmap screenshot = c->getFrameBufferRegion(rect); - - wxString desktopname = c->getDesktopName(); -#ifdef __WIN32__ - // windows doesn't like ':'s - desktopname.Replace(wxString(wxT(":")), wxString(wxT("-"))); -#endif - wxString filename = wxFileSelector(_("Save screenshot..."), - wxEmptyString, - desktopname + wxT("-Screenshot.png"), - wxT(".png"), - _("PNG files|*.png"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - - if(!filename.empty()) - { - wxBusyCursor busy; - screenshot.SaveFile(filename, wxBITMAP_TYPE_PNG); - } - } -} - - - -void MyFrameMain::machine_grabkeyboard(wxCommandEvent &event) -{ - if(connections.size()) - connections.at(notebook_connections->GetSelection()).viewerwindow->grabKeyboard(event.IsChecked()); -} - - - -void MyFrameMain::machine_save_stats(wxCommandEvent &event) -{ - if(connections.size()) - { - int sel = notebook_connections->GetSelection(); - VNCConn* c = connections.at(sel).conn; - saveStats(c, sel, c->getStats(), c->isMulticast() ? wxT("MulticastVNC") : wxT("VNC"), false); - } -} - - - -void MyFrameMain::machine_input_record(wxCommandEvent &event) -{ - if(connections.size()) - { - int sel = notebook_connections->GetSelection(); - VNCConn* c = connections.at(sel).conn; - - if(c->isReplaying()) - return; // bail out - - - if(c->isRecording()) - { - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_RECORD, bitmapBundleFromSVGResource(prefix + "/" + "record")); - frame_main_toolbar->FindById(ID_INPUT_RECORD)->SetLabel(_("Record Input")); - - wxArrayString recorded_input; - - if(c->recordUserInputStop(recorded_input)) - { - wxLogStatus(_("Stopped recording user input!")); - frame_main_menubar->SetLabel(ID_INPUT_RECORD,_("Record Input")); - - // re-enable replay buttons - GetToolBar()->EnableTool(ID_INPUT_REPLAY, true); - frame_main_menubar->Enable(ID_INPUT_REPLAY, true); - - if(recorded_input.IsEmpty()) - { - wxLogMessage(_("Nothing to save!")); - return; - } - - wxString filename = wxGetHostName() + wxT(" to ") + c->getDesktopName() + wxString::Format(wxT("(%i)"), sel) + - + wxT(" input on ") + wxNow() + wxT(".csv"); -#ifdef __WIN32__ - // windows doesn't like ':'s - filename.Replace(wxString(wxT(":")), wxString(wxT("-"))); -#endif - - filename = wxFileSelector(_("Save recorded input..."), - wxEmptyString, - filename, - wxT(".csv"), - _("CSV files|*.csv"), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - - wxLogDebug(wxT("About to save recorded input to ") + filename); - - if(!filename.empty()) - { - wxBusyCursor busy; - ofstream ostream(filename.char_str()); - if(! ostream) - { - wxLogError(_("Could not save file!")); - return; - } - - for(size_t i=0; i < recorded_input.GetCount(); ++i) - ostream << recorded_input[i].char_str() << endl; - } - } - - } - else // not recording - { - wxLogMessage(_("From now on, all your mouse and keyboard input will be recorded. Click the stop button to finish recording and save your input.")); - - if( c->recordUserInputStart()) - { - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_RECORD, bitmapBundleFromSVGResource(prefix + "/" + "stop")); - frame_main_toolbar->FindById(ID_INPUT_RECORD)->SetLabel(_("Stop")); - frame_main_menubar->SetLabel(ID_INPUT_RECORD, _("Stop Recording")); - - wxLogStatus(_("Recording user input...")); - - // disable replay buttons - GetToolBar()->EnableTool(ID_INPUT_REPLAY, false); - frame_main_menubar->Enable(ID_INPUT_REPLAY, false); - } - } - - } -} - - -void MyFrameMain::machine_input_replay(wxCommandEvent &event) -{ - bool shift_was_down = wxGetMouseState().ShiftDown(); - - if(connections.size()) - { - int sel = notebook_connections->GetSelection(); - VNCConn* c = connections.at(sel).conn; - - if(c->isRecording()) - return; // bail out - - - if(c->isReplaying()) - { - c->replayUserInputStop(); - - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "replay")); - frame_main_toolbar->FindById(ID_INPUT_REPLAY)->SetLabel(_("Replay Input")); - frame_main_menubar->SetLabel(ID_INPUT_REPLAY,_("Replay Input")); - - wxLogStatus(_("Stopped replaying user input!")); - - // re-enable record buttons - GetToolBar()->EnableTool(ID_INPUT_RECORD, true); - frame_main_menubar->Enable(ID_INPUT_RECORD, true); - } - else // not replaying - { - wxArrayString recorded_input; - - // load recorded input - wxString filename = wxFileSelector(_("Load recorded input..."), - wxEmptyString, - wxEmptyString, - wxT(".csv"), - _("CSV files|*.csv"), - wxFD_OPEN|wxFD_FILE_MUST_EXIST); - - if(!filename.empty()) - { - wxBusyCursor busy; - - wxLogDebug(wxT("About to load recorded input from ") + filename); - - ifstream istream(filename.char_str()); - if(! istream) - { - wxLogError(_("Could not open file!")); - return; - } - - char buf[1024]; - while(istream.getline(buf, 1024)) - recorded_input.Add(wxString(buf, wxConvUTF8)); - } - else - return; // canceled - - - // start replay - if(c->replayUserInputStart(recorded_input, shift_was_down)) - { - wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; - frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "stop")); - frame_main_menubar->SetLabel(ID_INPUT_REPLAY, _("Stop Replaying")); - frame_main_toolbar->FindById(ID_INPUT_REPLAY)->SetLabel(_("Stop")); - - if(shift_was_down) - wxLogStatus(_("Replaying user input in loop...")); - else - wxLogStatus(_("Replaying user input...")); - - // disable record buttons - GetToolBar()->EnableTool(ID_INPUT_RECORD, false); - frame_main_menubar->Enable(ID_INPUT_RECORD, false); - } - } - - } -} - - - -void MyFrameMain::machine_exit(wxCommandEvent &event) -{ - Close(true); -} - - - - -void MyFrameMain::view_toggletoolbar(wxCommandEvent &event) -{ - show_toolbar = !show_toolbar; - - frame_main_toolbar->Show(show_toolbar); - - if(show_toolbar) - { - bool enable = connections.size() != 0; - - frame_main_toolbar->EnableTool(wxID_STOP, enable); // disconnect - frame_main_toolbar->EnableTool(wxID_SAVE, enable); // screenshot - - frame_main_toolbar->Show(); - } - else - { - frame_main_toolbar->Hide(); - } - - // this does more than Layout() which only deals with sizers - SendSizeEvent(); - - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_SHOWTOOLBAR, show_toolbar); -} - - - -void MyFrameMain::view_togglediscovered(wxCommandEvent &event) -{ - show_discovered = !show_discovered; - - splitwinlayout(); - - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_SHOWDISCOVERED, show_discovered); -} - - -void MyFrameMain::view_togglebookmarks(wxCommandEvent &event) -{ - show_bookmarks = !show_bookmarks; - - splitwinlayout(); - - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_SHOWBOOKMARKS, show_bookmarks); -} - - - -void MyFrameMain::view_togglestatistics(wxCommandEvent &event) -{ - show_stats = !show_stats; - - // for now, toggle all connections - for(size_t i=0; i < connections.size(); ++i) - { - connections.at(i).conn->doStats(show_stats); - connections.at(i).viewerwindow->showStats(show_stats); - } - - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_SHOWSTATS, show_stats); -} - - - - - -void MyFrameMain::view_togglefullscreen(wxCommandEvent &event) -{ - show_fullscreen = ! show_fullscreen; - - wxLogDebug("view_togglefullscreen %d", show_fullscreen); - -#ifdef __WXMAC__ - ShowFullScreen(show_fullscreen); -#else - ShowFullScreen(show_fullscreen, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); -#endif - - // according to https://docs.wxwidgets.org/3.2/classwx_full_screen_event.html - // the event is not fired when using ShowFullScreen(), so manually do this here - // (it is fired on OSX when entering fullscreen via the green button, so we need to - // have the extra event handler). - wxFullScreenEvent e = wxFullScreenEvent(0, show_fullscreen); - onFullScreenChanged(e); -} - - -void MyFrameMain::view_toggle1to1(wxCommandEvent &event) -{ - show_1to1 = ! show_1to1; - wxLogDebug("view_toggle1to1 %d", show_1to1); - - // keep toolbar and menu entries in sync - frame_main_menubar->Check(ID_ONE_TO_ONE, show_1to1); - GetToolBar()->ToggleTool(ID_ONE_TO_ONE, show_1to1); - - // for now, toggle all connections - for(size_t i=0; i < connections.size(); ++i) { - connections.at(i).viewerwindow->showOneToOne(show_1to1); - } - - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_SHOW1TO1, show_1to1); -} - - - - -void MyFrameMain::view_seamless(wxCommandEvent &event) -{ - if(frame_main_menubar->IsChecked(ID_SEAMLESS_NORTH)) - show_seamless = EDGE_NORTH; - else if - (frame_main_menubar->IsChecked(ID_SEAMLESS_EAST)) - show_seamless = EDGE_EAST; - else if - (frame_main_menubar->IsChecked(ID_SEAMLESS_WEST)) - show_seamless = EDGE_WEST; - else if - (frame_main_menubar->IsChecked(ID_SEAMLESS_SOUTH)) - show_seamless = EDGE_SOUTH; - else - show_seamless = EDGE_NONE; - - //change for active connection - if(connections.size()) - { - ConnBlob* cbp = &connections.at(notebook_connections->GetSelection()); - if(cbp->seamlessconnector) - { - delete cbp->seamlessconnector; - cbp->seamlessconnector = 0; - } - if(show_seamless != EDGE_NONE) - cbp->seamlessconnector = new VNCSeamlessConnector(this, cbp->conn, show_seamless); - } - - - wxConfigBase *pConfig = wxConfigBase::Get(); - pConfig->Write(K_SHOWSEAMLESS, show_seamless); -} - - - - - -void MyFrameMain::bookmarks_add(wxCommandEvent &event) -{ - VNCConn* c = connections.at(notebook_connections->GetSelection()).conn; - wxConfigBase *cfg = wxConfigBase::Get(); - - if(c->getServerHost().IsEmpty()) - { - wxLogError(_("Cannot bookmark a reverse connection!")); - return; - } - - wxString name = wxGetTextFromUser(_("Enter bookmark name:"), - _("Saving bookmark"), - (! c->getUserName().IsEmpty() ? c->getUserName() + "@" : "") + c->getDesktopName()); - - if(name != wxEmptyString) - { - if(cfg->Exists(G_BOOKMARKS + name)) - { - wxLogError(_("A bookmark with this name already exists!")); - return; - } - - cfg->SetPath(G_BOOKMARKS + name); - - cfg->Write(K_BOOKMARKS_HOST, c->getServerHost()); - cfg->Write(K_BOOKMARKS_PORT, c->getServerPort()); - cfg->Write(K_BOOKMARKS_USER, c->getUserName()); - -#if wxUSE_SECRETSTORE - wxSecretStore store = wxSecretStore::GetDefault(); - if(store.IsOk() && c->getPassword().IsOk()) { //check if destination and source are ok - if(!store.Save("MultiVNC/Bookmarks/" + (c->getUserName().IsEmpty() ? "" : c->getUserName() + "@") + c->getServerHost() + ":" + c->getServerPort(), c->getUserName(), c->getPassword())) //FIXME the service should use the user-given bookmark name, but that requires a rework of our internal bookmarking - wxLogWarning(_("Failed to save credentials to the system secret store.")); - } -#endif - - //reset path - cfg->SetPath(wxT("/")); - - // and load into listbox - loadbookmarks(); - } -} - - - -void MyFrameMain::bookmarks_edit(wxCommandEvent &event) -{ - wxString sel = list_box_bookmarks->GetStringSelection(); - - if(sel.IsEmpty()) - { - wxLogError(_("No bookmark selected!")); - return; - } - - wxString newname = wxGetTextFromUser(_("New bookmark name:"), - _("Edit bookmark")); - - if(newname.IsEmpty()) - return; - - wxConfigBase *cfg = wxConfigBase::Get(); - - cfg->SetPath(G_BOOKMARKS); - cfg->RenameGroup(sel, newname); - //reset path - cfg->SetPath(wxT("/")); - - // and load into listbox - loadbookmarks(); -} - - - -void MyFrameMain::bookmarks_delete(wxCommandEvent &event) -{ - wxString name = list_box_bookmarks->GetStringSelection(); - - if(name.IsEmpty()) - { - wxLogError(_("No bookmark selected!")); - return; - } - - wxConfigBase *cfg = wxConfigBase::Get(); - if(!cfg->DeleteGroup(G_BOOKMARKS + name)) - wxLogError(_("No bookmark with this name!")); - -#if wxUSE_SECRETSTORE - int sel = list_box_bookmarks->GetSelection(); - if(sel != wxNOT_FOUND) { - wxString service = bookmarks[sel]; - wxSecretStore store = wxSecretStore::GetDefault(); - if(store.IsOk()) - store.Delete("MultiVNC/Bookmarks/" + service); - } -#endif - - // and re-read - loadbookmarks(); -} - - - - - -void MyFrameMain::help_about(wxCommandEvent &event) -{ - wxAboutDialogInfo info; - wxIcon icon = bitmapBundleFromSVGResource("about").GetIcon(this->FromDIP(wxSize(128,128))); - - wxString desc = "\n"; - desc += _("MultiVNC is a cross-platform Multicast-enabled VNC client."); - desc += "\n\n"; - desc += _("Built with") + " " + (wxString() << wxVERSION_STRING); - desc += "\n\n"; - desc += _("Supported Security Types:"); - desc += "\n"; - desc += _("VNC Authentication"); -#if defined LIBVNCSERVER_HAVE_GNUTLS || defined LIBVNCSERVER_HAVE_LIBSSL - desc += wxT(", Anonymous TLS, VeNCrypt"); -#endif -#if defined LIBVNCSERVER_HAVE_LIBGCRYPT || defined LIBVNCSERVER_HAVE_LIBSSL - desc += wxT(", Apple Remote Desktop"); -#endif - desc += "\n\n"; - desc += _("Supported Encodings:"); - desc += "\n"; - desc += wxT("Raw, RRE, coRRE, CopyRect, Hextile, Ultra"); -#ifdef LIBVNCSERVER_HAVE_LIBZ - desc += wxT(", UltraZip, Zlib, ZlibHex, ZRLE, ZYWRLE"); -#ifdef LIBVNCSERVER_HAVE_LIBJPEG - desc += wxT(", Tight"); -#endif -#endif - -#ifndef __WXMAC__ - info.SetIcon(icon); - info.SetVersion(VERSION); - info.SetWebSite(wxString(PACKAGE_URL)); -#endif - info.SetName(wxT("MultiVNC")); - info.SetDescription(desc); - info.SetCopyright(wxT(COPYRIGHT)); - info.AddDeveloper("Christian Beier"); - info.AddDeveloper("Evgeny Zinoviev"); - info.AddDeveloper("Audrey Dutcher"); - - wxAboutBox(info); -} - - - - -void MyFrameMain::help_contents(wxCommandEvent &e) -{ - wxLogMessage(_("\ -If there are VNC servers advertising themselves via ZeroConf, you can select a host in the\ -'Available VNC Servers' list. Otherwise, use the 'Connect' button or menu item.\ -\n\nWhen connected, a blue or green icon on the tab label shows if you are running in unicast \ -or multicast mode.")); -} - - - -void MyFrameMain::help_issue_list(wxCommandEvent &e) -{ - wxString platform; -#if defined __LINUX__ - platform = "linux"; -#elif defined __WINDOWS__ - platform = "windows"; -#elif defined __APPLE__ - platform = "mac"; -#endif - wxLaunchDefaultBrowser("https://github.com/bk138/multivnc/issues?q=is%3Aissue+is%3Aopen+label%3Aplatform-" + platform); -} - - - -void MyFrameMain::listbox_services_select(wxCommandEvent &event) -{ - // intentionally empty - wxLogStatus(_("VNC Server") + " " + list_box_services->GetStringSelection()); -} - - -void MyFrameMain::listbox_services_dclick(wxCommandEvent &event) -{ - int timeout; - wxBusyCursor busy; - int sel = event.GetInt(); - - if(sel < 0) // seems this happens when we update the list - return; - - wxLogStatus(_("Looking up host address...")); - - - // lookup hostname and port - { - wxServDisc namescan(0, servscan->getResults().at(sel).name, QTYPE_SRV); - - timeout = 5000; - while(!namescan.getResultCount() && timeout > 0) - { - wxMilliSleep(25); - timeout-=25; - } - if(timeout <= 0) - { - wxLogError(_("Timeout looking up hostname.")); - wxLogStatus(_("Timeout looking up hostname.")); - services_hostname = services_addr = services_port = wxEmptyString; - return; - } - services_hostname = namescan.getResults().at(0).name; - services_port = wxString() << namescan.getResults().at(0).port; - } - - // check if we actually have to resolve the IP address ourselves - bool is_system_resolving_mdns = false; -#ifdef __WXMAC__ - is_system_resolving_mdns = true; -#else - wxLog::EnableLogging(false); - wxFileInputStream input("/etc/nsswitch.conf"); - wxLog::EnableLogging(true); - wxTextInputStream text(input); - while(input.IsOk() && !input.Eof()) - if(text.ReadLine().Contains("mdns")) { - is_system_resolving_mdns = true; - wxLogDebug("System resolver does mDNS, skipping IP address lookup"); - break; - } -#endif - - if(!is_system_resolving_mdns) - // lookup ip address - { - wxServDisc addrscan(0, services_hostname, QTYPE_A); - - timeout = 5000; - while(!addrscan.getResultCount() && timeout > 0) - { - wxMilliSleep(25); - timeout-=25; - } - if(timeout <= 0) - { - wxLogError(_("Timeout looking up IP address.")); - wxLogStatus(_("Timeout looking up IP address.")); - services_hostname = services_addr = services_port = wxEmptyString; - return; - } - services_addr = addrscan.getResults().at(0).ip; - } - else - services_addr = services_hostname; // system resolves mDNS - - wxLogStatus(services_hostname + wxT(" (") + services_addr + wxT(":") + services_port + wxT(")")); - - - spawn_conn(services_addr + wxT(":") + services_port); -} - - - -void MyFrameMain::listbox_bookmarks_select(wxCommandEvent &event) -{ - int sel = event.GetInt(); - - if(sel >= 0) // something selected - { - wxLogStatus(_("Bookmark %s"), bookmarks[sel]); - } -} - - -void MyFrameMain::listbox_bookmarks_dclick(wxCommandEvent &event) -{ - int sel = event.GetInt(); - - if(sel < 0) { // nothing selected - // this gets set by Connect() in loadbookmarks(), in this case sel is always -1 - if(event.m_callbackUserData) - sel = wxAtoi(*(wxString*)event.m_callbackUserData); - else - return; - } - - spawn_conn(bookmarks[sel]); -} - - -void MyFrameMain::listbox_bookmarks_context(wxContextMenuEvent &event) -{ - wxPoint clientPosition = - list_box_bookmarks->ScreenToClient(event.GetPosition()); - - int itemIndex = list_box_bookmarks->HitTest(clientPosition); - - if (itemIndex != wxNOT_FOUND) { - // Programmatically select the item. This is for UX purposes and, - // more important, so that edit and delete function work on the - // correct item. - list_box_bookmarks->SetSelection(itemIndex); - - // and show the context menu - wxMenu menu; - menu.Append(wxID_EDIT, _("&Edit Bookmark")); - menu.Bind(wxEVT_MENU, &MyFrameMain::bookmarks_edit, this, wxID_EDIT); - menu.Append(wxID_DELETE, _("&Delete Bookmark")); - menu.Bind(wxEVT_MENU, &MyFrameMain::bookmarks_delete, this, wxID_DELETE); - PopupMenu(&menu); - } -} - -void MyFrameMain::notebook_connections_pagechanged(wxNotebookEvent &event) -{ - ConnBlob* cb; - if(connections.size()) - cb = &connections.at(notebook_connections->GetSelection()); - else - return; - - bool isSharing = cb->windowshare_proc ? true : false; - wxLogDebug(wxT("notebook_connections_pagechanged(): VNCConn %p sharing is %d"), cb->conn, isSharing); - // this is "share window" - frame_main_menubar->Enable(wxID_UP, !isSharing); - // this is "stop share window" - frame_main_menubar->Enable(wxID_CANCEL, isSharing); - - if(cb->seamlessconnector) - { - switch(cb->seamlessconnector->getEdge()) - { - case EDGE_NORTH: - frame_main_menubar->Check(ID_SEAMLESS_NORTH, true); - break; - case EDGE_EAST: - frame_main_menubar->Check(ID_SEAMLESS_EAST, true); - break; - case EDGE_WEST: - frame_main_menubar->Check(ID_SEAMLESS_WEST, true); - break; - case EDGE_SOUTH: - frame_main_menubar->Check(ID_SEAMLESS_SOUTH, true); - break; - default: - frame_main_menubar->Check(ID_SEAMLESS_DISABLED, true); - } - - cb->seamlessconnector->Raise(); - } - else // no object, i.e. disabled - frame_main_menubar->Check(ID_SEAMLESS_DISABLED, true); -} - - - -void MyFrameMain::cmdline_connect(wxString& hostarg) -{ - spawn_conn(hostarg); -} - - - - -void MyFrameMain::windowshare_start(wxCommandEvent &event) -{ - wxBusyCursor busy; - - ConnBlob* cb = 0; - if(connections.size()) - cb = &connections.at(notebook_connections->GetSelection()); - else - return; - - // right now x11vnc and WinVNC behave differently :-/ -#ifdef __WIN32__ - wxString window = wxGetTextFromUser(_("Enter name of window to share:"), _("Share a Window")); - if(window == wxEmptyString) - return; -#else - if(wxMessageBox(_("The MultiVNC window will be minimized and a cross-shaped cursor will appear. Use it to select the window you want to share."), - _("Share a Window"), wxOK|wxCANCEL) - == wxCANCEL) - return; - - Iconize(true); -#endif - - // handle %a and %p - wxString cmd = windowshare_cmd_template; - cmd.Replace(wxT("%a"), cb->conn->getServerHost()); - // cmd.Replace(_T("%p"), port); //unused by now -#ifdef __WIN32__ - cmd.Replace(wxT("%w"), window); -#endif - - // terminate old one - wxCommandEvent unused; - windowshare_stop(unused); - - // and start new one - cb->windowshare_proc = new wxProcess(this, ID_WINDOWSHARE_PROC_END); - cb->windowshare_proc_pid = wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER, cb->windowshare_proc); - wxLogDebug(wxT("windowshare_start() spawned %d."), cb->windowshare_proc_pid); - - if(cb->windowshare_proc_pid == 0) - { - SetStatusText(_("Window sharing helper execution failed.")); - wxLogError( _("Could not share window, external program execution failed.")); - - delete cb->windowshare_proc; - cb->windowshare_proc = 0; - cb->windowshare_proc_pid = 0; - return; - } - - SetStatusText(wxString::Format(_("Sharing window with %s"), cb->conn->getDesktopName())); - - // this is "share window" - frame_main_menubar->Enable(wxID_UP, false); - // this is "stop share window" - frame_main_menubar->Enable(wxID_CANCEL, true); -} - - - - -void MyFrameMain::windowshare_stop(wxCommandEvent &event) -{ - wxBusyCursor busy; - - ConnBlob* cb; - if(connections.size()) - cb = &connections.at(notebook_connections->GetSelection()); - else - return; - - if(!cb->windowshare_proc || !cb->windowshare_proc_pid) - return; - - wxLogDebug(wxT("windowshare_stop(): tries to kill %d."), cb->windowshare_proc_pid); - - if(!wxProcess::Exists(cb->windowshare_proc_pid)) - { - wxLogDebug(wxT("windowshare_stop(): PID does not exist, exiting!")); - return; - } - - // avoid callback call, now obj pointed to by windowshare_proc deletes itself! - cb->windowshare_proc->Detach(); - - if(wxKill(cb->windowshare_proc_pid, wxSIGTERM, NULL, wxKILL_CHILDREN) == 0) - { - wxLogDebug(wxT("windowshare_stop(): successfully killed %d."), cb->windowshare_proc_pid); - cb->windowshare_proc = 0; // obj deleted itself because of Detach()! - cb->windowshare_proc_pid = 0; - - wxLogStatus(_("Stopped sharing window with %s"), cb->conn->getDesktopName()); - - // this is "share window" - frame_main_menubar->Enable(wxID_UP, true); - // this is "stop share window" - frame_main_menubar->Enable(wxID_CANCEL, false); - } - else - wxLogDebug(wxT("windowshare_stop(): Could not kill %d. Not good."), cb->windowshare_proc_pid); -} - - - - + +#include "gui/bitmapFromMem.h" +#include "gui/evtids.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MyFrameMain.h" +#include "MyDialogSettings.h" +#include "DialogLogin.h" +#include "../dfltcfg.h" +#include "../MultiVNCApp.h" + + +using namespace std; + +#ifdef __WXGTK__ // only GTK supports this atm +#define MULTIVNC_GRABKEYBOARD +#endif + +#ifndef EVT_FULLSCREEN +// workaround for missing EVT_FULLSCREEN +#define wxFullScreenEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxFullScreenEventFunction, func) +typedef void (wxEvtHandler::*wxFullScreenEventFunction)(wxFullScreenEvent&); +#define EVT_FULLSCREEN(func) wx__DECLARE_EVT0(wxEVT_FULLSCREEN, wxFullScreenEventHandler(func)) +#endif + +// map recv of custom events to handler methods +BEGIN_EVENT_TABLE(MyFrameMain, FrameMain) + EVT_COMMAND (wxID_ANY, MyFrameLogCloseNOTIFY, MyFrameMain::onMyFrameLogCloseNotify) + EVT_COMMAND (wxID_ANY, wxServDiscNOTIFY, MyFrameMain::onSDNotify) + EVT_COMMAND (wxID_ANY, VNCConnListenNOTIFY, MyFrameMain::onVNCConnListenNotify) + EVT_COMMAND (wxID_ANY, VNCConnInitNOTIFY, MyFrameMain::onVNCConnInitNotify) + EVT_COMMAND (wxID_ANY, VNCConnGetPasswordNOTIFY, MyFrameMain::onVNCConnGetPasswordNotify) + EVT_COMMAND (wxID_ANY, VNCConnGetCredentialsNOTIFY, MyFrameMain::onVNCConnGetCredentialsNotify) + EVT_VNCCONNUPDATENOTIFY (wxID_ANY, MyFrameMain::onVNCConnUpdateNotify) + EVT_COMMAND (wxID_ANY, VNCConnUniMultiChangedNOTIFY, MyFrameMain::onVNCConnUniMultiChangedNotify) + EVT_COMMAND (wxID_ANY, VNCConnReplayFinishedNOTIFY, MyFrameMain::onVNCConnReplayFinishedNotify) + EVT_COMMAND (wxID_ANY, VNCConnFBResizeNOTIFY, MyFrameMain::onVNCConnFBResizeNotify) + EVT_COMMAND (wxID_ANY, VNCConnCuttextNOTIFY, MyFrameMain::onVNCConnCuttextNotify) + EVT_COMMAND (wxID_ANY, VNCConnBellNOTIFY, MyFrameMain::onVNCConnBellNotify) + EVT_COMMAND (wxID_ANY, VNCConnDisconnectNOTIFY, MyFrameMain::onVNCConnDisconnectNotify) + EVT_COMMAND (wxID_ANY, VNCConnIncomingConnectionNOTIFY, MyFrameMain::onVNCConnIncomingConnectionNotify) + EVT_END_PROCESS (ID_WINDOWSHARE_PROC_END, MyFrameMain::onWindowshareTerminate) + EVT_FULLSCREEN (MyFrameMain::onFullScreenChanged) + EVT_SYS_COLOUR_CHANGED(MyFrameMain::onSysColourChanged) +END_EVENT_TABLE() + + +/* + constructor/destructor +*/ + +MyFrameMain::MyFrameMain(wxWindow* parent, int id, const wxString& title, + const wxPoint& pos, + const wxSize& size, + long style): + FrameMain(parent, id, title, pos, size, style) +{ + int x,y; + bool grab_keyboard; + // get default config object, created on demand if not exist + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Read(K_SHOWTOOLBAR, &show_toolbar, V_SHOWTOOLBAR); + pConfig->Read(K_SHOWDISCOVERED, &show_discovered, V_SHOWDISCOVERED); + pConfig->Read(K_SHOWBOOKMARKS, &show_bookmarks, V_SHOWBOOKMARKS); + pConfig->Read(K_SHOWSTATS, &show_stats, V_SHOWSTATS); + pConfig->Read(K_SHOWSEAMLESS, &show_seamless, V_SHOWSEAMLESS); + pConfig->Read(K_SHOW1TO1, &show_1to1, V_SHOW1TO1); + pConfig->Read(K_GRABKEYBOARD, &grab_keyboard, V_GRABKEYBOARD); + pConfig->Read(K_SIZE_X, &x, V_SIZE_X); + pConfig->Read(K_SIZE_Y, &y, V_SIZE_Y); + + bool do_log; + pConfig->Read(K_LOGSAVETOFILE, &do_log, V_LOGSAVETOFILE); + VNCConn::doLogfile(do_log); + + // windowshare template + windowshare_cmd_template = pConfig->Read(K_WINDOWSHARE, V_DFLTWINDOWSHARE); + + + // window size + show_fullscreen = false; + SetMinSize(wxSize(640, 480)); + splitwin_main->SetMinimumPaneSize(160); + splitwin_left->SetMinimumPaneSize(250); + SetSize(x, y); + EnableFullScreenView(); + + + // assign image list to notebook_connections + notebook_connections->AssignImageList(new wxImageList(24, 24)); + notebook_connections->GetImageList()->Add(bitmapBundleFromSVGResource("unicast").GetBitmapFor(this)); + notebook_connections->GetImageList()->Add(bitmapBundleFromSVGResource("multicast").GetBitmapFor(this)); + + + /* + setup menu items for the frame + */ + // "disconnect" + frame_main_menubar->Enable(wxID_STOP, false); + // "screenshot" + frame_main_menubar->Enable(wxID_SAVE, false); + // stats + frame_main_menubar->Enable(ID_STATS_SAVE, false); + // record/replay + frame_main_menubar->Enable(ID_INPUT_RECORD, false); + frame_main_menubar->Enable(ID_INPUT_REPLAY, false); + // bookmarks + frame_main_menubar->Enable(wxID_ADD, false); + // window sharing + frame_main_menubar->Enable(wxID_UP, false); + frame_main_menubar->Enable(wxID_CANCEL, false); +#ifdef __WXGTK__ + wxString sessionType, flatpakId; + wxGetEnv("XDG_SESSION_TYPE", &sessionType); + wxGetEnv("FLATPAK_ID", &flatpakId); + // don't show for flatpak and wayland + if(!flatpakId.IsEmpty() || !sessionType.IsSameAs("x11")) + frame_main_menubar->Remove(frame_main_menubar->FindMenu(_("Window &Sharing"))); +#elif defined __WXMSW__ + // always on +#else + // always off so far + frame_main_menubar->Remove(frame_main_menubar->FindMenu(_("Window &Sharing"))); +#endif + // edge connector + if(!VNCSeamlessConnector::isSupportedByCurrentPlatform()) + frame_main_menubar->FindItem(ID_SEAMLESS)->GetMenu()->Delete(ID_SEAMLESS); + + // toolbar setup +#ifdef MULTIVNC_GRABKEYBOARD + frame_main_toolbar->ToggleTool(ID_GRABKEYBOARD, grab_keyboard); +#else + frame_main_toolbar->DeleteTool(ID_GRABKEYBOARD); +#endif + + + if(show_toolbar) + { + frame_main_toolbar->EnableTool(wxID_STOP, false); // disconnect + frame_main_toolbar->EnableTool(wxID_SAVE, false); // screenshot + frame_main_toolbar->EnableTool(ID_INPUT_REPLAY, false); + frame_main_toolbar->EnableTool(ID_INPUT_RECORD, false); + + frame_main_menubar->Check(ID_TOOLBAR, true); + } + else + { + frame_main_toolbar->Show(false); + } + + + splitwinlayout(); + + loadbookmarks(); + + if(show_discovered) + frame_main_menubar->Check(ID_DISCOVERED, true); + if(show_bookmarks) + frame_main_menubar->Check(ID_BOOKMARKS, true); + if(show_stats) + frame_main_menubar->Check(ID_STATISTICS, true); + + switch(show_seamless) + { + case EDGE_NORTH: + frame_main_menubar->Check(ID_SEAMLESS_NORTH, true); + break; + case EDGE_EAST: + frame_main_menubar->Check(ID_SEAMLESS_EAST, true); + break; + case EDGE_WEST: + frame_main_menubar->Check(ID_SEAMLESS_WEST, true); + break; + case EDGE_SOUTH: + frame_main_menubar->Check(ID_SEAMLESS_SOUTH, true); + break; + default: + frame_main_menubar->Check(ID_SEAMLESS_DISABLED, true); + } + + if(show_1to1) { + frame_main_menubar->Check(ID_ONE_TO_ONE, true); + GetToolBar()->ToggleTool(ID_ONE_TO_ONE, true); + } + + // setup clipboard +#ifdef __WXGTK__ + // always use middle mouse button paste + if(wxTheClipboard->Open()) + { + wxTheClipboard->UsePrimarySelection(true); + wxTheClipboard->Close(); + } +#endif + + + // theres no log window at startup + logwindow = 0; + + // finally, our mdns service scanner + servscan = new wxServDisc(this, wxT("_rfb._tcp.local."), QTYPE_PTR); + + // right click handler for bookmarks + list_box_bookmarks->Bind(wxEVT_CONTEXT_MENU, &MyFrameMain::listbox_bookmarks_context, this); +} + + + + +MyFrameMain::~MyFrameMain() +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + int x,y; + GetSize(&x, &y); + pConfig->Write(K_SIZE_X, x); + pConfig->Write(K_SIZE_Y, y); +#ifdef MULTIVNC_GRABKEYBOARD + pConfig->Write(K_GRABKEYBOARD, frame_main_toolbar->GetToolState(ID_GRABKEYBOARD)); +#endif + + // this has to be from end to start in order for stats autosave to assign right connection numbers! + for(int i = connections.size()-1; i >= 0; --i) + terminate_conn(i); + + delete servscan; + + + // since we use the userData parameter in Connect() in loadbookmarks() + // in a nonstandard way, we have to have this workaround here, otherwise + // the library would segfault while trying to delete the m_callbackUserData + // member of m_dynamicEvents + if (m_dynamicEvents) + for ( wxVector::iterator it = m_dynamicEvents->begin(), + end = m_dynamicEvents->end(); + it != end; + ++it ) + { +#if WXWIN_COMPATIBILITY_EVENT_TYPES + wxEventTableEntry *entry = (wxEventTableEntry*)*it; +#else + wxDynamicEventTableEntry *entry = (wxDynamicEventTableEntry*)*it; +#endif + entry->m_callbackUserData = 0; + } +} + + + + + +/* + private members +*/ + + +// handlers + + +void MyFrameMain::onMyFrameLogCloseNotify(wxCommandEvent& event) +{ + logwindow = 0; +} + + +void MyFrameMain::onVNCConnListenNotify(wxCommandEvent& event) +{ + VNCConn* c = static_cast(event.GetEventObject()); + if (event.GetInt() == 0) { + setup_conn(c); + } else { + // listen error + wxLogError(c->getErr()); + delete c; + } +} + +void MyFrameMain::onVNCConnInitNotify(wxCommandEvent& event) +{ + wxEndBusyCursor(); + + VNCConn* c = static_cast(event.GetEventObject()); + + if (event.GetInt() == 0) { + setup_conn(c); + } else { + // error. only show error if this was not a auth case with empty password, i.e. a canceled one + if (c->getRequireAuth() +#if wxUSE_SECRETSTORE + && !c->getPassword().IsOk()) { +#else + && c->getPassword().IsEmpty()) { +#endif + wxLogStatus(_("Authentication canceled.")); + } else { + wxLogStatus(_("Connection failed.")); + wxArrayString log = VNCConn::getLog(); + // show last 3 log strings + for (size_t i = log.GetCount() >= 3 ? log.GetCount() - 3 : 0; + i < log.GetCount(); ++i) + wxLogMessage(log[i]); + wxLogError(c->getErr()); + } + + // find out if we already setup this this connection. + // this happens if it was a listening one that failed it's Init() + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + if (index < connections.size()) { + // found it. terminate! + terminate_conn(index); + } else { + // not yet setup in UI, simply delete + delete c; + } + } +} + + + +void MyFrameMain::onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event) +{ + VNCConn* sending_conn = static_cast(event.GetEventObject()); + + // only draw something for the currently selected connection + int sel; + if((sel = notebook_connections->GetSelection()) != -1) + { + VNCConn* selected_conn = connections.at(sel).conn; + if(selected_conn == sending_conn) + wxPostEvent((wxEvtHandler*)connections.at(sel).viewerwindow, event); + } +} + + + +void MyFrameMain::onVNCConnUniMultiChangedNotify(wxCommandEvent& event) +{ + // get sender + VNCConn* c = static_cast(event.GetEventObject()); + + // find index of this connection + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + + if(index < connections.size()) // found + { + // update icon + if(c->isMulticast()) + { + wxLogStatus( _("Connection to %s is now multicast."), c->getServerHost().c_str()); + notebook_connections->SetPageImage(index, 1); + } + else + { + wxArrayString log = VNCConn::getLog(); + // show last 3 log strings + for(size_t i = log.GetCount() >= 3 ? log.GetCount()-3 : 0; i < log.GetCount(); ++i) + wxLogMessage(log[i]); + wxLogMessage( _("Connection to %s switched to unicast."), c->getServerHost().c_str()); + + wxLogStatus( _("Connection to %s is now unicast."), c->getServerHost().c_str()); + notebook_connections->SetPageImage(index, 0); + } + } +} + + + + +void MyFrameMain::onVNCConnReplayFinishedNotify(wxCommandEvent& event) +{ + // get sender + VNCConn* c = static_cast(event.GetEventObject()); + + // find index of this connection + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + + if(index < connections.size()) // found + { + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "replay")); + frame_main_toolbar->FindById(ID_INPUT_REPLAY)->SetLabel(_("Replay Input")); + frame_main_menubar->SetLabel(ID_INPUT_REPLAY, _("Replay Input")); + + // re-enable record buttons + GetToolBar()->EnableTool(ID_INPUT_RECORD, true); + frame_main_menubar->Enable(ID_INPUT_RECORD, true); + + wxLogMessage( _("Replay finished!")); + wxLogStatus(_("Replay finished!")); + } +} + + + +void MyFrameMain::onVNCConnFBResizeNotify(wxCommandEvent& event) +{ + // get sender + VNCConn* c = static_cast(event.GetEventObject()); + + // find index of this connection + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + + if(index < connections.size()) // found + connections.at(index).viewerwindow->adjustCanvasSize(); +} + + + +void MyFrameMain::onVNCConnCuttextNotify(wxCommandEvent& event) +{ + if(wxTheClipboard->Open()) + { + // get sender + VNCConn* c = (VNCConn*)event.GetEventObject(); + // these data objects are held by the clipboard, so do not delete them in the app. + wxTheClipboard->SetData(new wxTextDataObject(c->getCuttext())); + wxTheClipboard->Close(); + } +} + + + +void MyFrameMain::onVNCConnBellNotify(wxCommandEvent& event) +{ + wxBell(); +} + + + + + +void MyFrameMain::onVNCConnDisconnectNotify(wxCommandEvent& event) +{ + // get sender + VNCConn* c = static_cast(event.GetEventObject()); + + if (!c->getServerHost().IsEmpty()) { + wxLogStatus(_("Connection to %s:%s terminated."), c->getServerHost().c_str(), c->getServerPort().c_str()); + } else { + wxLogStatus(_("Reverse connection terminated.")); + } + + wxArrayString log = VNCConn::getLog(); + // show last 3 log strings + for(size_t i = log.GetCount() >= 3 ? log.GetCount()-3 : 0; i < log.GetCount(); ++i) + wxLogMessage(log[i]); + if (!c->getServerHost().IsEmpty()) { + wxLogMessage(_("Connection to %s:%s terminated."), c->getServerHost().c_str(), c->getServerPort().c_str()); + } else { + wxLogMessage(_("Reverse connection terminated.")); + } + + // find index of this connection + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + + if(index < connections.size()) + terminate_conn(index); +} + + + + +void MyFrameMain::onVNCConnIncomingConnectionNotify(wxCommandEvent& event) +{ + wxLogStatus(_("Incoming Connection.")); + + // get sender + VNCConn* c = static_cast(event.GetEventObject()); + + // get connection settings + int compresslevel, quality; + wxString encodings; + bool enc_enabled; + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Read(K_COMPRESSLEVEL, &compresslevel, V_COMPRESSLEVEL); + pConfig->Read(K_QUALITY, &quality, V_QUALITY); + + pConfig->Read(K_ENC_TIGHT, &enc_enabled, V_ENC_TIGHT); + if(enc_enabled) + encodings += wxT(" tight"); + pConfig->Read(K_ENC_ULTRA, &enc_enabled, V_ENC_ULTRA); + if(enc_enabled) + encodings += wxT(" ultra"); + pConfig->Read(K_ENC_ZLIBHEX, &enc_enabled, V_ENC_ZLIBHEX); + if(enc_enabled) + encodings += wxT(" zlibhex"); + pConfig->Read(K_ENC_ZLIB, &enc_enabled, V_ENC_ZLIB); + if(enc_enabled) + encodings += wxT(" zlib"); + pConfig->Read(K_ENC_ZYWRLE, &enc_enabled, V_ENC_ZYWRLE); + if(enc_enabled) + encodings += wxT(" zywrle"); + pConfig->Read(K_ENC_ZRLE, &enc_enabled, V_ENC_ZRLE); + if(enc_enabled) + encodings += wxT(" zrle"); + pConfig->Read(K_ENC_CORRE, &enc_enabled, V_ENC_CORRE); + if(enc_enabled) + encodings += wxT(" corre"); + pConfig->Read(K_ENC_RRE, &enc_enabled, V_ENC_RRE); + if(enc_enabled) + encodings += wxT(" rre"); + pConfig->Read(K_ENC_HEXTILE, &enc_enabled, V_ENC_HEXTILE); + if(enc_enabled) + encodings += wxT(" hextile"); + pConfig->Read(K_ENC_COPYRECT, &enc_enabled, V_ENC_COPYRECT); + if(enc_enabled) + encodings += wxT(" copyrect"); + // chomp leading space + encodings = encodings.AfterFirst(' '); + + + c->Init(wxEmptyString, wxEmptyString, +#if wxUSE_SECRETSTORE + wxSecretValue(), // Creates an empty secret value (not the same as an empty password). +#endif + encodings, compresslevel, quality); +} + + +void MyFrameMain::onSDNotify(wxCommandEvent& event) +{ + if(event.GetEventObject() == servscan) + { + wxArrayString items; + + // length of query plus leading dot + size_t qlen = servscan->getQuery().Len() + 1; + + vector entries = servscan->getResults(); + vector::const_iterator it; + for(it=entries.begin(); it != entries.end(); it++) + items.Add(it->name.Mid(0, it->name.Len() - qlen)); + + list_box_services->Set(items); + } +} + + + + +void MyFrameMain::onWindowshareTerminate(wxProcessEvent& event) +{ + int status = event.GetExitCode(); + int pid = event.GetPid(); + wxLogDebug(wxT("onWindowshareTerminate() called for %d with exit code %d."), pid, status); + + ConnBlob* cb; + // find index of this connection + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->windowshare_proc_pid != pid) + { + ++it; + ++index; + } + + //found? + if(index < connections.size()) + cb = &*it; + else + { + wxLogError(_("Window share helper exited without an associated connection. That should not happen.")); + return; + } + + if(status == 0) + { + wxString msg = wxString::Format(_("Window sharing with %s stopped. Either the other side does not support receiving windows or the window was closed there."), cb->conn->getDesktopName()); + wxLogMessage(msg); + SetStatusText(msg); + } + else + if(status == -1 || status == 1) + SetStatusText( _("Window sharing stopped. Shared window was closed.")); + else + { + SetStatusText(_("Could not run window share helper.")); + wxLogError(_("Could not run window share helper.")); + } + + delete cb->windowshare_proc; + cb->windowshare_proc = 0; + cb->windowshare_proc_pid = 0; + + // the window sharer of the currently selected session terminated, + // so update menu + if(notebook_connections->GetSelection() == (int)index) + { + // this is "share window" + frame_main_menubar->Enable(wxID_UP, true); + // "stop window share" + frame_main_menubar->Enable(wxID_CANCEL, false); + } +} + +void MyFrameMain::onFullScreenChanged(wxFullScreenEvent &event) { + wxLogDebug("onFullScreenChanged %d", event.IsFullScreen()); + // update this here as well as it might have been triggered from the WM buttons outside of our control + show_fullscreen = event.IsFullScreen(); + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + if (show_fullscreen) { + // tick menu item + frame_main_menubar->Check(ID_FULLSCREEN, true); + GetToolBar()->SetToolNormalBitmap(ID_FULLSCREEN, bitmapBundleFromSVGResource(prefix + "/" + "restore")); +#ifdef __WXMAC__ + // only disable affected view items + frame_main_menubar->Enable(ID_BOOKMARKS, false); + frame_main_menubar->Enable(ID_DISCOVERED, false); +#else + // hide whole menu + frame_main_menubar->Show(false); +#endif + // hide bookmarks and discovered servers + show_bookmarks = show_discovered = false; + splitwinlayout(); + // hide toolbar labels + GetToolBar()->SetWindowStyle(GetToolBar()->GetWindowStyle() & ~wxTB_TEXT); + // hide and unlink status bar + GetStatusBar()->Hide(); + SetStatusBar(nullptr); + } else { + // untick menu item + frame_main_menubar->Check(ID_FULLSCREEN, false); + GetToolBar()->SetToolNormalBitmap(ID_FULLSCREEN, bitmapBundleFromSVGResource(prefix + "/" + "fullscreen")); +#ifdef __WXMAC__ + // only enable affected view items + frame_main_menubar->Enable(ID_BOOKMARKS, true); + frame_main_menubar->Enable(ID_DISCOVERED, true); +#else + // show whole menu again + frame_main_menubar->Show(true); +#endif + // restore bookmarks and discovered servers to saved state + wxConfigBase::Get()->Read(K_SHOWDISCOVERED, &show_discovered, V_SHOWDISCOVERED); + wxConfigBase::Get()->Read(K_SHOWBOOKMARKS, &show_bookmarks, V_SHOWBOOKMARKS); + splitwinlayout(); + // show toolbar labels + GetToolBar()->SetWindowStyle(GetToolBar()->GetWindowStyle() | wxTB_TEXT); + // reattach and status bar + SetStatusBar(frame_main_statusbar); + GetStatusBar()->Show(); + } + // needed at least on MacOS to let the status bar re-appear correctly on restore + SendSizeEvent(); +} + + +void MyFrameMain::onSysColourChanged(wxSysColourChangedEvent& event) +{ + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + GetToolBar()->SetToolNormalBitmap(wxID_YES, bitmapBundleFromSVGResource(prefix + "/" + "connect")); + GetToolBar()->SetToolNormalBitmap(wxID_REDO, bitmapBundleFromSVGResource(prefix + "/" + "listen")); + GetToolBar()->SetToolNormalBitmap(wxID_STOP, bitmapBundleFromSVGResource(prefix + "/" + "disconnect")); + GetToolBar()->SetToolNormalBitmap(ID_GRABKEYBOARD, bitmapBundleFromSVGResource(prefix + "/" + "toggle-keyboard-grab")); + GetToolBar()->SetToolNormalBitmap(wxID_SAVE, bitmapBundleFromSVGResource(prefix + "/" + "screenshot")); + GetToolBar()->SetToolNormalBitmap(ID_INPUT_RECORD, bitmapBundleFromSVGResource(prefix + "/" + "record")); + GetToolBar()->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "replay")); + GetToolBar()->SetToolNormalBitmap(ID_FULLSCREEN, bitmapBundleFromSVGResource(prefix + "/" + (show_fullscreen ? "restore" : "fullscreen"))); + GetToolBar()->SetToolNormalBitmap(ID_ONE_TO_ONE, bitmapBundleFromSVGResource(prefix + "/" + "one-to-one")); +} + + + +void MyFrameMain::onVNCConnGetPasswordNotify(wxCommandEvent &event) +{ + // get sender + VNCConn *conn = static_cast(event.GetEventObject()); + + // Get password. We are only called if the password is needed! + wxString pass = wxGetPasswordFromUser(_("Enter password:"), _("Password required!")); + // And set password at conn. +#if wxUSE_SECRETSTORE + conn->setPassword(pass.IsEmpty() ? wxSecretValue() : wxSecretValue(pass)); +#else + conn->setPassword(pass); +#endif +} + + +void MyFrameMain::onVNCConnGetCredentialsNotify(wxCommandEvent &event) +{ + // get sender + VNCConn *conn = static_cast(event.GetEventObject()); + + // stop showing connection setup busy cursor when entering creds + wxEndBusyCursor(); + + if(!event.GetInt()) { + // without user prompt, get only password + wxString pass = wxGetPasswordFromUser(wxString::Format(_("Please enter password for user '%s'"), conn->getUserName()), + _("Credentials required...")); + // And set password at conn. +#if wxUSE_SECRETSTORE + conn->setPassword(wxSecretValue(pass)); +#else + conn->setPassword(pass); +#endif + } else { + // with user prompt + DialogLogin formLogin(0, wxID_ANY, _("Credentials required...")); + if (formLogin.ShowModal() == wxID_OK) { + conn->setUserName(formLogin.getUserName()); +#if wxUSE_SECRETSTORE + conn->setPassword(wxSecretValue(formLogin.getPassword())); +#else + conn->setPassword(formLogin.getPassword()); +#endif + } else { + // canceled +#if wxUSE_SECRETSTORE + conn->setPassword(wxSecretValue()); +#else + conn->setPassword(wxEmptyString); +#endif + } + } +} + + + +bool MyFrameMain::saveStats(VNCConn* c, int conn_index, const wxArrayString& stats, wxString desc, bool autosave) +{ + if(stats.IsEmpty()) + { + if(!autosave) + wxLogMessage(_("Nothing to save!")); + return true; + } + + wxString filename = wxGetHostName() + wxT(" to ") + c->getDesktopName() + wxString::Format(wxT("(%i)"), conn_index) + + wxT(" ") + desc + wxT(" stats on ") + wxNow() + wxT(".csv"); +#ifdef __WIN32__ + // windows doesn't like ':'s + filename.Replace(wxString(wxT(":")), wxString(wxT("-"))); +#endif + + if(!autosave) + filename = wxFileSelector(wxString::Format(_("Saving %s statistics..."), desc), + wxEmptyString, + filename, + wxT(".txt"), + _("CSV files|*.csv"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + wxLogDebug(wxT("About to save stats to ") + filename); + + if(!filename.empty()) + { + wxBusyCursor busy; + + ofstream ostream(filename.char_str()); + if(! ostream) + { + wxLogError(_("Could not save file!")); + return false; + } + + for(size_t i=0; i < stats.GetCount(); ++i) + ostream << stats[i].char_str() << endl; + } + + return true; +} + + + + + +// connection initiation and shutdown +void MyFrameMain::spawn_conn(wxString service, int listenPort) +{ + // get connection settings + int compresslevel, quality, multicast_socketrecvbuf, multicast_recvbuf; + bool multicast, enc_enabled; + wxString encodings; + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Read(K_MULTICAST, &multicast, V_MULTICAST); + pConfig->Read(K_MULTICASTSOCKETRECVBUF, &multicast_socketrecvbuf, V_MULTICASTSOCKETRECVBUF); + pConfig->Read(K_MULTICASTRECVBUF, &multicast_recvbuf, V_MULTICASTRECVBUF); + + pConfig->Read(K_ENC_TIGHT, &enc_enabled, V_ENC_TIGHT); + if(enc_enabled) + encodings += wxT(" tight"); + pConfig->Read(K_ENC_ULTRA, &enc_enabled, V_ENC_ULTRA); + if(enc_enabled) + encodings += wxT(" ultra"); + pConfig->Read(K_ENC_ZLIBHEX, &enc_enabled, V_ENC_ZLIBHEX); + if(enc_enabled) + encodings += wxT(" zlibhex"); + pConfig->Read(K_ENC_ZLIB, &enc_enabled, V_ENC_ZLIB); + if(enc_enabled) + encodings += wxT(" zlib"); + pConfig->Read(K_ENC_ZYWRLE, &enc_enabled, V_ENC_ZYWRLE); + if(enc_enabled) + encodings += wxT(" zywrle"); + pConfig->Read(K_ENC_ZRLE, &enc_enabled, V_ENC_ZRLE); + if(enc_enabled) + encodings += wxT(" zrle"); + pConfig->Read(K_ENC_CORRE, &enc_enabled, V_ENC_CORRE); + if(enc_enabled) + encodings += wxT(" corre"); + pConfig->Read(K_ENC_RRE, &enc_enabled, V_ENC_RRE); + if(enc_enabled) + encodings += wxT(" rre"); + pConfig->Read(K_ENC_HEXTILE, &enc_enabled, V_ENC_HEXTILE); + if(enc_enabled) + encodings += wxT(" hextile"); + pConfig->Read(K_ENC_COPYRECT, &enc_enabled, V_ENC_COPYRECT); + if(enc_enabled) + encodings += wxT(" copyrect"); + // chomp leading space + encodings = encodings.AfterFirst(' '); + + pConfig->Read(K_COMPRESSLEVEL, &compresslevel, V_COMPRESSLEVEL); + pConfig->Read(K_QUALITY, &quality, V_QUALITY); + + VNCConn* c = new VNCConn(this); + + if(listenPort > 0) + { + wxLogStatus(_("Listening on port") + " " + (wxString() << listenPort) + wxT(" ...")); + c->Listen(listenPort); + } + else // normal init without previous listen + { + wxBeginBusyCursor(); + wxLogStatus(_("Connecting to %s..."), service); + + wxString user = service.Contains("@") ? service.BeforeFirst('@') : ""; + wxString host = service.Contains("@") ? service.AfterFirst('@') : service; +#if wxUSE_SECRETSTORE + wxSecretValue password; + wxSecretStore store = wxSecretStore::GetDefault(); + if (store.IsOk()) { + wxString username; // this will not be used + store.Load("MultiVNC/Bookmarks/" + service, username, + password); // if Load() fails, password will still be empty + } +#endif + c->Init(host, user, +#if wxUSE_SECRETSTORE + password, +#endif + encodings, compresslevel, quality, multicast, + multicast_socketrecvbuf, multicast_recvbuf); + } +} + + +void MyFrameMain::setup_conn(VNCConn *c) { + + // first, find out if we already setup this this connection. + // this happens if it was a listening one that's now connected. + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + if (index < connections.size()) { + // found it, just update the label and skip the rest we already did + notebook_connections->SetPageText(index, c->getDesktopName() + " " + _("(Reverse Connection)")); + return; + } + + // get more settings + int fastrequest_interval; + bool fastrequest, qos_ef; + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Read(K_FASTREQUEST, &fastrequest, V_FASTREQUEST); + pConfig->Read(K_FASTREQUESTINTERVAL, &fastrequest_interval, V_FASTREQUESTINTERVAL); + pConfig->Read(K_QOS_EF, &qos_ef, V_QOS_EF); + + if(show_stats) + c->doStats(true); + + ViewerWindow* win = new ViewerWindow(notebook_connections, c); + win->showStats(show_stats); + win->showOneToOne(show_1to1); +#ifdef MULTIVNC_GRABKEYBOARD + win->grabKeyboard(frame_main_toolbar->GetToolState(ID_GRABKEYBOARD)); +#endif + + VNCSeamlessConnector* sc = 0; + if(VNCSeamlessConnector::isSupportedByCurrentPlatform() && show_seamless != EDGE_NONE) + sc = new VNCSeamlessConnector(this, c, show_seamless); + + ConnBlob cb; + cb.conn = c; + cb.viewerwindow = win; + cb.seamlessconnector = sc; + cb.windowshare_proc = 0; + cb.windowshare_proc_pid = 0; + + connections.push_back(cb); + + if(!c->getListenPort().IsEmpty()) + notebook_connections->AddPage(win, _("Listening on port") + " " + c->getListenPort(), true); + else + notebook_connections->AddPage(win, c->getDesktopName() + wxT(" (") + c->getServerHost() + wxT(")") , true); + + if(c->isMulticast()) + notebook_connections->SetPageImage(notebook_connections->GetSelection(), 1); + else + notebook_connections->SetPageImage(notebook_connections->GetSelection(), 0); + + if(fastrequest) + c->setFastRequest(fastrequest_interval); + + if(qos_ef) + c->setDSCP(184); // 184 == 0xb8 == expedited forwarding + + + // "end connection" + frame_main_menubar->Enable(wxID_STOP, true); + // "screenshot" + frame_main_menubar->Enable(wxID_SAVE, true); + // stats + frame_main_menubar->Enable(ID_STATS_SAVE, true); + // record/replay + frame_main_menubar->Enable(ID_INPUT_RECORD, true); + frame_main_menubar->Enable(ID_INPUT_REPLAY, true); + // bookmarks + frame_main_menubar->Enable(wxID_ADD, true); + // window sharing + frame_main_menubar->Enable(wxID_UP, true); + frame_main_menubar->Enable(wxID_CANCEL, false); + + if(GetToolBar()) + { + GetToolBar()->EnableTool(wxID_STOP, true); // disconnect + GetToolBar()->EnableTool(wxID_SAVE, true); // screenshot + GetToolBar()->EnableTool(ID_INPUT_REPLAY, true); + GetToolBar()->EnableTool(ID_INPUT_RECORD, true); + } + +} + + +void MyFrameMain::terminate_conn(int which) +{ + if(which == wxNOT_FOUND) + return; + + ConnBlob* cb = &connections.at(which); + + wxConfigBase *pConfig = wxConfigBase::Get(); + bool autosave_stats; + pConfig->Read(K_STATSAUTOSAVE, &autosave_stats, V_STATSAUTOSAVE); + if(autosave_stats) + { + VNCConn* c = cb->conn; + + // find index of this connection + vector::iterator it = connections.begin(); + size_t index = 0; + while(it != connections.end() && it->conn != c) + { + ++it; + ++index; + } + + if(index < connections.size()) // found + { + if(!saveStats(c, index, c->getStats(), c->isMulticast() ? wxT("MulticastVNC") : wxT("VNC"), true)) + wxLogError(_("Could not autosave statistics!")); + } + } + + + if(cb->conn->isReverse()) + listen_ports.erase(wxAtoi(cb->conn->getServerPort())); + // this deletes the ViewerWindow plus canvas + notebook_connections->DeletePage(which); + // this deletes the seamless connector + if(cb->seamlessconnector) + delete cb->seamlessconnector; + // this deletes the VNCConn + delete cb->conn; + // this deletes the windowsharer, if there's any + if(cb->windowshare_proc) + { + wxLogDebug(wxT("terminate_conn(): trying to kill %d."), cb->windowshare_proc_pid); + if(!wxProcess::Exists(cb->windowshare_proc_pid)) + { + wxLogDebug(wxT("terminate_conn(): window sharing helper PID does not exist!")); + } + else + { + // avoid callback call, now obj pointed to by windowshare_proc deletes itself! + cb->windowshare_proc->Detach(); + if(wxKill(cb->windowshare_proc_pid, wxSIGTERM, NULL, wxKILL_CHILDREN) == 0) + wxLogDebug(wxT("terminate_conn(): successfully killed %d."), cb->windowshare_proc_pid); + else + wxLogDebug(wxT("terminate_conn(): could not kill %d."), cb->windowshare_proc_pid); + } + } + // erase the ConnBlob + connections.erase(connections.begin() + which); + + if(connections.size() == 0) // nothing to end + { + // "end connection" + frame_main_menubar->Enable(wxID_STOP, false); + // "screenshot" + frame_main_menubar->Enable(wxID_SAVE, false); + // stats + frame_main_menubar->Enable(ID_STATS_SAVE, false); + // record/replay + frame_main_menubar->Enable(ID_INPUT_RECORD, false); + frame_main_menubar->Enable(ID_INPUT_REPLAY, false); + // bookmarks + frame_main_menubar->Enable(wxID_ADD, false); + // window sharing + frame_main_menubar->Enable(wxID_UP, false); + frame_main_menubar->Enable(wxID_CANCEL, false); + + if(GetToolBar()) + { + GetToolBar()->EnableTool(wxID_STOP, false); // disconnect + GetToolBar()->EnableTool(wxID_SAVE, false); // screenshot + GetToolBar()->EnableTool(ID_INPUT_REPLAY, false); + GetToolBar()->EnableTool(ID_INPUT_RECORD, false); + } + } + + wxLogStatus( _("Connection terminated.")); +} + + + + + + +void MyFrameMain::splitwinlayout() +{ + // setup layout in respect to every possible combination + + if(!show_discovered && !show_bookmarks) // 00 + { + splitwin_main->Unsplit(splitwin_main_pane_1); + } + + if(!show_discovered && show_bookmarks) // 01 + { + splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2); + + splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); + splitwin_left->Unsplit(splitwin_left_pane_1); + } + + if(show_discovered && !show_bookmarks) // 10 + { + splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2); + + splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); + splitwin_left->Unsplit(splitwin_left_pane_2); + } + + if(show_discovered && show_bookmarks) // 11 + { + splitwin_main->SplitVertically(splitwin_main_pane_1, splitwin_main_pane_2); + + splitwin_left->SplitHorizontally(splitwin_left_pane_1, splitwin_left_pane_2); + } + + + // and set proportions + int w,h; + GetSize(&w, &h); + + splitwin_main->SetSashPosition(w * 0.1); + splitwin_left->SetSashPosition(h * 0.4); +} + + +bool MyFrameMain::loadbookmarks() +{ + wxConfigBase *cfg = wxConfigBase::Get(); + + wxArrayString bookmarknames; + + // enumeration variables + wxString str; + long dummy; + + // first, get all bookmark names + cfg->SetPath(G_BOOKMARKS); + bool cont = cfg->GetFirstGroup(str, dummy); + while(cont) + { + bookmarknames.Add(str); + cont = cfg->GetNextGroup(str, dummy); + } + + + // clean up + bookmarks.Clear(); + wxMenu* bm_menu = frame_main_menubar->GetMenu(frame_main_menubar->FindMenu(_("Bookmarks"))); + for(int i = bm_menu->GetMenuItemCount()-1; i > 0; --i) + bm_menu->Destroy(bm_menu->FindItemByPosition(i)); + bm_menu->AppendSeparator(); + + + // then read in each bookmark value pair + for(size_t i=0; i < bookmarknames.GetCount(); ++i) + { + wxString host, port, user; + + cfg->SetPath(G_BOOKMARKS + bookmarknames[i]); + + if(!cfg->Read(K_BOOKMARKS_HOST, &host)) + { + wxLogError(_("Error reading hostname of bookmark '%s'!"), bookmarknames[i].c_str()); + cfg->SetPath(wxT("/")); + return false; + } + + if(!cfg->Read(K_BOOKMARKS_PORT, &port)) + { + wxLogError(_("Error reading port of bookmark '%s'!"), bookmarknames[i].c_str()); + cfg->SetPath(wxT("/")); + return false; + } + + // user is optional + cfg->Read(K_BOOKMARKS_USER, &user); + + // add brackets if host is an IPv6 address + if(host.Freq(':') > 0) + host = wxT("[") + host + wxT("]"); + + // all fine, add it + bookmarks.Add((user != wxEmptyString ? user + "@" : "") + host + wxT(":") + port); + + // and add to bookmarks menu + int id = NewControlId(); + wxString* index_str = new wxString; // pack i into a wxObject, we use wxString here + *index_str << i; + bm_menu->Append(id, bookmarknames[i]); + bm_menu->SetHelpString(id, _("Bookmark") + " " + host + wxT(":") + port); + Connect(id, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(FrameMain::listbox_bookmarks_dclick), (wxObject*)index_str); + } + + cfg->SetPath(wxT("/")); + + list_box_bookmarks->Set(bookmarknames); + + return true; +} + + + + + +/* + public members +*/ + + +void MyFrameMain::machine_connect(wxCommandEvent &event) +{ + wxConfigBase *pConfig = wxConfigBase::Get(); + wxString host; + pConfig->Read(K_LASTHOST, &host); + + wxString s = wxGetTextFromUser(_("Enter host to connect to:"), + _("Connect to a specific host."), + host); + + if (s != wxEmptyString) { + pConfig->Write(K_LASTHOST, s); + spawn_conn(s); + } +} + + + +void MyFrameMain::machine_listen(wxCommandEvent &event) +{ + // find a free port + set::iterator it; + bool foundfree = false; + int port = LISTEN_PORT_OFFSET; + while(!foundfree) + { + if(listen_ports.find(port) == listen_ports.end()) // not in set + foundfree = true; + else + ++port; + } + + listen_ports.insert(port); + spawn_conn(wxEmptyString, port); +} + + + +void MyFrameMain::machine_disconnect(wxCommandEvent &event) +{ + // terminate connection thats currently selected + terminate_conn(notebook_connections->GetSelection()); +} + + + + +void MyFrameMain::machine_showlog(wxCommandEvent &event) +{ + if(!logwindow) + { + logwindow = new MyFrameLog(this, wxID_ANY, _("Detailed VNC Log")); + logwindow->Show(); + } + else + logwindow->Raise(); +} + + + + + +void MyFrameMain::machine_preferences(wxCommandEvent &event) +{ + MyDialogSettings dialog_settings(this, wxID_ANY, _("Preferences")); + + if(dialog_settings.ShowModal() == wxID_OK) + { + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_STATSAUTOSAVE, dialog_settings.getStatsAutosave()); + pConfig->Write(K_LOGSAVETOFILE, dialog_settings.getLogSavetofile()); + pConfig->Write(K_MULTICAST, dialog_settings.getDoMulticast()); + pConfig->Write(K_MULTICASTSOCKETRECVBUF, dialog_settings.getMulticastSocketRecvBuf()); + pConfig->Write(K_MULTICASTRECVBUF, dialog_settings.getMulticastRecvBuf()); + pConfig->Write(K_FASTREQUEST, dialog_settings.getDoFastRequest()); + pConfig->Write(K_FASTREQUESTINTERVAL, dialog_settings.getFastRequestInterval()); + pConfig->Write(K_QOS_EF, dialog_settings.getQoS_EF()); + + pConfig->Write(K_ENC_COPYRECT, dialog_settings.getEncCopyRect()); + pConfig->Write(K_ENC_HEXTILE, dialog_settings.getEncHextile()); + pConfig->Write(K_ENC_RRE, dialog_settings.getEncRRE()); + pConfig->Write(K_ENC_CORRE, dialog_settings.getEncCoRRE()); + pConfig->Write(K_ENC_ZLIB, dialog_settings.getEncZlib()); + pConfig->Write(K_ENC_ZLIBHEX, dialog_settings.getEncZlibHex()); + pConfig->Write(K_ENC_ZRLE, dialog_settings.getEncZRLE()); + pConfig->Write(K_ENC_ZYWRLE, dialog_settings.getEncZYWRLE()); + pConfig->Write(K_ENC_ULTRA, dialog_settings.getEncUltra()); + pConfig->Write(K_ENC_TIGHT, dialog_settings.getEncTight()); + pConfig->Write(K_COMPRESSLEVEL, dialog_settings.getCompressLevel()); + pConfig->Write(K_QUALITY, dialog_settings.getQuality()); + + VNCConn::doLogfile(dialog_settings.getLogSavetofile()); + } +} + + +void MyFrameMain::machine_screenshot(wxCommandEvent &event) +{ + if(connections.size()) + { + VNCConn* c = connections.at(notebook_connections->GetSelection()).conn; + + wxRect rect(0, 0, c->getFrameBufferWidth(), c->getFrameBufferHeight()); + if(rect.IsEmpty()) + return; + wxBitmap screenshot = c->getFrameBufferRegion(rect); + + wxString desktopname = c->getDesktopName(); +#ifdef __WIN32__ + // windows doesn't like ':'s + desktopname.Replace(wxString(wxT(":")), wxString(wxT("-"))); +#endif + wxString filename = wxFileSelector(_("Save screenshot..."), + wxEmptyString, + desktopname + wxT("-Screenshot.png"), + wxT(".png"), + _("PNG files|*.png"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if(!filename.empty()) + { + wxBusyCursor busy; + screenshot.SaveFile(filename, wxBITMAP_TYPE_PNG); + } + } +} + + + +void MyFrameMain::machine_grabkeyboard(wxCommandEvent &event) +{ + if(connections.size()) + connections.at(notebook_connections->GetSelection()).viewerwindow->grabKeyboard(event.IsChecked()); +} + + + +void MyFrameMain::machine_save_stats(wxCommandEvent &event) +{ + if(connections.size()) + { + int sel = notebook_connections->GetSelection(); + VNCConn* c = connections.at(sel).conn; + saveStats(c, sel, c->getStats(), c->isMulticast() ? wxT("MulticastVNC") : wxT("VNC"), false); + } +} + + + +void MyFrameMain::machine_input_record(wxCommandEvent &event) +{ + if(connections.size()) + { + int sel = notebook_connections->GetSelection(); + VNCConn* c = connections.at(sel).conn; + + if(c->isReplaying()) + return; // bail out + + + if(c->isRecording()) + { + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_RECORD, bitmapBundleFromSVGResource(prefix + "/" + "record")); + frame_main_toolbar->FindById(ID_INPUT_RECORD)->SetLabel(_("Record Input")); + + wxArrayString recorded_input; + + if(c->recordUserInputStop(recorded_input)) + { + wxLogStatus(_("Stopped recording user input!")); + frame_main_menubar->SetLabel(ID_INPUT_RECORD,_("Record Input")); + + // re-enable replay buttons + GetToolBar()->EnableTool(ID_INPUT_REPLAY, true); + frame_main_menubar->Enable(ID_INPUT_REPLAY, true); + + if(recorded_input.IsEmpty()) + { + wxLogMessage(_("Nothing to save!")); + return; + } + + wxString filename = wxGetHostName() + wxT(" to ") + c->getDesktopName() + wxString::Format(wxT("(%i)"), sel) + + + wxT(" input on ") + wxNow() + wxT(".csv"); +#ifdef __WIN32__ + // windows doesn't like ':'s + filename.Replace(wxString(wxT(":")), wxString(wxT("-"))); +#endif + + filename = wxFileSelector(_("Save recorded input..."), + wxEmptyString, + filename, + wxT(".csv"), + _("CSV files|*.csv"), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + wxLogDebug(wxT("About to save recorded input to ") + filename); + + if(!filename.empty()) + { + wxBusyCursor busy; + ofstream ostream(filename.char_str()); + if(! ostream) + { + wxLogError(_("Could not save file!")); + return; + } + + for(size_t i=0; i < recorded_input.GetCount(); ++i) + ostream << recorded_input[i].char_str() << endl; + } + } + + } + else // not recording + { + wxLogMessage(_("From now on, all your mouse and keyboard input will be recorded. Click the stop button to finish recording and save your input.")); + + if( c->recordUserInputStart()) + { + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_RECORD, bitmapBundleFromSVGResource(prefix + "/" + "stop")); + frame_main_toolbar->FindById(ID_INPUT_RECORD)->SetLabel(_("Stop")); + frame_main_menubar->SetLabel(ID_INPUT_RECORD, _("Stop Recording")); + + wxLogStatus(_("Recording user input...")); + + // disable replay buttons + GetToolBar()->EnableTool(ID_INPUT_REPLAY, false); + frame_main_menubar->Enable(ID_INPUT_REPLAY, false); + } + } + + } +} + + +void MyFrameMain::machine_input_replay(wxCommandEvent &event) +{ + bool shift_was_down = wxGetMouseState().ShiftDown(); + + if(connections.size()) + { + int sel = notebook_connections->GetSelection(); + VNCConn* c = connections.at(sel).conn; + + if(c->isRecording()) + return; // bail out + + + if(c->isReplaying()) + { + c->replayUserInputStop(); + + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "replay")); + frame_main_toolbar->FindById(ID_INPUT_REPLAY)->SetLabel(_("Replay Input")); + frame_main_menubar->SetLabel(ID_INPUT_REPLAY,_("Replay Input")); + + wxLogStatus(_("Stopped replaying user input!")); + + // re-enable record buttons + GetToolBar()->EnableTool(ID_INPUT_RECORD, true); + frame_main_menubar->Enable(ID_INPUT_RECORD, true); + } + else // not replaying + { + wxArrayString recorded_input; + + // load recorded input + wxString filename = wxFileSelector(_("Load recorded input..."), + wxEmptyString, + wxEmptyString, + wxT(".csv"), + _("CSV files|*.csv"), + wxFD_OPEN|wxFD_FILE_MUST_EXIST); + + if(!filename.empty()) + { + wxBusyCursor busy; + + wxLogDebug(wxT("About to load recorded input from ") + filename); + + ifstream istream(filename.char_str()); + if(! istream) + { + wxLogError(_("Could not open file!")); + return; + } + + char buf[1024]; + while(istream.getline(buf, 1024)) + recorded_input.Add(wxString(buf, wxConvUTF8)); + } + else + return; // canceled + + + // start replay + if(c->replayUserInputStart(recorded_input, shift_was_down)) + { + wxString prefix = wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light"; + frame_main_toolbar->SetToolNormalBitmap(ID_INPUT_REPLAY, bitmapBundleFromSVGResource(prefix + "/" + "stop")); + frame_main_menubar->SetLabel(ID_INPUT_REPLAY, _("Stop Replaying")); + frame_main_toolbar->FindById(ID_INPUT_REPLAY)->SetLabel(_("Stop")); + + if(shift_was_down) + wxLogStatus(_("Replaying user input in loop...")); + else + wxLogStatus(_("Replaying user input...")); + + // disable record buttons + GetToolBar()->EnableTool(ID_INPUT_RECORD, false); + frame_main_menubar->Enable(ID_INPUT_RECORD, false); + } + } + + } +} + + + +void MyFrameMain::machine_exit(wxCommandEvent &event) +{ + Close(true); +} + + + + +void MyFrameMain::view_toggletoolbar(wxCommandEvent &event) +{ + show_toolbar = !show_toolbar; + + frame_main_toolbar->Show(show_toolbar); + + if(show_toolbar) + { + bool enable = connections.size() != 0; + + frame_main_toolbar->EnableTool(wxID_STOP, enable); // disconnect + frame_main_toolbar->EnableTool(wxID_SAVE, enable); // screenshot + + frame_main_toolbar->Show(); + } + else + { + frame_main_toolbar->Hide(); + } + + // this does more than Layout() which only deals with sizers + SendSizeEvent(); + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_SHOWTOOLBAR, show_toolbar); +} + + + +void MyFrameMain::view_togglediscovered(wxCommandEvent &event) +{ + show_discovered = !show_discovered; + + splitwinlayout(); + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_SHOWDISCOVERED, show_discovered); +} + + +void MyFrameMain::view_togglebookmarks(wxCommandEvent &event) +{ + show_bookmarks = !show_bookmarks; + + splitwinlayout(); + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_SHOWBOOKMARKS, show_bookmarks); +} + + + +void MyFrameMain::view_togglestatistics(wxCommandEvent &event) +{ + show_stats = !show_stats; + + // for now, toggle all connections + for(size_t i=0; i < connections.size(); ++i) + { + connections.at(i).conn->doStats(show_stats); + connections.at(i).viewerwindow->showStats(show_stats); + } + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_SHOWSTATS, show_stats); +} + + + + + +void MyFrameMain::view_togglefullscreen(wxCommandEvent &event) +{ + show_fullscreen = ! show_fullscreen; + + wxLogDebug("view_togglefullscreen %d", show_fullscreen); + +#ifdef __WXMAC__ + ShowFullScreen(show_fullscreen); +#else + ShowFullScreen(show_fullscreen, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); +#endif + + // according to https://docs.wxwidgets.org/3.2/classwx_full_screen_event.html + // the event is not fired when using ShowFullScreen(), so manually do this here + // (it is fired on OSX when entering fullscreen via the green button, so we need to + // have the extra event handler). + wxFullScreenEvent e = wxFullScreenEvent(0, show_fullscreen); + onFullScreenChanged(e); +} + + +void MyFrameMain::view_toggle1to1(wxCommandEvent &event) +{ + show_1to1 = ! show_1to1; + wxLogDebug("view_toggle1to1 %d", show_1to1); + + // keep toolbar and menu entries in sync + frame_main_menubar->Check(ID_ONE_TO_ONE, show_1to1); + GetToolBar()->ToggleTool(ID_ONE_TO_ONE, show_1to1); + + // for now, toggle all connections + for(size_t i=0; i < connections.size(); ++i) { + connections.at(i).viewerwindow->showOneToOne(show_1to1); + } + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_SHOW1TO1, show_1to1); +} + + + + +void MyFrameMain::view_seamless(wxCommandEvent &event) +{ + if(frame_main_menubar->IsChecked(ID_SEAMLESS_NORTH)) + show_seamless = EDGE_NORTH; + else if + (frame_main_menubar->IsChecked(ID_SEAMLESS_EAST)) + show_seamless = EDGE_EAST; + else if + (frame_main_menubar->IsChecked(ID_SEAMLESS_WEST)) + show_seamless = EDGE_WEST; + else if + (frame_main_menubar->IsChecked(ID_SEAMLESS_SOUTH)) + show_seamless = EDGE_SOUTH; + else + show_seamless = EDGE_NONE; + + //change for active connection + if(connections.size()) + { + ConnBlob* cbp = &connections.at(notebook_connections->GetSelection()); + if(cbp->seamlessconnector) + { + delete cbp->seamlessconnector; + cbp->seamlessconnector = 0; + } + if(show_seamless != EDGE_NONE) + cbp->seamlessconnector = new VNCSeamlessConnector(this, cbp->conn, show_seamless); + } + + + wxConfigBase *pConfig = wxConfigBase::Get(); + pConfig->Write(K_SHOWSEAMLESS, show_seamless); +} + + + + + +void MyFrameMain::bookmarks_add(wxCommandEvent &event) +{ + VNCConn* c = connections.at(notebook_connections->GetSelection()).conn; + wxConfigBase *cfg = wxConfigBase::Get(); + + if(c->getServerHost().IsEmpty()) + { + wxLogError(_("Cannot bookmark a reverse connection!")); + return; + } + + wxString name = wxGetTextFromUser(_("Enter bookmark name:"), + _("Saving bookmark"), + (! c->getUserName().IsEmpty() ? c->getUserName() + "@" : "") + c->getDesktopName()); + + if(name != wxEmptyString) + { + if(cfg->Exists(G_BOOKMARKS + name)) + { + wxLogError(_("A bookmark with this name already exists!")); + return; + } + + cfg->SetPath(G_BOOKMARKS + name); + + cfg->Write(K_BOOKMARKS_HOST, c->getServerHost()); + cfg->Write(K_BOOKMARKS_PORT, c->getServerPort()); + cfg->Write(K_BOOKMARKS_USER, c->getUserName()); + +#if wxUSE_SECRETSTORE + wxSecretStore store = wxSecretStore::GetDefault(); + if(store.IsOk() && c->getPassword().IsOk()) { //check if destination and source are ok + if(!store.Save("MultiVNC/Bookmarks/" + (c->getUserName().IsEmpty() ? "" : c->getUserName() + "@") + c->getServerHost() + ":" + c->getServerPort(), c->getUserName(), c->getPassword())) //FIXME the service should use the user-given bookmark name, but that requires a rework of our internal bookmarking + wxLogWarning(_("Failed to save credentials to the system secret store.")); + } +#endif + + //reset path + cfg->SetPath(wxT("/")); + + // and load into listbox + loadbookmarks(); + } +} + + + +void MyFrameMain::bookmarks_edit(wxCommandEvent &event) +{ + wxString sel = list_box_bookmarks->GetStringSelection(); + + if(sel.IsEmpty()) + { + wxLogError(_("No bookmark selected!")); + return; + } + + wxString newname = wxGetTextFromUser(_("New bookmark name:"), + _("Edit bookmark")); + + if(newname.IsEmpty()) + return; + + wxConfigBase *cfg = wxConfigBase::Get(); + + cfg->SetPath(G_BOOKMARKS); + cfg->RenameGroup(sel, newname); + //reset path + cfg->SetPath(wxT("/")); + + // and load into listbox + loadbookmarks(); +} + + + +void MyFrameMain::bookmarks_delete(wxCommandEvent &event) +{ + wxString name = list_box_bookmarks->GetStringSelection(); + + if(name.IsEmpty()) + { + wxLogError(_("No bookmark selected!")); + return; + } + + wxConfigBase *cfg = wxConfigBase::Get(); + if(!cfg->DeleteGroup(G_BOOKMARKS + name)) + wxLogError(_("No bookmark with this name!")); + +#if wxUSE_SECRETSTORE + int sel = list_box_bookmarks->GetSelection(); + if(sel != wxNOT_FOUND) { + wxString service = bookmarks[sel]; + wxSecretStore store = wxSecretStore::GetDefault(); + if(store.IsOk()) + store.Delete("MultiVNC/Bookmarks/" + service); + } +#endif + + // and re-read + loadbookmarks(); +} + + + + + +void MyFrameMain::help_about(wxCommandEvent &event) +{ + wxAboutDialogInfo info; + wxIcon icon = bitmapBundleFromSVGResource("about").GetIcon(this->FromDIP(wxSize(128,128))); + + wxString desc = "\n"; + desc += _("MultiVNC is a cross-platform Multicast-enabled VNC client."); + desc += "\n\n"; + desc += _("Built with") + " " + (wxString() << wxVERSION_STRING); + desc += "\n\n"; + desc += _("Supported Security Types:"); + desc += "\n"; + desc += _("VNC Authentication"); +#if defined LIBVNCSERVER_HAVE_GNUTLS || defined LIBVNCSERVER_HAVE_LIBSSL + desc += wxT(", Anonymous TLS, VeNCrypt"); +#endif +#if defined LIBVNCSERVER_HAVE_LIBGCRYPT || defined LIBVNCSERVER_HAVE_LIBSSL + desc += wxT(", Apple Remote Desktop"); +#endif + desc += "\n\n"; + desc += _("Supported Encodings:"); + desc += "\n"; + desc += wxT("Raw, RRE, coRRE, CopyRect, Hextile, Ultra"); +#ifdef LIBVNCSERVER_HAVE_LIBZ + desc += wxT(", UltraZip, Zlib, ZlibHex, ZRLE, ZYWRLE"); +#ifdef LIBVNCSERVER_HAVE_LIBJPEG + desc += wxT(", Tight"); +#endif +#endif + +#ifndef __WXMAC__ + info.SetIcon(icon); + info.SetVersion(VERSION); + info.SetWebSite(wxString(PACKAGE_URL)); +#endif + info.SetName(wxT("MultiVNC")); + info.SetDescription(desc); + info.SetCopyright(wxT(COPYRIGHT)); + info.AddDeveloper("Christian Beier"); + info.AddDeveloper("Evgeny Zinoviev"); + info.AddDeveloper("Audrey Dutcher"); + + wxAboutBox(info); +} + + + + +void MyFrameMain::help_contents(wxCommandEvent &e) +{ + wxLogMessage(_("\ +If there are VNC servers advertising themselves via ZeroConf, you can select a host in the\ +'Available VNC Servers' list. Otherwise, use the 'Connect' button or menu item.\ +\n\nWhen connected, a blue or green icon on the tab label shows if you are running in unicast \ +or multicast mode.")); +} + + + +void MyFrameMain::help_issue_list(wxCommandEvent &e) +{ + wxString platform; +#if defined __LINUX__ + platform = "linux"; +#elif defined __WINDOWS__ + platform = "windows"; +#elif defined __APPLE__ + platform = "mac"; +#endif + wxLaunchDefaultBrowser("https://github.com/bk138/multivnc/issues?q=is%3Aissue+is%3Aopen+label%3Aplatform-" + platform); +} + + + +void MyFrameMain::listbox_services_select(wxCommandEvent &event) +{ + // intentionally empty + wxLogStatus(_("VNC Server") + " " + list_box_services->GetStringSelection()); +} + + +void MyFrameMain::listbox_services_dclick(wxCommandEvent &event) +{ + int timeout; + wxBusyCursor busy; + int sel = event.GetInt(); + + if(sel < 0) // seems this happens when we update the list + return; + + wxLogStatus(_("Looking up host address...")); + + + // lookup hostname and port + { + wxServDisc namescan(0, servscan->getResults().at(sel).name, QTYPE_SRV); + + timeout = 5000; + while(!namescan.getResultCount() && timeout > 0) + { + wxMilliSleep(25); + timeout-=25; + } + if(timeout <= 0) + { + wxLogError(_("Timeout looking up hostname.")); + wxLogStatus(_("Timeout looking up hostname.")); + services_hostname = services_addr = services_port = wxEmptyString; + return; + } + services_hostname = namescan.getResults().at(0).name; + services_port = wxString() << namescan.getResults().at(0).port; + } + + // check if we actually have to resolve the IP address ourselves + bool is_system_resolving_mdns = false; +#ifdef __WXMAC__ + is_system_resolving_mdns = true; +#else + wxLog::EnableLogging(false); + wxFileInputStream input("/etc/nsswitch.conf"); + wxLog::EnableLogging(true); + wxTextInputStream text(input); + while(input.IsOk() && !input.Eof()) + if(text.ReadLine().Contains("mdns")) { + is_system_resolving_mdns = true; + wxLogDebug("System resolver does mDNS, skipping IP address lookup"); + break; + } +#endif + + if(!is_system_resolving_mdns) + // lookup ip address + { + wxServDisc addrscan(0, services_hostname, QTYPE_A); + + timeout = 5000; + while(!addrscan.getResultCount() && timeout > 0) + { + wxMilliSleep(25); + timeout-=25; + } + if(timeout <= 0) + { + wxLogError(_("Timeout looking up IP address.")); + wxLogStatus(_("Timeout looking up IP address.")); + services_hostname = services_addr = services_port = wxEmptyString; + return; + } + services_addr = addrscan.getResults().at(0).ip; + } + else + services_addr = services_hostname; // system resolves mDNS + + wxLogStatus(services_hostname + wxT(" (") + services_addr + wxT(":") + services_port + wxT(")")); + + + spawn_conn(services_addr + wxT(":") + services_port); +} + + + +void MyFrameMain::listbox_bookmarks_select(wxCommandEvent &event) +{ + int sel = event.GetInt(); + + if(sel >= 0) // something selected + { + wxLogStatus(_("Bookmark %s"), bookmarks[sel]); + } +} + + +void MyFrameMain::listbox_bookmarks_dclick(wxCommandEvent &event) +{ + int sel = event.GetInt(); + + if(sel < 0) { // nothing selected + // this gets set by Connect() in loadbookmarks(), in this case sel is always -1 + if(event.m_callbackUserData) + sel = wxAtoi(*(wxString*)event.m_callbackUserData); + else + return; + } + + spawn_conn(bookmarks[sel]); +} + + +void MyFrameMain::listbox_bookmarks_context(wxContextMenuEvent &event) +{ + wxPoint clientPosition = + list_box_bookmarks->ScreenToClient(event.GetPosition()); + + int itemIndex = list_box_bookmarks->HitTest(clientPosition); + + if (itemIndex != wxNOT_FOUND) { + // Programmatically select the item. This is for UX purposes and, + // more important, so that edit and delete function work on the + // correct item. + list_box_bookmarks->SetSelection(itemIndex); + + // and show the context menu + wxMenu menu; + menu.Append(wxID_EDIT, _("&Edit Bookmark")); + menu.Bind(wxEVT_MENU, &MyFrameMain::bookmarks_edit, this, wxID_EDIT); + menu.Append(wxID_DELETE, _("&Delete Bookmark")); + menu.Bind(wxEVT_MENU, &MyFrameMain::bookmarks_delete, this, wxID_DELETE); + PopupMenu(&menu); + } +} + +void MyFrameMain::notebook_connections_pagechanged(wxNotebookEvent &event) +{ + ConnBlob* cb; + if(connections.size()) + cb = &connections.at(notebook_connections->GetSelection()); + else + return; + + bool isSharing = cb->windowshare_proc ? true : false; + wxLogDebug(wxT("notebook_connections_pagechanged(): VNCConn %p sharing is %d"), cb->conn, isSharing); + // this is "share window" + frame_main_menubar->Enable(wxID_UP, !isSharing); + // this is "stop share window" + frame_main_menubar->Enable(wxID_CANCEL, isSharing); + + if(cb->seamlessconnector) + { + switch(cb->seamlessconnector->getEdge()) + { + case EDGE_NORTH: + frame_main_menubar->Check(ID_SEAMLESS_NORTH, true); + break; + case EDGE_EAST: + frame_main_menubar->Check(ID_SEAMLESS_EAST, true); + break; + case EDGE_WEST: + frame_main_menubar->Check(ID_SEAMLESS_WEST, true); + break; + case EDGE_SOUTH: + frame_main_menubar->Check(ID_SEAMLESS_SOUTH, true); + break; + default: + frame_main_menubar->Check(ID_SEAMLESS_DISABLED, true); + } + + cb->seamlessconnector->Raise(); + } + else // no object, i.e. disabled + frame_main_menubar->Check(ID_SEAMLESS_DISABLED, true); +} + + + +void MyFrameMain::cmdline_connect(wxString& hostarg) +{ + spawn_conn(hostarg); +} + + + + +void MyFrameMain::windowshare_start(wxCommandEvent &event) +{ + wxBusyCursor busy; + + ConnBlob* cb = 0; + if(connections.size()) + cb = &connections.at(notebook_connections->GetSelection()); + else + return; + + // right now x11vnc and WinVNC behave differently :-/ +#ifdef __WIN32__ + wxString window = wxGetTextFromUser(_("Enter name of window to share:"), _("Share a Window")); + if(window == wxEmptyString) + return; +#else + if(wxMessageBox(_("The MultiVNC window will be minimized and a cross-shaped cursor will appear. Use it to select the window you want to share."), + _("Share a Window"), wxOK|wxCANCEL) + == wxCANCEL) + return; + + Iconize(true); +#endif + + // handle %a and %p + wxString cmd = windowshare_cmd_template; + cmd.Replace(wxT("%a"), cb->conn->getServerHost()); + // cmd.Replace(_T("%p"), port); //unused by now +#ifdef __WIN32__ + cmd.Replace(wxT("%w"), window); +#endif + + // terminate old one + wxCommandEvent unused; + windowshare_stop(unused); + + // and start new one + cb->windowshare_proc = new wxProcess(this, ID_WINDOWSHARE_PROC_END); + cb->windowshare_proc_pid = wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER, cb->windowshare_proc); + wxLogDebug(wxT("windowshare_start() spawned %d."), cb->windowshare_proc_pid); + + if(cb->windowshare_proc_pid == 0) + { + SetStatusText(_("Window sharing helper execution failed.")); + wxLogError( _("Could not share window, external program execution failed.")); + + delete cb->windowshare_proc; + cb->windowshare_proc = 0; + cb->windowshare_proc_pid = 0; + return; + } + + SetStatusText(wxString::Format(_("Sharing window with %s"), cb->conn->getDesktopName())); + + // this is "share window" + frame_main_menubar->Enable(wxID_UP, false); + // this is "stop share window" + frame_main_menubar->Enable(wxID_CANCEL, true); +} + + + + +void MyFrameMain::windowshare_stop(wxCommandEvent &event) +{ + wxBusyCursor busy; + + ConnBlob* cb; + if(connections.size()) + cb = &connections.at(notebook_connections->GetSelection()); + else + return; + + if(!cb->windowshare_proc || !cb->windowshare_proc_pid) + return; + + wxLogDebug(wxT("windowshare_stop(): tries to kill %d."), cb->windowshare_proc_pid); + + if(!wxProcess::Exists(cb->windowshare_proc_pid)) + { + wxLogDebug(wxT("windowshare_stop(): PID does not exist, exiting!")); + return; + } + + // avoid callback call, now obj pointed to by windowshare_proc deletes itself! + cb->windowshare_proc->Detach(); + + if(wxKill(cb->windowshare_proc_pid, wxSIGTERM, NULL, wxKILL_CHILDREN) == 0) + { + wxLogDebug(wxT("windowshare_stop(): successfully killed %d."), cb->windowshare_proc_pid); + cb->windowshare_proc = 0; // obj deleted itself because of Detach()! + cb->windowshare_proc_pid = 0; + + wxLogStatus(_("Stopped sharing window with %s"), cb->conn->getDesktopName()); + + // this is "share window" + frame_main_menubar->Enable(wxID_UP, true); + // this is "stop share window" + frame_main_menubar->Enable(wxID_CANCEL, false); + } + else + wxLogDebug(wxT("windowshare_stop(): Could not kill %d. Not good."), cb->windowshare_proc_pid); +} + + + + diff --git a/src/gui/MyFrameMain.h b/src/gui/MyFrameMain.h index d4aa8bf8..7b79f600 100644 --- a/src/gui/MyFrameMain.h +++ b/src/gui/MyFrameMain.h @@ -1,149 +1,149 @@ -// -*- C++ -*- - -#ifndef MYFRAMEMAIN_H -#define MYFRAMEMAIN_H - - -#include -#include -#include -#include "FrameMain.h" -#include "MyFrameLog.h" -#include "wxServDisc.h" -#include "VNCConn.h" -#include "ViewerWindow.h" -#include "VNCSeamlessConnector.h" - - - -// this hold all info regarding one connection -struct ConnBlob -{ - VNCConn* conn; - ViewerWindow* viewerwindow; - VNCSeamlessConnector* seamlessconnector; - wxProcess* windowshare_proc; - long windowshare_proc_pid; // this should be saved in the wxProcess, but isn't -}; - - -class MyFrameMain: public FrameMain -{ - // main service scanner - wxServDisc* servscan; - - // array of connections - std::vector connections; - // set of reverse VNC (listen) connection's port numbers - std::set listen_ports; - - // listbox_services_select() stores values here for listbox_services_dclick() - wxString services_hostname, services_addr, services_port; - - // gui layout stuff - bool show_toolbar; - bool show_discovered; - bool show_bookmarks; - bool show_stats; - bool show_fullscreen; - int show_seamless; - bool show_1to1; - - // log window - MyFrameLog* logwindow; - - void splitwinlayout(); - - // array of bookmark strings - wxArrayString bookmarks; - bool loadbookmarks(); - - void spawn_conn(wxString service, int listenPort = -1); - void setup_conn(VNCConn *conn); - void terminate_conn(int which); - - // collab features - wxString windowshare_cmd_template; - void onWindowshareTerminate(wxProcessEvent& event); - - - // private handlers - void onMyFrameLogCloseNotify(wxCommandEvent& event); - void onVNCConnListenNotify(wxCommandEvent& event); - void onVNCConnInitNotify(wxCommandEvent& event); - void onVNCConnGetPasswordNotify(wxCommandEvent& event); - void onVNCConnGetCredentialsNotify(wxCommandEvent& event); - void onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event); - void onVNCConnUniMultiChangedNotify(wxCommandEvent& event); - void onVNCConnReplayFinishedNotify(wxCommandEvent& event); - void onVNCConnFBResizeNotify(wxCommandEvent& event); - void onVNCConnDisconnectNotify(wxCommandEvent& event); - void onVNCConnIncomingConnectionNotify(wxCommandEvent& event); - void onVNCConnCuttextNotify(wxCommandEvent& event); - void onVNCConnBellNotify(wxCommandEvent& event); - void onSDNotify(wxCommandEvent& event); - void onFullScreenChanged(wxFullScreenEvent &event); - void onSysColourChanged(wxSysColourChangedEvent& event); - - bool saveStats(VNCConn* c, int conn_index, const wxArrayString& stats, wxString desc, bool autosave); - - -protected: - DECLARE_EVENT_TABLE(); - -public: - MyFrameMain(wxWindow* parent, int id, const wxString& title, - const wxPoint& pos=wxDefaultPosition, - const wxSize& size=wxDefaultSize, - long style=wxDEFAULT_FRAME_STYLE); - ~MyFrameMain(); - - - // handlers - void listbox_services_select(wxCommandEvent &event); - void listbox_services_dclick(wxCommandEvent &event); - void listbox_bookmarks_select(wxCommandEvent &event); - void listbox_bookmarks_dclick(wxCommandEvent &event); - void listbox_bookmarks_context(wxContextMenuEvent &event); - - void notebook_connections_pagechanged(wxNotebookEvent &event); - - void machine_connect(wxCommandEvent &event); - void machine_listen(wxCommandEvent &event); - void machine_disconnect(wxCommandEvent &event); - void machine_preferences(wxCommandEvent &event); - void machine_showlog(wxCommandEvent &event); - void machine_screenshot(wxCommandEvent &event); - void machine_grabkeyboard(wxCommandEvent &event); - void machine_save_stats(wxCommandEvent &event); - void machine_input_record(wxCommandEvent &event); - void machine_input_replay(wxCommandEvent &event); - void machine_exit(wxCommandEvent &event); - - void view_toggletoolbar(wxCommandEvent &event); - void view_togglediscovered(wxCommandEvent &event); - void view_togglebookmarks(wxCommandEvent &event); - void view_togglestatistics(wxCommandEvent &event); - void view_togglefullscreen(wxCommandEvent &event); - void view_toggle1to1(wxCommandEvent &event); - void view_seamless(wxCommandEvent &event); - - void bookmarks_add(wxCommandEvent &event); - void bookmarks_edit(wxCommandEvent &event); - void bookmarks_delete(wxCommandEvent &event); - - void windowshare_start(wxCommandEvent &event); - void windowshare_stop(wxCommandEvent &event); - - void help_about(wxCommandEvent &event); - void help_contents(wxCommandEvent &event); - void help_issue_list(wxCommandEvent &event); - - // to be called from the App - void cmdline_connect(wxString& hostarg); - -}; - - - -#endif +// -*- C++ -*- + +#ifndef MYFRAMEMAIN_H +#define MYFRAMEMAIN_H + + +#include +#include +#include +#include "FrameMain.h" +#include "MyFrameLog.h" +#include "wxServDisc.h" +#include "VNCConn.h" +#include "ViewerWindow.h" +#include "VNCSeamlessConnector.h" + + + +// this hold all info regarding one connection +struct ConnBlob +{ + VNCConn* conn; + ViewerWindow* viewerwindow; + VNCSeamlessConnector* seamlessconnector; + wxProcess* windowshare_proc; + long windowshare_proc_pid; // this should be saved in the wxProcess, but isn't +}; + + +class MyFrameMain: public FrameMain +{ + // main service scanner + wxServDisc* servscan; + + // array of connections + std::vector connections; + // set of reverse VNC (listen) connection's port numbers + std::set listen_ports; + + // listbox_services_select() stores values here for listbox_services_dclick() + wxString services_hostname, services_addr, services_port; + + // gui layout stuff + bool show_toolbar; + bool show_discovered; + bool show_bookmarks; + bool show_stats; + bool show_fullscreen; + int show_seamless; + bool show_1to1; + + // log window + MyFrameLog* logwindow; + + void splitwinlayout(); + + // array of bookmark strings + wxArrayString bookmarks; + bool loadbookmarks(); + + void spawn_conn(wxString service, int listenPort = -1); + void setup_conn(VNCConn *conn); + void terminate_conn(int which); + + // collab features + wxString windowshare_cmd_template; + void onWindowshareTerminate(wxProcessEvent& event); + + + // private handlers + void onMyFrameLogCloseNotify(wxCommandEvent& event); + void onVNCConnListenNotify(wxCommandEvent& event); + void onVNCConnInitNotify(wxCommandEvent& event); + void onVNCConnGetPasswordNotify(wxCommandEvent& event); + void onVNCConnGetCredentialsNotify(wxCommandEvent& event); + void onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event); + void onVNCConnUniMultiChangedNotify(wxCommandEvent& event); + void onVNCConnReplayFinishedNotify(wxCommandEvent& event); + void onVNCConnFBResizeNotify(wxCommandEvent& event); + void onVNCConnDisconnectNotify(wxCommandEvent& event); + void onVNCConnIncomingConnectionNotify(wxCommandEvent& event); + void onVNCConnCuttextNotify(wxCommandEvent& event); + void onVNCConnBellNotify(wxCommandEvent& event); + void onSDNotify(wxCommandEvent& event); + void onFullScreenChanged(wxFullScreenEvent &event); + void onSysColourChanged(wxSysColourChangedEvent& event); + + bool saveStats(VNCConn* c, int conn_index, const wxArrayString& stats, wxString desc, bool autosave); + + +protected: + DECLARE_EVENT_TABLE(); + +public: + MyFrameMain(wxWindow* parent, int id, const wxString& title, + const wxPoint& pos=wxDefaultPosition, + const wxSize& size=wxDefaultSize, + long style=wxDEFAULT_FRAME_STYLE); + ~MyFrameMain(); + + + // handlers + void listbox_services_select(wxCommandEvent &event); + void listbox_services_dclick(wxCommandEvent &event); + void listbox_bookmarks_select(wxCommandEvent &event); + void listbox_bookmarks_dclick(wxCommandEvent &event); + void listbox_bookmarks_context(wxContextMenuEvent &event); + + void notebook_connections_pagechanged(wxNotebookEvent &event); + + void machine_connect(wxCommandEvent &event); + void machine_listen(wxCommandEvent &event); + void machine_disconnect(wxCommandEvent &event); + void machine_preferences(wxCommandEvent &event); + void machine_showlog(wxCommandEvent &event); + void machine_screenshot(wxCommandEvent &event); + void machine_grabkeyboard(wxCommandEvent &event); + void machine_save_stats(wxCommandEvent &event); + void machine_input_record(wxCommandEvent &event); + void machine_input_replay(wxCommandEvent &event); + void machine_exit(wxCommandEvent &event); + + void view_toggletoolbar(wxCommandEvent &event); + void view_togglediscovered(wxCommandEvent &event); + void view_togglebookmarks(wxCommandEvent &event); + void view_togglestatistics(wxCommandEvent &event); + void view_togglefullscreen(wxCommandEvent &event); + void view_toggle1to1(wxCommandEvent &event); + void view_seamless(wxCommandEvent &event); + + void bookmarks_add(wxCommandEvent &event); + void bookmarks_edit(wxCommandEvent &event); + void bookmarks_delete(wxCommandEvent &event); + + void windowshare_start(wxCommandEvent &event); + void windowshare_stop(wxCommandEvent &event); + + void help_about(wxCommandEvent &event); + void help_contents(wxCommandEvent &event); + void help_issue_list(wxCommandEvent &event); + + // to be called from the App + void cmdline_connect(wxString& hostarg); + +}; + + + +#endif diff --git a/src/gui/VNCSeamlessConnector.cpp b/src/gui/VNCSeamlessConnector.cpp index fe21df92..20ddbb6b 100644 --- a/src/gui/VNCSeamlessConnector.cpp +++ b/src/gui/VNCSeamlessConnector.cpp @@ -1,1609 +1,1609 @@ - -#include -#include -#include -#ifdef __WXGTK__ -#include -#include -#endif -#include "MultiVNCApp.h" -#include "VNCSeamlessConnector.h" - - - - -/******************************************** - - VNCSeamlessConnector class - -********************************************/ - - -/* - public members -*/ - -#define RAISETIMER_ID 666 -BEGIN_EVENT_TABLE(VNCSeamlessConnector, wxFrame) - EVT_TIMER (RAISETIMER_ID, VNCSeamlessConnector::onRaiseTimer) -END_EVENT_TABLE(); - - - -#define EDGE_EW (edge == EDGE_EAST || edge==EDGE_WEST) -#define EDGE_NS (edge == EDGE_NORTH || edge==EDGE_SOUTH) -#define EDGE_ES (edge == EDGE_EAST || edge==EDGE_SOUTH) -#define EDGE_NS (edge == EDGE_NORTH || edge==EDGE_SOUTH) - - -VNCSeamlessConnector::VNCSeamlessConnector(wxWindow* parent, VNCConn* c, int e, size_t ew, float accel) - : wxFrame(parent, wxID_ANY, c->getDesktopName(), wxDefaultPosition, wxDefaultSize, - wxFRAME_SHAPED | wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP) -{ - conn = c; - edge = e; - edge_width = ew; - acceleration = accel; - - pointer_warp_threshold=5; - - pointer_speed = 0.0; - grabbed = false; - - /* - // init all x2vnc stuff start - grabCursor=0; - hidden=0; - client_selection_text=0; - client_selection_text_length=0; - saved_xpos=-1; - saved_ypos=-1; - debug = true; - resurface = false; - // init all x2vnc stuff end - for (int i = 0; i < 256; i++) - modifierPressed[i] = False; - - if (!(dpy = XOpenDisplay(NULL))) - fprintf(stderr," unable to open display %s\n", XDisplayName(NULL)); - - - // this sets: topLevel, deskopt atoms - //if(CreateXWindow()) - // fprintf(stderr, "sucessfully created xwindow!\n"); - // topLevel = GDK_WINDOW_XID(GetHandle()->window); - - - topLevel = 0; - //runtimer.SetOwner(this, 666); - //runtimer.Start(5); - */ - - adjustSize(); - -#ifdef __WXGTK__ - // this is needed cause since newer gtk version the gnome panel - // raises above the connector sometimes...grrr - raisetimer.SetOwner(this, RAISETIMER_ID); - raisetimer.Start(100); -#endif - - canvas = new VNCSeamlessConnectorCanvas(this); -#ifdef NDEBUG - canvas->SetCursor(wxCursor(wxCURSOR_BLANK)); -#endif - wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(canvas, 1, wxEXPAND, 0); - SetSizer(sizer); - Layout(); - - - this->Show(true); - // seems that has to come after Show() to take effect - SetBackgroundColour(*wxRED); - SetTransparent(100); -} - - -VNCSeamlessConnector::~VNCSeamlessConnector() -{ - /* - runtimer.Stop(); - - if(topLevel) - XDestroyWindow(dpy, topLevel); - */ -} - - -bool VNCSeamlessConnector::isSupportedByCurrentPlatform() { -#ifdef __WXGTK__ - wxString sessionType; - wxGetEnv("XDG_SESSION_TYPE", &sessionType); - return sessionType.IsSameAs("x11"); -#endif - return false; -} - - - -void VNCSeamlessConnector::adjustSize() -{ - wxLogDebug(wxT("VNCSeamLessConnector %p: adjusting size"), this); - - // use the biggest screen - wxSize biggest_size; - for(size_t i=0; i < wxDisplay::GetCount(); ++i) - { - wxSize this_size = wxDisplay(i).GetGeometry().GetSize(); - - if(this_size.GetWidth() * this_size.GetHeight() > - biggest_size.GetWidth() * biggest_size.GetHeight()) - { - biggest_size = this_size; - display_index = i; - } - } - // set offset accordingly - multiscreen_offset = wxDisplay(display_index).GetGeometry().GetPosition(); - - // get local display size - display_size = wxDisplay(display_index).GetGeometry().GetSize(); - - /* - saved_remote_xpos = display_size.GetWidth() / 2; - saved_remote_ypos = display_size.GetHeight() / 2; - */ - - // get remote display size - framebuffer_size.SetWidth(conn->getFrameBufferWidth()); - framebuffer_size.SetHeight(conn->getFrameBufferHeight()); - - // compute a pos to hide cursor, so user won't get confused - // which keyboard has control - remoteParkingPos.x = framebuffer_size.GetWidth() -2; - remoteParkingPos.y = framebuffer_size.GetHeight() -2; - - wxMouseEvent e; - e.m_x = remoteParkingPos.x; - e.m_y = remoteParkingPos.y; - conn->sendPointerEvent(e); - - // calc pointer speed from sizes - pointer_speed = acceleration * pow( (framebuffer_size.GetWidth() * framebuffer_size.GetHeight()) / (float)(display_size.GetWidth() * display_size.GetHeight()), 0.25 ); - - wxLogDebug(wxT("VNCSeamlessConnector %p: pointer multiplier %f"), this, pointer_speed); - - - - // Compute two identifiable locations, as far as possible from each other - if(display_size.GetWidth() * 2 > display_size.GetHeight()) - { - origo1=wxPoint(display_size.GetWidth()/3,display_size.GetHeight()/2); - origo2=wxPoint(display_size.GetWidth()*2/3,display_size.GetHeight()/2); - origo_separation=display_size.GetWidth()/3; - } - else if(display_size.GetHeight() * 2 > display_size.GetWidth()) - { - origo1=wxPoint(display_size.GetWidth()/2,display_size.GetHeight()/3); - origo2=wxPoint(display_size.GetWidth()/2,display_size.GetHeight()*2/3); - origo_separation=display_size.GetHeight()/3; - } - else - { - int N=(int)( (2*(display_size.GetWidth()+display_size.GetHeight())- - sqrt((-3*display_size.GetWidth()*display_size.GetWidth()-3*display_size.GetHeight()*display_size.GetHeight()+8*display_size.GetWidth()*display_size.GetHeight())) - )/7.0 ); - origo1=wxPoint(N,N); - origo2=wxPoint(display_size.GetWidth()-N,display_size.GetHeight()-N); - origo_separation=N; - } - origo1.x+=multiscreen_offset.x; - origo1.y+=multiscreen_offset.y; - origo2.x+=multiscreen_offset.x; - origo2.y+=multiscreen_offset.y; - - wxLogDebug(wxT("VNCSeamlessConnector %p: Origo1 (%d, %d)"), this, origo1.x,origo1.y); - wxLogDebug(wxT("VNCSeamlessConnector %p: Origo2 (%d, %d)"), this, origo2.x,origo2.y); - wxLogDebug(wxT("VNCSeamlessConnector %p: multiscreen offset: (%d, %d)"), this, multiscreen_offset.x,multiscreen_offset.y); - - - - // the following code sizes and places our window - int ew = edge_width; - if(!ew) - ew=1; - - int width=ew; - int height=ew; - int x = multiscreen_offset.x; - int y = multiscreen_offset.y; - - switch(edge) - { - case EDGE_EAST: x = display_size.GetWidth() - ew + multiscreen_offset.x; - case EDGE_WEST: height = display_size.GetHeight(); - break; - - case EDGE_SOUTH: y = display_size.GetHeight() - ew + multiscreen_offset.y; - case EDGE_NORTH: width = display_size.GetWidth(); - break; - - default: - width = height = 0; - } - - - SetSize(width, height); - Move(x, y); - -#ifdef __WXGTK__ - gtk_window_set_type_hint(GTK_WINDOW(GetHandle()), GDK_WINDOW_TYPE_HINT_DOCK); - gtk_window_set_keep_above(GTK_WINDOW(GetHandle()), true); -#endif - - /* - if(edge_width) - Raise(); - else - hidden=1;*/ -} - - - -/* - protected members -*/ - - - - - -/* - private members -*/ - -void VNCSeamlessConnector::onRaiseTimer(wxTimerEvent& event) -{ - Raise(); -} - - -void VNCSeamlessConnector::handleMouse(wxMouseEvent& event) -{ - wxPoint evt_root_pos = ClientToScreen(event.GetPosition()); - wxLogDebug(wxT("VNCSeamLessConnector %p: new mouse evt: (%d, %d)"), this, evt_root_pos.x, evt_root_pos.y); - - int x,y; - - doWarp(); - - if(event.Entering()) - { - // if(!HasCapture()) - wxLogDebug(wxT("VNCSeamlessConnector %p: mouse entering!"), this); - - // set cuttext from clipboard - { - if(wxTheClipboard->Open()) - { - if(wxTheClipboard->IsSupported(wxDF_TEXT)) - { - wxTextDataObject data; - wxTheClipboard->GetData(data); - wxString text = data.GetText(); - wxLogDebug(wxT("VNCSeamlessConnector %p: setting cuttext: '%s'"), this, text.c_str()); - conn->setCuttext(text); - } - wxTheClipboard->Close(); - } - } - - // check for a change of display size - if(display_size != wxDisplay(display_index).GetGeometry().GetSize()) - adjustSize(); - - if(!grabbed) - { - wxLogDebug(wxT("VNCSeamlessConnector %p: mouse entering, grabbing!"), this); - grabit(enter_translate(EDGE_EW,display_size.GetWidth() , (evt_root_pos.x - multiscreen_offset.x)), - enter_translate(EDGE_NS,display_size.GetHeight(), (evt_root_pos.y - multiscreen_offset.y)), - 0); - // ev->xcrossing.state); //FIXME buttons - } - return; - } - - if(event.Moving() || event.Dragging()) - { - wxLogDebug(wxT("VNCSeamlessConnector %p: mouse moving/dragging!"), this); - if(grabbed) - { - wxLogDebug(wxT("VNCSeamlessConnector %p: mouse moving/dragging while grabbed!"), this); - int d=0; - - wxPoint offset(0,0); - - wxPoint new_location(evt_root_pos.x, evt_root_pos.y); - - motion_events++; - - if(next_origo && - (coord_dist_sq(new_location, *next_origo) < coord_dist_sq(new_location, current_location) || - coord_dist_from_edge(new_location) < motion_events)) - { - current_origo=next_origo; - current_location=*next_origo; - next_origo=NULL; - motion_events=0; - } - - current_speed = new_location - current_location; - if(current_origo) - offset= offset + current_speed; - current_location=new_location; - - - if(pointer_speed > 1 && - offset.x*offset.x + offset.y*offset.y < - pointer_warp_threshold) - { - remotePos.x+=offset.x; - remotePos.y+=offset.y; - wxLogDebug(wxT("VNCSeamlessConnector %p: if: remotePos (%f, %f)"), this, remotePos.x, remotePos.y); - } - else - { - remotePos.x+=offset.x * pointer_speed; - remotePos.y+=offset.y * pointer_speed; - wxLogDebug(wxT("VNCSeamlessConnector %p: else: remotePos (%f, %f)"), this, remotePos.x, remotePos.y); - } - - - // if(!(ev->xmotion.state & 0x1f00)) //FIXME - if(!event.Dragging()) - { - switch(edge) - { - case EDGE_NORTH: - d=remotePos.y >= framebuffer_size.GetHeight(); - y = edge_width; /* FIXME */ - x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); - break; - case EDGE_SOUTH: - d=remotePos.y < 0; - y = display_size.GetHeight() - edge_width -1; /* FIXME */ - x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); - break; - case EDGE_EAST: - d=remotePos.x < 0; - x = display_size.GetWidth() - edge_width -1; /* FIXME */ - y = remotePos.y * display_size.GetHeight() /framebuffer_size.GetHeight() ; - break; - case EDGE_WEST: - d=remotePos.x > framebuffer_size.GetWidth(); - x = edge_width; /* FIXME */ - y = remotePos.y * display_size.GetHeight() / framebuffer_size.GetHeight(); - break; - } - } - - if(d) - { - wxLogDebug(wxT("VNCSeamlessConnector %p: d is set!"), this); - if(x<0) x=0; - if(y<0) y=0; - if(y>=display_size.GetHeight()) y=display_size.GetHeight()-1; - if(x>=display_size.GetWidth()) x=display_size.GetWidth()-1; - ungrabit(x, y); - return; - } - else - { - if(remotePos.x < 0) remotePos.x=0; - if(remotePos.y < 0) remotePos.y=0; - - if(remotePos.x >= framebuffer_size.GetWidth()) - remotePos.x=framebuffer_size.GetWidth()-1; - - if(remotePos.y >= framebuffer_size.GetHeight()) - remotePos.y=framebuffer_size.GetHeight()-1; - - event.m_x = remotePos.x; - event.m_y = remotePos.y; - wxLogDebug(wxT("VNCSeamlessConnector %p: moving/dragging sending (%d, %d)"), this, event.m_x, event.m_y); - conn->sendPointerEvent(event); - } - - } - return; - } - - // a non-moving event (button, wheel, whatever...) - event.m_x = remotePos.x; - event.m_y = remotePos.y; - conn->sendPointerEvent(event); - return; -} - - - - -void VNCSeamlessConnector::handleKey(wxKeyEvent& evt) -{ - doWarp(); - - wxLogDebug(wxT("VNCSeamlessConnector %p: got some key event!"), this); - - if(evt.GetEventType() == wxEVT_KEY_DOWN) - conn->sendKeyEvent(evt, true, false); - - if(evt.GetEventType() == wxEVT_KEY_UP) - conn->sendKeyEvent(evt, false, false); - - if(evt.GetEventType() == wxEVT_CHAR) - conn->sendKeyEvent(evt, true, true); -} - - - -void VNCSeamlessConnector::handleFocusLoss() -{ - wxLogDebug(wxT("VNCSeamlessConnector %p: lost focus, upping key modifiers"), this); - - wxKeyEvent key_event; - - key_event.m_keyCode = WXK_SHIFT; - conn->sendKeyEvent(key_event, false, false); - key_event.m_keyCode = WXK_ALT; - conn->sendKeyEvent(key_event, false, false); - key_event.m_keyCode = WXK_CONTROL; - conn->sendKeyEvent(key_event, false, false); -} - - -void VNCSeamlessConnector::handleCaptureLoss() -{ - wxLogDebug(wxT("VNCSeamlessConnector %p: lost capture, emergency ungrab"), this); - - ungrabit(display_size.x/2, display_size.y/2); - handleFocusLoss(); -} - - -void VNCSeamlessConnector::doWarp(void) -{ - if(grabbed) - { - if(next_origo) return; - if(current_origo && - current_location.x == current_origo->x && - current_location.y == current_origo->y) - return; - - if(current_origo == &origo1) - next_origo=&origo2; - else - next_origo=&origo1; - - wxLogDebug(wxT("VNCSeamlessConnector %p: WARP to (%d, %d)"), this, next_origo->x, next_origo->y); - /*XWarpPointer(dpy,None, - DefaultRootWindow(dpy),0,0,0,0, - next_origo->x, - next_origo->y);*/ - - - wxPoint warp_pos= canvas->ScreenToClient(*next_origo); - wxLogDebug(wxT("VNCSeamlessConnector %p: WARP translated (%d, %d)"), this, warp_pos.x, warp_pos.y); - canvas->WarpPointer(warp_pos.x, warp_pos.y); - - motion_events=0; - } -} - - - -int VNCSeamlessConnector::enter_translate(int isedge, int width, int pos) -{ - if(!isedge) - return pos; - if(EDGE_ES) - return 0; - return width-1; -} - -int VNCSeamlessConnector::leave_translate(int isedge, int width, int pos) -{ - if(!isedge) - return pos; - if(EDGE_ES) - return width-edge_width; - return 0; -} - - -void VNCSeamlessConnector::grabit(int x, int y, int state) -{ - //Window selection_owner; - - wxLogDebug(wxT("VNCSeamlessConnector %p: GRAB!"), this); - - /* - if(hidden) - { - XMapRaised(dpy, topLevel); - hidden=0; - } - - if(!topLevel) - */ - { - canvas->CaptureMouse(); - canvas->SetFocus(); -#ifdef __WXGTK__ - gdk_keyboard_grab(gtk_widget_get_window(canvas->GetHandle()), False, GDK_CURRENT_TIME); -#endif - } - /* - else - { - XGrabPointer(dpy, topLevel, True, - PointerMotionMask | ButtonPressMask | ButtonReleaseMask, - GrabModeAsync, GrabModeAsync, - None, grabCursor, CurrentTime); - XGrabKeyboard(dpy, topLevel, True, - GrabModeAsync, GrabModeAsync, - CurrentTime); - } - */ - - grabbed=1; - next_origo=NULL; - current_origo=NULL; - - if(x > -1 && y > -1) - { - doWarp(); - -#define EDGE(X) ((X)?edge_width:0) -#define SCALEX(X) \ - ( ((X)-EDGE(edge==EDGE_WEST ))*(framebuffer_size.GetWidth()-1 )/(display_size.GetWidth() -1-EDGE(EDGE_EW)) ) -#define SCALEY(Y) \ - ( ((Y)-EDGE(edge==EDGE_NORTH))*(framebuffer_size.GetHeight()-1)/(display_size.GetHeight()-1-EDGE(EDGE_NS)) ) - - /* Whut? How can this be right? */ - remotePos.x=SCALEX(x); - remotePos.y=SCALEY(y); - wxMouseEvent evt; - evt.m_x = remotePos.x; - evt.m_y = remotePos.y; - wxLogDebug(wxT("VNCSeamlessConnector %p: GRAB got position (%d, %d)"), this, x, y); - wxLogDebug(wxT("VNCSeamlessConnector %p: GRAB sending pointer event (%d, %d)"), this, evt.m_x, evt.m_y); - //sendpointerevent(remotePos.x, remotePos.y, (state & 0x1f00) >> 8); - conn->sendPointerEvent(evt); - } - - - // mouseOnScreen = 1; - - - /* - selection_owner=XGetSelectionOwner(dpy, XA_PRIMARY); - // fprintf(stderr,"Selection owner: %lx\n",(long)selection_owner); - - if(selection_owner != None && selection_owner != topLevel) - { - XConvertSelection(dpy, - XA_PRIMARY, XA_STRING, XA_CUT_BUFFER0, - topLevel, CurrentTime); - } - */ - // XSync(dpy, False); -} - -void VNCSeamlessConnector::ungrabit(int x, int y) -{ - // int i; - - wxMouseEvent e; - e.m_x = remoteParkingPos.x; - e.m_y = remoteParkingPos.y; - - conn->sendPointerEvent(e); - - wxLogDebug(wxT("VNCSeamlessConnector %p: UNGRAB!"), this); - - - if(x > -1 && y > -1 ) - { - //XWarpPointer(dpy,None, warpWindow, 0,0,0,0, multiscreen_offset.x + x, multiscreen_offset.y + y); - //XFlush(dpy); - - wxPoint warp_pos= ScreenToClient(wxPoint(multiscreen_offset.x+x, multiscreen_offset.y+y)); - canvas->WarpPointer(warp_pos.x, warp_pos.y); - - wxLogDebug(wxT("VNCSeamlessConnector %p: UNGRAB WARP!"), this); - } - - /* if(topLevel) - { - XUngrabKeyboard(dpy, CurrentTime); - XUngrabPointer(dpy, CurrentTime); - } - else*/ - { -#ifdef __WXGTK__ - gdk_keyboard_ungrab(GDK_CURRENT_TIME); -#endif - canvas->ReleaseMouse(); - } - - /* - mouseOnScreen = warpWindow == DefaultRootWindow(dpy); - XFlush(dpy); - */ - - /* - for (i = 255; i >= 0; i--) - { - if (modifierPressed[i]) - { - wxKeyEvent key_event; - - key_event.m_keyCode = WXK_SHIFT; - conn->sendKeyEvent(key_event, false, false); - key_event.m_keyCode = WXK_ALT; - conn->sendKeyEvent(key_event, false, false); - key_event.m_keyCode = WXK_CONTROL; - conn->sendKeyEvent(key_event, false, false); - - //if (!SendKeyEvent(XKeycodeToKeysym(dpy, i, 0), False)) - // return; - modifierPressed[i]=False; - } - } - */ - - - //if(!edge_width) hidewindow(); - - grabbed=0; -} - - -int VNCSeamlessConnector::coord_dist_sq(wxPoint a, wxPoint b) -{ - a= a-b; - return a.x*a.x + a.y*a.y; -} - -int VNCSeamlessConnector::coord_dist_from_edge(wxPoint a) -{ - int n,ret=a.x; - if(a.y < ret) ret=a.y; - n=display_size.GetHeight() - a.y; - if(n < ret) ret=n; - n=display_size.GetWidth() - a.x; - if(n < ret) ret=n; - return ret; -} - - - - - -/******************************************** - - VNCSeamlessConnectorCanvas class - -********************************************/ - -BEGIN_EVENT_TABLE(VNCSeamlessConnectorCanvas, wxPanel) - EVT_MOUSE_EVENTS(VNCSeamlessConnectorCanvas::onMouse) - EVT_KEY_DOWN (VNCSeamlessConnectorCanvas::onKeyDown) - EVT_KEY_UP (VNCSeamlessConnectorCanvas::onKeyUp) - EVT_CHAR (VNCSeamlessConnectorCanvas::onChar) - EVT_KILL_FOCUS(VNCSeamlessConnectorCanvas::onFocusLoss) - EVT_MOUSE_CAPTURE_LOST(VNCSeamlessConnectorCanvas::onCaptureLoss) -END_EVENT_TABLE(); - - -VNCSeamlessConnectorCanvas::VNCSeamlessConnectorCanvas(VNCSeamlessConnector* parent) - : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS) -{ - p = parent; - SetBackgroundColour(*wxGREEN); -} - - -void VNCSeamlessConnectorCanvas::onMouse(wxMouseEvent &event) -{ - p->handleMouse(event); -} - - - -void VNCSeamlessConnectorCanvas::onKeyDown(wxKeyEvent &event) -{ - p->handleKey(event); -} - - -void VNCSeamlessConnectorCanvas::onKeyUp(wxKeyEvent &event) -{ - p->handleKey(event); -} - - -void VNCSeamlessConnectorCanvas::onChar(wxKeyEvent &event) -{ - p->handleKey(event); -} - -void VNCSeamlessConnectorCanvas::onFocusLoss(wxFocusEvent &event) -{ - p->handleFocusLoss(); -} - -void VNCSeamlessConnectorCanvas::onCaptureLoss(wxMouseCaptureLostEvent &event) -{ - p->handleCaptureLoss(); -} - - - - - - - - - - - -#if 0 - - -/* - x2vnc stuff -*/ - - -void VNCSeamlessConnector::onRuntimer(wxTimerEvent& event) -{ - //doWarp(); - - - HandleXEvents(); -} - - - -Bool -AllXEventsPredicate(Display *dpy, XEvent *ev, char *arg) -{ - return True; -} - -Bool VNCSeamlessConnector::CreateXWindow(void) -{ - XSetWindowAttributes attr; - char defaultGeometry[256]; - XSizeHints wmHints; - int ew; - int topLevelWidth, topLevelHeight; - Pixmap nullPixmap; - XColor dummyColor; - - - /* - * check extensions - */ -#ifdef HAVE_XINERAMA - { - int x,y; - if(XineramaQueryExtension(dpy,&x,&y) && - XineramaIsActive(dpy)) - { - int pos,e; - int num_heads; - int bestpos=0; - int besthead=0; - XineramaScreenInfo *heads; - - if(heads=XineramaQueryScreens(dpy, &num_heads)) - { - /* Loop over all heads and find whatever head - * corresponds best with the edge the user has - * selected. If the user selects "north", the - * bigger screen will normally be selected. - * - * This is actually kind of stupid, it should really - * use the biggest screen for off-screen stuff. - */ - - for(e=0;e>8); - break; - case EDGE_WEST: - pos=1000-heads[e].x_org + (heads[e].height>>8); - break; - case EDGE_SOUTH: - pos=heads[e].y_org + heads[e].height + (heads[e].width>>8); - break; - case EDGE_NORTH: - pos=1000-heads[e].y_org + (heads[e].width>>8); - break; - } - fprintf(stderr,"screen[%d] pos=%d\n",e,pos); - - if(pos > bestpos) - { - bestpos=pos; - besthead=e; - } - } - - - printf("Xinerama detected, x2vnc will use screen %d.\n", - besthead+1); - - multiscreen_offset.x = heads[besthead].x_org; - multiscreen_offset.y = heads[besthead].y_org; - display_size.SetWidth(heads[besthead].width); - display_size.SetHeight(heads[besthead].height); -#if 0 - fprintf(stderr,"[%d,%d-%d,%d]\n",multiscreen_offset.x,multiscreen_offset.y, - display_size.GetWidth(),display_size.GetHeight()); -#endif - } - XFree(heads); - } - } -#endif - - ew=edge_width; - if(!ew) ew=1; - topLevelWidth=ew; - topLevelHeight=ew; - wmHints.x=multiscreen_offset.x; - wmHints.y=multiscreen_offset.y; - - switch(edge) - { - case EDGE_EAST: wmHints.x=display_size.GetWidth()-ew+multiscreen_offset.x; - case EDGE_WEST: topLevelHeight=display_size.GetHeight(); - break; - - case EDGE_SOUTH: wmHints.y=display_size.GetHeight()-ew+multiscreen_offset.y; - case EDGE_NORTH: topLevelWidth=display_size.GetWidth(); - break; - } - - wmHints.flags = PMaxSize | PMinSize |PPosition |PBaseSize; - - wmHints.min_width = topLevelWidth; - wmHints.min_height = topLevelHeight; - - wmHints.max_width = topLevelWidth; - wmHints.max_height = topLevelHeight; - - wmHints.base_width = topLevelWidth; - wmHints.base_height = topLevelHeight; - - sprintf(defaultGeometry, "%dx%d+%d+%d", - topLevelWidth, topLevelHeight, - wmHints.x, wmHints.y); - - XWMGeometry(dpy, DefaultScreen(dpy), NULL, defaultGeometry, 0, - &wmHints, &wmHints.x, &wmHints.y, - &topLevelWidth, &topLevelHeight, &wmHints.win_gravity); - - /* Create the top-level window */ - - attr.border_pixel = 0; /* needed to allow 8-bit cmap on 24-bit display - - otherwise we get a Match error! */ - attr.background_pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(dpy)); - attr.event_mask = ( LeaveWindowMask| - StructureNotifyMask| - ButtonPressMask| - ButtonReleaseMask| - PointerMotionMask| - KeyPressMask| - KeyReleaseMask| - EnterWindowMask| - (resurface?VisibilityChangeMask:0) ); - - attr.override_redirect=1; - - topLevel = XCreateWindow(dpy, DefaultRootWindow(dpy), wmHints.x, wmHints.y, - topLevelWidth, topLevelHeight, 0, CopyFromParent, - InputOutput, CopyFromParent, - (CWBorderPixel| - CWEventMask| - CWOverrideRedirect| - CWBackPixel), - &attr); - - - Atom t = XInternAtom(dpy, "_NET_WM_WINDOW_DOCK", False); - - XChangeProperty(dpy, - topLevel, - XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False), - XA_ATOM, - 32, - PropModeReplace, - (unsigned char *)&t, - 1); - - wmHints.flags |= USPosition; /* try to force WM to place window */ - XSetWMNormalHints(dpy, topLevel, &wmHints); - - wmProtocols = XInternAtom(dpy, "WM_PROTOCOLS", False); - wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False); - XSetWMProtocols(dpy, topLevel, &wmDeleteWindow, 1); - - - - if(edge_width) - XMapRaised(dpy, topLevel); - else - hidden=1; - - /* - * For multi-screen setups, we need to know if the mouse is on the - * screen. - * - GRM - */ - if (ScreenCount(dpy) > 1) { - Window root, child; - int root_x, root_y; - int win_x, win_y; - unsigned int keys_buttons; - - XSelectInput(dpy, DefaultRootWindow(dpy), - PropertyChangeMask | - EnterWindowMask | - LeaveWindowMask | - KeyPressMask); - /* Cut buffer happens on screen 0 only. */ - if (DefaultRootWindow(dpy) != RootWindow(dpy, 0)) { - XSelectInput(dpy, RootWindow(dpy, 0), PropertyChangeMask); - } - XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child, - &root_x, &root_y, &win_x, &win_y, &keys_buttons); - mouseOnScreen = root == DefaultRootWindow(dpy); - } else { - XSelectInput(dpy, DefaultRootWindow(dpy), PropertyChangeMask); - mouseOnScreen = 1; - } - -#ifdef HAVE_XRANDR - { - int major=0, minor=0; - - if(XRRQueryVersion(dpy, &major, &minor) && major >= 1) - { - XRRQueryExtension(dpy, &xrandr_event_base, &xrandr_error_base); - XRRSelectInput(dpy, DefaultRootWindow(dpy), RRScreenChangeNotifyMask); - server_has_randr=1; - } - } -#endif - - nullPixmap = XCreatePixmap(dpy, DefaultRootWindow(dpy), 1, 1, 1); - if(!debug) - grabCursor = - XCreatePixmapCursor(dpy, nullPixmap, nullPixmap, - &dummyColor, &dummyColor, 0, 0); - - - - - return True; -} - - - - - - -/* - * HandleXEvents. - */ - -Bool VNCSeamlessConnector::HandleXEvents(void) -{ - - - - XEvent ev; - - - /* presumably XCheckMaskEvent is more efficient than XCheckIfEvent -GRM */ - while(XCheckIfEvent(dpy, &ev, AllXEventsPredicate, NULL)) - { - if (ev.xany.window == topLevel) - { - if (!HandleTopLevelEvent(&ev)) - return False; - } - /* else if (ev.xany.window == DefaultRootWindow(dpy) || - ev.xany.window == RootWindow(dpy, 0)) { - if (!HandleRootEvent(&ev)) - return False; - }*/ - /* else if (ev.type == MappingNotify) - { - XRefreshKeyboardMapping(&ev.xmapping); - }*/ - } - - - doWarp(); - - return True; -} - - - - - -int VNCSeamlessConnector::sendpointerevent(int x, int y, int buttonmask) -{ - wxMouseEvent e; - e.m_x = x; - e.m_y = y; - - if(buttonmask & rfbButton1Mask) - e.m_leftDown = true; - - if(buttonmask & rfbButton2Mask) - e.m_middleDown = true; - - if(buttonmask & rfbButton3Mask) - e.m_rightDown = true; - - if(buttonmask & rfbWheelUpMask) - e.m_wheelRotation = 1; - - if(buttonmask & rfbWheelDownMask) - e.m_wheelRotation = -1; - - conn->sendPointerEvent(e); - return 1; -} - - -void VNCSeamlessConnector::mapwindow(void) -{ - if(edge_width) - { - hidden=0; - XMapRaised(dpy, topLevel); - } -} - -void VNCSeamlessConnector::hidewindow(void) -{ - hidden=1; - XUnmapWindow(dpy, topLevel); -} - - - - - - - -void VNCSeamlessConnector::dumpMotionEvent(XEvent *ev) -{ - fprintf(stderr,"{ %d, %d } -> { %d, %d } = %d, %d\n", - current_location.x, - current_location.y, - ev->xmotion.x_root - multiscreen_offset.x, - ev->xmotion.y_root- multiscreen_offset.y, - (ev->xmotion.x_root - multiscreen_offset.x) - current_location.x, - (ev->xmotion.y_root - multiscreen_offset.y)-current_location.y); -} - - - - -/* - * HandleTopLevelEvent. - */ -Bool VNCSeamlessConnector::HandleTopLevelEvent(XEvent *ev) -{ - int x, y; - - int buttonMask; - KeySym ks; - static Atom COMPOUND_TEXT; - /* Atom: requestor asks for a list of supported targets (GRM 24 Oct 2003) */ - static Atom TARGETS; - - switch (ev->type) - { - case SelectionRequest: - { - XEvent reply; - - XSelectionRequestEvent *req=& ev->xselectionrequest; - - reply.xselection.type=SelectionNotify; - reply.xselection.display=req->display; - reply.xselection.selection=req->selection; - reply.xselection.requestor=req->requestor; - reply.xselection.target=req->target; - reply.xselection.time=req->time; - reply.xselection.property=None; - - if(COMPOUND_TEXT == None) - COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", True); - - /* Initialize TARGETS atom if required (GRM 24 Oct 2003) */ - if(TARGETS == None) TARGETS = XInternAtom(dpy, "TARGETS", True); - - if(client_selection_text) - { - if(req->target == TARGETS) - { - /* - * Requestor is asking what targets we support. Reply with what we - * support (XA_STRING and COMPOUND_TEXT). - * - * (GRM 24 Oct 2003) - */ - Atom targets[2]; - if(req->property == None) - req->property = TARGETS; - - targets[0] = XA_STRING; - targets[1] = COMPOUND_TEXT; - XChangeProperty(dpy, - req->requestor, - req->property, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char *) targets, - 2); - - reply.xselection.property=req->property; - } - else if(req->target == XA_STRING || req->target == COMPOUND_TEXT) - { - if(req->property == None) - req->property = XA_CUT_BUFFER0; - -#if 0 - fprintf(stderr,"Setting property %d on window %x to: %s (len=%d)\n",req->property, req->requestor, client_selection_text,client_selection_text_length); -#endif - - XChangeProperty(dpy, - req->requestor, - req->property, - req->target, - 8, - PropModeReplace, - (const unsigned char*)client_selection_text, - client_selection_text_length); - - reply.xselection.property=req->property; - - } - else if(debug) - { - /* - * Requestor is asking for something we don't understand. Complain. - * (GRM 24 Oct 2003) - */ - char *name; - name = XGetAtomName(dpy, req->target); - fprintf(stderr, "Unknown property target request %s\n", name); - XFree(name); - } - } - XSendEvent(dpy, req->requestor, 0,0, &reply); - XFlush (dpy); - } - return 1; - - case SelectionNotify: - { - Atom t; - unsigned long len=0; - unsigned long bytes_left=0; - int format; - unsigned char *data=0; - int ret; - - ret=XGetWindowProperty(dpy, - topLevel, - XA_CUT_BUFFER0, - 0, - 0x100000, - 1, /* delete */ - XA_STRING, - &t, - &format, - &len, - &bytes_left, - &data); - - if(format == 8) - conn->setCuttext( wxString((char *)data, wxConvUTF8)); - //SendClientCutText((char *)data, len); -#ifdef DEBUG - fprintf(stderr,"GOT selection info: ret=%d type=%d fmt=%d len=%d bytes_left=%d %p '%s'\n", - ret, (int)t,format,len,bytes_left,data,data); -#endif - if(data) XFree(data); - - } - return 1; - - case VisibilityNotify: - /* - * I avoid resurfacing when the window becomes fully obscured, because - * that *probably* means that somebody is running xlock. - * Please tell me if you have a problem with this. - * - Hubbe - */ - if (ev->xvisibility.state == VisibilityPartiallyObscured && - resurface && !hidden) - { - static long last_resurface=0; - long t=time(0); - - if(t == last_resurface) - wxMilliSleep(5); - - last_resurface=t; -#ifdef DEBUG - fprintf(stderr,"Raising window!\n"); -#endif - mapwindow(); - /* XRaiseWindow(dpy, topLevel); */ - } - return 1; - - /* - * We don't need to worry about multi-screen here; that's handled - * below, as a root event. - * - GRM - */ - case EnterNotify: - if(!grabbed && ev->xcrossing.mode==NotifyNormal) - { - grabit(enter_translate(EDGE_EW,display_size.GetWidth() , (ev->xcrossing.x_root - multiscreen_offset.x)), - enter_translate(EDGE_NS,display_size.GetHeight(), (ev->xcrossing.y_root - multiscreen_offset.y)), - ev->xcrossing.state); - } - return 1; - - case MotionNotify: - - if(grabbed) - { - int i, d=0; - Window warpWindow; - wxPoint offset(0,0); - - - - wxPoint new_location(ev->xmotion.x_root, ev->xmotion.y_root); - if(debug) - { - dumpMotionEvent(ev); - - if(next_origo) - fprintf(stderr," {%d} 1 && - offset.x*offset.x + offset.y*offset.y < - pointer_warp_threshold) - { - remotePos.x+=offset.x; - remotePos.y+=offset.y; - }else{ - remotePos.x+=offset.x * pointer_speed; - remotePos.y+=offset.y * pointer_speed; - } - -#if 0 - fprintf(stderr," ==> {%f, %f} (%d,%d)\n", - remotePos.x, remotePos.y, - framebuffer_size.GetWidth(), - framebuffer_size.GetHeight()); -#endif - - if(!(ev->xmotion.state & 0x1f00)) - { - fprintf(stderr, "d gets set!!!\n"); - warpWindow = DefaultRootWindow(dpy); - switch(edge) - { - case EDGE_NORTH: - d=remotePos.y >= framebuffer_size.GetHeight(); - y = edge_width; /* FIXME */ - x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); - break; - case EDGE_SOUTH: - d=remotePos.y < 0; - y = display_size.GetHeight() - edge_width -1; /* FIXME */ - x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); - break; - case EDGE_EAST: - d=remotePos.x < 0; - x = display_size.GetWidth() - edge_width -1; /* FIXME */ - y = remotePos.y * display_size.GetHeight() /framebuffer_size.GetHeight() ; - break; - case EDGE_WEST: - d=remotePos.x > framebuffer_size.GetWidth(); - x = edge_width; /* FIXME */ - y = remotePos.y * display_size.GetHeight() / framebuffer_size.GetHeight(); - break; - } - } - - if(d) - { - fprintf(stderr, "d is set!!!\n"); - if(x<0) x=0; - if(y<0) y=0; - if(y>=display_size.GetHeight()) y=display_size.GetHeight()-1; - if(x>=display_size.GetWidth()) x=display_size.GetWidth()-1; - ungrabit(x, y, warpWindow); - return 1; - }else{ - if(remotePos.x < 0) remotePos.x=0; - if(remotePos.y < 0) remotePos.y=0; - - if(remotePos.x >= framebuffer_size.GetWidth()) - remotePos.x=framebuffer_size.GetWidth()-1; - - if(remotePos.y >= framebuffer_size.GetHeight()) - remotePos.y=framebuffer_size.GetHeight()-1; - - i=sendpointerevent((int)remotePos.x, - (int)remotePos.y, - (ev->xmotion.state & 0x1f00) >> 8); - } - - } - return 1; - - case ButtonPress: - case ButtonRelease: - if (ev->type == ButtonPress) { - buttonMask = (((ev->xbutton.state & 0x1f00) >> 8) | - (1 << (ev->xbutton.button - 1))); - } else { - buttonMask = (((ev->xbutton.state & 0x1f00) >> 8) & - ~(1 << (ev->xbutton.button - 1))); - } - - return sendpointerevent(remotePos.x, - remotePos.y, - buttonMask); - - case KeyPress: - case KeyRelease: - { - char keyname[256]; - keyname[0] = '\0'; - - - XLookupString(&ev->xkey, keyname, 256, &ks, NULL); - /* fprintf(stderr,"Pressing %x (%c) name=%s code=%d\n",ks,ks,keyname,ev->xkey.keycode); */ - - if (IsModifierKey(ks)) { - ks = XKeycodeToKeysym(dpy, ev->xkey.keycode, 0); - - /* Ignore AltGr key, it is handled by XLookupString */ - if( ks == XK_Mode_switch ) return True; - - modifierPressed[ev->xkey.keycode] = (ev->type == KeyPress); - } else { - /* This fixes the 'shift-tab' problem - Hubbe */ - switch(ks) - { -#if XK_ISO_Left_Tab != 0x1000ff74 - case 0x1000ff74: /* HP-UX */ -#endif - case XK_ISO_Left_Tab: ks=XK_Tab; break; - } - } - - // if(debug) - // fprintf(stderr," --> %x (%c) name=%s (%s)\n",ks,ks,keyname, ev->type == KeyPress ? "down" : "up"); - - - // return SendKeyEvent(ks, (ev->type == KeyPress)); - wxKeyEvent key_event; - key_event.m_keyCode = ks; - conn->sendKeyEvent(key_event, ev->type == KeyPress, true); - return 1; - } - - break; - } - - return True; -} - - - - -/* - * HandleRootEvent. - */ - -Bool VNCSeamlessConnector::HandleRootEvent(XEvent *ev) -{ - char *str; - int len; - - Bool nowOnScreen; - Bool grab; - - int x, y; - - switch (ev->type) - { - default: -#ifdef HAVE_XRANDR - if(ev->type == RRScreenChangeNotify + xrandr_event_base) - ;//exit(0); -#endif - break; - - case KeyPress: - { - KeySym ks; - char keyname[256]; - keyname[0] = '\0'; - XLookupString(&ev->xkey, keyname, 256, &ks, NULL); - - //fprintf(stderr,"ROOT: Pressing %x (%c) name=%s code=%d\n",ks,ks,keyname,ev->xkey.keycode); - - if(ev->xkey.keycode) - { - saved_xpos=(ev->xkey.x_root - multiscreen_offset.x); - saved_ypos=(ev->xkey.y_root - multiscreen_offset.y); - grabit(saved_remote_xpos, saved_remote_ypos, 0); - } - break; - } - - case EnterNotify: - case LeaveNotify: - /* - * Ignore the event if it's due to leaving our window. This will - * be after an ungrab. - */ - if (ev->xcrossing.subwindow == topLevel && - !ev->xcrossing.same_screen) { - break; - } - - grab = 0; - if(!grabbed) - { - nowOnScreen = ev->xcrossing.same_screen; - if (mouseOnScreen != nowOnScreen) { - /* - * Mouse has left, or entered, the screen. We must grab if - * the mouse is now near the edge we're watching. - * - * If we do grab, the mouse coordinates are left alone. - * The test, however, depends on whether the mouse entered - * or left the screen. - * - * - GRM - */ - x = (ev->xcrossing.x_root - multiscreen_offset.x); - y = (ev->xcrossing.y_root - multiscreen_offset.y); - if (!nowOnScreen) { - x = enter_translate(EDGE_EW,display_size.GetWidth(),(ev->xcrossing.x_root - multiscreen_offset.x)); - y = enter_translate(EDGE_NS,display_size.GetHeight(),(ev->xcrossing.y_root - multiscreen_offset.y)); - } - switch(edge) - { - case EDGE_NORTH: - grab=y < display_size.GetHeight() / 2; - break; - case EDGE_SOUTH: - grab=y > display_size.GetHeight() / 2; - break; - case EDGE_EAST: - grab=x > display_size.GetWidth() / 2; - break; - case EDGE_WEST: - grab=x < display_size.GetWidth() / 2; - break; - } - } - mouseOnScreen = nowOnScreen; - } - - /* - * Do not grab if this is the result of an ungrab - * or grab (caused by us, usually). - * - * - GRM - */ - if(grab && ev->xcrossing.mode == NotifyNormal) - { - grabit(enter_translate(EDGE_EW,display_size.GetWidth() ,(ev->xcrossing.x_root - multiscreen_offset.x)), - enter_translate(EDGE_NS,display_size.GetHeight(),(ev->xcrossing.y_root - multiscreen_offset.y)), - ev->xcrossing.state); - } - break; - - case PropertyNotify: - if (ev->xproperty.atom == XA_CUT_BUFFER0) - { - str = XFetchBytes(dpy, &len); - if (str) { -#ifdef DEBUG - fprintf(stderr,"GOT CUT TEXT: \"%s\"\n",str); -#endif - //if (!SendClientCutText(str, len)) - // return False; - conn->setCuttext(wxString(str, wxConvUTF8)); - XFree(str); - } - } - - break; - } - - return True; -} - -void VNCSeamlessConnector::handle_cut_text(char *str, size_t len) -{ - XWindowAttributes attrs; - - if(client_selection_text) - free((char *)client_selection_text); - - client_selection_text_length=len; - client_selection_text = str; - - XGetWindowAttributes(dpy, RootWindow(dpy, 0), &attrs); - XSelectInput(dpy, RootWindow(dpy, 0), - attrs.your_event_mask & ~PropertyChangeMask); - XStoreBytes(dpy, str, len); - XSetSelectionOwner(dpy, XA_PRIMARY, topLevel, CurrentTime); - XSelectInput(dpy, RootWindow(dpy, 0), attrs.your_event_mask); -} - -#endif - + +#include +#include +#include +#ifdef __WXGTK__ +#include +#include +#endif +#include "MultiVNCApp.h" +#include "VNCSeamlessConnector.h" + + + + +/******************************************** + + VNCSeamlessConnector class + +********************************************/ + + +/* + public members +*/ + +#define RAISETIMER_ID 666 +BEGIN_EVENT_TABLE(VNCSeamlessConnector, wxFrame) + EVT_TIMER (RAISETIMER_ID, VNCSeamlessConnector::onRaiseTimer) +END_EVENT_TABLE(); + + + +#define EDGE_EW (edge == EDGE_EAST || edge==EDGE_WEST) +#define EDGE_NS (edge == EDGE_NORTH || edge==EDGE_SOUTH) +#define EDGE_ES (edge == EDGE_EAST || edge==EDGE_SOUTH) +#define EDGE_NS (edge == EDGE_NORTH || edge==EDGE_SOUTH) + + +VNCSeamlessConnector::VNCSeamlessConnector(wxWindow* parent, VNCConn* c, int e, size_t ew, float accel) + : wxFrame(parent, wxID_ANY, c->getDesktopName(), wxDefaultPosition, wxDefaultSize, + wxFRAME_SHAPED | wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP) +{ + conn = c; + edge = e; + edge_width = ew; + acceleration = accel; + + pointer_warp_threshold=5; + + pointer_speed = 0.0; + grabbed = false; + + /* + // init all x2vnc stuff start + grabCursor=0; + hidden=0; + client_selection_text=0; + client_selection_text_length=0; + saved_xpos=-1; + saved_ypos=-1; + debug = true; + resurface = false; + // init all x2vnc stuff end + for (int i = 0; i < 256; i++) + modifierPressed[i] = False; + + if (!(dpy = XOpenDisplay(NULL))) + fprintf(stderr," unable to open display %s\n", XDisplayName(NULL)); + + + // this sets: topLevel, deskopt atoms + //if(CreateXWindow()) + // fprintf(stderr, "sucessfully created xwindow!\n"); + // topLevel = GDK_WINDOW_XID(GetHandle()->window); + + + topLevel = 0; + //runtimer.SetOwner(this, 666); + //runtimer.Start(5); + */ + + adjustSize(); + +#ifdef __WXGTK__ + // this is needed cause since newer gtk version the gnome panel + // raises above the connector sometimes...grrr + raisetimer.SetOwner(this, RAISETIMER_ID); + raisetimer.Start(100); +#endif + + canvas = new VNCSeamlessConnectorCanvas(this); +#ifdef NDEBUG + canvas->SetCursor(wxCursor(wxCURSOR_BLANK)); +#endif + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(canvas, 1, wxEXPAND, 0); + SetSizer(sizer); + Layout(); + + + this->Show(true); + // seems that has to come after Show() to take effect + SetBackgroundColour(*wxRED); + SetTransparent(100); +} + + +VNCSeamlessConnector::~VNCSeamlessConnector() +{ + /* + runtimer.Stop(); + + if(topLevel) + XDestroyWindow(dpy, topLevel); + */ +} + + +bool VNCSeamlessConnector::isSupportedByCurrentPlatform() { +#ifdef __WXGTK__ + wxString sessionType; + wxGetEnv("XDG_SESSION_TYPE", &sessionType); + return sessionType.IsSameAs("x11"); +#endif + return false; +} + + + +void VNCSeamlessConnector::adjustSize() +{ + wxLogDebug(wxT("VNCSeamLessConnector %p: adjusting size"), this); + + // use the biggest screen + wxSize biggest_size; + for(size_t i=0; i < wxDisplay::GetCount(); ++i) + { + wxSize this_size = wxDisplay(i).GetGeometry().GetSize(); + + if(this_size.GetWidth() * this_size.GetHeight() > + biggest_size.GetWidth() * biggest_size.GetHeight()) + { + biggest_size = this_size; + display_index = i; + } + } + // set offset accordingly + multiscreen_offset = wxDisplay(display_index).GetGeometry().GetPosition(); + + // get local display size + display_size = wxDisplay(display_index).GetGeometry().GetSize(); + + /* + saved_remote_xpos = display_size.GetWidth() / 2; + saved_remote_ypos = display_size.GetHeight() / 2; + */ + + // get remote display size + framebuffer_size.SetWidth(conn->getFrameBufferWidth()); + framebuffer_size.SetHeight(conn->getFrameBufferHeight()); + + // compute a pos to hide cursor, so user won't get confused + // which keyboard has control + remoteParkingPos.x = framebuffer_size.GetWidth() -2; + remoteParkingPos.y = framebuffer_size.GetHeight() -2; + + wxMouseEvent e; + e.m_x = remoteParkingPos.x; + e.m_y = remoteParkingPos.y; + conn->sendPointerEvent(e); + + // calc pointer speed from sizes + pointer_speed = acceleration * pow( (framebuffer_size.GetWidth() * framebuffer_size.GetHeight()) / (float)(display_size.GetWidth() * display_size.GetHeight()), 0.25 ); + + wxLogDebug(wxT("VNCSeamlessConnector %p: pointer multiplier %f"), this, pointer_speed); + + + + // Compute two identifiable locations, as far as possible from each other + if(display_size.GetWidth() * 2 > display_size.GetHeight()) + { + origo1=wxPoint(display_size.GetWidth()/3,display_size.GetHeight()/2); + origo2=wxPoint(display_size.GetWidth()*2/3,display_size.GetHeight()/2); + origo_separation=display_size.GetWidth()/3; + } + else if(display_size.GetHeight() * 2 > display_size.GetWidth()) + { + origo1=wxPoint(display_size.GetWidth()/2,display_size.GetHeight()/3); + origo2=wxPoint(display_size.GetWidth()/2,display_size.GetHeight()*2/3); + origo_separation=display_size.GetHeight()/3; + } + else + { + int N=(int)( (2*(display_size.GetWidth()+display_size.GetHeight())- + sqrt((-3*display_size.GetWidth()*display_size.GetWidth()-3*display_size.GetHeight()*display_size.GetHeight()+8*display_size.GetWidth()*display_size.GetHeight())) + )/7.0 ); + origo1=wxPoint(N,N); + origo2=wxPoint(display_size.GetWidth()-N,display_size.GetHeight()-N); + origo_separation=N; + } + origo1.x+=multiscreen_offset.x; + origo1.y+=multiscreen_offset.y; + origo2.x+=multiscreen_offset.x; + origo2.y+=multiscreen_offset.y; + + wxLogDebug(wxT("VNCSeamlessConnector %p: Origo1 (%d, %d)"), this, origo1.x,origo1.y); + wxLogDebug(wxT("VNCSeamlessConnector %p: Origo2 (%d, %d)"), this, origo2.x,origo2.y); + wxLogDebug(wxT("VNCSeamlessConnector %p: multiscreen offset: (%d, %d)"), this, multiscreen_offset.x,multiscreen_offset.y); + + + + // the following code sizes and places our window + int ew = edge_width; + if(!ew) + ew=1; + + int width=ew; + int height=ew; + int x = multiscreen_offset.x; + int y = multiscreen_offset.y; + + switch(edge) + { + case EDGE_EAST: x = display_size.GetWidth() - ew + multiscreen_offset.x; + case EDGE_WEST: height = display_size.GetHeight(); + break; + + case EDGE_SOUTH: y = display_size.GetHeight() - ew + multiscreen_offset.y; + case EDGE_NORTH: width = display_size.GetWidth(); + break; + + default: + width = height = 0; + } + + + SetSize(width, height); + Move(x, y); + +#ifdef __WXGTK__ + gtk_window_set_type_hint(GTK_WINDOW(GetHandle()), GDK_WINDOW_TYPE_HINT_DOCK); + gtk_window_set_keep_above(GTK_WINDOW(GetHandle()), true); +#endif + + /* + if(edge_width) + Raise(); + else + hidden=1;*/ +} + + + +/* + protected members +*/ + + + + + +/* + private members +*/ + +void VNCSeamlessConnector::onRaiseTimer(wxTimerEvent& event) +{ + Raise(); +} + + +void VNCSeamlessConnector::handleMouse(wxMouseEvent& event) +{ + wxPoint evt_root_pos = ClientToScreen(event.GetPosition()); + wxLogDebug(wxT("VNCSeamLessConnector %p: new mouse evt: (%d, %d)"), this, evt_root_pos.x, evt_root_pos.y); + + int x,y; + + doWarp(); + + if(event.Entering()) + { + // if(!HasCapture()) + wxLogDebug(wxT("VNCSeamlessConnector %p: mouse entering!"), this); + + // set cuttext from clipboard + { + if(wxTheClipboard->Open()) + { + if(wxTheClipboard->IsSupported(wxDF_TEXT)) + { + wxTextDataObject data; + wxTheClipboard->GetData(data); + wxString text = data.GetText(); + wxLogDebug(wxT("VNCSeamlessConnector %p: setting cuttext: '%s'"), this, text.c_str()); + conn->setCuttext(text); + } + wxTheClipboard->Close(); + } + } + + // check for a change of display size + if(display_size != wxDisplay(display_index).GetGeometry().GetSize()) + adjustSize(); + + if(!grabbed) + { + wxLogDebug(wxT("VNCSeamlessConnector %p: mouse entering, grabbing!"), this); + grabit(enter_translate(EDGE_EW,display_size.GetWidth() , (evt_root_pos.x - multiscreen_offset.x)), + enter_translate(EDGE_NS,display_size.GetHeight(), (evt_root_pos.y - multiscreen_offset.y)), + 0); + // ev->xcrossing.state); //FIXME buttons + } + return; + } + + if(event.Moving() || event.Dragging()) + { + wxLogDebug(wxT("VNCSeamlessConnector %p: mouse moving/dragging!"), this); + if(grabbed) + { + wxLogDebug(wxT("VNCSeamlessConnector %p: mouse moving/dragging while grabbed!"), this); + int d=0; + + wxPoint offset(0,0); + + wxPoint new_location(evt_root_pos.x, evt_root_pos.y); + + motion_events++; + + if(next_origo && + (coord_dist_sq(new_location, *next_origo) < coord_dist_sq(new_location, current_location) || + coord_dist_from_edge(new_location) < motion_events)) + { + current_origo=next_origo; + current_location=*next_origo; + next_origo=NULL; + motion_events=0; + } + + current_speed = new_location - current_location; + if(current_origo) + offset= offset + current_speed; + current_location=new_location; + + + if(pointer_speed > 1 && + offset.x*offset.x + offset.y*offset.y < + pointer_warp_threshold) + { + remotePos.x+=offset.x; + remotePos.y+=offset.y; + wxLogDebug(wxT("VNCSeamlessConnector %p: if: remotePos (%f, %f)"), this, remotePos.x, remotePos.y); + } + else + { + remotePos.x+=offset.x * pointer_speed; + remotePos.y+=offset.y * pointer_speed; + wxLogDebug(wxT("VNCSeamlessConnector %p: else: remotePos (%f, %f)"), this, remotePos.x, remotePos.y); + } + + + // if(!(ev->xmotion.state & 0x1f00)) //FIXME + if(!event.Dragging()) + { + switch(edge) + { + case EDGE_NORTH: + d=remotePos.y >= framebuffer_size.GetHeight(); + y = edge_width; /* FIXME */ + x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); + break; + case EDGE_SOUTH: + d=remotePos.y < 0; + y = display_size.GetHeight() - edge_width -1; /* FIXME */ + x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); + break; + case EDGE_EAST: + d=remotePos.x < 0; + x = display_size.GetWidth() - edge_width -1; /* FIXME */ + y = remotePos.y * display_size.GetHeight() /framebuffer_size.GetHeight() ; + break; + case EDGE_WEST: + d=remotePos.x > framebuffer_size.GetWidth(); + x = edge_width; /* FIXME */ + y = remotePos.y * display_size.GetHeight() / framebuffer_size.GetHeight(); + break; + } + } + + if(d) + { + wxLogDebug(wxT("VNCSeamlessConnector %p: d is set!"), this); + if(x<0) x=0; + if(y<0) y=0; + if(y>=display_size.GetHeight()) y=display_size.GetHeight()-1; + if(x>=display_size.GetWidth()) x=display_size.GetWidth()-1; + ungrabit(x, y); + return; + } + else + { + if(remotePos.x < 0) remotePos.x=0; + if(remotePos.y < 0) remotePos.y=0; + + if(remotePos.x >= framebuffer_size.GetWidth()) + remotePos.x=framebuffer_size.GetWidth()-1; + + if(remotePos.y >= framebuffer_size.GetHeight()) + remotePos.y=framebuffer_size.GetHeight()-1; + + event.m_x = remotePos.x; + event.m_y = remotePos.y; + wxLogDebug(wxT("VNCSeamlessConnector %p: moving/dragging sending (%d, %d)"), this, event.m_x, event.m_y); + conn->sendPointerEvent(event); + } + + } + return; + } + + // a non-moving event (button, wheel, whatever...) + event.m_x = remotePos.x; + event.m_y = remotePos.y; + conn->sendPointerEvent(event); + return; +} + + + + +void VNCSeamlessConnector::handleKey(wxKeyEvent& evt) +{ + doWarp(); + + wxLogDebug(wxT("VNCSeamlessConnector %p: got some key event!"), this); + + if(evt.GetEventType() == wxEVT_KEY_DOWN) + conn->sendKeyEvent(evt, true, false); + + if(evt.GetEventType() == wxEVT_KEY_UP) + conn->sendKeyEvent(evt, false, false); + + if(evt.GetEventType() == wxEVT_CHAR) + conn->sendKeyEvent(evt, true, true); +} + + + +void VNCSeamlessConnector::handleFocusLoss() +{ + wxLogDebug(wxT("VNCSeamlessConnector %p: lost focus, upping key modifiers"), this); + + wxKeyEvent key_event; + + key_event.m_keyCode = WXK_SHIFT; + conn->sendKeyEvent(key_event, false, false); + key_event.m_keyCode = WXK_ALT; + conn->sendKeyEvent(key_event, false, false); + key_event.m_keyCode = WXK_CONTROL; + conn->sendKeyEvent(key_event, false, false); +} + + +void VNCSeamlessConnector::handleCaptureLoss() +{ + wxLogDebug(wxT("VNCSeamlessConnector %p: lost capture, emergency ungrab"), this); + + ungrabit(display_size.x/2, display_size.y/2); + handleFocusLoss(); +} + + +void VNCSeamlessConnector::doWarp(void) +{ + if(grabbed) + { + if(next_origo) return; + if(current_origo && + current_location.x == current_origo->x && + current_location.y == current_origo->y) + return; + + if(current_origo == &origo1) + next_origo=&origo2; + else + next_origo=&origo1; + + wxLogDebug(wxT("VNCSeamlessConnector %p: WARP to (%d, %d)"), this, next_origo->x, next_origo->y); + /*XWarpPointer(dpy,None, + DefaultRootWindow(dpy),0,0,0,0, + next_origo->x, + next_origo->y);*/ + + + wxPoint warp_pos= canvas->ScreenToClient(*next_origo); + wxLogDebug(wxT("VNCSeamlessConnector %p: WARP translated (%d, %d)"), this, warp_pos.x, warp_pos.y); + canvas->WarpPointer(warp_pos.x, warp_pos.y); + + motion_events=0; + } +} + + + +int VNCSeamlessConnector::enter_translate(int isedge, int width, int pos) +{ + if(!isedge) + return pos; + if(EDGE_ES) + return 0; + return width-1; +} + +int VNCSeamlessConnector::leave_translate(int isedge, int width, int pos) +{ + if(!isedge) + return pos; + if(EDGE_ES) + return width-edge_width; + return 0; +} + + +void VNCSeamlessConnector::grabit(int x, int y, int state) +{ + //Window selection_owner; + + wxLogDebug(wxT("VNCSeamlessConnector %p: GRAB!"), this); + + /* + if(hidden) + { + XMapRaised(dpy, topLevel); + hidden=0; + } + + if(!topLevel) + */ + { + canvas->CaptureMouse(); + canvas->SetFocus(); +#ifdef __WXGTK__ + gdk_keyboard_grab(gtk_widget_get_window(canvas->GetHandle()), False, GDK_CURRENT_TIME); +#endif + } + /* + else + { + XGrabPointer(dpy, topLevel, True, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, + None, grabCursor, CurrentTime); + XGrabKeyboard(dpy, topLevel, True, + GrabModeAsync, GrabModeAsync, + CurrentTime); + } + */ + + grabbed=1; + next_origo=NULL; + current_origo=NULL; + + if(x > -1 && y > -1) + { + doWarp(); + +#define EDGE(X) ((X)?edge_width:0) +#define SCALEX(X) \ + ( ((X)-EDGE(edge==EDGE_WEST ))*(framebuffer_size.GetWidth()-1 )/(display_size.GetWidth() -1-EDGE(EDGE_EW)) ) +#define SCALEY(Y) \ + ( ((Y)-EDGE(edge==EDGE_NORTH))*(framebuffer_size.GetHeight()-1)/(display_size.GetHeight()-1-EDGE(EDGE_NS)) ) + + /* Whut? How can this be right? */ + remotePos.x=SCALEX(x); + remotePos.y=SCALEY(y); + wxMouseEvent evt; + evt.m_x = remotePos.x; + evt.m_y = remotePos.y; + wxLogDebug(wxT("VNCSeamlessConnector %p: GRAB got position (%d, %d)"), this, x, y); + wxLogDebug(wxT("VNCSeamlessConnector %p: GRAB sending pointer event (%d, %d)"), this, evt.m_x, evt.m_y); + //sendpointerevent(remotePos.x, remotePos.y, (state & 0x1f00) >> 8); + conn->sendPointerEvent(evt); + } + + + // mouseOnScreen = 1; + + + /* + selection_owner=XGetSelectionOwner(dpy, XA_PRIMARY); + // fprintf(stderr,"Selection owner: %lx\n",(long)selection_owner); + + if(selection_owner != None && selection_owner != topLevel) + { + XConvertSelection(dpy, + XA_PRIMARY, XA_STRING, XA_CUT_BUFFER0, + topLevel, CurrentTime); + } + */ + // XSync(dpy, False); +} + +void VNCSeamlessConnector::ungrabit(int x, int y) +{ + // int i; + + wxMouseEvent e; + e.m_x = remoteParkingPos.x; + e.m_y = remoteParkingPos.y; + + conn->sendPointerEvent(e); + + wxLogDebug(wxT("VNCSeamlessConnector %p: UNGRAB!"), this); + + + if(x > -1 && y > -1 ) + { + //XWarpPointer(dpy,None, warpWindow, 0,0,0,0, multiscreen_offset.x + x, multiscreen_offset.y + y); + //XFlush(dpy); + + wxPoint warp_pos= ScreenToClient(wxPoint(multiscreen_offset.x+x, multiscreen_offset.y+y)); + canvas->WarpPointer(warp_pos.x, warp_pos.y); + + wxLogDebug(wxT("VNCSeamlessConnector %p: UNGRAB WARP!"), this); + } + + /* if(topLevel) + { + XUngrabKeyboard(dpy, CurrentTime); + XUngrabPointer(dpy, CurrentTime); + } + else*/ + { +#ifdef __WXGTK__ + gdk_keyboard_ungrab(GDK_CURRENT_TIME); +#endif + canvas->ReleaseMouse(); + } + + /* + mouseOnScreen = warpWindow == DefaultRootWindow(dpy); + XFlush(dpy); + */ + + /* + for (i = 255; i >= 0; i--) + { + if (modifierPressed[i]) + { + wxKeyEvent key_event; + + key_event.m_keyCode = WXK_SHIFT; + conn->sendKeyEvent(key_event, false, false); + key_event.m_keyCode = WXK_ALT; + conn->sendKeyEvent(key_event, false, false); + key_event.m_keyCode = WXK_CONTROL; + conn->sendKeyEvent(key_event, false, false); + + //if (!SendKeyEvent(XKeycodeToKeysym(dpy, i, 0), False)) + // return; + modifierPressed[i]=False; + } + } + */ + + + //if(!edge_width) hidewindow(); + + grabbed=0; +} + + +int VNCSeamlessConnector::coord_dist_sq(wxPoint a, wxPoint b) +{ + a= a-b; + return a.x*a.x + a.y*a.y; +} + +int VNCSeamlessConnector::coord_dist_from_edge(wxPoint a) +{ + int n,ret=a.x; + if(a.y < ret) ret=a.y; + n=display_size.GetHeight() - a.y; + if(n < ret) ret=n; + n=display_size.GetWidth() - a.x; + if(n < ret) ret=n; + return ret; +} + + + + + +/******************************************** + + VNCSeamlessConnectorCanvas class + +********************************************/ + +BEGIN_EVENT_TABLE(VNCSeamlessConnectorCanvas, wxPanel) + EVT_MOUSE_EVENTS(VNCSeamlessConnectorCanvas::onMouse) + EVT_KEY_DOWN (VNCSeamlessConnectorCanvas::onKeyDown) + EVT_KEY_UP (VNCSeamlessConnectorCanvas::onKeyUp) + EVT_CHAR (VNCSeamlessConnectorCanvas::onChar) + EVT_KILL_FOCUS(VNCSeamlessConnectorCanvas::onFocusLoss) + EVT_MOUSE_CAPTURE_LOST(VNCSeamlessConnectorCanvas::onCaptureLoss) +END_EVENT_TABLE(); + + +VNCSeamlessConnectorCanvas::VNCSeamlessConnectorCanvas(VNCSeamlessConnector* parent) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS) +{ + p = parent; + SetBackgroundColour(*wxGREEN); +} + + +void VNCSeamlessConnectorCanvas::onMouse(wxMouseEvent &event) +{ + p->handleMouse(event); +} + + + +void VNCSeamlessConnectorCanvas::onKeyDown(wxKeyEvent &event) +{ + p->handleKey(event); +} + + +void VNCSeamlessConnectorCanvas::onKeyUp(wxKeyEvent &event) +{ + p->handleKey(event); +} + + +void VNCSeamlessConnectorCanvas::onChar(wxKeyEvent &event) +{ + p->handleKey(event); +} + +void VNCSeamlessConnectorCanvas::onFocusLoss(wxFocusEvent &event) +{ + p->handleFocusLoss(); +} + +void VNCSeamlessConnectorCanvas::onCaptureLoss(wxMouseCaptureLostEvent &event) +{ + p->handleCaptureLoss(); +} + + + + + + + + + + + +#if 0 + + +/* + x2vnc stuff +*/ + + +void VNCSeamlessConnector::onRuntimer(wxTimerEvent& event) +{ + //doWarp(); + + + HandleXEvents(); +} + + + +Bool +AllXEventsPredicate(Display *dpy, XEvent *ev, char *arg) +{ + return True; +} + +Bool VNCSeamlessConnector::CreateXWindow(void) +{ + XSetWindowAttributes attr; + char defaultGeometry[256]; + XSizeHints wmHints; + int ew; + int topLevelWidth, topLevelHeight; + Pixmap nullPixmap; + XColor dummyColor; + + + /* + * check extensions + */ +#ifdef HAVE_XINERAMA + { + int x,y; + if(XineramaQueryExtension(dpy,&x,&y) && + XineramaIsActive(dpy)) + { + int pos,e; + int num_heads; + int bestpos=0; + int besthead=0; + XineramaScreenInfo *heads; + + if(heads=XineramaQueryScreens(dpy, &num_heads)) + { + /* Loop over all heads and find whatever head + * corresponds best with the edge the user has + * selected. If the user selects "north", the + * bigger screen will normally be selected. + * + * This is actually kind of stupid, it should really + * use the biggest screen for off-screen stuff. + */ + + for(e=0;e>8); + break; + case EDGE_WEST: + pos=1000-heads[e].x_org + (heads[e].height>>8); + break; + case EDGE_SOUTH: + pos=heads[e].y_org + heads[e].height + (heads[e].width>>8); + break; + case EDGE_NORTH: + pos=1000-heads[e].y_org + (heads[e].width>>8); + break; + } + fprintf(stderr,"screen[%d] pos=%d\n",e,pos); + + if(pos > bestpos) + { + bestpos=pos; + besthead=e; + } + } + + + printf("Xinerama detected, x2vnc will use screen %d.\n", + besthead+1); + + multiscreen_offset.x = heads[besthead].x_org; + multiscreen_offset.y = heads[besthead].y_org; + display_size.SetWidth(heads[besthead].width); + display_size.SetHeight(heads[besthead].height); +#if 0 + fprintf(stderr,"[%d,%d-%d,%d]\n",multiscreen_offset.x,multiscreen_offset.y, + display_size.GetWidth(),display_size.GetHeight()); +#endif + } + XFree(heads); + } + } +#endif + + ew=edge_width; + if(!ew) ew=1; + topLevelWidth=ew; + topLevelHeight=ew; + wmHints.x=multiscreen_offset.x; + wmHints.y=multiscreen_offset.y; + + switch(edge) + { + case EDGE_EAST: wmHints.x=display_size.GetWidth()-ew+multiscreen_offset.x; + case EDGE_WEST: topLevelHeight=display_size.GetHeight(); + break; + + case EDGE_SOUTH: wmHints.y=display_size.GetHeight()-ew+multiscreen_offset.y; + case EDGE_NORTH: topLevelWidth=display_size.GetWidth(); + break; + } + + wmHints.flags = PMaxSize | PMinSize |PPosition |PBaseSize; + + wmHints.min_width = topLevelWidth; + wmHints.min_height = topLevelHeight; + + wmHints.max_width = topLevelWidth; + wmHints.max_height = topLevelHeight; + + wmHints.base_width = topLevelWidth; + wmHints.base_height = topLevelHeight; + + sprintf(defaultGeometry, "%dx%d+%d+%d", + topLevelWidth, topLevelHeight, + wmHints.x, wmHints.y); + + XWMGeometry(dpy, DefaultScreen(dpy), NULL, defaultGeometry, 0, + &wmHints, &wmHints.x, &wmHints.y, + &topLevelWidth, &topLevelHeight, &wmHints.win_gravity); + + /* Create the top-level window */ + + attr.border_pixel = 0; /* needed to allow 8-bit cmap on 24-bit display - + otherwise we get a Match error! */ + attr.background_pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(dpy)); + attr.event_mask = ( LeaveWindowMask| + StructureNotifyMask| + ButtonPressMask| + ButtonReleaseMask| + PointerMotionMask| + KeyPressMask| + KeyReleaseMask| + EnterWindowMask| + (resurface?VisibilityChangeMask:0) ); + + attr.override_redirect=1; + + topLevel = XCreateWindow(dpy, DefaultRootWindow(dpy), wmHints.x, wmHints.y, + topLevelWidth, topLevelHeight, 0, CopyFromParent, + InputOutput, CopyFromParent, + (CWBorderPixel| + CWEventMask| + CWOverrideRedirect| + CWBackPixel), + &attr); + + + Atom t = XInternAtom(dpy, "_NET_WM_WINDOW_DOCK", False); + + XChangeProperty(dpy, + topLevel, + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False), + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *)&t, + 1); + + wmHints.flags |= USPosition; /* try to force WM to place window */ + XSetWMNormalHints(dpy, topLevel, &wmHints); + + wmProtocols = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(dpy, topLevel, &wmDeleteWindow, 1); + + + + if(edge_width) + XMapRaised(dpy, topLevel); + else + hidden=1; + + /* + * For multi-screen setups, we need to know if the mouse is on the + * screen. + * - GRM + */ + if (ScreenCount(dpy) > 1) { + Window root, child; + int root_x, root_y; + int win_x, win_y; + unsigned int keys_buttons; + + XSelectInput(dpy, DefaultRootWindow(dpy), + PropertyChangeMask | + EnterWindowMask | + LeaveWindowMask | + KeyPressMask); + /* Cut buffer happens on screen 0 only. */ + if (DefaultRootWindow(dpy) != RootWindow(dpy, 0)) { + XSelectInput(dpy, RootWindow(dpy, 0), PropertyChangeMask); + } + XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child, + &root_x, &root_y, &win_x, &win_y, &keys_buttons); + mouseOnScreen = root == DefaultRootWindow(dpy); + } else { + XSelectInput(dpy, DefaultRootWindow(dpy), PropertyChangeMask); + mouseOnScreen = 1; + } + +#ifdef HAVE_XRANDR + { + int major=0, minor=0; + + if(XRRQueryVersion(dpy, &major, &minor) && major >= 1) + { + XRRQueryExtension(dpy, &xrandr_event_base, &xrandr_error_base); + XRRSelectInput(dpy, DefaultRootWindow(dpy), RRScreenChangeNotifyMask); + server_has_randr=1; + } + } +#endif + + nullPixmap = XCreatePixmap(dpy, DefaultRootWindow(dpy), 1, 1, 1); + if(!debug) + grabCursor = + XCreatePixmapCursor(dpy, nullPixmap, nullPixmap, + &dummyColor, &dummyColor, 0, 0); + + + + + return True; +} + + + + + + +/* + * HandleXEvents. + */ + +Bool VNCSeamlessConnector::HandleXEvents(void) +{ + + + + XEvent ev; + + + /* presumably XCheckMaskEvent is more efficient than XCheckIfEvent -GRM */ + while(XCheckIfEvent(dpy, &ev, AllXEventsPredicate, NULL)) + { + if (ev.xany.window == topLevel) + { + if (!HandleTopLevelEvent(&ev)) + return False; + } + /* else if (ev.xany.window == DefaultRootWindow(dpy) || + ev.xany.window == RootWindow(dpy, 0)) { + if (!HandleRootEvent(&ev)) + return False; + }*/ + /* else if (ev.type == MappingNotify) + { + XRefreshKeyboardMapping(&ev.xmapping); + }*/ + } + + + doWarp(); + + return True; +} + + + + + +int VNCSeamlessConnector::sendpointerevent(int x, int y, int buttonmask) +{ + wxMouseEvent e; + e.m_x = x; + e.m_y = y; + + if(buttonmask & rfbButton1Mask) + e.m_leftDown = true; + + if(buttonmask & rfbButton2Mask) + e.m_middleDown = true; + + if(buttonmask & rfbButton3Mask) + e.m_rightDown = true; + + if(buttonmask & rfbWheelUpMask) + e.m_wheelRotation = 1; + + if(buttonmask & rfbWheelDownMask) + e.m_wheelRotation = -1; + + conn->sendPointerEvent(e); + return 1; +} + + +void VNCSeamlessConnector::mapwindow(void) +{ + if(edge_width) + { + hidden=0; + XMapRaised(dpy, topLevel); + } +} + +void VNCSeamlessConnector::hidewindow(void) +{ + hidden=1; + XUnmapWindow(dpy, topLevel); +} + + + + + + + +void VNCSeamlessConnector::dumpMotionEvent(XEvent *ev) +{ + fprintf(stderr,"{ %d, %d } -> { %d, %d } = %d, %d\n", + current_location.x, + current_location.y, + ev->xmotion.x_root - multiscreen_offset.x, + ev->xmotion.y_root- multiscreen_offset.y, + (ev->xmotion.x_root - multiscreen_offset.x) - current_location.x, + (ev->xmotion.y_root - multiscreen_offset.y)-current_location.y); +} + + + + +/* + * HandleTopLevelEvent. + */ +Bool VNCSeamlessConnector::HandleTopLevelEvent(XEvent *ev) +{ + int x, y; + + int buttonMask; + KeySym ks; + static Atom COMPOUND_TEXT; + /* Atom: requestor asks for a list of supported targets (GRM 24 Oct 2003) */ + static Atom TARGETS; + + switch (ev->type) + { + case SelectionRequest: + { + XEvent reply; + + XSelectionRequestEvent *req=& ev->xselectionrequest; + + reply.xselection.type=SelectionNotify; + reply.xselection.display=req->display; + reply.xselection.selection=req->selection; + reply.xselection.requestor=req->requestor; + reply.xselection.target=req->target; + reply.xselection.time=req->time; + reply.xselection.property=None; + + if(COMPOUND_TEXT == None) + COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", True); + + /* Initialize TARGETS atom if required (GRM 24 Oct 2003) */ + if(TARGETS == None) TARGETS = XInternAtom(dpy, "TARGETS", True); + + if(client_selection_text) + { + if(req->target == TARGETS) + { + /* + * Requestor is asking what targets we support. Reply with what we + * support (XA_STRING and COMPOUND_TEXT). + * + * (GRM 24 Oct 2003) + */ + Atom targets[2]; + if(req->property == None) + req->property = TARGETS; + + targets[0] = XA_STRING; + targets[1] = COMPOUND_TEXT; + XChangeProperty(dpy, + req->requestor, + req->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *) targets, + 2); + + reply.xselection.property=req->property; + } + else if(req->target == XA_STRING || req->target == COMPOUND_TEXT) + { + if(req->property == None) + req->property = XA_CUT_BUFFER0; + +#if 0 + fprintf(stderr,"Setting property %d on window %x to: %s (len=%d)\n",req->property, req->requestor, client_selection_text,client_selection_text_length); +#endif + + XChangeProperty(dpy, + req->requestor, + req->property, + req->target, + 8, + PropModeReplace, + (const unsigned char*)client_selection_text, + client_selection_text_length); + + reply.xselection.property=req->property; + + } + else if(debug) + { + /* + * Requestor is asking for something we don't understand. Complain. + * (GRM 24 Oct 2003) + */ + char *name; + name = XGetAtomName(dpy, req->target); + fprintf(stderr, "Unknown property target request %s\n", name); + XFree(name); + } + } + XSendEvent(dpy, req->requestor, 0,0, &reply); + XFlush (dpy); + } + return 1; + + case SelectionNotify: + { + Atom t; + unsigned long len=0; + unsigned long bytes_left=0; + int format; + unsigned char *data=0; + int ret; + + ret=XGetWindowProperty(dpy, + topLevel, + XA_CUT_BUFFER0, + 0, + 0x100000, + 1, /* delete */ + XA_STRING, + &t, + &format, + &len, + &bytes_left, + &data); + + if(format == 8) + conn->setCuttext( wxString((char *)data, wxConvUTF8)); + //SendClientCutText((char *)data, len); +#ifdef DEBUG + fprintf(stderr,"GOT selection info: ret=%d type=%d fmt=%d len=%d bytes_left=%d %p '%s'\n", + ret, (int)t,format,len,bytes_left,data,data); +#endif + if(data) XFree(data); + + } + return 1; + + case VisibilityNotify: + /* + * I avoid resurfacing when the window becomes fully obscured, because + * that *probably* means that somebody is running xlock. + * Please tell me if you have a problem with this. + * - Hubbe + */ + if (ev->xvisibility.state == VisibilityPartiallyObscured && + resurface && !hidden) + { + static long last_resurface=0; + long t=time(0); + + if(t == last_resurface) + wxMilliSleep(5); + + last_resurface=t; +#ifdef DEBUG + fprintf(stderr,"Raising window!\n"); +#endif + mapwindow(); + /* XRaiseWindow(dpy, topLevel); */ + } + return 1; + + /* + * We don't need to worry about multi-screen here; that's handled + * below, as a root event. + * - GRM + */ + case EnterNotify: + if(!grabbed && ev->xcrossing.mode==NotifyNormal) + { + grabit(enter_translate(EDGE_EW,display_size.GetWidth() , (ev->xcrossing.x_root - multiscreen_offset.x)), + enter_translate(EDGE_NS,display_size.GetHeight(), (ev->xcrossing.y_root - multiscreen_offset.y)), + ev->xcrossing.state); + } + return 1; + + case MotionNotify: + + if(grabbed) + { + int i, d=0; + Window warpWindow; + wxPoint offset(0,0); + + + + wxPoint new_location(ev->xmotion.x_root, ev->xmotion.y_root); + if(debug) + { + dumpMotionEvent(ev); + + if(next_origo) + fprintf(stderr," {%d} 1 && + offset.x*offset.x + offset.y*offset.y < + pointer_warp_threshold) + { + remotePos.x+=offset.x; + remotePos.y+=offset.y; + }else{ + remotePos.x+=offset.x * pointer_speed; + remotePos.y+=offset.y * pointer_speed; + } + +#if 0 + fprintf(stderr," ==> {%f, %f} (%d,%d)\n", + remotePos.x, remotePos.y, + framebuffer_size.GetWidth(), + framebuffer_size.GetHeight()); +#endif + + if(!(ev->xmotion.state & 0x1f00)) + { + fprintf(stderr, "d gets set!!!\n"); + warpWindow = DefaultRootWindow(dpy); + switch(edge) + { + case EDGE_NORTH: + d=remotePos.y >= framebuffer_size.GetHeight(); + y = edge_width; /* FIXME */ + x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); + break; + case EDGE_SOUTH: + d=remotePos.y < 0; + y = display_size.GetHeight() - edge_width -1; /* FIXME */ + x = remotePos.x * display_size.GetWidth() / framebuffer_size.GetWidth(); + break; + case EDGE_EAST: + d=remotePos.x < 0; + x = display_size.GetWidth() - edge_width -1; /* FIXME */ + y = remotePos.y * display_size.GetHeight() /framebuffer_size.GetHeight() ; + break; + case EDGE_WEST: + d=remotePos.x > framebuffer_size.GetWidth(); + x = edge_width; /* FIXME */ + y = remotePos.y * display_size.GetHeight() / framebuffer_size.GetHeight(); + break; + } + } + + if(d) + { + fprintf(stderr, "d is set!!!\n"); + if(x<0) x=0; + if(y<0) y=0; + if(y>=display_size.GetHeight()) y=display_size.GetHeight()-1; + if(x>=display_size.GetWidth()) x=display_size.GetWidth()-1; + ungrabit(x, y, warpWindow); + return 1; + }else{ + if(remotePos.x < 0) remotePos.x=0; + if(remotePos.y < 0) remotePos.y=0; + + if(remotePos.x >= framebuffer_size.GetWidth()) + remotePos.x=framebuffer_size.GetWidth()-1; + + if(remotePos.y >= framebuffer_size.GetHeight()) + remotePos.y=framebuffer_size.GetHeight()-1; + + i=sendpointerevent((int)remotePos.x, + (int)remotePos.y, + (ev->xmotion.state & 0x1f00) >> 8); + } + + } + return 1; + + case ButtonPress: + case ButtonRelease: + if (ev->type == ButtonPress) { + buttonMask = (((ev->xbutton.state & 0x1f00) >> 8) | + (1 << (ev->xbutton.button - 1))); + } else { + buttonMask = (((ev->xbutton.state & 0x1f00) >> 8) & + ~(1 << (ev->xbutton.button - 1))); + } + + return sendpointerevent(remotePos.x, + remotePos.y, + buttonMask); + + case KeyPress: + case KeyRelease: + { + char keyname[256]; + keyname[0] = '\0'; + + + XLookupString(&ev->xkey, keyname, 256, &ks, NULL); + /* fprintf(stderr,"Pressing %x (%c) name=%s code=%d\n",ks,ks,keyname,ev->xkey.keycode); */ + + if (IsModifierKey(ks)) { + ks = XKeycodeToKeysym(dpy, ev->xkey.keycode, 0); + + /* Ignore AltGr key, it is handled by XLookupString */ + if( ks == XK_Mode_switch ) return True; + + modifierPressed[ev->xkey.keycode] = (ev->type == KeyPress); + } else { + /* This fixes the 'shift-tab' problem - Hubbe */ + switch(ks) + { +#if XK_ISO_Left_Tab != 0x1000ff74 + case 0x1000ff74: /* HP-UX */ +#endif + case XK_ISO_Left_Tab: ks=XK_Tab; break; + } + } + + // if(debug) + // fprintf(stderr," --> %x (%c) name=%s (%s)\n",ks,ks,keyname, ev->type == KeyPress ? "down" : "up"); + + + // return SendKeyEvent(ks, (ev->type == KeyPress)); + wxKeyEvent key_event; + key_event.m_keyCode = ks; + conn->sendKeyEvent(key_event, ev->type == KeyPress, true); + return 1; + } + + break; + } + + return True; +} + + + + +/* + * HandleRootEvent. + */ + +Bool VNCSeamlessConnector::HandleRootEvent(XEvent *ev) +{ + char *str; + int len; + + Bool nowOnScreen; + Bool grab; + + int x, y; + + switch (ev->type) + { + default: +#ifdef HAVE_XRANDR + if(ev->type == RRScreenChangeNotify + xrandr_event_base) + ;//exit(0); +#endif + break; + + case KeyPress: + { + KeySym ks; + char keyname[256]; + keyname[0] = '\0'; + XLookupString(&ev->xkey, keyname, 256, &ks, NULL); + + //fprintf(stderr,"ROOT: Pressing %x (%c) name=%s code=%d\n",ks,ks,keyname,ev->xkey.keycode); + + if(ev->xkey.keycode) + { + saved_xpos=(ev->xkey.x_root - multiscreen_offset.x); + saved_ypos=(ev->xkey.y_root - multiscreen_offset.y); + grabit(saved_remote_xpos, saved_remote_ypos, 0); + } + break; + } + + case EnterNotify: + case LeaveNotify: + /* + * Ignore the event if it's due to leaving our window. This will + * be after an ungrab. + */ + if (ev->xcrossing.subwindow == topLevel && + !ev->xcrossing.same_screen) { + break; + } + + grab = 0; + if(!grabbed) + { + nowOnScreen = ev->xcrossing.same_screen; + if (mouseOnScreen != nowOnScreen) { + /* + * Mouse has left, or entered, the screen. We must grab if + * the mouse is now near the edge we're watching. + * + * If we do grab, the mouse coordinates are left alone. + * The test, however, depends on whether the mouse entered + * or left the screen. + * + * - GRM + */ + x = (ev->xcrossing.x_root - multiscreen_offset.x); + y = (ev->xcrossing.y_root - multiscreen_offset.y); + if (!nowOnScreen) { + x = enter_translate(EDGE_EW,display_size.GetWidth(),(ev->xcrossing.x_root - multiscreen_offset.x)); + y = enter_translate(EDGE_NS,display_size.GetHeight(),(ev->xcrossing.y_root - multiscreen_offset.y)); + } + switch(edge) + { + case EDGE_NORTH: + grab=y < display_size.GetHeight() / 2; + break; + case EDGE_SOUTH: + grab=y > display_size.GetHeight() / 2; + break; + case EDGE_EAST: + grab=x > display_size.GetWidth() / 2; + break; + case EDGE_WEST: + grab=x < display_size.GetWidth() / 2; + break; + } + } + mouseOnScreen = nowOnScreen; + } + + /* + * Do not grab if this is the result of an ungrab + * or grab (caused by us, usually). + * + * - GRM + */ + if(grab && ev->xcrossing.mode == NotifyNormal) + { + grabit(enter_translate(EDGE_EW,display_size.GetWidth() ,(ev->xcrossing.x_root - multiscreen_offset.x)), + enter_translate(EDGE_NS,display_size.GetHeight(),(ev->xcrossing.y_root - multiscreen_offset.y)), + ev->xcrossing.state); + } + break; + + case PropertyNotify: + if (ev->xproperty.atom == XA_CUT_BUFFER0) + { + str = XFetchBytes(dpy, &len); + if (str) { +#ifdef DEBUG + fprintf(stderr,"GOT CUT TEXT: \"%s\"\n",str); +#endif + //if (!SendClientCutText(str, len)) + // return False; + conn->setCuttext(wxString(str, wxConvUTF8)); + XFree(str); + } + } + + break; + } + + return True; +} + +void VNCSeamlessConnector::handle_cut_text(char *str, size_t len) +{ + XWindowAttributes attrs; + + if(client_selection_text) + free((char *)client_selection_text); + + client_selection_text_length=len; + client_selection_text = str; + + XGetWindowAttributes(dpy, RootWindow(dpy, 0), &attrs); + XSelectInput(dpy, RootWindow(dpy, 0), + attrs.your_event_mask & ~PropertyChangeMask); + XStoreBytes(dpy, str, len); + XSetSelectionOwner(dpy, XA_PRIMARY, topLevel, CurrentTime); + XSelectInput(dpy, RootWindow(dpy, 0), attrs.your_event_mask); +} + +#endif + diff --git a/src/gui/ViewerWindow.cpp b/src/gui/ViewerWindow.cpp index 71e671d7..fba50f65 100644 --- a/src/gui/ViewerWindow.cpp +++ b/src/gui/ViewerWindow.cpp @@ -1,665 +1,665 @@ - - -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __WXGTK__ -#define GSocket GlibGSocket -#include -#include -#undef GSocket -#endif -#include "res/vnccursor.xbm" -#include "res/vnccursor-mask.xbm" -#include "MultiVNCApp.h" -#include "ViewerWindow.h" - - - -/******************************************** - - VNCCanvas class - -********************************************/ - - -/* - canvas that displays a VNCConn's framebuffer - and submits mouse and key events. - -*/ -class VNCCanvas: public wxPanel -{ - bool saved_enable_mnemonics; - bool saved_enable_accels; - char *saved_menubar_accel; - bool keyboard_grabbed; - - wxTimer update_timer; - void onUpdateTimer(wxTimerEvent& event); - void onPaint(wxPaintEvent &event); - void onMouseAction(wxMouseEvent &event); - void onKeyDown(wxKeyEvent &event); - void onKeyUp(wxKeyEvent &event); - void onChar(wxKeyEvent &event); - void onFocusGain(wxFocusEvent &event); - void onFocusLoss(wxFocusEvent &event); - -protected: - DECLARE_EVENT_TABLE(); - -public: - /// Creates a new canvas with 0,0 size. Need to call adjustCanvasSize()! - VNCCanvas(wxWindow* parent, VNCConn* c); - void grab_keyboard(); - void ungrab_keyboard(); - - VNCConn* conn; - wxRegion updated_area; - double scale_factor = 1.0; - bool do_keyboard_grab; -}; - - - -#define VNCCANVAS_UPDATE_TIMER_ID 1 -#define VNCCANVAS_UPDATE_TIMER_INTERVAL 30 - -BEGIN_EVENT_TABLE(VNCCanvas, wxPanel) - EVT_PAINT (VNCCanvas::onPaint) - EVT_MOUSE_EVENTS (VNCCanvas::onMouseAction) - EVT_KEY_DOWN (VNCCanvas::onKeyDown) - EVT_KEY_UP (VNCCanvas::onKeyUp) - EVT_CHAR (VNCCanvas::onChar) - EVT_SET_FOCUS (VNCCanvas::onFocusGain) - EVT_KILL_FOCUS(VNCCanvas::onFocusLoss) - EVT_TIMER (VNCCANVAS_UPDATE_TIMER_ID, VNCCanvas::onUpdateTimer) -END_EVENT_TABLE(); - - - -/* - constructor/destructor - (make sure size is set to 0,0 or win32 gets stuck sending - paint events in listen mode) -*/ - -VNCCanvas::VNCCanvas(wxWindow* parent, VNCConn* c): - wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(0,0), wxWANTS_CHARS) -{ - conn = c; - - keyboard_grabbed = do_keyboard_grab = false; - - // this kinda cursor creation works everywhere - wxBitmap vnccursor_bitmap((char*)vnccursor_bits, 16, 16); - wxBitmap vnccursor_mask_bitmap((char*)vnccursor_mask_bits, 16, 16); - vnccursor_bitmap.SetMask(new wxMask(vnccursor_mask_bitmap)); - wxImage vnccursor_image = vnccursor_bitmap.ConvertToImage(); - vnccursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 8); - vnccursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 8); - SetCursor(wxCursor(vnccursor_image)); - - update_timer.SetOwner(this, VNCCANVAS_UPDATE_TIMER_ID); - update_timer.Start(VNCCANVAS_UPDATE_TIMER_INTERVAL); - -#ifndef NDEBUG - SetBackgroundColour(*wxRED); -#endif -} - - - -/* - private members -*/ - - -void VNCCanvas::grab_keyboard() -{ - if(!keyboard_grabbed) - { -#ifdef __WXGTK__ - // grab - gdk_keyboard_grab(gtk_widget_get_window(GetHandle()), True, GDK_CURRENT_TIME); - - // save previous settings - GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default()); - g_object_get(settings, "gtk-enable-mnemonics", &saved_enable_mnemonics, NULL); - g_object_get(settings, "gtk-enable-accels", &saved_enable_accels, NULL); - g_object_get(settings, "gtk-menu-bar-accel", &saved_menubar_accel, NULL); - - // and disable keyboard shortcuts - g_object_set(settings, "gtk-enable-mnemonics", false, NULL); - g_object_set(settings, "gtk-enable-accels", false, NULL); - g_object_set(settings, "gtk-menu-bar-accel", NULL, NULL); -#endif - - keyboard_grabbed = true; - wxLogDebug(wxT("VNCCanvas %p: grabbed keyboard"), this); - } -} - - - -void VNCCanvas::ungrab_keyboard() -{ -#ifdef __WXGTK__ - // ungrab - gdk_keyboard_ungrab(GDK_CURRENT_TIME); - // restore to saved values - GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default()); - g_object_set(settings, "gtk-enable-mnemonics", saved_enable_mnemonics, NULL); - g_object_set(settings, "gtk-enable-accels", saved_enable_accels, NULL); - g_object_set(settings, "gtk-menu-bar-accel", saved_menubar_accel, NULL); - -#endif - - keyboard_grabbed = false; - wxLogDebug(wxT("VNCCanvas %p: ungrabbed keyboard"), this); -} - - - -void VNCCanvas::onUpdateTimer(wxTimerEvent& event) -{ - // get the update rect list - wxRegionIterator upd(updated_area); - while(upd) - { - wxRect update_rect(upd.GetRect()); - - if(scale_factor != 1.0) { - update_rect.x *= scale_factor; - update_rect.y *= scale_factor; - update_rect.width *= scale_factor; - update_rect.height *= scale_factor; - - // fixes artifacts. +2 because double->int cuttofs can happen for x,y _and_ w,h scaling - update_rect.width += 2; - update_rect.height += 2; - } - - wxLogDebug(wxT("VNCCanvas %p: invalidating updated rect: (%i,%i,%i,%i)"), - this, - update_rect.x, - update_rect.y, - update_rect.width, - update_rect.height); - - // triggers onPaint() - Refresh(false, &update_rect); - ++upd; - } - - updated_area.Clear(); - -} - - -void VNCCanvas::onPaint(wxPaintEvent &WXUNUSED(event)) -{ -#ifndef NDEBUG - wxLongLong t0 = wxGetLocalTimeMillis(); - size_t nr_rects = 0; - size_t nr_pixels = 0; -#endif - - // this happens on GTK even if our size is (0,0) - if(GetSize().GetWidth() == 0 || GetSize().GetHeight() == 0) - return; - - wxPaintDC dc(this); - dc.SetUserScale(scale_factor, scale_factor); - - // get the update rect list - wxRegionIterator upd(GetUpdateRegion()); - while(upd) - { - wxRect update_rect(upd.GetRect()); - - if(scale_factor != 1.0) { - update_rect.x /= scale_factor; - update_rect.y /= scale_factor; - update_rect.width /= scale_factor; - update_rect.height /= scale_factor; - - // fixes artifacts. +2 because double->int cuttofs can happen for x,y _and_ w,h scaling - update_rect.width += 2; - update_rect.height += 2; - - // make sure this is always within the framebuffer boudaries; - // might not always be due to the artifact fix above, would not be drawn then - update_rect.Intersect(wxRect(0, - 0, - conn->getFrameBufferWidth(), - conn->getFrameBufferHeight())); - } - - wxLogDebug(wxT("VNCCanvas %p: got repaint event: (%i,%i,%i,%i)"), - this, - update_rect.x, - update_rect.y, - update_rect.width, - update_rect.height); - -#ifndef NDEBUG - ++nr_rects; - nr_pixels += update_rect.width * update_rect.height; -#endif - - const wxBitmap& region = conn->getFrameBufferRegion(update_rect); - if(region.IsOk()) - dc.DrawBitmap(region, update_rect.x, update_rect.y); - - ++upd; - } - -#ifndef NDEBUG - wxLongLong t1 = wxGetLocalTimeMillis(); - wxLogDebug(wxT("VNCCanvas %p: updating %zu rects (%f megapixels) took %lld ms"), - this, - nr_rects, - (double)nr_pixels/(1024.0*1024.0), - (t1-t0).GetValue()); -#endif -} - - - - - - -void VNCCanvas::onMouseAction(wxMouseEvent &event) -{ - if(event.Entering()) - { - SetFocus(); - - if(do_keyboard_grab) - grab_keyboard(); - - if(wxTheClipboard->Open()) - { - if(wxTheClipboard->IsSupported(wxDF_TEXT)) - { - wxTextDataObject data; - wxTheClipboard->GetData(data); - wxString text = data.GetText(); - wxLogDebug(wxT("VNCCanvas %p: setting cuttext: '%s'"), this, text.c_str()); - conn->setCuttext(text); - } - wxTheClipboard->Close(); - } - } - - if(event.Leaving()) - { - if(do_keyboard_grab) - ungrab_keyboard(); - } - - // use rounding here to be a bit more correct if scaling causes fractional values - event.m_x = std::round(event.m_x / scale_factor); - event.m_y = std::round(event.m_y / scale_factor); - - conn->sendPointerEvent(event); -} - - -void VNCCanvas::onKeyDown(wxKeyEvent &event) -{ - conn->sendKeyEvent(event, true, false); -} - - -void VNCCanvas::onKeyUp(wxKeyEvent &event) -{ - conn->sendKeyEvent(event, false, false); -} - - -void VNCCanvas::onChar(wxKeyEvent &event) -{ - conn->sendKeyEvent(event, true, true); -} - - -void VNCCanvas::onFocusGain(wxFocusEvent &event) -{ - wxLogDebug(wxT("VNCCanvas %p: got focus"), this); -} - - -void VNCCanvas::onFocusLoss(wxFocusEvent &event) -{ - wxLogDebug(wxT("VNCCanvas %p: lost focus, upping key modifiers"), this); - - wxKeyEvent key_event; - - key_event.m_keyCode = WXK_SHIFT; - conn->sendKeyEvent(key_event, false, false); - key_event.m_keyCode = WXK_ALT; - conn->sendKeyEvent(key_event, false, false); - key_event.m_keyCode = WXK_CONTROL; - conn->sendKeyEvent(key_event, false, false); -} - - - - -/* - public members -*/ - - - - - - -/******************************************** - - ViewerWindow class - -********************************************/ - -#define VIEWERWINDOW_SCROLL_RATE 10 -#define VIEWERWINDOW_STATS_TIMER_INTERVAL 100 -#define VIEWERWINDOW_STATS_TIMER_ID 0 - -// map recv of custom events to handler methods -BEGIN_EVENT_TABLE(ViewerWindow, wxPanel) - EVT_TIMER (VIEWERWINDOW_STATS_TIMER_ID, ViewerWindow::onStatsTimer) - EVT_VNCCONNUPDATENOTIFY (wxID_ANY, ViewerWindow::onVNCConnUpdateNotify) - EVT_SIZE (ViewerWindow::onResize) -END_EVENT_TABLE() - - -/* - constructor/destructor - */ - -ViewerWindow::ViewerWindow(wxWindow* parent, VNCConn* conn): - wxPanel(parent) -{ - /* - setup main window - */ - // a sizer dividing the window vertically - SetSizer(new wxBoxSizer(wxVERTICAL)); - - // the upper subwindow - canvas_container = new wxScrolledWindow(this); -#ifndef NDEBUG - canvas_container->SetBackgroundColour(*wxBLUE); -#endif - - canvas_container->SetScrollRate(VIEWERWINDOW_SCROLL_RATE, VIEWERWINDOW_SCROLL_RATE); - GetSizer()->Add(canvas_container, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 3); - - - // the lower subwindow - stats_container = new wxScrolledWindow(this); - stats_container->SetScrollRate(VIEWERWINDOW_SCROLL_RATE, VIEWERWINDOW_SCROLL_RATE); - GetSizer()->Add(stats_container, 0, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALL, 3); - - - - /* - setup upper window - */ - // set some default for now - this->show_1to1 = false; - canvas_container->SetSizer(new wxBoxSizer(wxHORIZONTAL)); - canvas = new VNCCanvas(canvas_container, conn); - adjustCanvasSize(); - wxBoxSizer* sizer_vert_canvas = new wxBoxSizer(wxVERTICAL); - sizer_vert_canvas->Add(canvas, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); - canvas_container->GetSizer()->Add(sizer_vert_canvas, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); - - /* - setup lower window - */ - // fill lower subwindow with a vertical sizer with a horizontal static box sizer in it so all is nicely centered - wxBoxSizer* sizer_vert = new wxBoxSizer(wxVERTICAL); - stats_container->SetSizer(sizer_vert); - wxStaticBoxSizer* sizer_stats = new wxStaticBoxSizer(new wxStaticBox(stats_container, -1, _("Statistics")), wxHORIZONTAL); - sizer_vert->Add(sizer_stats, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); - - - // create statitistics widgets - label_updrawbytes = new wxStaticText(stats_container, wxID_ANY, _("Eff. KB/s:")); - text_ctrl_updrawbytes = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); - label_updcount = new wxStaticText(stats_container, wxID_ANY, _("Updates/s:")); - text_ctrl_updcount = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); - label_latency = new wxStaticText(stats_container, wxID_ANY, _("Latency ms:")); - text_ctrl_latency = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); - label_lossratio = new wxStaticText(stats_container, wxID_ANY, _("Loss Ratio:")); - text_ctrl_lossratio = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); - label_recvbuf = new wxStaticText(stats_container, wxID_ANY, _("Rcv Buffer:")); - gauge_recvbuf = new wxGauge(stats_container, wxID_ANY, 10, wxDefaultPosition, wxSize(80,20), wxGA_HORIZONTAL|wxGA_SMOOTH); - - dflt_fg = gauge_recvbuf->GetForegroundColour(); - - // create grid sizers, two cause we wanna hide the multicast one sometimes - wxGridSizer* grid_sizer_stats_uni = new wxGridSizer(2, 3, 0, 0); - wxGridSizer* grid_sizer_stats_multi = new wxGridSizer(2, 2, 0, 0); - // insert widgets into grid sizers - grid_sizer_stats_uni->Add(label_updrawbytes, 0, wxALL, 3); - grid_sizer_stats_uni->Add(label_updcount, 1, wxALL, 3); - grid_sizer_stats_uni->Add(label_latency, 0, wxALL, 3); - grid_sizer_stats_uni->Add(text_ctrl_updrawbytes, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); - grid_sizer_stats_uni->Add(text_ctrl_updcount, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); - grid_sizer_stats_uni->Add(text_ctrl_latency, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); - grid_sizer_stats_multi->Add(label_lossratio, 0, wxALL, 3); - grid_sizer_stats_multi->Add(label_recvbuf, 0, wxALL, 3); - grid_sizer_stats_multi->Add(text_ctrl_lossratio, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); - grid_sizer_stats_multi->Add(gauge_recvbuf, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); - // insert grid sizer into static box sizer - sizer_stats->Add(grid_sizer_stats_uni, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM|wxALL, 0); - sizer_stats->Add(grid_sizer_stats_multi, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM|wxALL, 0); - - // IMPORTANT: make sizer obey to size hints! - stats_container->GetSizer()->SetSizeHints(stats_container); - - stats_timer.SetOwner(this, VIEWERWINDOW_STATS_TIMER_ID); - -#ifndef NDEBUG - SetBackgroundColour(*wxGREEN); -#endif -} - - - - -ViewerWindow::~ViewerWindow() -{ - if(canvas) - delete canvas; -} - - - -/* - private members -*/ - - -void ViewerWindow::onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event) -{ - VNCConn* sending_conn = static_cast(event.GetEventObject()); - - // only do something if this is our VNCConn - if(sending_conn == canvas->conn) - canvas->updated_area.Union(event.rect); -} - - -void ViewerWindow::onStatsTimer(wxTimerEvent& event) -{ - if(canvas && canvas->conn) - { - VNCConn* c = canvas->conn; - - text_ctrl_updrawbytes->Clear(); - text_ctrl_updcount->Clear(); - text_ctrl_latency->Clear(); - text_ctrl_lossratio->Clear(); - - if(!c->isMulticast()) - { - label_lossratio->Show(false); - text_ctrl_lossratio->Show(false); - label_recvbuf->Show(false); - gauge_recvbuf->Show(false); - } - else - { - label_lossratio->Show(true); - text_ctrl_lossratio->Show(true); - label_recvbuf->Show(true); - gauge_recvbuf->Show(true); - } - stats_container->Layout(); - - if( ! c->getStats().IsEmpty() ) - { - // it is imperative here to obey the format of the sample string! - wxStringTokenizer tokenizer(c->getStats().Last(), wxT(",")); - tokenizer.GetNextToken(); // skip UTC time - tokenizer.GetNextToken(); // skip conn time - tokenizer.GetNextToken(); // skip rcvd bytes - *text_ctrl_updrawbytes << wxAtoi(tokenizer.GetNextToken())/1024; // inflated bytes - *text_ctrl_updcount << tokenizer.GetNextToken(); - int latency = wxAtoi(tokenizer.GetNextToken()); - if(latency >= 0) - *text_ctrl_latency << latency; - - double lossratio = c->getMCLossRatio(); - if(lossratio >= 0) // can be -1 if nothing to measure - *text_ctrl_lossratio << lossratio; - } - - gauge_recvbuf->SetRange(c->getMCBufSize()); - gauge_recvbuf->SetValue(c->getMCBufFill()); - - // flash red when buffer full - if(gauge_recvbuf->GetRange() == gauge_recvbuf->GetValue()) - label_recvbuf->SetForegroundColour(*wxRED); - else - label_recvbuf->SetForegroundColour(dflt_fg); - } -} - -void ViewerWindow::onResize(wxSizeEvent &event) -{ - wxLogDebug("ViewerWindow %p: resized to (%i, %i)", this, GetSize().GetWidth(), GetSize().GetHeight()); - adjustCanvasSize(); -} - -/* - public members -*/ - - -void ViewerWindow::adjustCanvasSize() -{ - if(canvas) - { - if(show_1to1) { - // reset scale factor - canvas->scale_factor = 1.0; - // and enable scroll bars - canvas_container->SetScrollRate(VIEWERWINDOW_SCROLL_RATE, VIEWERWINDOW_SCROLL_RATE); - } else { - /* - calculate scale factor - */ - wxLogDebug("ViewerWindow %p: adjustCanvasSize: framebuffer is %d x %d", - this, - canvas->conn->getFrameBufferWidth(), - canvas->conn->getFrameBufferHeight()); - wxLogDebug("ViewerWindow %p: adjustCanvasSize: window is %d x %d", - this, - GetSize().GetWidth(), - GetSize().GetHeight()); - - float width_factor = GetSize().GetWidth() / (float)canvas->conn->getFrameBufferWidth(); - - // stats shown? - int stats_height = 0; - if(GetSizer()->IsShown(1)) { - // compute correct size for initial case - Layout(); - stats_height = stats_container->GetSize().GetHeight(); - } - float height_factor = (GetSize().GetHeight() - stats_height) / (float)canvas->conn->getFrameBufferHeight(); - - wxLogDebug("ViewerWindow %p: adjustCanvasSize: width factor is %f", this, width_factor); - wxLogDebug("ViewerWindow %p: adjustCanvasSize: height factor is %f", this, height_factor); - - canvas->scale_factor = wxMin(width_factor, height_factor); - - /* - and disable scroll bars - */ - canvas_container->SetScrollRate(0, 0); - } - wxSize dimensions(canvas->conn->getFrameBufferWidth() * canvas->scale_factor, - canvas->conn->getFrameBufferHeight() * canvas->scale_factor); - - wxLogDebug(wxT("ViewerWindow %p: adjustCanvasSize: adjusting canvas size to (%i, %i)"), - this, - dimensions.GetWidth(), - dimensions.GetHeight()); - - // SetSize() isn't enough... - canvas->SetInitialSize(dimensions); - canvas->CentreOnParent(); - Layout(); - } -} - - - - -void ViewerWindow::showStats(bool show_stats) -{ - if(show_stats) - { - stats_timer.Start(VIEWERWINDOW_STATS_TIMER_INTERVAL); - GetSizer()->Show(1, true); - } - else - { - stats_timer.Stop(); - GetSizer()->Show(1, false); - } - adjustCanvasSize(); - Layout(); - - text_ctrl_updrawbytes->Clear(); - text_ctrl_updcount->Clear(); - text_ctrl_latency->Clear(); - text_ctrl_lossratio->Clear(); - gauge_recvbuf->SetValue(0); -} - -void ViewerWindow::showOneToOne(bool show_1to1) -{ - this->show_1to1 = show_1to1; - adjustCanvasSize(); -} - - -void ViewerWindow::grabKeyboard(bool grabit) -{ - if(canvas) - // grab later on enter/leave - canvas->do_keyboard_grab = grabit; -} + + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __WXGTK__ +#define GSocket GlibGSocket +#include +#include +#undef GSocket +#endif +#include "res/vnccursor.xbm" +#include "res/vnccursor-mask.xbm" +#include "MultiVNCApp.h" +#include "ViewerWindow.h" + + + +/******************************************** + + VNCCanvas class + +********************************************/ + + +/* + canvas that displays a VNCConn's framebuffer + and submits mouse and key events. + +*/ +class VNCCanvas: public wxPanel +{ + bool saved_enable_mnemonics; + bool saved_enable_accels; + char *saved_menubar_accel; + bool keyboard_grabbed; + + wxTimer update_timer; + void onUpdateTimer(wxTimerEvent& event); + void onPaint(wxPaintEvent &event); + void onMouseAction(wxMouseEvent &event); + void onKeyDown(wxKeyEvent &event); + void onKeyUp(wxKeyEvent &event); + void onChar(wxKeyEvent &event); + void onFocusGain(wxFocusEvent &event); + void onFocusLoss(wxFocusEvent &event); + +protected: + DECLARE_EVENT_TABLE(); + +public: + /// Creates a new canvas with 0,0 size. Need to call adjustCanvasSize()! + VNCCanvas(wxWindow* parent, VNCConn* c); + void grab_keyboard(); + void ungrab_keyboard(); + + VNCConn* conn; + wxRegion updated_area; + double scale_factor = 1.0; + bool do_keyboard_grab; +}; + + + +#define VNCCANVAS_UPDATE_TIMER_ID 1 +#define VNCCANVAS_UPDATE_TIMER_INTERVAL 30 + +BEGIN_EVENT_TABLE(VNCCanvas, wxPanel) + EVT_PAINT (VNCCanvas::onPaint) + EVT_MOUSE_EVENTS (VNCCanvas::onMouseAction) + EVT_KEY_DOWN (VNCCanvas::onKeyDown) + EVT_KEY_UP (VNCCanvas::onKeyUp) + EVT_CHAR (VNCCanvas::onChar) + EVT_SET_FOCUS (VNCCanvas::onFocusGain) + EVT_KILL_FOCUS(VNCCanvas::onFocusLoss) + EVT_TIMER (VNCCANVAS_UPDATE_TIMER_ID, VNCCanvas::onUpdateTimer) +END_EVENT_TABLE(); + + + +/* + constructor/destructor + (make sure size is set to 0,0 or win32 gets stuck sending + paint events in listen mode) +*/ + +VNCCanvas::VNCCanvas(wxWindow* parent, VNCConn* c): + wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(0,0), wxWANTS_CHARS) +{ + conn = c; + + keyboard_grabbed = do_keyboard_grab = false; + + // this kinda cursor creation works everywhere + wxBitmap vnccursor_bitmap((char*)vnccursor_bits, 16, 16); + wxBitmap vnccursor_mask_bitmap((char*)vnccursor_mask_bits, 16, 16); + vnccursor_bitmap.SetMask(new wxMask(vnccursor_mask_bitmap)); + wxImage vnccursor_image = vnccursor_bitmap.ConvertToImage(); + vnccursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 8); + vnccursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 8); + SetCursor(wxCursor(vnccursor_image)); + + update_timer.SetOwner(this, VNCCANVAS_UPDATE_TIMER_ID); + update_timer.Start(VNCCANVAS_UPDATE_TIMER_INTERVAL); + +#ifndef NDEBUG + SetBackgroundColour(*wxRED); +#endif +} + + + +/* + private members +*/ + + +void VNCCanvas::grab_keyboard() +{ + if(!keyboard_grabbed) + { +#ifdef __WXGTK__ + // grab + gdk_keyboard_grab(gtk_widget_get_window(GetHandle()), True, GDK_CURRENT_TIME); + + // save previous settings + GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default()); + g_object_get(settings, "gtk-enable-mnemonics", &saved_enable_mnemonics, NULL); + g_object_get(settings, "gtk-enable-accels", &saved_enable_accels, NULL); + g_object_get(settings, "gtk-menu-bar-accel", &saved_menubar_accel, NULL); + + // and disable keyboard shortcuts + g_object_set(settings, "gtk-enable-mnemonics", false, NULL); + g_object_set(settings, "gtk-enable-accels", false, NULL); + g_object_set(settings, "gtk-menu-bar-accel", NULL, NULL); +#endif + + keyboard_grabbed = true; + wxLogDebug(wxT("VNCCanvas %p: grabbed keyboard"), this); + } +} + + + +void VNCCanvas::ungrab_keyboard() +{ +#ifdef __WXGTK__ + // ungrab + gdk_keyboard_ungrab(GDK_CURRENT_TIME); + // restore to saved values + GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default()); + g_object_set(settings, "gtk-enable-mnemonics", saved_enable_mnemonics, NULL); + g_object_set(settings, "gtk-enable-accels", saved_enable_accels, NULL); + g_object_set(settings, "gtk-menu-bar-accel", saved_menubar_accel, NULL); + +#endif + + keyboard_grabbed = false; + wxLogDebug(wxT("VNCCanvas %p: ungrabbed keyboard"), this); +} + + + +void VNCCanvas::onUpdateTimer(wxTimerEvent& event) +{ + // get the update rect list + wxRegionIterator upd(updated_area); + while(upd) + { + wxRect update_rect(upd.GetRect()); + + if(scale_factor != 1.0) { + update_rect.x *= scale_factor; + update_rect.y *= scale_factor; + update_rect.width *= scale_factor; + update_rect.height *= scale_factor; + + // fixes artifacts. +2 because double->int cuttofs can happen for x,y _and_ w,h scaling + update_rect.width += 2; + update_rect.height += 2; + } + + wxLogDebug(wxT("VNCCanvas %p: invalidating updated rect: (%i,%i,%i,%i)"), + this, + update_rect.x, + update_rect.y, + update_rect.width, + update_rect.height); + + // triggers onPaint() + Refresh(false, &update_rect); + ++upd; + } + + updated_area.Clear(); + +} + + +void VNCCanvas::onPaint(wxPaintEvent &WXUNUSED(event)) +{ +#ifndef NDEBUG + wxLongLong t0 = wxGetLocalTimeMillis(); + size_t nr_rects = 0; + size_t nr_pixels = 0; +#endif + + // this happens on GTK even if our size is (0,0) + if(GetSize().GetWidth() == 0 || GetSize().GetHeight() == 0) + return; + + wxPaintDC dc(this); + dc.SetUserScale(scale_factor, scale_factor); + + // get the update rect list + wxRegionIterator upd(GetUpdateRegion()); + while(upd) + { + wxRect update_rect(upd.GetRect()); + + if(scale_factor != 1.0) { + update_rect.x /= scale_factor; + update_rect.y /= scale_factor; + update_rect.width /= scale_factor; + update_rect.height /= scale_factor; + + // fixes artifacts. +2 because double->int cuttofs can happen for x,y _and_ w,h scaling + update_rect.width += 2; + update_rect.height += 2; + + // make sure this is always within the framebuffer boudaries; + // might not always be due to the artifact fix above, would not be drawn then + update_rect.Intersect(wxRect(0, + 0, + conn->getFrameBufferWidth(), + conn->getFrameBufferHeight())); + } + + wxLogDebug(wxT("VNCCanvas %p: got repaint event: (%i,%i,%i,%i)"), + this, + update_rect.x, + update_rect.y, + update_rect.width, + update_rect.height); + +#ifndef NDEBUG + ++nr_rects; + nr_pixels += update_rect.width * update_rect.height; +#endif + + const wxBitmap& region = conn->getFrameBufferRegion(update_rect); + if(region.IsOk()) + dc.DrawBitmap(region, update_rect.x, update_rect.y); + + ++upd; + } + +#ifndef NDEBUG + wxLongLong t1 = wxGetLocalTimeMillis(); + wxLogDebug(wxT("VNCCanvas %p: updating %zu rects (%f megapixels) took %lld ms"), + this, + nr_rects, + (double)nr_pixels/(1024.0*1024.0), + (t1-t0).GetValue()); +#endif +} + + + + + + +void VNCCanvas::onMouseAction(wxMouseEvent &event) +{ + if(event.Entering()) + { + SetFocus(); + + if(do_keyboard_grab) + grab_keyboard(); + + if(wxTheClipboard->Open()) + { + if(wxTheClipboard->IsSupported(wxDF_TEXT)) + { + wxTextDataObject data; + wxTheClipboard->GetData(data); + wxString text = data.GetText(); + wxLogDebug(wxT("VNCCanvas %p: setting cuttext: '%s'"), this, text.c_str()); + conn->setCuttext(text); + } + wxTheClipboard->Close(); + } + } + + if(event.Leaving()) + { + if(do_keyboard_grab) + ungrab_keyboard(); + } + + // use rounding here to be a bit more correct if scaling causes fractional values + event.m_x = std::round(event.m_x / scale_factor); + event.m_y = std::round(event.m_y / scale_factor); + + conn->sendPointerEvent(event); +} + + +void VNCCanvas::onKeyDown(wxKeyEvent &event) +{ + conn->sendKeyEvent(event, true, false); +} + + +void VNCCanvas::onKeyUp(wxKeyEvent &event) +{ + conn->sendKeyEvent(event, false, false); +} + + +void VNCCanvas::onChar(wxKeyEvent &event) +{ + conn->sendKeyEvent(event, true, true); +} + + +void VNCCanvas::onFocusGain(wxFocusEvent &event) +{ + wxLogDebug(wxT("VNCCanvas %p: got focus"), this); +} + + +void VNCCanvas::onFocusLoss(wxFocusEvent &event) +{ + wxLogDebug(wxT("VNCCanvas %p: lost focus, upping key modifiers"), this); + + wxKeyEvent key_event; + + key_event.m_keyCode = WXK_SHIFT; + conn->sendKeyEvent(key_event, false, false); + key_event.m_keyCode = WXK_ALT; + conn->sendKeyEvent(key_event, false, false); + key_event.m_keyCode = WXK_CONTROL; + conn->sendKeyEvent(key_event, false, false); +} + + + + +/* + public members +*/ + + + + + + +/******************************************** + + ViewerWindow class + +********************************************/ + +#define VIEWERWINDOW_SCROLL_RATE 10 +#define VIEWERWINDOW_STATS_TIMER_INTERVAL 100 +#define VIEWERWINDOW_STATS_TIMER_ID 0 + +// map recv of custom events to handler methods +BEGIN_EVENT_TABLE(ViewerWindow, wxPanel) + EVT_TIMER (VIEWERWINDOW_STATS_TIMER_ID, ViewerWindow::onStatsTimer) + EVT_VNCCONNUPDATENOTIFY (wxID_ANY, ViewerWindow::onVNCConnUpdateNotify) + EVT_SIZE (ViewerWindow::onResize) +END_EVENT_TABLE() + + +/* + constructor/destructor + */ + +ViewerWindow::ViewerWindow(wxWindow* parent, VNCConn* conn): + wxPanel(parent) +{ + /* + setup main window + */ + // a sizer dividing the window vertically + SetSizer(new wxBoxSizer(wxVERTICAL)); + + // the upper subwindow + canvas_container = new wxScrolledWindow(this); +#ifndef NDEBUG + canvas_container->SetBackgroundColour(*wxBLUE); +#endif + + canvas_container->SetScrollRate(VIEWERWINDOW_SCROLL_RATE, VIEWERWINDOW_SCROLL_RATE); + GetSizer()->Add(canvas_container, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 3); + + + // the lower subwindow + stats_container = new wxScrolledWindow(this); + stats_container->SetScrollRate(VIEWERWINDOW_SCROLL_RATE, VIEWERWINDOW_SCROLL_RATE); + GetSizer()->Add(stats_container, 0, wxEXPAND|wxALIGN_CENTER_HORIZONTAL|wxALL, 3); + + + + /* + setup upper window + */ + // set some default for now + this->show_1to1 = false; + canvas_container->SetSizer(new wxBoxSizer(wxHORIZONTAL)); + canvas = new VNCCanvas(canvas_container, conn); + adjustCanvasSize(); + wxBoxSizer* sizer_vert_canvas = new wxBoxSizer(wxVERTICAL); + sizer_vert_canvas->Add(canvas, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); + canvas_container->GetSizer()->Add(sizer_vert_canvas, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); + + /* + setup lower window + */ + // fill lower subwindow with a vertical sizer with a horizontal static box sizer in it so all is nicely centered + wxBoxSizer* sizer_vert = new wxBoxSizer(wxVERTICAL); + stats_container->SetSizer(sizer_vert); + wxStaticBoxSizer* sizer_stats = new wxStaticBoxSizer(new wxStaticBox(stats_container, -1, _("Statistics")), wxHORIZONTAL); + sizer_vert->Add(sizer_stats, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); + + + // create statitistics widgets + label_updrawbytes = new wxStaticText(stats_container, wxID_ANY, _("Eff. KB/s:")); + text_ctrl_updrawbytes = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); + label_updcount = new wxStaticText(stats_container, wxID_ANY, _("Updates/s:")); + text_ctrl_updcount = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); + label_latency = new wxStaticText(stats_container, wxID_ANY, _("Latency ms:")); + text_ctrl_latency = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); + label_lossratio = new wxStaticText(stats_container, wxID_ANY, _("Loss Ratio:")); + text_ctrl_lossratio = new wxTextCtrl(stats_container, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80,20), wxTE_READONLY); + label_recvbuf = new wxStaticText(stats_container, wxID_ANY, _("Rcv Buffer:")); + gauge_recvbuf = new wxGauge(stats_container, wxID_ANY, 10, wxDefaultPosition, wxSize(80,20), wxGA_HORIZONTAL|wxGA_SMOOTH); + + dflt_fg = gauge_recvbuf->GetForegroundColour(); + + // create grid sizers, two cause we wanna hide the multicast one sometimes + wxGridSizer* grid_sizer_stats_uni = new wxGridSizer(2, 3, 0, 0); + wxGridSizer* grid_sizer_stats_multi = new wxGridSizer(2, 2, 0, 0); + // insert widgets into grid sizers + grid_sizer_stats_uni->Add(label_updrawbytes, 0, wxALL, 3); + grid_sizer_stats_uni->Add(label_updcount, 1, wxALL, 3); + grid_sizer_stats_uni->Add(label_latency, 0, wxALL, 3); + grid_sizer_stats_uni->Add(text_ctrl_updrawbytes, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); + grid_sizer_stats_uni->Add(text_ctrl_updcount, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); + grid_sizer_stats_uni->Add(text_ctrl_latency, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); + grid_sizer_stats_multi->Add(label_lossratio, 0, wxALL, 3); + grid_sizer_stats_multi->Add(label_recvbuf, 0, wxALL, 3); + grid_sizer_stats_multi->Add(text_ctrl_lossratio, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); + grid_sizer_stats_multi->Add(gauge_recvbuf, 0, wxLEFT|wxRIGHT|wxBOTTOM, 3); + // insert grid sizer into static box sizer + sizer_stats->Add(grid_sizer_stats_uni, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM|wxALL, 0); + sizer_stats->Add(grid_sizer_stats_multi, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_BOTTOM|wxALL, 0); + + // IMPORTANT: make sizer obey to size hints! + stats_container->GetSizer()->SetSizeHints(stats_container); + + stats_timer.SetOwner(this, VIEWERWINDOW_STATS_TIMER_ID); + +#ifndef NDEBUG + SetBackgroundColour(*wxGREEN); +#endif +} + + + + +ViewerWindow::~ViewerWindow() +{ + if(canvas) + delete canvas; +} + + + +/* + private members +*/ + + +void ViewerWindow::onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event) +{ + VNCConn* sending_conn = static_cast(event.GetEventObject()); + + // only do something if this is our VNCConn + if(sending_conn == canvas->conn) + canvas->updated_area.Union(event.rect); +} + + +void ViewerWindow::onStatsTimer(wxTimerEvent& event) +{ + if(canvas && canvas->conn) + { + VNCConn* c = canvas->conn; + + text_ctrl_updrawbytes->Clear(); + text_ctrl_updcount->Clear(); + text_ctrl_latency->Clear(); + text_ctrl_lossratio->Clear(); + + if(!c->isMulticast()) + { + label_lossratio->Show(false); + text_ctrl_lossratio->Show(false); + label_recvbuf->Show(false); + gauge_recvbuf->Show(false); + } + else + { + label_lossratio->Show(true); + text_ctrl_lossratio->Show(true); + label_recvbuf->Show(true); + gauge_recvbuf->Show(true); + } + stats_container->Layout(); + + if( ! c->getStats().IsEmpty() ) + { + // it is imperative here to obey the format of the sample string! + wxStringTokenizer tokenizer(c->getStats().Last(), wxT(",")); + tokenizer.GetNextToken(); // skip UTC time + tokenizer.GetNextToken(); // skip conn time + tokenizer.GetNextToken(); // skip rcvd bytes + *text_ctrl_updrawbytes << wxAtoi(tokenizer.GetNextToken())/1024; // inflated bytes + *text_ctrl_updcount << tokenizer.GetNextToken(); + int latency = wxAtoi(tokenizer.GetNextToken()); + if(latency >= 0) + *text_ctrl_latency << latency; + + double lossratio = c->getMCLossRatio(); + if(lossratio >= 0) // can be -1 if nothing to measure + *text_ctrl_lossratio << lossratio; + } + + gauge_recvbuf->SetRange(c->getMCBufSize()); + gauge_recvbuf->SetValue(c->getMCBufFill()); + + // flash red when buffer full + if(gauge_recvbuf->GetRange() == gauge_recvbuf->GetValue()) + label_recvbuf->SetForegroundColour(*wxRED); + else + label_recvbuf->SetForegroundColour(dflt_fg); + } +} + +void ViewerWindow::onResize(wxSizeEvent &event) +{ + wxLogDebug("ViewerWindow %p: resized to (%i, %i)", this, GetSize().GetWidth(), GetSize().GetHeight()); + adjustCanvasSize(); +} + +/* + public members +*/ + + +void ViewerWindow::adjustCanvasSize() +{ + if(canvas) + { + if(show_1to1) { + // reset scale factor + canvas->scale_factor = 1.0; + // and enable scroll bars + canvas_container->SetScrollRate(VIEWERWINDOW_SCROLL_RATE, VIEWERWINDOW_SCROLL_RATE); + } else { + /* + calculate scale factor + */ + wxLogDebug("ViewerWindow %p: adjustCanvasSize: framebuffer is %d x %d", + this, + canvas->conn->getFrameBufferWidth(), + canvas->conn->getFrameBufferHeight()); + wxLogDebug("ViewerWindow %p: adjustCanvasSize: window is %d x %d", + this, + GetSize().GetWidth(), + GetSize().GetHeight()); + + float width_factor = GetSize().GetWidth() / (float)canvas->conn->getFrameBufferWidth(); + + // stats shown? + int stats_height = 0; + if(GetSizer()->IsShown(1)) { + // compute correct size for initial case + Layout(); + stats_height = stats_container->GetSize().GetHeight(); + } + float height_factor = (GetSize().GetHeight() - stats_height) / (float)canvas->conn->getFrameBufferHeight(); + + wxLogDebug("ViewerWindow %p: adjustCanvasSize: width factor is %f", this, width_factor); + wxLogDebug("ViewerWindow %p: adjustCanvasSize: height factor is %f", this, height_factor); + + canvas->scale_factor = wxMin(width_factor, height_factor); + + /* + and disable scroll bars + */ + canvas_container->SetScrollRate(0, 0); + } + wxSize dimensions(canvas->conn->getFrameBufferWidth() * canvas->scale_factor, + canvas->conn->getFrameBufferHeight() * canvas->scale_factor); + + wxLogDebug(wxT("ViewerWindow %p: adjustCanvasSize: adjusting canvas size to (%i, %i)"), + this, + dimensions.GetWidth(), + dimensions.GetHeight()); + + // SetSize() isn't enough... + canvas->SetInitialSize(dimensions); + canvas->CentreOnParent(); + Layout(); + } +} + + + + +void ViewerWindow::showStats(bool show_stats) +{ + if(show_stats) + { + stats_timer.Start(VIEWERWINDOW_STATS_TIMER_INTERVAL); + GetSizer()->Show(1, true); + } + else + { + stats_timer.Stop(); + GetSizer()->Show(1, false); + } + adjustCanvasSize(); + Layout(); + + text_ctrl_updrawbytes->Clear(); + text_ctrl_updcount->Clear(); + text_ctrl_latency->Clear(); + text_ctrl_lossratio->Clear(); + gauge_recvbuf->SetValue(0); +} + +void ViewerWindow::showOneToOne(bool show_1to1) +{ + this->show_1to1 = show_1to1; + adjustCanvasSize(); +} + + +void ViewerWindow::grabKeyboard(bool grabit) +{ + if(canvas) + // grab later on enter/leave + canvas->do_keyboard_grab = grabit; +} diff --git a/src/gui/ViewerWindow.h b/src/gui/ViewerWindow.h index 3a7cfdb6..21c05373 100644 --- a/src/gui/ViewerWindow.h +++ b/src/gui/ViewerWindow.h @@ -1,65 +1,65 @@ -// -*- C++ -*- - -#ifndef VIEWERWINDOW_H -#define VIEWERWINDOW_H - -#include -#include -#include -#include "VNCConn.h" - -class VNCCanvas; - -/* - consists of two scrollable windows, one a container for a VNCCanvas - that centers the canvas in itself, the other one containing the stats window, - also centered -*/ -class ViewerWindow: public wxPanel -{ -public: - ViewerWindow(wxWindow* parent, VNCConn* connection); - ~ViewerWindow(); - - void adjustCanvasSize(); - - void showStats(bool yesno); - void showOneToOne(bool show_1to1); - void grabKeyboard(bool yesno); - - -protected: - wxStaticText* label_updrawbytes; - wxTextCtrl* text_ctrl_updrawbytes; - wxStaticText* label_updcount; - wxTextCtrl* text_ctrl_updcount; - wxStaticText* label_latency; - wxTextCtrl* text_ctrl_latency; - wxStaticText* label_lossratio; - wxTextCtrl* text_ctrl_lossratio; - wxStaticText* label_recvbuf; - wxGauge* gauge_recvbuf; - - DECLARE_EVENT_TABLE(); - -private: - - wxScrolledWindow* canvas_container; - wxScrolledWindow* stats_container; - VNCCanvas* canvas; - - // timer to update stats win - wxTimer stats_timer; - void onStatsTimer(wxTimerEvent& event); - void onResize(wxSizeEvent &event); - void onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event); - - // save default foreground colour to be able to flash when buffer full - wxColour dflt_fg; - - bool show_1to1; - -}; - - -#endif +// -*- C++ -*- + +#ifndef VIEWERWINDOW_H +#define VIEWERWINDOW_H + +#include +#include +#include +#include "VNCConn.h" + +class VNCCanvas; + +/* + consists of two scrollable windows, one a container for a VNCCanvas + that centers the canvas in itself, the other one containing the stats window, + also centered +*/ +class ViewerWindow: public wxPanel +{ +public: + ViewerWindow(wxWindow* parent, VNCConn* connection); + ~ViewerWindow(); + + void adjustCanvasSize(); + + void showStats(bool yesno); + void showOneToOne(bool show_1to1); + void grabKeyboard(bool yesno); + + +protected: + wxStaticText* label_updrawbytes; + wxTextCtrl* text_ctrl_updrawbytes; + wxStaticText* label_updcount; + wxTextCtrl* text_ctrl_updcount; + wxStaticText* label_latency; + wxTextCtrl* text_ctrl_latency; + wxStaticText* label_lossratio; + wxTextCtrl* text_ctrl_lossratio; + wxStaticText* label_recvbuf; + wxGauge* gauge_recvbuf; + + DECLARE_EVENT_TABLE(); + +private: + + wxScrolledWindow* canvas_container; + wxScrolledWindow* stats_container; + VNCCanvas* canvas; + + // timer to update stats win + wxTimer stats_timer; + void onStatsTimer(wxTimerEvent& event); + void onResize(wxSizeEvent &event); + void onVNCConnUpdateNotify(VNCConnUpdateNotifyEvent& event); + + // save default foreground colour to be able to flash when buffer full + wxColour dflt_fg; + + bool show_1to1; + +}; + + +#endif diff --git a/src/gui/bitmapFromMem.h b/src/gui/bitmapFromMem.h index d4bde801..62a9ca21 100644 --- a/src/gui/bitmapFromMem.h +++ b/src/gui/bitmapFromMem.h @@ -1,11 +1,11 @@ -#ifndef BITMAPFROMMEM_H -#define BITMAPFROMMEM_H - -#include -#include -#include - -#define bitmapBundleFromSVGResource(name) wxBitmapBundle::FromSVGFile(wxFileName(wxStandardPaths::Get().GetResourcesDir(), name, "svg").GetFullPath(), wxSize(24,24)) - -#endif - +#ifndef BITMAPFROMMEM_H +#define BITMAPFROMMEM_H + +#include +#include +#include + +#define bitmapBundleFromSVGResource(name) wxBitmapBundle::FromSVGFile(wxFileName(wxStandardPaths::Get().GetResourcesDir(), name, "svg").GetFullPath(), wxSize(24,24)) + +#endif + diff --git a/src/gui/evtids.h b/src/gui/evtids.h index 3c7b2a49..eba6ab5e 100644 --- a/src/gui/evtids.h +++ b/src/gui/evtids.h @@ -1,30 +1,30 @@ -#ifndef EVTIDS_H -#define EVTIDS_H - -enum - { - ID_TOOLBAR = wxID_HIGHEST + 42, - ID_DISCOVERED, - ID_BOOKMARKS, - ID_STATISTICS, - ID_FULLSCREEN, - ID_ONE_TO_ONE, - ID_STATS_SAVE, - ID_LISTBOX_SERVICES, - ID_LISTBOX_BOOKMARKS, - ID_WINDOWSHARE_PROC_END, - ID_SEAMLESS, - ID_SEAMLESS_NORTH, - ID_SEAMLESS_EAST, - ID_SEAMLESS_SOUTH, - ID_SEAMLESS_WEST, - ID_SEAMLESS_DISABLED, - ID_GRABKEYBOARD, - ID_INPUT_RECORD, - ID_INPUT_REPLAY, - ID_ISSUE_LIST, - }; - - -#endif - +#ifndef EVTIDS_H +#define EVTIDS_H + +enum + { + ID_TOOLBAR = wxID_HIGHEST + 42, + ID_DISCOVERED, + ID_BOOKMARKS, + ID_STATISTICS, + ID_FULLSCREEN, + ID_ONE_TO_ONE, + ID_STATS_SAVE, + ID_LISTBOX_SERVICES, + ID_LISTBOX_BOOKMARKS, + ID_WINDOWSHARE_PROC_END, + ID_SEAMLESS, + ID_SEAMLESS_NORTH, + ID_SEAMLESS_EAST, + ID_SEAMLESS_SOUTH, + ID_SEAMLESS_WEST, + ID_SEAMLESS_DISABLED, + ID_GRABKEYBOARD, + ID_INPUT_RECORD, + ID_INPUT_REPLAY, + ID_ISSUE_LIST, + }; + + +#endif + diff --git a/src/gui/multivnc.wxg b/src/gui/multivnc.wxg index 385f29f3..e78a8ffc 100644 --- a/src/gui/multivnc.wxg +++ b/src/gui/multivnc.wxg @@ -1,816 +1,816 @@ - - - - - - #include "bitmapFromMem.h"\n#ifndef ICON_XPM\n#define ICON_XPM\n#include "res/multivnc.xpm"\n#endif\n - 1089, 1030 - MultiVNC - - code:wxICON(icon) - 1 - 1 - 1 - - \n#include "evtids.h" - - - - - wxID_YES - Connect to a specific host. - machine_connect - - - - wxID_REDO - Listen for an incoming connection. - machine_listen - - - - wxID_STOP - Terminate connection. - machine_disconnect - - - - wxID_FILE - Show detailed log. - machine_showlog - - - wxID_PREFERENCES - Change preferences. - machine_preferences - - - - wxID_SAVE - machine_screenshot - - - - ID_STATS_SAVE - machine_save_stats - - - - ID_INPUT_RECORD - machine_input_record - - - - ID_INPUT_REPLAY - machine_input_replay - - - - --- - --- - - - wxID_EXIT - Exit MultiVNC. - machine_exit - - - - - - ID_TOOLBAR - 1 - view_toggletoolbar - - - - ID_DISCOVERED - 1 - view_togglediscovered - - - - ID_BOOKMARKS - 1 - view_togglebookmarks - - - - ID_STATISTICS - 1 - view_togglestatistics - - - - - ID_SEAMLESS_NORTH - 1 - view_seamless - - - - ID_SEAMLESS_EAST - 1 - view_seamless - - - - ID_SEAMLESS_WEST - 1 - view_seamless - - - - ID_SEAMLESS_SOUTH - 1 - view_seamless - - - - ID_SEAMLESS_DISABLED - 1 - view_seamless - - - - - --- - --- - - - - ID_FULLSCREEN - 1 - view_togglefullscreen - - - - ID_ONE_TO_ONE - 1 - view_toggle1to1 - - - - - - wxID_ADD - bookmarks_add - - - - - - wxID_UP - Beam a window to the server. - windowshare_start - - - - wxID_CANCEL - Stop Window Sharing. - windowshare_stop - - - - - - wxID_HELP - Show Help. - help_contents - - - - ID_ISSUE_LIST - help_issue_list - - - wxID_ABOUT - help_about - - - - - - - Status - - - - \n#include "evtids.h"\n\n - - - - wxID_YES - - 0 - Connect to a specific host. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "connect") - - machine_connect - - - wxID_REDO - - 0 - Listen for an incoming connection. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "listen") - - machine_listen - - - --- - - 0 - --- - - --- - --- - - - wxID_STOP - - 0 - Terminate connection. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "disconnect") - - machine_disconnect - - - ID_GRABKEYBOARD - - 1 - Intercept all keyboard input. Allows you to use special keys that would otherwise be interpreted by the local computer. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "toggle-keyboard-grab") - - machine_grabkeyboard - - - wxID_SAVE - - 0 - Take a screenshot of the current connection. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "screenshot") - - machine_screenshot - - - --- - - 0 - --- - - --- - --- - - - ID_INPUT_RECORD - - 0 - Record mouse and keyboard input for later replay as a macro. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "record") - - machine_input_record - - - ID_INPUT_REPLAY - - 0 - Replay a recorded mouse and keyboard input macro. If <Shift> is held down while clicking this button, the macro is replayed in a loop. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "replay") - - machine_input_replay - - - --- - - 0 - --- - - --- - --- - - - ID_ONE_TO_ONE - - 1 - Toggle 1:1 view, disabling all scaling. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "one-to-one") - - view_toggle1to1 - - - ID_FULLSCREEN - - 0 - Toggle fullscreen view. - - code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "fullscreen") - - view_togglefullscreen - - - - - wxHORIZONTAL - - - 0 - wxEXPAND - - - - wxHORIZONTAL - - - 3 - wxALL|wxEXPAND - - - 31 - 20 - wxSPLIT_VERTICAL - splitwin_main_pane_1 - splitwin_main_pane_2 - - 200, -1 - - - wxHORIZONTAL - - - 3 - wxALL|wxEXPAND - - - 20 - wxSPLIT_HORIZONTAL - splitwin_left_pane_1 - splitwin_left_pane_2 - - - - wxHORIZONTAL - - - - 3 - wxALL|wxEXPAND - - - listbox_services_select - listbox_services_dclick - - ID_LISTBOX_SERVICES - - -1 - - - - - - - - - - wxHORIZONTAL - - - - 3 - wxALL|wxEXPAND - - - listbox_bookmarks_select - listbox_bookmarks_dclick - - ID_LISTBOX_BOOKMARKS - - -1 - - - - - - - - - - - - - - wxHORIZONTAL - - - 3 - wxALL|wxEXPAND - - - notebook_connections_pagechanged - - - - - - - - - - - - - - - - - - wxVERTICAL - - - 0 - wxEXPAND - - 800, 600 - - - wxVERTICAL - - - 3 - wxALL|wxEXPAND - - - - - - - 0 - wxALIGN_RIGHT - - wxHORIZONTAL - - - 3 - wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND - - - log_clear - - - CLEAR - - - - - 3 - wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND - - - log_saveas - - - SAVEAS - - - - - 3 - wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND - - - log_close - - - 1 - CLOSE - - - - - - - - - - - - - wxVERTICAL - - - 3 - wxALL|wxEXPAND - - - Connections - Encodings - Logging - - - - - wxVERTICAL - - - 3 - wxALL|wxEXPAND - - wxVERTICAL - - - - 3 - wxALL|wxEXPAND - - - - - - - 3 - wxALL - - - 1 - - - - - 3 - wxALL|wxEXPAND - - Continously ask the server for updates instead of just asking after each received server message. Use this on high latency links. - - 1, 100 - 1 - - - - - - - 3 - wxALL|wxEXPAND - - wxHORIZONTAL - - - - 3 - wxALL|wxEXPAND - - - - - - - - - 3 - wxALL|wxEXPAND - - wxVERTICAL - - - - 3 - wxALL|wxEXPAND - - - - - - - 3 - wxALL - - - 1 - - - - - 3 - wxALL|wxEXPAND - - Set the multicast socket receive buffer size. Increasing the value may help against packet loss. Note that the maximum value is operating system dependent. - - 65, 9750 - 65 - - - - - 3 - wxALL - - - 1 - - - - - 3 - wxALL|wxEXPAND - - Set the multicast receive buffer size. Increasing the value may help against packet loss. The size of this buffer is independent of the operating system. - - 65, 9750 - 65 - - - - - - - - - - wxVERTICAL - - - 3 - wxALL|wxEXPAND - - wxHORIZONTAL - - - - 3 - - 4 - 3 - 0 - 0 - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - 3 - wxALL - - - - - - - - - - - - - 3 - wxALL|wxEXPAND - - wxVERTICAL - - - - 3 - wxALL - - - 1 - - - - - 3 - wxALL|wxEXPAND - - Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. Level 1 uses minimum of CPU time and achieves weak compression ratios, while level 9 offers best compression but is slow in terms of CPU time consumption on the server side. Use high levels with very slow network connections, and low levels when working over high-speed LANs. - - 0, 9 - 0 - - - - - 3 - wxALL - - - 1 - - - - - 3 - wxALL|wxEXPAND - - Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' encodings. Quality level 0 denotes bad image quality but very impressive compression ratios, while level 9 offers very good image quality at lower compression ratios. Note that the "tight" encoder uses JPEG to encode only those screen areas that look suitable for lossy compression, so quality level 0 does not always mean unacceptable image quality. - - 0, 9 - 0 - - - - - - - - - - wxVERTICAL - - - 6 - wxALL - - - - - - - 6 - wxALL - - - - - - - - - - - + + + + + + #include "bitmapFromMem.h"\n#ifndef ICON_XPM\n#define ICON_XPM\n#include "res/multivnc.xpm"\n#endif\n + 1089, 1030 + MultiVNC + + code:wxICON(icon) + 1 + 1 + 1 + + \n#include "evtids.h" + + + + + wxID_YES + Connect to a specific host. + machine_connect + + + + wxID_REDO + Listen for an incoming connection. + machine_listen + + + + wxID_STOP + Terminate connection. + machine_disconnect + + + + wxID_FILE + Show detailed log. + machine_showlog + + + wxID_PREFERENCES + Change preferences. + machine_preferences + + + + wxID_SAVE + machine_screenshot + + + + ID_STATS_SAVE + machine_save_stats + + + + ID_INPUT_RECORD + machine_input_record + + + + ID_INPUT_REPLAY + machine_input_replay + + + + --- + --- + + + wxID_EXIT + Exit MultiVNC. + machine_exit + + + + + + ID_TOOLBAR + 1 + view_toggletoolbar + + + + ID_DISCOVERED + 1 + view_togglediscovered + + + + ID_BOOKMARKS + 1 + view_togglebookmarks + + + + ID_STATISTICS + 1 + view_togglestatistics + + + + + ID_SEAMLESS_NORTH + 1 + view_seamless + + + + ID_SEAMLESS_EAST + 1 + view_seamless + + + + ID_SEAMLESS_WEST + 1 + view_seamless + + + + ID_SEAMLESS_SOUTH + 1 + view_seamless + + + + ID_SEAMLESS_DISABLED + 1 + view_seamless + + + + + --- + --- + + + + ID_FULLSCREEN + 1 + view_togglefullscreen + + + + ID_ONE_TO_ONE + 1 + view_toggle1to1 + + + + + + wxID_ADD + bookmarks_add + + + + + + wxID_UP + Beam a window to the server. + windowshare_start + + + + wxID_CANCEL + Stop Window Sharing. + windowshare_stop + + + + + + wxID_HELP + Show Help. + help_contents + + + + ID_ISSUE_LIST + help_issue_list + + + wxID_ABOUT + help_about + + + + + + + Status + + + + \n#include "evtids.h"\n\n + + + + wxID_YES + + 0 + Connect to a specific host. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "connect") + + machine_connect + + + wxID_REDO + + 0 + Listen for an incoming connection. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "listen") + + machine_listen + + + --- + + 0 + --- + + --- + --- + + + wxID_STOP + + 0 + Terminate connection. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "disconnect") + + machine_disconnect + + + ID_GRABKEYBOARD + + 1 + Intercept all keyboard input. Allows you to use special keys that would otherwise be interpreted by the local computer. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "toggle-keyboard-grab") + + machine_grabkeyboard + + + wxID_SAVE + + 0 + Take a screenshot of the current connection. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "screenshot") + + machine_screenshot + + + --- + + 0 + --- + + --- + --- + + + ID_INPUT_RECORD + + 0 + Record mouse and keyboard input for later replay as a macro. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "record") + + machine_input_record + + + ID_INPUT_REPLAY + + 0 + Replay a recorded mouse and keyboard input macro. If <Shift> is held down while clicking this button, the macro is replayed in a loop. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "replay") + + machine_input_replay + + + --- + + 0 + --- + + --- + --- + + + ID_ONE_TO_ONE + + 1 + Toggle 1:1 view, disabling all scaling. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "one-to-one") + + view_toggle1to1 + + + ID_FULLSCREEN + + 0 + Toggle fullscreen view. + + code:bitmapBundleFromSVGResource(wxString(wxSystemSettings::GetAppearance().IsDark() ? "dark" : "light") + "/" + "fullscreen") + + view_togglefullscreen + + + + + wxHORIZONTAL + + + 0 + wxEXPAND + + + + wxHORIZONTAL + + + 3 + wxALL|wxEXPAND + + + 31 + 20 + wxSPLIT_VERTICAL + splitwin_main_pane_1 + splitwin_main_pane_2 + + 200, -1 + + + wxHORIZONTAL + + + 3 + wxALL|wxEXPAND + + + 20 + wxSPLIT_HORIZONTAL + splitwin_left_pane_1 + splitwin_left_pane_2 + + + + wxHORIZONTAL + + + + 3 + wxALL|wxEXPAND + + + listbox_services_select + listbox_services_dclick + + ID_LISTBOX_SERVICES + + -1 + + + + + + + + + + wxHORIZONTAL + + + + 3 + wxALL|wxEXPAND + + + listbox_bookmarks_select + listbox_bookmarks_dclick + + ID_LISTBOX_BOOKMARKS + + -1 + + + + + + + + + + + + + + wxHORIZONTAL + + + 3 + wxALL|wxEXPAND + + + notebook_connections_pagechanged + + + + + + + + + + + + + + + + + + wxVERTICAL + + + 0 + wxEXPAND + + 800, 600 + + + wxVERTICAL + + + 3 + wxALL|wxEXPAND + + + + + + + 0 + wxALIGN_RIGHT + + wxHORIZONTAL + + + 3 + wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND + + + log_clear + + + CLEAR + + + + + 3 + wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND + + + log_saveas + + + SAVEAS + + + + + 3 + wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND + + + log_close + + + 1 + CLOSE + + + + + + + + + + + + + wxVERTICAL + + + 3 + wxALL|wxEXPAND + + + Connections + Encodings + Logging + + + + + wxVERTICAL + + + 3 + wxALL|wxEXPAND + + wxVERTICAL + + + + 3 + wxALL|wxEXPAND + + + + + + + 3 + wxALL + + + 1 + + + + + 3 + wxALL|wxEXPAND + + Continously ask the server for updates instead of just asking after each received server message. Use this on high latency links. + + 1, 100 + 1 + + + + + + + 3 + wxALL|wxEXPAND + + wxHORIZONTAL + + + + 3 + wxALL|wxEXPAND + + + + + + + + + 3 + wxALL|wxEXPAND + + wxVERTICAL + + + + 3 + wxALL|wxEXPAND + + + + + + + 3 + wxALL + + + 1 + + + + + 3 + wxALL|wxEXPAND + + Set the multicast socket receive buffer size. Increasing the value may help against packet loss. Note that the maximum value is operating system dependent. + + 65, 9750 + 65 + + + + + 3 + wxALL + + + 1 + + + + + 3 + wxALL|wxEXPAND + + Set the multicast receive buffer size. Increasing the value may help against packet loss. The size of this buffer is independent of the operating system. + + 65, 9750 + 65 + + + + + + + + + + wxVERTICAL + + + 3 + wxALL|wxEXPAND + + wxHORIZONTAL + + + + 3 + + 4 + 3 + 0 + 0 + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + 3 + wxALL + + + + + + + + + + + + + 3 + wxALL|wxEXPAND + + wxVERTICAL + + + + 3 + wxALL + + + 1 + + + + + 3 + wxALL|wxEXPAND + + Use specified compression level (0..9) for 'Tight' and 'Zlib' encodings. Level 1 uses minimum of CPU time and achieves weak compression ratios, while level 9 offers best compression but is slow in terms of CPU time consumption on the server side. Use high levels with very slow network connections, and low levels when working over high-speed LANs. + + 0, 9 + 0 + + + + + 3 + wxALL + + + 1 + + + + + 3 + wxALL|wxEXPAND + + Use the specified quality level (0..9) for the 'Tight' and 'ZYWRLE' encodings. Quality level 0 denotes bad image quality but very impressive compression ratios, while level 9 offers very good image quality at lower compression ratios. Note that the "tight" encoder uses JPEG to encode only those screen areas that look suitable for lossy compression, so quality level 0 does not always mean unacceptable image quality. + + 0, 9 + 0 + + + + + + + + + + wxVERTICAL + + + 6 + wxALL + + + + + + + 6 + wxALL + + + + + + + + + + + diff --git a/src/gui/res/about.svg b/src/gui/res/about.svg index 5abd9bce..d1dcfd7b 100644 --- a/src/gui/res/about.svg +++ b/src/gui/res/about.svg @@ -1,38 +1,38 @@ - - - - - - image/svg+xml - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/gui/res/dark/connect.svg b/src/gui/res/dark/connect.svg index 1b958cbf..7830fbd7 100644 --- a/src/gui/res/dark/connect.svg +++ b/src/gui/res/dark/connect.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/disconnect.svg b/src/gui/res/dark/disconnect.svg index 0dced668..6b733228 100644 --- a/src/gui/res/dark/disconnect.svg +++ b/src/gui/res/dark/disconnect.svg @@ -1,9 +1,9 @@ - - - - - - - + + + + + + + diff --git a/src/gui/res/dark/fullscreen.svg b/src/gui/res/dark/fullscreen.svg index d215f6bb..c3b235ad 100644 --- a/src/gui/res/dark/fullscreen.svg +++ b/src/gui/res/dark/fullscreen.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/listen.svg b/src/gui/res/dark/listen.svg index 48f72109..3c2add92 100644 --- a/src/gui/res/dark/listen.svg +++ b/src/gui/res/dark/listen.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/one-to-one.svg b/src/gui/res/dark/one-to-one.svg index a7659fe5..92395823 100644 --- a/src/gui/res/dark/one-to-one.svg +++ b/src/gui/res/dark/one-to-one.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/record.svg b/src/gui/res/dark/record.svg index a082412d..da66b8e5 100644 --- a/src/gui/res/dark/record.svg +++ b/src/gui/res/dark/record.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/replay.svg b/src/gui/res/dark/replay.svg index 3676b9f9..1c9e2d89 100644 --- a/src/gui/res/dark/replay.svg +++ b/src/gui/res/dark/replay.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/restore.svg b/src/gui/res/dark/restore.svg index 541a17ad..c0f2ea4a 100644 --- a/src/gui/res/dark/restore.svg +++ b/src/gui/res/dark/restore.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/screenshot.svg b/src/gui/res/dark/screenshot.svg index ff54b823..6736682e 100644 --- a/src/gui/res/dark/screenshot.svg +++ b/src/gui/res/dark/screenshot.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/stop.svg b/src/gui/res/dark/stop.svg index 0c93fc1e..03e4759c 100644 --- a/src/gui/res/dark/stop.svg +++ b/src/gui/res/dark/stop.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/dark/toggle-keyboard-grab.svg b/src/gui/res/dark/toggle-keyboard-grab.svg index 149640fd..f9dc6ceb 100644 --- a/src/gui/res/dark/toggle-keyboard-grab.svg +++ b/src/gui/res/dark/toggle-keyboard-grab.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/connect.svg b/src/gui/res/light/connect.svg index e8227a54..a04ed854 100644 --- a/src/gui/res/light/connect.svg +++ b/src/gui/res/light/connect.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/disconnect.svg b/src/gui/res/light/disconnect.svg index a3b0ebd0..50ad2fd0 100644 --- a/src/gui/res/light/disconnect.svg +++ b/src/gui/res/light/disconnect.svg @@ -1,9 +1,9 @@ - - - - - - - + + + + + + + diff --git a/src/gui/res/light/fullscreen.svg b/src/gui/res/light/fullscreen.svg index 5a9eeed8..d8110519 100644 --- a/src/gui/res/light/fullscreen.svg +++ b/src/gui/res/light/fullscreen.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/listen.svg b/src/gui/res/light/listen.svg index 22247e1d..349016fe 100644 --- a/src/gui/res/light/listen.svg +++ b/src/gui/res/light/listen.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/one-to-one.svg b/src/gui/res/light/one-to-one.svg index 8aba2e0b..6acd13ed 100644 --- a/src/gui/res/light/one-to-one.svg +++ b/src/gui/res/light/one-to-one.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/record.svg b/src/gui/res/light/record.svg index 794d2c7b..4a0eab5f 100644 --- a/src/gui/res/light/record.svg +++ b/src/gui/res/light/record.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/replay.svg b/src/gui/res/light/replay.svg index 8cb45309..d5c79d59 100644 --- a/src/gui/res/light/replay.svg +++ b/src/gui/res/light/replay.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/restore.svg b/src/gui/res/light/restore.svg index b0a3d8be..95a32348 100644 --- a/src/gui/res/light/restore.svg +++ b/src/gui/res/light/restore.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/screenshot.svg b/src/gui/res/light/screenshot.svg index 262db3e1..720eb5b9 100644 --- a/src/gui/res/light/screenshot.svg +++ b/src/gui/res/light/screenshot.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/stop.svg b/src/gui/res/light/stop.svg index a4bbf0ed..0ef4078d 100644 --- a/src/gui/res/light/stop.svg +++ b/src/gui/res/light/stop.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/light/toggle-keyboard-grab.svg b/src/gui/res/light/toggle-keyboard-grab.svg index d7d30a44..5812ae7a 100644 --- a/src/gui/res/light/toggle-keyboard-grab.svg +++ b/src/gui/res/light/toggle-keyboard-grab.svg @@ -1,8 +1,8 @@ - - - - - - + + + + + + diff --git a/src/gui/res/multicast.svg b/src/gui/res/multicast.svg index 5abd9bce..d1dcfd7b 100644 --- a/src/gui/res/multicast.svg +++ b/src/gui/res/multicast.svg @@ -1,38 +1,38 @@ - - - - - - image/svg+xml - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/gui/res/net.christianbeier.MultiVNC.svg b/src/gui/res/net.christianbeier.MultiVNC.svg index a9573ca3..ef07fe9f 100644 --- a/src/gui/res/net.christianbeier.MultiVNC.svg +++ b/src/gui/res/net.christianbeier.MultiVNC.svg @@ -1,72 +1,72 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/gui/res/unicast.svg b/src/gui/res/unicast.svg index d84d27e1..be683d59 100644 --- a/src/gui/res/unicast.svg +++ b/src/gui/res/unicast.svg @@ -1,50 +1,50 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/net.christianbeier.MultiVNC.desktop b/src/net.christianbeier.MultiVNC.desktop index b5be1e26..29909cdf 100644 --- a/src/net.christianbeier.MultiVNC.desktop +++ b/src/net.christianbeier.MultiVNC.desktop @@ -1,11 +1,11 @@ -[Desktop Entry] -Encoding=UTF-8 -Exec=multivnc -Icon=net.christianbeier.MultiVNC -Name=MultiVNC -Type=Application -Comment=A multicast enabled VNC viewer -Terminal=false -StartupNotify=false -Categories=Application;Network; -Keywords=remote desktop;vnc;client;viewer; +[Desktop Entry] +Encoding=UTF-8 +Exec=multivnc +Icon=net.christianbeier.MultiVNC +Name=MultiVNC +Type=Application +Comment=A multicast enabled VNC viewer +Terminal=false +StartupNotify=false +Categories=Application;Network; +Keywords=remote desktop;vnc;client;viewer;