diff options
153 files changed, 5429 insertions, 3592 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9d5f33af..760cab7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,8 @@ jobs: build-linux: runs-on: ubuntu-latest timeout-minutes: 10 + env: + VERBOSE: 1 steps: - uses: actions/checkout@v4 - name: Install dependencies @@ -18,7 +20,15 @@ jobs: sudo apt-get install -y libavcodec-dev libavutil-dev libswscale-dev sudo apt-get install -y libgtest-dev - name: Configure - run: cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build + run: | + cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_NLS=ON \ + -DENABLE_H264=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DBUILD_VIEWER=ON \ + -S . -B build - name: Build working-directory: build run: make @@ -42,27 +52,36 @@ jobs: build-windows: runs-on: windows-latest timeout-minutes: 20 + env: + VERBOSE: 1 defaults: run: shell: msys2 {0} steps: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 + with: + update: true - name: Install dependencies run: | pacman --sync --noconfirm --needed \ make mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake pacman --sync --noconfirm --needed \ - mingw-w64-x86_64-libjpeg-turbo \ + mingw-w64-x86_64-fltk1.3 mingw-w64-x86_64-libjpeg-turbo \ mingw-w64-x86_64-gnutls mingw-w64-x86_64-pixman \ mingw-w64-x86_64-nettle mingw-w64-x86_64-gmp \ mingw-w64-x86_64-gtest - # MSYS2 only packages FLTK 1.4 now: - # https://github.com/msys2/MINGW-packages/issues/22769 - pacman --upgrade --noconfirm --needed \ - https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-fltk-1.3.9-2-any.pkg.tar.zst - name: Configure - run: cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug -S . -B build + run: | + cmake \ + -G "MSYS Makefiles" \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_NLS=ON \ + -DENABLE_H264=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DBUILD_VIEWER=ON \ + -S . -B build - name: Build working-directory: build run: make @@ -88,6 +107,8 @@ jobs: build-macos: runs-on: macos-latest timeout-minutes: 20 + env: + VERBOSE: 1 steps: - uses: actions/checkout@v4 - name: Install dependencies @@ -97,7 +118,15 @@ jobs: brew install googletest brew link fltk@1.3 - name: Configure - run: cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build + run: | + cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_NLS=ON \ + -DENABLE_H264=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DBUILD_VIEWER=ON \ + -S . -B build - name: Build working-directory: build run: make @@ -107,7 +136,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: macOS - path: build/TigerVNC-*.dmg + path: build/release/TigerVNC-*.dmg - name: Test working-directory: build run: ctest --test-dir tests/unit/ --output-junit test-results.xml @@ -121,6 +150,8 @@ jobs: build-java: runs-on: ubuntu-latest timeout-minutes: 5 + env: + VERBOSE: 1 strategy: matrix: java: [ '8', '11', '17', '21' ] diff --git a/BUILDING.txt b/BUILDING.txt index 7e73d72e..8315a1d3 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -260,16 +260,6 @@ with MinGW for Windows and can be built from source on OS X and other Unix variants. However, GnuTLS versions > 2.12.x && < 3.3.x should be avoided because of potential incompatibilities during initial handshaking. -You can override the GNUTLS_LIBRARY and GNUTLS_INCLUDE_DIR CMake variables -to specify the locations of libgnutls and any dependencies. For instance, -adding - - -DGNUTLS_INCLUDE_DIR=/usr/local/include \ - -DGNUTLS_LIBRARY=/usr/local/lib/libgnutls.a - -to the CMake command line would link TigerVNC against a static version of -libgnutls located under /usr/local. - ====================================== Building native language support (NLS) @@ -279,23 +269,6 @@ NLS requires gettext, which is supplied with most Linux distributions and with MinGW for Windows and which can easily be built from source on OS X and other Unix variants. -You can override the ICONV_LIBRARIES and LIBINTL_LIBRARY CMake variables to -specify the locations of libiconv and libintl, respectively. For instance, -adding - - -DLIBINTL_LIBRARY=/opt/gettext/lib/libintl.a - -to the CMake command line would link TigerVNC against a static version of -libintl located under /opt/gettext. Adding - - -DICONV_INCLUDE_DIR=/mingw/include \ - -DICONV_LIBRARIES=/mingw/lib/libiconv.a \ - -DGETTEXT_INCLUDE_DIR=/mingw/include \ - -DLIBINTL_LIBRARY=/mingw/lib/libintl.a - -to the CMake command line would link TigerVNC against the static versions of -libiconv and libintl included in the MinGW Developer Toolkit. - =================== Installing TigerVNC @@ -327,9 +300,9 @@ make tarball Create a binary tarball containing the TigerVNC viewer -make servertarball - Create a binary tarball containing both the TigerVNC server and viewer +macOS +----- make dmg diff --git a/CMakeLists.txt b/CMakeLists.txt index 75773460..b4a28881 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,6 @@ include(CheckCSourceRuns) include(CMakeMacroLibtoolFile) -include(cmake/TargetLinkDirectories.cmake) - project(tigervnc) set(VERSION 1.15.80) @@ -66,25 +64,27 @@ set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -UNDEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -UNDEBUG") # But extra debug checks are still gated by this custom define -IF(CMAKE_BUILD_TYPE MATCHES Debug) - add_definitions(-D_DEBUG) -ENDIF() +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") + +# Enable debug friendly optimizations for debug builds +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") # Make sure we get a sane C and C++ version set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") # Tell the compiler to be stringent +add_compile_definitions(_FORTIFY_SOURCE=2) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wformat=2 -Wvla") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wzero-as-null-pointer-constant") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow") # Make sure we catch these issues whilst developing -IF(CMAKE_BUILD_TYPE MATCHES Debug) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") -ENDIF() +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror") # clang doesn't support format_arg, which breaks this warning if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format-nonliteral -Wno-format-security") @@ -112,6 +112,13 @@ if(NOT DEFINED BUILD_WINVNC) set(BUILD_WINVNC 1) endif() +# libstdc++ doesn't implicitly include this, although it is very much +# required when using any of the C++ threading features (at least on +# systems where pthread is a separate library, e.g. glibc < 2.34) +if(UNIX) + link_libraries(pthread) +endif() + # Minimum version is Windows 7 if(WIN32) add_definitions(-D_WIN32_WINNT=0x0601) @@ -156,46 +163,20 @@ find_package(Pixman REQUIRED) trioption(ENABLE_NLS "Enable translation of program messages") if(ENABLE_NLS) # Tools - find_package(Gettext) - - # Gettext needs iconv if(ENABLE_NLS STREQUAL "AUTO") - find_package(Iconv) + find_package(Gettext) else() - find_package(Iconv REQUIRED) + find_package(Gettext REQUIRED) endif() - if(ICONV_FOUND) - # Headers and libraries (copied from licq) - set(GETTEXT_FOUND FALSE) - - find_path(GETTEXT_INCLUDE_DIR libintl.h) - if(GETTEXT_INCLUDE_DIR) - set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) - set(CMAKE_REQUIRED_FLAGS -fno-builtin-dgettext) - check_function_exists(dgettext LIBC_HAS_DGETTEXT) - if(LIBC_HAS_DGETTEXT) - set(GETTEXT_FOUND TRUE) - else() - find_library(LIBINTL_LIBRARY NAMES intl libintl) - if(LIBINTL_LIBRARY) - check_library_exists(${LIBINTL_LIBRARY} "dgettext" "" LIBINTL_HAS_DGETTEXT) - if(LIBINTL_HAS_DGETTEXT) - set(GETTEXT_LIBRARIES ${LIBINTL_LIBRARY} ${ICONV_LIBRARIES}) - set(GETTEXT_FOUND TRUE) - endif() - endif() - endif() - set(CMAKE_REQUIRED_LIBRARIES) - set(CMAKE_REQUIRED_FLAGS) - endif() - - if(NOT GETTEXT_FOUND AND NOT ENABLE_NLS STREQUAL "AUTO") - message(FATAL_ERROR "Could not find GETTEXT") - endif() + # Runtime library + if(ENABLE_NLS STREQUAL "AUTO") + find_package(Intl) + else() + find_package(Intl REQUIRED) endif() - if(NOT GETTEXT_FOUND OR NOT ICONV_FOUND) + if(NOT GETTEXT_FOUND OR NOT INTL_FOUND) message(WARNING "Gettext NOT found. Native Language Support disabled.") set(ENABLE_NLS 0) endif() @@ -216,14 +197,17 @@ if(ENABLE_H264) endif() else() if(ENABLE_H264 STREQUAL "AUTO") - find_package(Ffmpeg) + find_package(AVCodec) + find_package(AVUtil) + find_package(SWScale) else() - find_package(Ffmpeg REQUIRED) + find_package(AVCodec REQUIRED) + find_package(AVUtil REQUIRED) + find_package(SWScale REQUIRED) endif() if (AVCODEC_FOUND AND AVUTIL_FOUND AND SWSCALE_FOUND) set(H264_INCLUDE_DIRS ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} ${SWSCALE_INCLUDE_DIRS}) set(H264_LIBRARIES ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES} ${SWSCALE_LIBRARIES}) - set(H264_LIBRARY_DIRS ${AVCODEC_LIBRARY_DIRS} ${AVUTIL_LIBRARY_DIRS} ${SWSCALE_LIBRARY_DIRS}) add_definitions("-D__STDC_CONSTANT_MACROS") add_definitions("-DHAVE_H264") set(H264_LIBS "LIBAV") @@ -276,13 +260,22 @@ if(BUILD_JAVA) add_subdirectory(java) endif() -option(BUILD_VIEWER "Build TigerVNC viewer" ON) +trioption(BUILD_VIEWER "Build TigerVNC viewer") if(BUILD_VIEWER) # Check for FLTK set(FLTK_SKIP_FLUID TRUE) set(FLTK_SKIP_OPENGL TRUE) set(FLTK_SKIP_FORMS TRUE) - find_package(FLTK REQUIRED) + if(BUILD_VIEWER STREQUAL "AUTO") + find_package(FLTK) + else() + find_package(FLTK REQUIRED) + endif() + + if(NOT FLTK_FOUND) + message(WARNING "FLTK NOT found. TigerVNC viewer disabled.") + set(BUILD_VIEWER 0) + endif() if(UNIX AND NOT APPLE) # No proper handling for extra X11 libs that FLTK might need... @@ -306,18 +299,20 @@ if(BUILD_VIEWER) endif() endif() - set(CMAKE_REQUIRED_FLAGS "-Wno-error") - set(CMAKE_REQUIRED_INCLUDES ${FLTK_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${FLTK_LIBRARIES}) + if(FLTK_FOUND) + set(CMAKE_REQUIRED_FLAGS "-Wno-error") + set(CMAKE_REQUIRED_INCLUDES ${FLTK_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${FLTK_LIBRARIES}) - check_cxx_source_compiles("#include <FL/Fl.H>\n#if FL_MAJOR_VERSION != 1 || FL_MINOR_VERSION != 3\n#error Wrong FLTK version\n#endif\nint main(int, char**) { return 0; }" OK_FLTK_VERSION) - if(NOT OK_FLTK_VERSION) - message(FATAL_ERROR "Incompatible version of FLTK") - endif() + check_cxx_source_compiles("#include <FL/Fl.H>\n#if FL_MAJOR_VERSION != 1 || FL_MINOR_VERSION != 3\n#error Wrong FLTK version\n#endif\nint main(int, char**) { return 0; }" OK_FLTK_VERSION) + if(NOT OK_FLTK_VERSION) + message(FATAL_ERROR "Incompatible version of FLTK") + endif() - set(CMAKE_REQUIRED_FLAGS) - set(CMAKE_REQUIRED_INCLUDES) - set(CMAKE_REQUIRED_LIBRARIES) + set(CMAKE_REQUIRED_FLAGS) + set(CMAKE_REQUIRED_INCLUDES) + set(CMAKE_REQUIRED_LIBRARIES) + endif() endif() # Check for GNUTLS library @@ -347,36 +342,34 @@ endif() # Check for PAM library if(UNIX AND NOT APPLE) - check_include_files(security/pam_appl.h HAVE_PAM_H) - set(CMAKE_REQUIRED_LIBRARIES -lpam) - check_function_exists(pam_start HAVE_PAM_START) - set(CMAKE_REQUIRED_LIBRARIES) - if(HAVE_PAM_H AND HAVE_PAM_START) - set(PAM_LIBS pam) - else() - message(FATAL_ERROR "Could not find PAM development files") - endif() + find_package(PAM REQUIRED) endif() # Check for SELinux library if(UNIX AND NOT APPLE) - check_include_files(selinux/selinux.h HAVE_SELINUX_H) - if(HAVE_SELINUX_H) - set(CMAKE_REQUIRED_LIBRARIES -lselinux) - set(CMAKE_REQUIRED_LIBRARIES) - set(SELINUX_LIBS selinux) - add_definitions("-DHAVE_SELINUX") - else() - message(WARNING "Could not find SELinux development files") + trioption(ENABLE_SELINUX "Enable SELinux support") + if(ENABLE_SELINUX) + if(ENABLE_SELINUX STREQUAL "AUTO") + find_package(SELinux) + else() + find_package(SELinux REQUIRED) + endif() + if(SELINUX_FOUND) + add_definitions("-DHAVE_SELINUX") + endif() endif() endif() # check for systemd support (socket activation) if(UNIX AND NOT APPLE) - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_check_modules(LIBSYSTEMD libsystemd) - if (LIBSYSTEMD_FOUND) + trioption(ENABLE_SYSTEMD "Enable systemd support") + if(ENABLE_SYSTEMD) + if(ENABLE_SYSTEMD STREQUAL "AUTO") + find_package(Systemd) + else() + find_package(Systemd REQUIRED) + endif() + if (SYSTEMD_FOUND) add_definitions(-DHAVE_LIBSYSTEMD) endif() endif() @@ -384,14 +377,15 @@ endif() # check for password pwquality check support if(UNIX AND NOT APPLE) - option(ENABLE_PWQUALITY "Enable password pwquality check" ON) + trioption(ENABLE_PWQUALITY "Enable password pwquality check") if(ENABLE_PWQUALITY) - find_package(PkgConfig) - if(PKG_CONFIG_FOUND) - pkg_check_modules(PWQUALITY pwquality) - if(PWQUALITY_FOUND) - add_definitions(-DHAVE_PWQUALITY) - endif() + if(ENABLE_PWQUALITY STREQUAL "AUTO") + find_package(PWQuality) + else() + find_package(PWQuality REQUIRED) + endif() + if(PWQUALITY_FOUND) + add_definitions(-DHAVE_PWQUALITY) endif() endif() endif() diff --git a/cmake/Modules/FindAVCodec.cmake b/cmake/Modules/FindAVCodec.cmake new file mode 100644 index 00000000..9667160f --- /dev/null +++ b/cmake/Modules/FindAVCodec.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindAVCodec +----------- + +Find the FFmpeg avcodec library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``AVCODEC_INCLUDE_DIRS`` + where to find libavcodec/avcodec.h, etc. +``AVCODEC_LIBRARIES`` + the libraries to link against to use avcodec. +``AVCODEC_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_AVCodec QUIET libavcodec) +endif() + +find_path(AVCodec_INCLUDE_DIR NAMES libavcodec/avcodec.h + HINTS + ${PC_AVCodec_INCLUDE_DIRS} +) +mark_as_advanced(AVCodec_INCLUDE_DIR) + +find_library(AVCodec_LIBRARY NAMES avcodec + HINTS + ${PC_AVCodec_LIBRARY_DIRS} +) +mark_as_advanced(AVCodec_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(AVCodec + REQUIRED_VARS + AVCodec_LIBRARY AVCodec_INCLUDE_DIR +) + +if(AVCodec_FOUND) + set(AVCODEC_INCLUDE_DIRS ${AVCodec_INCLUDE_DIR}) + set(AVCODEC_LIBRARIES ${AVCodec_LIBRARY}) +endif() diff --git a/cmake/Modules/FindAVUtil.cmake b/cmake/Modules/FindAVUtil.cmake new file mode 100644 index 00000000..4b7e17cc --- /dev/null +++ b/cmake/Modules/FindAVUtil.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindAVUtil +---------- + +Find the FFmpeg avutil library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``AVUTIL_INCLUDE_DIRS`` + where to find libavutil/avutil.h, etc. +``AVUTIL_LIBRARIES`` + the libraries to link against to use avutil. +``AVUTIL_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_AVUtil QUIET libavutil) +endif() + +find_path(AVUtil_INCLUDE_DIR NAMES libavutil/avutil.h + HINTS + ${PC_AVUtil_INCLUDE_DIRS} +) +mark_as_advanced(AVUtil_INCLUDE_DIR) + +find_library(AVUtil_LIBRARY NAMES avutil + HINTS + ${PC_AVUtil_LIBRARY_DIRS} +) +mark_as_advanced(AVUtil_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(AVUtil + REQUIRED_VARS + AVUtil_LIBRARY AVUtil_INCLUDE_DIR +) + +if(AVUtil_FOUND) + set(AVUTIL_INCLUDE_DIRS ${AVUtil_INCLUDE_DIR}) + set(AVUTIL_LIBRARIES ${AVUtil_LIBRARY}) +endif() diff --git a/cmake/Modules/FindFfmpeg.cmake b/cmake/Modules/FindFfmpeg.cmake deleted file mode 100644 index 1a082449..00000000 --- a/cmake/Modules/FindFfmpeg.cmake +++ /dev/null @@ -1,22 +0,0 @@ -find_package(PkgConfig) - -if (PKG_CONFIG_FOUND) - pkg_check_modules(AVCODEC libavcodec) - pkg_check_modules(AVUTIL libavutil) - pkg_check_modules(SWSCALE libswscale) -else() - find_path(AVCODEC_INCLUDE_DIRS NAMES avcodec.h PATH_SUFFIXES libavcodec) - find_library(AVCODEC_LIBRARIES NAMES avcodec) - find_package_handle_standard_args(AVCODEC DEFAULT_MSG AVCODEC_LIBRARIES AVCODEC_INCLUDE_DIRS) - find_path(AVUTIL_INCLUDE_DIRS NAMES avutil.h PATH_SUFFIXES libavutil) - find_library(AVUTIL_LIBRARIES NAMES avutil) - find_package_handle_standard_args(AVUTIL DEFAULT_MSG AVUTIL_LIBRARIES AVUTIL_INCLUDE_DIRS) - find_path(SWSCALE_INCLUDE_DIRS NAMES swscale.h PATH_SUFFIXES libswscale) - find_library(SWSCALE_LIBRARIES NAMES swscale) - find_package_handle_standard_args(SWSCALE DEFAULT_MSG SWSCALE_LIBRARIES SWSCALE_INCLUDE_DIRS) -endif() - -if(Ffmpeg_FIND_REQUIRED AND - (NOT AVCODEC_FOUND OR NOT AVUTIL_FOUND OR NOT SWSCALE_FOUND)) - message(FATAL_ERROR "Could not find FFMPEG") -endif() diff --git a/cmake/Modules/FindGMP.cmake b/cmake/Modules/FindGMP.cmake new file mode 100644 index 00000000..956d3f19 --- /dev/null +++ b/cmake/Modules/FindGMP.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindGMP +------- + +Find the GNU MP bignum library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``GMP_INCLUDE_DIRS`` + where to find gmp.h, etc. +``GMP_LIBRARIES`` + the libraries to link against to use GMP. +``GMP_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_GMP QUIET gmp) +endif() + +find_path(GMP_INCLUDE_DIR NAMES gmp.h + HINTS + ${PC_GMP_INCLUDE_DIRS} +) +mark_as_advanced(GMP_INCLUDE_DIR) + +find_library(GMP_LIBRARY NAMES gmp + HINTS + ${PC_GMP_LIBRARY_DIRS} +) +mark_as_advanced(GMP_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GMP + REQUIRED_VARS + GMP_LIBRARY GMP_INCLUDE_DIR +) + +if(GMP_FOUND) + set(GMP_INCLUDE_DIRS ${GMP_INCLUDE_DIR}) + set(GMP_LIBRARIES ${GMP_LIBRARY}) +endif() diff --git a/cmake/Modules/FindGmp.cmake b/cmake/Modules/FindGmp.cmake deleted file mode 100644 index 8711d68e..00000000 --- a/cmake/Modules/FindGmp.cmake +++ /dev/null @@ -1,17 +0,0 @@ -find_package(PkgConfig) - -if (PKG_CONFIG_FOUND) - pkg_check_modules(GMP gmp) -endif() - -# Only very recent versions of gmp has pkg-config support, so we have to -# fall back on a more classical search -if(NOT GMP_FOUND) - find_path(GMP_INCLUDE_DIRS NAMES gmp.h PATH_SUFFIXES) - find_library(GMP_LIBRARIES NAMES gmp) - find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARIES GMP_INCLUDE_DIRS) -endif() - -if(Gmp_FIND_REQUIRED AND NOT GMP_FOUND) - message(FATAL_ERROR "Could not find GMP") -endif() diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake deleted file mode 100644 index cf268ea0..00000000 --- a/cmake/Modules/FindIconv.cmake +++ /dev/null @@ -1,66 +0,0 @@ -# From: http://gitorious.org/gammu/mainline/blobs/master/cmake/FindIconv.cmake - -# - Try to find Iconv -# Once done this will define -# -# ICONV_FOUND - system has Iconv -# ICONV_INCLUDE_DIR - the Iconv include directory -# ICONV_LIBRARIES - Link these to use Iconv -# ICONV_SECOND_ARGUMENT_IS_CONST - the second argument for iconv() is const -# -include(CheckCCompilerFlag) -include(CheckCXXSourceCompiles) - -IF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) - # Already in cache, be silent - SET(ICONV_FIND_QUIETLY TRUE) -ENDIF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) - -FIND_PATH(ICONV_INCLUDE_DIR iconv.h) - -FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv c) - -IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) - SET(ICONV_FOUND TRUE) -ENDIF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) - -set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) -set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) -IF(ICONV_FOUND) - check_c_compiler_flag("-Werror" ICONV_HAVE_WERROR) - set (CMAKE_C_FLAGS_BACKUP "${CMAKE_C_FLAGS}") - if(ICONV_HAVE_WERROR) - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") - endif(ICONV_HAVE_WERROR) - check_c_source_compiles(" - #include <iconv.h> - int main(){ - iconv_t conv = 0; - const char* in = 0; - size_t ilen = 0; - char* out = 0; - size_t olen = 0; - iconv(conv, &in, &ilen, &out, &olen); - return 0; - } -" ICONV_SECOND_ARGUMENT_IS_CONST ) - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS_BACKUP}") -ENDIF(ICONV_FOUND) -set(CMAKE_REQUIRED_INCLUDES) -set(CMAKE_REQUIRED_LIBRARIES) - -IF(ICONV_FOUND) - IF(NOT ICONV_FIND_QUIETLY) - MESSAGE(STATUS "Found Iconv: ${ICONV_LIBRARIES}") - ENDIF(NOT ICONV_FIND_QUIETLY) -ELSE(ICONV_FOUND) - IF(Iconv_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find Iconv") - ENDIF(Iconv_FIND_REQUIRED) -ENDIF(ICONV_FOUND) - -MARK_AS_ADVANCED( - ICONV_INCLUDE_DIR - ICONV_LIBRARIES - ICONV_SECOND_ARGUMENT_IS_CONST -) diff --git a/cmake/Modules/FindNettle.cmake b/cmake/Modules/FindNettle.cmake index f5acf6ac..cdad9754 100644 --- a/cmake/Modules/FindNettle.cmake +++ b/cmake/Modules/FindNettle.cmake @@ -1,21 +1,63 @@ -find_package(Gmp) -find_package(PkgConfig) +#[=======================================================================[.rst: +FindNettle +---------- -if (PKG_CONFIG_FOUND) - pkg_check_modules(NETTLE nettle>=3.0) - pkg_check_modules(HOGWEED hogweed) +Find the Nettle and Hogweed libraries + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``NETTLE_INCLUDE_DIRS`` + where to find nettle/eax.h, etc. +``NETTLE_LIBRARIES`` + the libraries to link against to use Nettle. +``HOGWEED_LIBRARIES`` + the libraries to link against to use Hogweed. +``NETTLE_FOUND`` + TRUE if found + +#]=======================================================================] + +if(Nettle_FIND_REQUIRED) + find_package(GMP QUIET REQUIRED) else() - find_path(NETTLE_INCLUDE_DIRS NAMES eax.h PATH_SUFFIXES nettle) - find_library(NETTLE_LIBRARIES NAMES nettle) - find_package_handle_standard_args(NETTLE DEFAULT_MSG NETTLE_LIBRARIES NETTLE_INCLUDE_DIRS) - find_library(HOGWEED_LIBRARIES NAMES hogweed) - find_package_handle_standard_args(HOGWEED DEFAULT_MSG HOGWEED_LIBRARIES) + find_package(GMP QUIET) endif() -if (NOT HOGWEED_FOUND OR NOT GMP_FOUND) - set(NETTLE_FOUND 0) +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_Nettle QUIET nettle) + pkg_check_modules(PC_Hogweed QUIET hogweed) endif() -if(Nettle_FIND_REQUIRED AND NOT NETTLE_FOUND) - message(FATAL_ERROR "Could not find Nettle") +find_path(Nettle_INCLUDE_DIR NAMES nettle/eax.h + HINTS + ${PC_Nettle_INCLUDE_DIRS} +) +mark_as_advanced(Nettle_INCLUDE_DIR) + +find_library(Nettle_LIBRARY NAMES nettle + HINTS + ${PC_Nettle_LIBRARY_DIRS} +) +mark_as_advanced(Nettle_LIBRARY) + +find_library(Hogweed_LIBRARY NAMES hogweed + HINTS + ${PC_Hogweed_LIBRARY_DIRS} +) +mark_as_advanced(Hogweed_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Nettle + REQUIRED_VARS + Nettle_LIBRARY Nettle_INCLUDE_DIR Hogweed_LIBRARY +) + +if(Nettle_FOUND) + set(NETTLE_INCLUDE_DIRS ${Nettle_INCLUDE_DIR} ${GMP_INCLUDE_DIRS}) + set(NETTLE_LIBRARIES ${Nettle_LIBRARY}) + set(HOGWEED_LIBRARIES ${Hogweed_LIBRARY} ${GMP_LIBRARIES}) endif() diff --git a/cmake/Modules/FindPAM.cmake b/cmake/Modules/FindPAM.cmake new file mode 100644 index 00000000..a41cb421 --- /dev/null +++ b/cmake/Modules/FindPAM.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindPAM +------- + +Find the Pluggable Authentication Modules library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``PAM_INCLUDE_DIRS`` + where to find security/pam_appl.h, etc. +``PAM_LIBRARIES`` + the libraries to link against to use PAM. +``PAM_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PAM QUIET pam) +endif() + +find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h + HINTS + ${PC_PAM_INCLUDE_DIRS} +) +mark_as_advanced(PAM_INCLUDE_DIR) + +find_library(PAM_LIBRARY NAMES pam + HINTS + ${PC_PAM_LIBRARY_DIRS} +) +mark_as_advanced(PAM_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PAM + REQUIRED_VARS + PAM_LIBRARY PAM_INCLUDE_DIR +) + +if(PAM_FOUND) + set(PAM_INCLUDE_DIRS ${PAM_INCLUDE_DIR}) + set(PAM_LIBRARIES ${PAM_LIBRARY}) +endif() diff --git a/cmake/Modules/FindPWQuality.cmake b/cmake/Modules/FindPWQuality.cmake new file mode 100644 index 00000000..a1e5c1ff --- /dev/null +++ b/cmake/Modules/FindPWQuality.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindPWQuality +------------- + +Find the password quality library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``PWQUALITY_INCLUDE_DIRS`` + where to find pwquality.h, etc. +``PWQUALITY_LIBRARIES`` + the libraries to link against to use pwquality. +``PWQUALITY_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PWQuality QUIET pwquality) +endif() + +find_path(PWQuality_INCLUDE_DIR NAMES pwquality.h + HINTS + ${PC_PWQuality_INCLUDE_DIRS} +) +mark_as_advanced(PWQuality_INCLUDE_DIR) + +find_library(PWQuality_LIBRARY NAMES pwquality + HINTS + ${PC_PWQuality_LIBRARY_DIRS} +) +mark_as_advanced(PWQuality_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PWQuality + REQUIRED_VARS + PWQuality_LIBRARY PWQuality_INCLUDE_DIR +) + +if(PWQuality_FOUND) + set(PWQUALITY_INCLUDE_DIRS ${PWQuality_INCLUDE_DIR}) + set(PWQUALITY_LIBRARIES ${PWQuality_LIBRARY}) +endif() diff --git a/cmake/Modules/FindPixman.cmake b/cmake/Modules/FindPixman.cmake index f024b71a..a6f2fad2 100644 --- a/cmake/Modules/FindPixman.cmake +++ b/cmake/Modules/FindPixman.cmake @@ -1,13 +1,49 @@ -find_package(PkgConfig) - -if (PKG_CONFIG_FOUND) - pkg_check_modules(PIXMAN pixman-1) -else() - find_path(PIXMAN_INCLUDE_DIRS NAMES pixman.h PATH_SUFFIXES pixman-1) - find_library(PIXMAN_LIBRARIES NAMES pixman-1) - find_package_handle_standard_args(PIXMAN DEFAULT_MSG PIXMAN_LIBRARIES PIXMAN_INCLUDE_DIRS) +#[=======================================================================[.rst: +FindPixman +---------- + +Find the Pixman library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``PIXMAN_INCLUDE_DIRS`` + where to find pixman.h, etc. +``PIXMAN_LIBRARIES`` + the libraries to link against to use Pixman. +``PIXMAN_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_Pixman QUIET pixman-1) endif() -if(Pixman_FIND_REQUIRED AND NOT PIXMAN_FOUND) - message(FATAL_ERROR "Could not find Pixman") +find_path(Pixman_INCLUDE_DIR NAMES pixman.h + PATH_SUFFIXES + pixman-1 + HINTS + ${PC_Pixman_INCLUDE_DIRS} +) +mark_as_advanced(Pixman_INCLUDE_DIR) + +find_library(Pixman_LIBRARY NAMES pixman-1 + HINTS + ${PC_Pixman_LIBRARY_DIRS} +) +mark_as_advanced(Pixman_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Pixman + REQUIRED_VARS + Pixman_LIBRARY Pixman_INCLUDE_DIR +) + +if(Pixman_FOUND) + set(PIXMAN_INCLUDE_DIRS ${Pixman_INCLUDE_DIR}) + set(PIXMAN_LIBRARIES ${Pixman_LIBRARY}) endif() diff --git a/cmake/Modules/FindSELinux.cmake b/cmake/Modules/FindSELinux.cmake new file mode 100644 index 00000000..10be6736 --- /dev/null +++ b/cmake/Modules/FindSELinux.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindSELinux +----------- + +Find the SELinux library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``SELINUX_INCLUDE_DIRS`` + where to find selinux/selinux.h, etc. +``SELINUX_LIBRARIES`` + the libraries to link against to use libselinux. +``SELINUX_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_SELinux QUIET libselinux) +endif() + +find_path(SELinux_INCLUDE_DIR NAMES selinux/selinux.h + HINTS + ${PC_SELinux_INCLUDE_DIRS} +) +mark_as_advanced(SELinux_INCLUDE_DIR) + +find_library(SELinux_LIBRARY NAMES selinux + HINTS + ${PC_SELinux_LIBRARY_DIRS} +) +mark_as_advanced(SELinux_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SELinux + REQUIRED_VARS + SELinux_LIBRARY SELinux_INCLUDE_DIR +) + +if(SELinux_FOUND) + set(SELINUX_INCLUDE_DIRS ${SELinux_INCLUDE_DIR}) + set(SELINUX_LIBRARIES ${SELinux_LIBRARY}) +endif() diff --git a/cmake/Modules/FindSWScale.cmake b/cmake/Modules/FindSWScale.cmake new file mode 100644 index 00000000..61edb595 --- /dev/null +++ b/cmake/Modules/FindSWScale.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindSWScale +----------- + +Find the FFmpeg swscale library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``SWSCALE_INCLUDE_DIRS`` + where to find libswscale/swscale.h, etc. +``SWSCALE_LIBRARIES`` + the libraries to link against to use swscale. +``SWSCALE_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_SWScale QUIET libswscale) +endif() + +find_path(SWScale_INCLUDE_DIR NAMES libswscale/swscale.h + HINTS + ${PC_SWScale_INCLUDE_DIRS} +) +mark_as_advanced(SWScale_INCLUDE_DIR) + +find_library(SWScale_LIBRARY NAMES swscale + HINTS + ${PC_SWScale_LIBRARY_DIRS} +) +mark_as_advanced(SWScale_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SWScale + REQUIRED_VARS + SWScale_LIBRARY SWScale_INCLUDE_DIR +) + +if(SWScale_FOUND) + set(SWSCALE_INCLUDE_DIRS ${SWScale_INCLUDE_DIR}) + set(SWSCALE_LIBRARIES ${SWScale_LIBRARY}) +endif() diff --git a/cmake/Modules/FindSystemd.cmake b/cmake/Modules/FindSystemd.cmake new file mode 100644 index 00000000..1a3e40f5 --- /dev/null +++ b/cmake/Modules/FindSystemd.cmake @@ -0,0 +1,47 @@ +#[=======================================================================[.rst: +FindSystemd +----------- + +Find the systemd library + +Result variables +^^^^^^^^^^^^^^^^ + +This module will set the following variables if found: + +``SYSTEMD_INCLUDE_DIRS`` + where to find systemd/sd-daemon.h, etc. +``SYSTEMD_LIBRARIES`` + the libraries to link against to use libsystemd. +``SYSTEMD_FOUND`` + TRUE if found + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_Systemd QUIET libsystemd) +endif() + +find_path(Systemd_INCLUDE_DIR NAMES systemd/sd-daemon.h + HINTS + ${PC_Systemd_INCLUDE_DIRS} +) +mark_as_advanced(Systemd_INCLUDE_DIR) + +find_library(Systemd_LIBRARY NAMES systemd + HINTS + ${PC_Systemd_LIBRARY_DIRS} +) +mark_as_advanced(Systemd_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Systemd + REQUIRED_VARS + Systemd_LIBRARY Systemd_INCLUDE_DIR +) + +if(Systemd_FOUND) + set(SYSTEMD_INCLUDE_DIRS ${Systemd_INCLUDE_DIR}) + set(SYSTEMD_LIBRARIES ${Systemd_LIBRARY}) +endif() diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 991ad5be..f46e2154 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -23,34 +23,20 @@ if(BUILD_STATIC) set(PIXMAN_LIBRARIES "-Wl,-Bstatic -lpixman-1 -Wl,-Bdynamic") # gettext is included in libc on many unix systems + check_function_exists(dgettext LIBC_HAS_DGETTEXT) if(NOT LIBC_HAS_DGETTEXT) - FIND_LIBRARY(UNISTRING_LIBRARY NAMES unistring libunistring - HINTS ${PC_GETTEXT_LIBDIR} ${PC_GETTEXT_LIBRARY_DIRS}) - FIND_LIBRARY(INTL_LIBRARY NAMES intl libintl - HINTS ${PC_GETTEXT_LIBDIR} ${PC_GETTEXT_LIBRARY_DIRS}) - FIND_LIBRARY(ICONV_LIBRARY NAMES iconv libiconv - HINTS ${PC_GETTEXT_LIBDIR} ${PC_GETTEXT_LIBRARY_DIRS}) + FIND_LIBRARY(UNISTRING_LIBRARY NAMES unistring libunistring) - set(GETTEXT_LIBRARIES "-Wl,-Bstatic") + set(Intl_LIBRARIES "-Wl,-Bstatic -lintl -liconv") - if(INTL_LIBRARY) - set(GETTEXT_LIBRARIES "${GETTEXT_LIBRARIES} -lintl") - endif() - - if(ICONV_LIBRARY) - set(GETTEXT_LIBRARIES "${GETTEXT_LIBRARIES} -liconv") - endif() - - set(GETTEXT_LIBRARIES "${GETTEXT_LIBRARIES} -Wl,-Bdynamic") - - # FIXME: MSYS2 doesn't include a static version of this library, so - # we'll have to link it dynamically for now if(UNISTRING_LIBRARY) - set(GETTEXT_LIBRARIES "${GETTEXT_LIBRARIES} -lunistring") + set(Intl_LIBRARIES "${Intl_LIBRARIES} -lunistring") endif() + set(Intl_LIBRARIES "${Intl_LIBRARIES} -Wl,-Bdynamic") + if(APPLE) - set(GETTEXT_LIBRARIES "${GETTEXT_LIBRARIES} -framework Carbon") + set(Intl_LIBRARIES "${Intl_LIBRARIES} -framework Carbon") endif() endif() @@ -107,7 +93,7 @@ if(BUILD_STATIC) # GnuTLS uses gettext and zlib, so make sure those are always # included and in the proper order set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} ${ZLIB_LIBRARIES}") - set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} ${GETTEXT_LIBRARIES}") + set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} ${Intl_LIBRARIES}") # The last variables might introduce whitespace, which CMake # throws a hissy fit about @@ -116,8 +102,7 @@ if(BUILD_STATIC) if(NETTLE_FOUND) set(NETTLE_LIBRARIES "-Wl,-Bstatic -lnettle -Wl,-Bdynamic") - set(HOGWEED_LIBRARIES "-Wl,-Bstatic -lhogweed -Wl,-Bdynamic") - set(GMP_LIBRARIES "-Wl,-Bstatic -lgmp -Wl,-Bdynamic") + set(HOGWEED_LIBRARIES "-Wl,-Bstatic -lhogweed -lnettle -lgmp -Wl,-Bdynamic") endif() if(DEFINED FLTK_LIBRARIES) diff --git a/cmake/TargetLinkDirectories.cmake b/cmake/TargetLinkDirectories.cmake deleted file mode 100644 index 11b05670..00000000 --- a/cmake/TargetLinkDirectories.cmake +++ /dev/null @@ -1,12 +0,0 @@ -# Compatibility replacement of target_link_directories() for older cmake - -if(${CMAKE_VERSION} VERSION_LESS "3.13.0") - function(target_link_directories TARGET SCOPE) - get_target_property(INTERFACE_LINK_LIBRARIES ${TARGET} INTERFACE_LINK_LIBRARIES) - foreach(DIRECTORY ${ARGN}) - list(INSERT INTERFACE_LINK_LIBRARIES 0 "-L${DIRECTORY}") - endforeach() - set_target_properties(${TARGET} PROPERTIES - INTERFACE_LINK_LIBRARIES "${INTERFACE_LINK_LIBRARIES}") - endfunction() -endif() diff --git a/common/core/CMakeLists.txt b/common/core/CMakeLists.txt index 1932e5db..7e58acc0 100644 --- a/common/core/CMakeLists.txt +++ b/common/core/CMakeLists.txt @@ -5,10 +5,8 @@ add_library(core STATIC Logger_file.cxx Logger_stdio.cxx LogWriter.cxx - Mutex.cxx Region.cxx Timer.cxx - Thread.cxx string.cxx time.cxx xdgdirs.cxx) @@ -16,11 +14,9 @@ add_library(core STATIC target_include_directories(core PUBLIC ${CMAKE_SOURCE_DIR}/common) target_include_directories(core SYSTEM PUBLIC ${PIXMAN_INCLUDE_DIRS}) target_link_libraries(core ${PIXMAN_LIBRARIES}) -target_link_directories(core PUBLIC ${PIXMAN_LIBRARY_DIRS}) if(UNIX) target_sources(core PRIVATE Logger_syslog.cxx) - target_link_libraries(core pthread) endif() if(WIN32) diff --git a/common/core/Configuration.cxx b/common/core/Configuration.cxx index 129d1b9e..e6affb06 100644 --- a/common/core/Configuration.cxx +++ b/common/core/Configuration.cxx @@ -570,6 +570,10 @@ bool ListParameter<ValueType>::setParam(const char* v) entry.erase(0, entry.find_first_not_of(" \f\n\r\t\v")); entry.erase(entry.find_last_not_of(" \f\n\r\t\v")+1); + // Special case, entire v was just whitespace + if (entry.empty() && (entries.size() == 1)) + break; + if (!decodeEntry(entry.c_str(), &e)) { vlog.error("List parameter %s: Invalid value '%s'", getName(), entry.c_str()); diff --git a/common/core/Configuration.h b/common/core/Configuration.h index 57bc48e1..431dd0c5 100644 --- a/common/core/Configuration.h +++ b/common/core/Configuration.h @@ -86,6 +86,8 @@ namespace core { std::list<VoidParameter*>::iterator begin() { return params.begin(); } std::list<VoidParameter*>::iterator end() { return params.end(); } + // - Returns the number of parameters + int size() { return params.size(); } // - Get the Global Configuration object // NB: This call does NOT lock the Configuration system. diff --git a/common/core/Mutex.cxx b/common/core/Mutex.cxx deleted file mode 100644 index 6f72581e..00000000 --- a/common/core/Mutex.cxx +++ /dev/null @@ -1,157 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WIN32 -#include <windows.h> -#else -#include <pthread.h> -#endif - -#include <core/Exception.h> -#include <core/Mutex.h> - -using namespace core; - -Mutex::Mutex() -{ -#ifdef WIN32 - systemMutex = new CRITICAL_SECTION; - InitializeCriticalSection((CRITICAL_SECTION*)systemMutex); -#else - int ret; - - systemMutex = new pthread_mutex_t; - ret = pthread_mutex_init((pthread_mutex_t*)systemMutex, nullptr); - if (ret != 0) - throw posix_error("Failed to create mutex", ret); -#endif -} - -Mutex::~Mutex() -{ -#ifdef WIN32 - DeleteCriticalSection((CRITICAL_SECTION*)systemMutex); - delete (CRITICAL_SECTION*)systemMutex; -#else - pthread_mutex_destroy((pthread_mutex_t*)systemMutex); - delete (pthread_mutex_t*)systemMutex; -#endif -} - -void Mutex::lock() -{ -#ifdef WIN32 - EnterCriticalSection((CRITICAL_SECTION*)systemMutex); -#else - int ret; - - ret = pthread_mutex_lock((pthread_mutex_t*)systemMutex); - if (ret != 0) - throw posix_error("Failed to lock mutex", ret); -#endif -} - -void Mutex::unlock() -{ -#ifdef WIN32 - LeaveCriticalSection((CRITICAL_SECTION*)systemMutex); -#else - int ret; - - ret = pthread_mutex_unlock((pthread_mutex_t*)systemMutex); - if (ret != 0) - throw posix_error("Failed to unlock mutex", ret); -#endif -} - -Condition::Condition(Mutex* mutex_) -{ - this->mutex = mutex_; - -#ifdef WIN32 - systemCondition = new CONDITION_VARIABLE; - InitializeConditionVariable((CONDITION_VARIABLE*)systemCondition); -#else - int ret; - - systemCondition = new pthread_cond_t; - ret = pthread_cond_init((pthread_cond_t*)systemCondition, nullptr); - if (ret != 0) - throw posix_error("Failed to create condition variable", ret); -#endif -} - -Condition::~Condition() -{ -#ifdef WIN32 - delete (CONDITION_VARIABLE*)systemCondition; -#else - pthread_cond_destroy((pthread_cond_t*)systemCondition); - delete (pthread_cond_t*)systemCondition; -#endif -} - -void Condition::wait() -{ -#ifdef WIN32 - BOOL ret; - - ret = SleepConditionVariableCS((CONDITION_VARIABLE*)systemCondition, - (CRITICAL_SECTION*)mutex->systemMutex, - INFINITE); - if (!ret) - throw win32_error("Failed to wait on condition variable", GetLastError()); -#else - int ret; - - ret = pthread_cond_wait((pthread_cond_t*)systemCondition, - (pthread_mutex_t*)mutex->systemMutex); - if (ret != 0) - throw posix_error("Failed to wait on condition variable", ret); -#endif -} - -void Condition::signal() -{ -#ifdef WIN32 - WakeConditionVariable((CONDITION_VARIABLE*)systemCondition); -#else - int ret; - - ret = pthread_cond_signal((pthread_cond_t*)systemCondition); - if (ret != 0) - throw posix_error("Failed to signal condition variable", ret); -#endif -} - -void Condition::broadcast() -{ -#ifdef WIN32 - WakeAllConditionVariable((CONDITION_VARIABLE*)systemCondition); -#else - int ret; - - ret = pthread_cond_broadcast((pthread_cond_t*)systemCondition); - if (ret != 0) - throw posix_error("Failed to broadcast condition variable", ret); -#endif -} diff --git a/common/core/Mutex.h b/common/core/Mutex.h deleted file mode 100644 index 17a97d1b..00000000 --- a/common/core/Mutex.h +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * 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. - */ - -#ifndef __CORE_MUTEX_H__ -#define __CORE_MUTEX_H__ - -namespace core { - class Condition; - - class Mutex { - public: - Mutex(); - ~Mutex(); - - void lock(); - void unlock(); - - private: - friend class Condition; - - void* systemMutex; - }; - - class AutoMutex { - public: - AutoMutex(Mutex* mutex) { m = mutex; m->lock(); } - ~AutoMutex() { m->unlock(); } - private: - Mutex* m; - }; - - class Condition { - public: - Condition(Mutex* mutex); - ~Condition(); - - void wait(); - - void signal(); - void broadcast(); - - private: - Mutex* mutex; - void* systemCondition; - }; - -} - -#endif diff --git a/common/core/Thread.cxx b/common/core/Thread.cxx deleted file mode 100644 index be518cd4..00000000 --- a/common/core/Thread.cxx +++ /dev/null @@ -1,172 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WIN32 -#include <windows.h> -#else -#include <pthread.h> -#include <signal.h> -#include <unistd.h> -#endif - -#include <core/Exception.h> -#include <core/Mutex.h> -#include <core/Thread.h> - -using namespace core; - -Thread::Thread() : running(false), threadId(nullptr) -{ - mutex = new Mutex; - -#ifdef WIN32 - threadId = new HANDLE; -#else - threadId = new pthread_t; -#endif -} - -Thread::~Thread() -{ -#ifdef WIN32 - delete (HANDLE*)threadId; -#else - if (isRunning()) - pthread_cancel(*(pthread_t*)threadId); - delete (pthread_t*)threadId; -#endif - - delete mutex; -} - -void Thread::start() -{ - AutoMutex a(mutex); - -#ifdef WIN32 - *(HANDLE*)threadId = CreateThread(nullptr, 0, startRoutine, this, 0, nullptr); - if (*(HANDLE*)threadId == nullptr) - throw win32_error("Failed to create thread", GetLastError()); -#else - int ret; - sigset_t all, old; - - // Creating threads from libraries is a bit evil, so mitigate the - // issue by at least avoiding signals on these threads - sigfillset(&all); - ret = pthread_sigmask(SIG_SETMASK, &all, &old); - if (ret != 0) - throw posix_error("Failed to mask signals", ret); - - ret = pthread_create((pthread_t*)threadId, nullptr, startRoutine, this); - - pthread_sigmask(SIG_SETMASK, &old, nullptr); - - if (ret != 0) - throw posix_error("Failed to create thread", ret); -#endif - - running = true; -} - -void Thread::wait() -{ - if (!isRunning()) - return; - -#ifdef WIN32 - DWORD ret; - - ret = WaitForSingleObject(*(HANDLE*)threadId, INFINITE); - if (ret != WAIT_OBJECT_0) - throw win32_error("Failed to join thread", GetLastError()); -#else - int ret; - - ret = pthread_join(*(pthread_t*)threadId, nullptr); - if (ret != 0) - throw posix_error("Failed to join thread", ret); -#endif -} - -bool Thread::isRunning() -{ - AutoMutex a(mutex); - - return running; -} - -size_t Thread::getSystemCPUCount() -{ -#ifdef WIN32 - SYSTEM_INFO si; - size_t count; - DWORD mask; - - GetSystemInfo(&si); - - count = 0; - for (mask = si.dwActiveProcessorMask;mask != 0;mask >>= 1) { - if (mask & 0x1) - count++; - } - - if (count > si.dwNumberOfProcessors) - count = si.dwNumberOfProcessors; - - return count; -#else - long ret; - - ret = sysconf(_SC_NPROCESSORS_ONLN); - if (ret == -1) - return 0; - - return ret; -#endif -} - -#ifdef WIN32 -long unsigned __stdcall Thread::startRoutine(void* data) -#else -void* Thread::startRoutine(void* data) -#endif -{ - Thread *self; - - self = (Thread*)data; - - try { - self->worker(); - } catch(...) { - } - - self->mutex->lock(); - self->running = false; - self->mutex->unlock(); - -#ifdef WIN32 - return 0; -#else - return nullptr; -#endif -} diff --git a/common/core/Thread.h b/common/core/Thread.h deleted file mode 100644 index 53c5ef18..00000000 --- a/common/core/Thread.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright 2015 Pierre Ossman for Cendio AB - * - * 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. - */ - -#ifndef __CORE_THREAD_H__ -#define __CORE_THREAD_H__ - -#include <stddef.h> - -namespace core { - class Mutex; - - class Thread { - public: - Thread(); - virtual ~Thread(); - - void start(); - void wait(); - - bool isRunning(); - - public: - static size_t getSystemCPUCount(); - - protected: - virtual void worker() = 0; - - private: -#ifdef WIN32 - static long unsigned __stdcall startRoutine(void* data); -#else - static void* startRoutine(void* data); -#endif - - private: - Mutex *mutex; - bool running; - - void *threadId; - }; -} - -#endif diff --git a/common/core/string.cxx b/common/core/string.cxx index 49501a9f..091836db 100644 --- a/common/core/string.cxx +++ b/common/core/string.cxx @@ -64,6 +64,9 @@ namespace core { std::vector<std::string> out; const char *start, *stop; + if (src[0] == '\0') + return out; + start = src; do { stop = strchr(start, delimiter); diff --git a/common/network/Socket.cxx b/common/network/Socket.cxx index f5b44239..7fc39d1e 100644 --- a/common/network/Socket.cxx +++ b/common/network/Socket.cxx @@ -120,7 +120,7 @@ void Socket::shutdown() } isShutdown_ = true; - ::shutdown(getFd(), SHUT_RDWR); + ::shutdown(getFd(), SHUT_WR); } bool Socket::isShutdown() const diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index e941aa67..bf3a224c 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -698,7 +698,7 @@ TcpFilter::Pattern TcpFilter::parsePattern(const char* p) { if (parts.size() > 2) throw std::invalid_argument("Invalid filter specified"); - if (parts[0].empty()) { + if (parts.empty() || parts[0].empty()) { // Match any address memset (&pattern.address, 0, sizeof (pattern.address)); pattern.address.u.sa.sa_family = AF_UNSPEC; diff --git a/common/rdr/CMakeLists.txt b/common/rdr/CMakeLists.txt index 55c69132..526b2971 100644 --- a/common/rdr/CMakeLists.txt +++ b/common/rdr/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(rdr STATIC TLSException.cxx TLSInStream.cxx TLSOutStream.cxx + TLSSocket.cxx ZlibInStream.cxx ZlibOutStream.cxx) @@ -27,7 +28,6 @@ endif() if (NETTLE_FOUND) target_include_directories(rdr SYSTEM PUBLIC ${NETTLE_INCLUDE_DIRS}) target_link_libraries(rdr ${NETTLE_LIBRARIES}) - target_link_directories(rdr PUBLIC ${NETTLE_LIBRARY_DIRS}) endif() if(WIN32) target_link_libraries(rdr ws2_32) diff --git a/common/rdr/InStream.h b/common/rdr/InStream.h index 5bec276d..7ad4996f 100644 --- a/common/rdr/InStream.h +++ b/common/rdr/InStream.h @@ -187,9 +187,7 @@ namespace rdr { private: const uint8_t* restorePoint; -#ifdef RFB_INSTREAM_CHECK size_t checkedBytes; -#endif inline void check(size_t bytes) { #ifdef RFB_INSTREAM_CHECK @@ -209,11 +207,7 @@ namespace rdr { protected: - InStream() : restorePoint(nullptr) -#ifdef RFB_INSTREAM_CHECK - ,checkedBytes(0) -#endif - {} + InStream() : restorePoint(nullptr), checkedBytes(0) {} const uint8_t* ptr; const uint8_t* end; }; diff --git a/common/rdr/TLSException.cxx b/common/rdr/TLSException.cxx index a1896af4..8c93a3d3 100644 --- a/common/rdr/TLSException.cxx +++ b/common/rdr/TLSException.cxx @@ -35,11 +35,28 @@ using namespace rdr; #ifdef HAVE_GNUTLS -tls_error::tls_error(const char* s, int err_) noexcept +tls_error::tls_error(const char* s, int err_, int alert_) noexcept : std::runtime_error(core::format("%s: %s (%d)", s, - gnutls_strerror(err_), err_)), - err(err_) + strerror(err_, alert_), err_)), + err(err_), alert(alert_) { } + +const char* tls_error::strerror(int err_, int alert_) const noexcept +{ + const char* msg; + + msg = nullptr; + + if ((alert_ != -1) && + ((err_ == GNUTLS_E_WARNING_ALERT_RECEIVED) || + (err_ == GNUTLS_E_FATAL_ALERT_RECEIVED))) + msg = gnutls_alert_get_name((gnutls_alert_description_t)alert_); + + if (msg == nullptr) + msg = gnutls_strerror(err_); + + return msg; +} #endif /* HAVE_GNUTLS */ diff --git a/common/rdr/TLSException.h b/common/rdr/TLSException.h index b35a675f..75ee94f5 100644 --- a/common/rdr/TLSException.h +++ b/common/rdr/TLSException.h @@ -27,8 +27,10 @@ namespace rdr { class tls_error : public std::runtime_error { public: - int err; - tls_error(const char* s, int err_) noexcept; + int err, alert; + tls_error(const char* s, int err_, int alert_=-1) noexcept; + private: + const char* strerror(int err_, int alert_) const noexcept; }; } diff --git a/common/rdr/TLSInStream.cxx b/common/rdr/TLSInStream.cxx index 223e3ee6..3e5ea2be 100644 --- a/common/rdr/TLSInStream.cxx +++ b/common/rdr/TLSInStream.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,73 +23,25 @@ #include <config.h> #endif -#include <core/Exception.h> -#include <core/LogWriter.h> - -#include <rdr/TLSException.h> #include <rdr/TLSInStream.h> +#include <rdr/TLSSocket.h> -#include <errno.h> +#ifdef HAVE_GNUTLS -#ifdef HAVE_GNUTLS using namespace rdr; -static core::LogWriter vlog("TLSInStream"); - -ssize_t TLSInStream::pull(gnutls_transport_ptr_t str, void* data, size_t size) -{ - TLSInStream* self= (TLSInStream*) str; - InStream *in = self->in; - - self->streamEmpty = false; - self->saved_exception = nullptr; - - try { - if (!in->hasData(1)) { - self->streamEmpty = true; - gnutls_transport_set_errno(self->session, EAGAIN); - return -1; - } - - if (in->avail() < size) - size = in->avail(); - - in->readBytes((uint8_t*)data, size); - } catch (end_of_stream&) { - return 0; - } catch (std::exception& e) { - core::socket_error* se; - vlog.error("Failure reading TLS data: %s", e.what()); - se = dynamic_cast<core::socket_error*>(&e); - if (se) - gnutls_transport_set_errno(self->session, se->err); - else - gnutls_transport_set_errno(self->session, EINVAL); - self->saved_exception = std::current_exception(); - return -1; - } - - return size; -} - -TLSInStream::TLSInStream(InStream* _in, gnutls_session_t _session) - : session(_session), in(_in) +TLSInStream::TLSInStream(TLSSocket* sock_) + : sock(sock_) { - gnutls_transport_ptr_t recv, send; - - gnutls_transport_set_pull_function(session, pull); - gnutls_transport_get_ptr2(session, &recv, &send); - gnutls_transport_set_ptr2(session, this, send); } TLSInStream::~TLSInStream() { - gnutls_transport_set_pull_function(session, nullptr); } bool TLSInStream::fillBuffer() { - size_t n = readTLS((uint8_t*) end, availSpace()); + size_t n = sock->readTLS((uint8_t*) end, availSpace()); if (n == 0) return false; end += n; @@ -97,35 +49,4 @@ bool TLSInStream::fillBuffer() return true; } -size_t TLSInStream::readTLS(uint8_t* buf, size_t len) -{ - int n; - - while (true) { - streamEmpty = false; - n = gnutls_record_recv(session, (void *) buf, len); - if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) { - // GnuTLS returns GNUTLS_E_AGAIN for a bunch of other scenarios - // other than the pull function returning EAGAIN, so we have to - // double check that the underlying stream really is empty - if (!streamEmpty) - continue; - else - return 0; - } - break; - }; - - if (n == GNUTLS_E_PULL_ERROR) - std::rethrow_exception(saved_exception); - - if (n < 0) - throw tls_error("readTLS", n); - - if (n == 0) - throw end_of_stream(); - - return n; -} - #endif diff --git a/common/rdr/TLSInStream.h b/common/rdr/TLSInStream.h index bc9c74ba..94266e50 100644 --- a/common/rdr/TLSInStream.h +++ b/common/rdr/TLSInStream.h @@ -1,5 +1,6 @@ /* Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,28 +23,25 @@ #ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> #include <rdr/BufferedInStream.h> namespace rdr { + class TLSSocket; + class TLSInStream : public BufferedInStream { public: - TLSInStream(InStream* in, gnutls_session_t session); + TLSInStream(TLSSocket* sock); virtual ~TLSInStream(); private: bool fillBuffer() override; - size_t readTLS(uint8_t* buf, size_t len); - static ssize_t pull(gnutls_transport_ptr_t str, void* data, size_t size); - - gnutls_session_t session; - InStream* in; - bool streamEmpty; - std::exception_ptr saved_exception; + TLSSocket* sock; }; -}; + +} #endif + #endif diff --git a/common/rdr/TLSOutStream.cxx b/common/rdr/TLSOutStream.cxx index c3ae2d0a..ba9d182f 100644 --- a/common/rdr/TLSOutStream.cxx +++ b/common/rdr/TLSOutStream.cxx @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,103 +23,42 @@ #include <config.h> #endif -#include <core/Exception.h> -#include <core/LogWriter.h> - -#include <rdr/TLSException.h> #include <rdr/TLSOutStream.h> - -#include <errno.h> +#include <rdr/TLSSocket.h> #ifdef HAVE_GNUTLS -using namespace rdr; -static core::LogWriter vlog("TLSOutStream"); +using namespace rdr; -ssize_t TLSOutStream::push(gnutls_transport_ptr_t str, const void* data, - size_t size) +TLSOutStream::TLSOutStream(TLSSocket* sock_) + : sock(sock_) { - TLSOutStream* self= (TLSOutStream*) str; - OutStream *out = self->out; - - self->saved_exception = nullptr; - - try { - out->writeBytes((const uint8_t*)data, size); - out->flush(); - } catch (std::exception& e) { - core::socket_error* se; - vlog.error("Failure sending TLS data: %s", e.what()); - se = dynamic_cast<core::socket_error*>(&e); - if (se) - gnutls_transport_set_errno(self->session, se->err); - else - gnutls_transport_set_errno(self->session, EINVAL); - self->saved_exception = std::current_exception(); - return -1; - } - - return size; -} - -TLSOutStream::TLSOutStream(OutStream* _out, gnutls_session_t _session) - : session(_session), out(_out) -{ - gnutls_transport_ptr_t recv, send; - - gnutls_transport_set_push_function(session, push); - gnutls_transport_get_ptr2(session, &recv, &send); - gnutls_transport_set_ptr2(session, recv, this); } TLSOutStream::~TLSOutStream() { -#if 0 - try { -// flush(); - } catch (Exception&) { - } -#endif - gnutls_transport_set_push_function(session, nullptr); } void TLSOutStream::flush() { BufferedOutStream::flush(); - out->flush(); + sock->out->flush(); } void TLSOutStream::cork(bool enable) { BufferedOutStream::cork(enable); - out->cork(enable); + sock->out->cork(enable); } bool TLSOutStream::flushBuffer() { while (sentUpTo < ptr) { - size_t n = writeTLS(sentUpTo, ptr - sentUpTo); + size_t n = sock->writeTLS(sentUpTo, ptr - sentUpTo); sentUpTo += n; } return true; } -size_t TLSOutStream::writeTLS(const uint8_t* data, size_t length) -{ - int n; - - n = gnutls_record_send(session, data, length); - if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) - return 0; - - if (n == GNUTLS_E_PUSH_ERROR) - std::rethrow_exception(saved_exception); - - if (n < 0) - throw tls_error("writeTLS", n); - - return n; -} - #endif diff --git a/common/rdr/TLSOutStream.h b/common/rdr/TLSOutStream.h index 0ae9c460..aa9572ba 100644 --- a/common/rdr/TLSOutStream.h +++ b/common/rdr/TLSOutStream.h @@ -21,14 +21,16 @@ #define __RDR_TLSOUTSTREAM_H__ #ifdef HAVE_GNUTLS -#include <gnutls/gnutls.h> + #include <rdr/BufferedOutStream.h> namespace rdr { + class TLSSocket; + class TLSOutStream : public BufferedOutStream { public: - TLSOutStream(OutStream* out, gnutls_session_t session); + TLSOutStream(TLSSocket* out); virtual ~TLSOutStream(); void flush() override; @@ -36,15 +38,12 @@ namespace rdr { private: bool flushBuffer() override; - size_t writeTLS(const uint8_t* data, size_t length); - static ssize_t push(gnutls_transport_ptr_t str, const void* data, size_t size); - - gnutls_session_t session; - OutStream* out; - std::exception_ptr saved_exception; + TLSSocket* sock; }; -}; + +} #endif + #endif diff --git a/common/rdr/TLSSocket.cxx b/common/rdr/TLSSocket.cxx new file mode 100644 index 00000000..a29e41c1 --- /dev/null +++ b/common/rdr/TLSSocket.cxx @@ -0,0 +1,228 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010 TigerVNC Team + * Copyright (C) 2012-2025 Pierre Ossman for Cendio AB + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <core/Exception.h> +#include <core/LogWriter.h> + +#include <rdr/InStream.h> +#include <rdr/OutStream.h> +#include <rdr/TLSException.h> +#include <rdr/TLSSocket.h> + +#include <errno.h> + +#ifdef HAVE_GNUTLS + +using namespace rdr; + +static core::LogWriter vlog("TLSSocket"); + +TLSSocket::TLSSocket(InStream* in_, OutStream* out_, + gnutls_session_t session_) + : session(session_), in(in_), out(out_), tlsin(this), tlsout(this) +{ + gnutls_transport_set_pull_function( + session, [](gnutls_transport_ptr_t sock, void* data, size_t size) { + return ((TLSSocket*)sock)->pull(data, size); + }); + gnutls_transport_set_push_function( + session, [](gnutls_transport_ptr_t sock, const void* data, size_t size) { + return ((TLSSocket*)sock)->push(data, size); + }); + gnutls_transport_set_ptr(session, this); +} + +TLSSocket::~TLSSocket() +{ + gnutls_transport_set_pull_function(session, nullptr); + gnutls_transport_set_push_function(session, nullptr); + gnutls_transport_set_ptr(session, nullptr); +} + +bool TLSSocket::handshake() +{ + int err; + + err = gnutls_handshake(session); + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_description_t alert; + const char* msg; + + if ((err == GNUTLS_E_PULL_ERROR) || (err == GNUTLS_E_PUSH_ERROR)) + std::rethrow_exception(saved_exception); + + alert = gnutls_alert_get(session); + msg = nullptr; + + if ((err == GNUTLS_E_WARNING_ALERT_RECEIVED) || + (err == GNUTLS_E_FATAL_ALERT_RECEIVED)) + msg = gnutls_alert_get_name(alert); + + if (msg == nullptr) + msg = gnutls_strerror(err); + + if (!gnutls_error_is_fatal(err)) { + vlog.debug("Deferring completion of TLS handshake: %s", msg); + return false; + } + + vlog.error("TLS Handshake failed: %s\n", msg); + gnutls_alert_send_appropriate(session, err); + throw rdr::tls_error("TLS Handshake failed", err, alert); + } + + return true; +} + +void TLSSocket::shutdown() +{ + int ret; + + try { + if (tlsout.hasBufferedData()) { + tlsout.cork(false); + tlsout.flush(); + if (tlsout.hasBufferedData()) + vlog.error("Failed to flush remaining socket data on close"); + } + } catch (std::exception& e) { + vlog.error("Failed to flush remaining socket data on close: %s", e.what()); + } + + // FIXME: We can't currently wait for the response, so we only send + // our close and hope for the best + ret = gnutls_bye(session, GNUTLS_SHUT_WR); + if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION)) + vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret)); +} + +size_t TLSSocket::readTLS(uint8_t* buf, size_t len) +{ + int n; + + while (true) { + streamEmpty = false; + n = gnutls_record_recv(session, (void *) buf, len); + if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) { + // GnuTLS returns GNUTLS_E_AGAIN for a bunch of other scenarios + // other than the pull function returning EAGAIN, so we have to + // double check that the underlying stream really is empty + if (!streamEmpty) + continue; + else + return 0; + } + break; + }; + + if (n == GNUTLS_E_PULL_ERROR) + std::rethrow_exception(saved_exception); + + if (n < 0) { + gnutls_alert_send_appropriate(session, n); + throw tls_error("readTLS", n, gnutls_alert_get(session)); + } + + if (n == 0) + throw end_of_stream(); + + return n; +} + +size_t TLSSocket::writeTLS(const uint8_t* data, size_t length) +{ + int n; + + n = gnutls_record_send(session, data, length); + if (n == GNUTLS_E_INTERRUPTED || n == GNUTLS_E_AGAIN) + return 0; + + if (n == GNUTLS_E_PUSH_ERROR) + std::rethrow_exception(saved_exception); + + if (n < 0) { + gnutls_alert_send_appropriate(session, n); + throw tls_error("writeTLS", n, gnutls_alert_get(session)); + } + + return n; +} + +ssize_t TLSSocket::pull(void* data, size_t size) +{ + streamEmpty = false; + saved_exception = nullptr; + + try { + if (!in->hasData(1)) { + streamEmpty = true; + gnutls_transport_set_errno(session, EAGAIN); + return -1; + } + + if (in->avail() < size) + size = in->avail(); + + in->readBytes((uint8_t*)data, size); + } catch (end_of_stream&) { + return 0; + } catch (std::exception& e) { + core::socket_error* se; + vlog.error("Failure reading TLS data: %s", e.what()); + se = dynamic_cast<core::socket_error*>(&e); + if (se) + gnutls_transport_set_errno(session, se->err); + else + gnutls_transport_set_errno(session, EINVAL); + saved_exception = std::current_exception(); + return -1; + } + + return size; +} + +ssize_t TLSSocket::push(const void* data, size_t size) +{ + saved_exception = nullptr; + + try { + out->writeBytes((const uint8_t*)data, size); + out->flush(); + } catch (std::exception& e) { + core::socket_error* se; + vlog.error("Failure sending TLS data: %s", e.what()); + se = dynamic_cast<core::socket_error*>(&e); + if (se) + gnutls_transport_set_errno(session, se->err); + else + gnutls_transport_set_errno(session, EINVAL); + saved_exception = std::current_exception(); + return -1; + } + + return size; +} + +#endif diff --git a/common/rdr/TLSSocket.h b/common/rdr/TLSSocket.h new file mode 100644 index 00000000..ca29f8bc --- /dev/null +++ b/common/rdr/TLSSocket.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2005 Martin Koegler + * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB + * + * 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. + */ + +#ifndef __RDR_TLSSOCKET_H__ +#define __RDR_TLSSOCKET_H__ + +#ifdef HAVE_GNUTLS + +#include <exception> + +#include <gnutls/gnutls.h> + +#include <rdr/TLSInStream.h> +#include <rdr/TLSOutStream.h> + +namespace rdr { + + class InStream; + class OutStream; + + class TLSInStream; + class TLSOutStream; + + class TLSSocket { + public: + TLSSocket(InStream* in, OutStream* out, gnutls_session_t session); + virtual ~TLSSocket(); + + TLSInStream& inStream() { return tlsin; } + TLSOutStream& outStream() { return tlsout; } + + bool handshake(); + void shutdown(); + + protected: + /* Used by the stream classes */ + size_t readTLS(uint8_t* buf, size_t len); + size_t writeTLS(const uint8_t* data, size_t length); + + friend TLSInStream; + friend TLSOutStream; + + private: + ssize_t pull(void* data, size_t size); + ssize_t push(const void* data, size_t size); + + gnutls_session_t session; + + InStream* in; + OutStream* out; + + TLSInStream tlsin; + TLSOutStream tlsout; + + bool streamEmpty; + + std::exception_ptr saved_exception; + }; + +} + +#endif + +#endif diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index cbb62fe3..bbeef385 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -37,6 +37,7 @@ #include <rfb/CMsgReader.h> #include <rfb/CMsgWriter.h> #include <rfb/CSecurity.h> +#include <rfb/Cursor.h> #include <rfb/Decoder.h> #include <rfb/KeysymStr.h> #include <rfb/PixelBuffer.h> @@ -379,7 +380,6 @@ void CConnection::securityCompleted() reader_ = new CMsgReader(this, is); writer_ = new CMsgWriter(&server, os); vlog.debug("Authentication success!"); - authSuccess(); writer_->writeClientInit(shared); } @@ -410,7 +410,7 @@ void CConnection::setDesktopSize(int w, int h) { decoder.flush(); - CMsgHandler::setDesktopSize(w,h); + server.setDimensions(w, h); if (continuousUpdates) writer()->writeEnableContinuousUpdates(true, 0, 0, @@ -430,7 +430,10 @@ void CConnection::setExtendedDesktopSize(unsigned reason, { decoder.flush(); - CMsgHandler::setExtendedDesktopSize(reason, result, w, h, layout); + server.supportsSetDesktopSize = true; + + if ((reason != reasonClient) || (result == resultSuccess)) + server.setDimensions(w, h, layout); if ((reason == reasonClient) && (result != resultSuccess)) { vlog.error("SetDesktopSize failed: %d", result); @@ -448,9 +451,41 @@ void CConnection::setExtendedDesktopSize(unsigned reason, assert(framebuffer->height() == server.height()); } +void CConnection::setCursor(int width, int height, + const core::Point& hotspot, + const uint8_t* data) +{ + Cursor cursor(width, height, hotspot, data); + server.setCursor(cursor); +} + +void CConnection::setCursorPos(const core::Point& /*pos*/) +{ +} + +void CConnection::setName(const char* name) +{ + server.setName(name); +} + +void CConnection::fence(uint32_t flags, unsigned len, const uint8_t data[]) +{ + server.supportsFence = true; + + if (flags & fenceFlagRequest) { + // FIXME: We handle everything synchronously, and we assume anything + // using us also does so, which means we automatically handle + // these flags + flags = flags & (fenceFlagBlockBefore | fenceFlagBlockAfter); + + writer()->writeFence(flags, len, data); + return; + } +} + void CConnection::endOfContinuousUpdates() { - CMsgHandler::endOfContinuousUpdates(); + server.supportsContinuousUpdates = true; // We've gotten the marker for a format change, so make the pending // one active @@ -464,11 +499,23 @@ void CConnection::endOfContinuousUpdates() } } +void CConnection::supportsQEMUKeyEvent() +{ + server.supportsQEMUKeyEvent = true; +} + +void CConnection::supportsExtendedMouseButtons() +{ + server.supportsExtendedMouseButtons = true; +} + void CConnection::serverInit(int width, int height, const PixelFormat& pf, const char* name) { - CMsgHandler::serverInit(width, height, pf, name); + server.setDimensions(width, height); + server.setPF(pf); + server.setName(name); state_ = RFBSTATE_NORMAL; vlog.debug("Initialisation done"); @@ -502,8 +549,6 @@ bool CConnection::readAndDecodeRect(const core::Rect& r, int encoding, void CConnection::framebufferUpdateStart() { - CMsgHandler::framebufferUpdateStart(); - assert(framebuffer != nullptr); // Note: This might not be true if continuous updates are supported @@ -516,8 +561,6 @@ void CConnection::framebufferUpdateEnd() { decoder.flush(); - CMsgHandler::framebufferUpdateEnd(); - // A format change has been scheduled and we are now past the update // with the old format. Time to active the new one. if (pendingPFChange && !continuousUpdates) { @@ -543,6 +586,13 @@ bool CConnection::dataRect(const core::Rect& r, int encoding) return decoder.decodeRect(r, encoding, framebuffer); } +void CConnection::setColourMapEntries(int /*firstColour*/, + int /*nColours*/, + uint16_t* /*rgbs*/) +{ + vlog.error("Invalid SetColourMapEntries from server!"); +} + void CConnection::serverCutText(const char* str) { hasLocalClipboard = false; @@ -553,12 +603,53 @@ void CConnection::serverCutText(const char* str) handleClipboardAnnounce(true); } +void CConnection::setLEDState(unsigned int state) +{ + server.setLEDState(state); +} + void CConnection::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) { + int i; uint32_t sizes[] = { 0 }; - CMsgHandler::handleClipboardCaps(flags, lengths); + vlog.debug("Got server clipboard capabilities:"); + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) { + const char *type; + + switch (1 << i) { + case clipboardUTF8: + type = "Plain text"; + break; + case clipboardRTF: + type = "Rich text"; + break; + case clipboardHTML: + type = "HTML"; + break; + case clipboardDIB: + type = "Images"; + break; + case clipboardFiles: + type = "Files"; + break; + default: + vlog.debug(" Unknown format 0x%x", 1 << i); + continue; + } + + if (lengths[i] == 0) + vlog.debug(" %s (only notify)", type); + else { + vlog.debug(" %s (automatically send up to %s)", + type, core::iecPrefix(lengths[i], "B").c_str()); + } + } + } + + server.setClipboardCaps(flags, lengths); writer()->writeClipboardCaps(rfb::clipboardUTF8 | rfb::clipboardRequest | @@ -620,10 +711,6 @@ void CConnection::handleClipboardProvide(uint32_t flags, handleClipboardData(serverClipboard.c_str()); } -void CConnection::authSuccess() -{ -} - void CConnection::initDone() { } @@ -820,6 +907,11 @@ void CConnection::setCompressLevel(int level) encodingChange = true; } +int CConnection::getCompressLevel() +{ + return compressLevel; +} + void CConnection::setQualityLevel(int level) { if (qualityLevel == level) @@ -829,6 +921,11 @@ void CConnection::setQualityLevel(int level) encodingChange = true; } +int CConnection::getQualityLevel() +{ + return qualityLevel; +} + void CConnection::setPF(const PixelFormat& pf) { if (server.pf() == pf && !formatChange) @@ -843,19 +940,6 @@ bool CConnection::isSecure() const return csecurity ? csecurity->isSecure() : false; } -void CConnection::fence(uint32_t flags, unsigned len, const uint8_t data[]) -{ - CMsgHandler::fence(flags, len, data); - - if (!(flags & fenceFlagRequest)) - return; - - // We cannot guarantee any synchronisation at this level - flags = 0; - - writer()->writeFence(flags, len, data); -} - // requestNewUpdate() requests an update from the server, having set the // format and encoding appropriately. void CConnection::requestNewUpdate() diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index 9ffbd2c5..b28c5aa9 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -105,89 +105,6 @@ namespace rfb { // connection void close(); - - // Methods overridden from CMsgHandler - - // Note: These must be called by any deriving classes - - void setDesktopSize(int w, int h) override; - void setExtendedDesktopSize(unsigned reason, unsigned result, - int w, int h, - const ScreenSet& layout) override; - - void endOfContinuousUpdates() override; - - void serverInit(int width, int height, const PixelFormat& pf, - const char* name) override; - - bool readAndDecodeRect(const core::Rect& r, int encoding, - ModifiablePixelBuffer* pb) override; - - void framebufferUpdateStart() override; - void framebufferUpdateEnd() override; - bool dataRect(const core::Rect& r, int encoding) override; - - void serverCutText(const char* str) override; - - void handleClipboardCaps(uint32_t flags, - const uint32_t* lengths) override; - void handleClipboardRequest(uint32_t flags) override; - void handleClipboardPeek() override; - void handleClipboardNotify(uint32_t flags) override; - void handleClipboardProvide(uint32_t flags, const size_t* lengths, - const uint8_t* const* data) override; - - - // Methods to be overridden in a derived class - - // getUserPasswd() gets the username and password. This might - // involve a dialog, getpass(), etc. The user buffer pointer can be - // null, in which case no user name will be retrieved. - virtual void getUserPasswd(bool secure, std::string* user, - std::string* password) = 0; - - // showMsgBox() displays a message box with the specified style and - // contents. The return value is true if the user clicked OK/Yes. - virtual bool showMsgBox(MsgBoxFlags flags, const char *title, - const char *text) = 0; - - // authSuccess() is called when authentication has succeeded. - virtual void authSuccess(); - - // initDone() is called when the connection is fully established - // and standard messages can be sent. This is called before the - // initial FramebufferUpdateRequest giving a derived class the - // chance to modify pixel format and settings. The derived class - // must also make sure it has provided a valid framebuffer before - // returning. - virtual void initDone() = 0; - - // resizeFramebuffer() is called whenever the framebuffer - // dimensions or the screen layout changes. A subclass must make - // sure the pixel buffer has been updated once this call returns. - virtual void resizeFramebuffer(); - - // handleClipboardRequest() is called whenever the server requests - // the client to send over its clipboard data. It will only be - // called after the client has first announced a clipboard change - // via announceClipboard(). - virtual void handleClipboardRequest(); - - // handleClipboardAnnounce() is called to indicate a change in the - // clipboard on the server. Call requestClipboard() to access the - // actual data. - virtual void handleClipboardAnnounce(bool available); - - // handleClipboardData() is called when the server has sent over - // the clipboard data as a result of a previous call to - // requestClipboard(). Note that this function might never be - // called if the clipboard data was no longer available when the - // server received the request. - virtual void handleClipboardData(const char* data); - - - // Other methods - // requestClipboard() will result in a request to the server to // transfer its clipboard data. A call to handleClipboardData() // will be made once the data is available. @@ -227,7 +144,9 @@ namespace rfb { // setCompressLevel()/setQualityLevel() controls the encoding hints // sent to the server void setCompressLevel(int level); + int getCompressLevel(); void setQualityLevel(int level); + int getQualityLevel(); // setPF() controls the pixel format requested from the server. // server.pf() will automatically be adjusted once the new format // is active. @@ -260,8 +179,107 @@ namespace rfb { stateEnum state() { return state_; } + // Methods used by SSecurity classes + + // getUserPasswd() gets the username and password. This might + // involve a dialog, getpass(), etc. The user buffer pointer can be + // null, in which case no user name will be retrieved. + virtual void getUserPasswd(bool secure, std::string* user, + std::string* password) = 0; + + // showMsgBox() displays a message box with the specified style and + // contents. The return value is true if the user clicked OK/Yes. + virtual bool showMsgBox(MsgBoxFlags flags, const char *title, + const char *text) = 0; + + protected: + + // Methods overridden from CMsgHandler + + // Note: These must be called by any deriving classes + + void setDesktopSize(int w, int h) override; + void setExtendedDesktopSize(unsigned reason, unsigned result, + int w, int h, + const ScreenSet& layout) override; + + void setCursor(int width, int height, const core::Point& hotspot, + const uint8_t* data) override; + void setCursorPos(const core::Point& pos) override; + + void setName(const char* name) override; + + void fence(uint32_t flags, unsigned len, const uint8_t data[]) override; + + void endOfContinuousUpdates() override; + + void supportsQEMUKeyEvent() override; + + void supportsExtendedMouseButtons() override; + + void serverInit(int width, int height, const PixelFormat& pf, + const char* name) override; + + bool readAndDecodeRect(const core::Rect& r, int encoding, + ModifiablePixelBuffer* pb) override; + + void framebufferUpdateStart() override; + void framebufferUpdateEnd() override; + bool dataRect(const core::Rect& r, int encoding) override; + + void setColourMapEntries(int firstColour, int nColours, + uint16_t* rgbs) override; + + void serverCutText(const char* str) override; + + void setLEDState(unsigned int state) override; + + void handleClipboardCaps(uint32_t flags, + const uint32_t* lengths) override; + void handleClipboardRequest(uint32_t flags) override; + void handleClipboardPeek() override; + void handleClipboardNotify(uint32_t flags) override; + void handleClipboardProvide(uint32_t flags, const size_t* lengths, + const uint8_t* const* data) override; + + + // Methods to be overridden in a derived class + + // initDone() is called when the connection is fully established + // and standard messages can be sent. This is called before the + // initial FramebufferUpdateRequest giving a derived class the + // chance to modify pixel format and settings. The derived class + // must also make sure it has provided a valid framebuffer before + // returning. + virtual void initDone() = 0; + + // resizeFramebuffer() is called whenever the framebuffer + // dimensions or the screen layout changes. A subclass must make + // sure the pixel buffer has been updated once this call returns. + virtual void resizeFramebuffer(); + + // handleClipboardRequest() is called whenever the server requests + // the client to send over its clipboard data. It will only be + // called after the client has first announced a clipboard change + // via announceClipboard(). + virtual void handleClipboardRequest(); + + // handleClipboardAnnounce() is called to indicate a change in the + // clipboard on the server. Call requestClipboard() to access the + // actual data. + virtual void handleClipboardAnnounce(bool available); + + // handleClipboardData() is called when the server has sent over + // the clipboard data as a result of a previous call to + // requestClipboard(). Note that this function might never be + // called if the clipboard data was no longer available when the + // server received the request. + virtual void handleClipboardData(const char* data); + + protected: CSecurity *csecurity; SecurityClient security; + protected: void setState(stateEnum s) { state_ = s; } @@ -279,13 +297,6 @@ namespace rfb { bool supportsLEDState; private: - // This is a default implementation of fences that automatically - // responds to requests, stating no support for synchronisation. - // When overriding, call CMsgHandler::fence() directly in order to - // state correct support for fence flags. - void fence(uint32_t flags, unsigned len, const uint8_t data[]) override; - - private: bool processVersionMsg(); bool processSecurityTypesMsg(); bool processSecurityMsg(); diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt index 2b5eea68..d7467421 100644 --- a/common/rfb/CMakeLists.txt +++ b/common/rfb/CMakeLists.txt @@ -3,7 +3,6 @@ add_library(rfb STATIC Blacklist.cxx Congestion.cxx CConnection.cxx - CMsgHandler.cxx CMsgReader.cxx CMsgWriter.cxx CSecurityPlain.cxx @@ -32,7 +31,6 @@ add_library(rfb STATIC RawDecoder.cxx RawEncoder.cxx SConnection.cxx - SMsgHandler.cxx SMsgReader.cxx SMsgWriter.cxx ServerCore.cxx @@ -69,7 +67,6 @@ if(ENABLE_H264 AND NOT H264_LIBS STREQUAL "NONE") endif() target_include_directories(rfb SYSTEM PUBLIC ${H264_INCLUDE_DIRS}) target_link_libraries(rfb ${H264_LIBRARIES}) - target_link_directories(rfb PUBLIC ${H264_LIBRARY_DIRS}) endif() if(WIN32) @@ -78,8 +75,9 @@ if(WIN32) endif(WIN32) if(UNIX AND NOT APPLE) - target_sources(rfb PRIVATE UnixPasswordValidator.cxx pam.c) - target_link_libraries(rfb ${PAM_LIBS}) + target_sources(rfb PRIVATE UnixPasswordValidator.cxx) + target_include_directories(rfb SYSTEM PRIVATE ${PAM_INCLUDE_DIRS}) + target_link_libraries(rfb ${PAM_LIBRARIES}) endif() if(GNUTLS_FOUND) @@ -95,12 +93,8 @@ endif() if (NETTLE_FOUND) target_sources(rfb PRIVATE CSecurityDH.cxx CSecurityMSLogonII.cxx CSecurityRSAAES.cxx SSecurityRSAAES.cxx) - target_include_directories(rfb SYSTEM PUBLIC ${NETTLE_INCLUDE_DIRS} - ${GMP_INCLUDE_DIRS}) - target_link_libraries(rfb ${HOGWEED_LIBRARIES} - ${NETTLE_LIBRARIES} ${GMP_LIBRARIES}) - target_link_directories(rfb PUBLIC ${HOGWEED_LIBRARY_DIRS} - ${NETTLE_LIBRARY_DIRS} ${GMP_LIBRARY_DIRS}) + target_include_directories(rfb SYSTEM PUBLIC ${NETTLE_INCLUDE_DIRS}) + target_link_libraries(rfb ${HOGWEED_LIBRARIES} ${NETTLE_LIBRARIES}) endif() if(UNIX) diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx deleted file mode 100644 index 2a88d867..00000000 --- a/common/rfb/CMsgHandler.cxx +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdio.h> - -#include <core/LogWriter.h> -#include <core/string.h> - -#include <rfb/CMsgHandler.h> -#include <rfb/clipboardTypes.h> -#include <rfb/screenTypes.h> - -static core::LogWriter vlog("CMsgHandler"); - -using namespace rfb; - -CMsgHandler::CMsgHandler() -{ -} - -CMsgHandler::~CMsgHandler() -{ -} - -void CMsgHandler::setDesktopSize(int width, int height) -{ - server.setDimensions(width, height); -} - -void CMsgHandler::setExtendedDesktopSize(unsigned reason, unsigned result, - int width, int height, - const ScreenSet& layout) -{ - server.supportsSetDesktopSize = true; - - if ((reason == reasonClient) && (result != resultSuccess)) - return; - - server.setDimensions(width, height, layout); -} - -void CMsgHandler::setName(const char* name) -{ - server.setName(name); -} - -void CMsgHandler::fence(uint32_t /*flags*/, unsigned /*len*/, - const uint8_t /*data*/ []) -{ - server.supportsFence = true; -} - -void CMsgHandler::endOfContinuousUpdates() -{ - server.supportsContinuousUpdates = true; -} - -void CMsgHandler::supportsExtendedMouseButtons() -{ - server.supportsExtendedMouseButtons = true; -} - -void CMsgHandler::supportsQEMUKeyEvent() -{ - server.supportsQEMUKeyEvent = true; -} - -void CMsgHandler::serverInit(int width, int height, - const PixelFormat& pf, - const char* name) -{ - server.setDimensions(width, height); - server.setPF(pf); - server.setName(name); -} - -void CMsgHandler::framebufferUpdateStart() -{ -} - -void CMsgHandler::framebufferUpdateEnd() -{ -} - -void CMsgHandler::setLEDState(unsigned int state) -{ - server.setLEDState(state); -} - -void CMsgHandler::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) -{ - int i; - - vlog.debug("Got server clipboard capabilities:"); - for (i = 0;i < 16;i++) { - if (flags & (1 << i)) { - const char *type; - - switch (1 << i) { - case clipboardUTF8: - type = "Plain text"; - break; - case clipboardRTF: - type = "Rich text"; - break; - case clipboardHTML: - type = "HTML"; - break; - case clipboardDIB: - type = "Images"; - break; - case clipboardFiles: - type = "Files"; - break; - default: - vlog.debug(" Unknown format 0x%x", 1 << i); - continue; - } - - if (lengths[i] == 0) - vlog.debug(" %s (only notify)", type); - else { - vlog.debug(" %s (automatically send up to %s)", - type, core::iecPrefix(lengths[i], "B").c_str()); - } - } - } - - server.setClipboardCaps(flags, lengths); -} - -void CMsgHandler::handleClipboardRequest(uint32_t /*flags*/) -{ -} - -void CMsgHandler::handleClipboardPeek() -{ -} - -void CMsgHandler::handleClipboardNotify(uint32_t /*flags*/) -{ -} - -void CMsgHandler::handleClipboardProvide(uint32_t /*flags*/, - const size_t* /*lengths*/, - const uint8_t* const* /*data*/) -{ -} diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index e7ba2abc..d267ae47 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -33,8 +33,6 @@ namespace core { struct Rect; } -namespace rdr { class InStream; } - namespace rfb { class ModifiablePixelBuffer; @@ -42,29 +40,23 @@ namespace rfb { class CMsgHandler { public: - CMsgHandler(); - virtual ~CMsgHandler(); - // The following methods are called as corresponding messages are - // read. A derived class should override these methods as desired. - // Note that for the setDesktopSize(), setExtendedDesktopSize(), - // setName(), serverInit() and handleClipboardCaps() methods, a - // derived class should call on to CMsgHandler's methods to set the - // members of "server" appropriately. + // read. A derived class must override these methods. - virtual void setDesktopSize(int w, int h); + virtual void setDesktopSize(int w, int h) = 0; virtual void setExtendedDesktopSize(unsigned reason, unsigned result, int w, int h, - const ScreenSet& layout); + const ScreenSet& layout) = 0; virtual void setCursor(int width, int height, const core::Point& hotspot, const uint8_t* data) = 0; virtual void setCursorPos(const core::Point& pos) = 0; - virtual void setName(const char* name); - virtual void fence(uint32_t flags, unsigned len, const uint8_t data[]); - virtual void endOfContinuousUpdates(); - virtual void supportsQEMUKeyEvent(); - virtual void supportsExtendedMouseButtons(); + virtual void setName(const char* name) = 0; + virtual void fence(uint32_t flags, unsigned len, + const uint8_t data[]) = 0; + virtual void endOfContinuousUpdates() = 0; + virtual void supportsQEMUKeyEvent() = 0; + virtual void supportsExtendedMouseButtons() = 0; virtual void serverInit(int width, int height, const PixelFormat& pf, const char* name) = 0; @@ -72,8 +64,8 @@ namespace rfb { virtual bool readAndDecodeRect(const core::Rect& r, int encoding, ModifiablePixelBuffer* pb) = 0; - virtual void framebufferUpdateStart(); - virtual void framebufferUpdateEnd(); + virtual void framebufferUpdateStart() = 0; + virtual void framebufferUpdateEnd() = 0; virtual bool dataRect(const core::Rect& r, int encoding) = 0; virtual void setColourMapEntries(int firstColour, int nColours, @@ -81,16 +73,16 @@ namespace rfb { virtual void bell() = 0; virtual void serverCutText(const char* str) = 0; - virtual void setLEDState(unsigned int state); + virtual void setLEDState(unsigned int state) = 0; virtual void handleClipboardCaps(uint32_t flags, - const uint32_t* lengths); - virtual void handleClipboardRequest(uint32_t flags); - virtual void handleClipboardPeek(); - virtual void handleClipboardNotify(uint32_t flags); + const uint32_t* lengths) = 0; + virtual void handleClipboardRequest(uint32_t flags) = 0; + virtual void handleClipboardPeek() = 0; + virtual void handleClipboardNotify(uint32_t flags) = 0; virtual void handleClipboardProvide(uint32_t flags, const size_t* lengths, - const uint8_t* const* data); + const uint8_t* const* data) = 0; ServerParams server; }; diff --git a/common/rfb/CSecurityTLS.cxx b/common/rfb/CSecurityTLS.cxx index 44f738b4..6eefe73b 100644 --- a/common/rfb/CSecurityTLS.cxx +++ b/common/rfb/CSecurityTLS.cxx @@ -3,7 +3,7 @@ * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team * Copyright (C) 2010 m-privacy GmbH - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,8 +43,7 @@ #include <rfb/Exception.h> #include <rdr/TLSException.h> -#include <rdr/TLSInStream.h> -#include <rdr/TLSOutStream.h> +#include <rdr/TLSSocket.h> #include <gnutls/x509.h> @@ -75,7 +74,7 @@ static const char* configdirfn(const char* fn) CSecurityTLS::CSecurityTLS(CConnection* cc_, bool _anon) : CSecurity(cc_), session(nullptr), anon_cred(nullptr), cert_cred(nullptr), - anon(_anon), tlsis(nullptr), tlsos(nullptr), + anon(_anon), tlssock(nullptr), rawis(nullptr), rawos(nullptr) { int err = gnutls_global_init(); @@ -85,27 +84,8 @@ CSecurityTLS::CSecurityTLS(CConnection* cc_, bool _anon) void CSecurityTLS::shutdown() { - if (tlsos) { - try { - if (tlsos->hasBufferedData()) { - tlsos->cork(false); - tlsos->flush(); - if (tlsos->hasBufferedData()) - vlog.error("Failed to flush remaining socket data on close"); - } - } catch (std::exception& e) { - vlog.error("Failed to flush remaining socket data on close: %s", e.what()); - } - } - - if (session) { - int ret; - // FIXME: We can't currently wait for the response, so we only send - // our close and hope for the best - ret = gnutls_bye(session, GNUTLS_SHUT_WR); - if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION)) - vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret)); - } + if (tlssock) + tlssock->shutdown(); if (anon_cred) { gnutls_anon_free_client_credentials(anon_cred); @@ -123,13 +103,9 @@ void CSecurityTLS::shutdown() rawos = nullptr; } - if (tlsis) { - delete tlsis; - tlsis = nullptr; - } - if (tlsos) { - delete tlsos; - tlsos = nullptr; + if (tlssock) { + delete tlssock; + tlssock = nullptr; } if (session) { @@ -171,26 +147,18 @@ bool CSecurityTLS::processMsg() setParam(); - // Create these early as they set up the push/pull functions - // for GnuTLS - tlsis = new rdr::TLSInStream(is, session); - tlsos = new rdr::TLSOutStream(os, session); + tlssock = new rdr::TLSSocket(is, os, session); rawis = is; rawos = os; } - int err; - err = gnutls_handshake(session); - if (err != GNUTLS_E_SUCCESS) { - if (!gnutls_error_is_fatal(err)) { - vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err)); + try { + if (!tlssock->handshake()) return false; - } - - vlog.error("TLS Handshake failed: %s\n", gnutls_strerror (err)); + } catch (std::exception&) { shutdown(); - throw rdr::tls_error("TLS Handshake failed", err); + throw; } vlog.debug("TLS handshake completed with %s", @@ -198,33 +166,29 @@ bool CSecurityTLS::processMsg() checkSession(); - cc->setStreams(tlsis, tlsos); + cc->setStreams(&tlssock->inStream(), &tlssock->outStream()); return true; } void CSecurityTLS::setParam() { - static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH"; + static const char kx_anon_priority[] = "+ANON-ECDH:+ANON-DH"; int ret; // Custom priority string specified? if (strcmp(Security::GnuTLSPriority, "") != 0) { - char *prio; + std::string prio; const char *err; - prio = new char[strlen(Security::GnuTLSPriority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, Security::GnuTLSPriority); - if (anon) - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = (const char*)Security::GnuTLSPriority; + if (anon) { + prio += ":"; + prio += kx_anon_priority; + } + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); @@ -234,30 +198,22 @@ void CSecurityTLS::setParam() const char *err; #if GNUTLS_VERSION_NUMBER >= 0x030603 - // gnutls_set_default_priority_appends() expects a normal priority string that - // doesn't start with ":". - ret = gnutls_set_default_priority_append(session, kx_anon_priority + 1, &err, 0); + ret = gnutls_set_default_priority_append(session, kx_anon_priority, &err, 0); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); throw rdr::tls_error("gnutls_set_default_priority_append()", ret); } #else + std::string prio; + // We don't know what the system default priority is, so we guess // it's what upstream GnuTLS has - static const char gnutls_default_priority[] = "NORMAL"; - char *prio; - - prio = new char[malloc(strlen(gnutls_default_priority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, gnutls_default_priority); - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = "NORMAL"; + prio += ":"; + prio += kx_anon_priority; + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); @@ -277,6 +233,10 @@ void CSecurityTLS::setParam() vlog.debug("Anonymous session has been set"); } else { + const char* hostname; + size_t len; + bool valid; + ret = gnutls_certificate_allocate_credentials(&cert_cred); if (ret != GNUTLS_E_SUCCESS) throw rdr::tls_error("gnutls_certificate_allocate_credentials()", ret); @@ -294,10 +254,22 @@ void CSecurityTLS::setParam() if (ret != GNUTLS_E_SUCCESS) throw rdr::tls_error("gnutls_credentials_set()", ret); - if (gnutls_server_name_set(session, GNUTLS_NAME_DNS, - client->getServerName(), - strlen(client->getServerName())) != GNUTLS_E_SUCCESS) - vlog.error("Failed to configure the server name for TLS handshake"); + // Only DNS hostnames are allowed, and some servers will reject the + // connection if we provide anything else (e.g. an IPv6 address) + hostname = client->getServerName(); + len = strlen(hostname); + valid = true; + for (size_t i = 0; i < len; i++) { + if (!isalnum(hostname[i]) && hostname[i] != '.') + valid = false; + } + + if (valid) { + if (gnutls_server_name_set(session, GNUTLS_NAME_DNS, + client->getServerName(), + strlen(client->getServerName())) != GNUTLS_E_SUCCESS) + vlog.error("Failed to configure the server name for TLS handshake"); + } vlog.debug("X509 session has been set"); } @@ -324,12 +296,16 @@ void CSecurityTLS::checkSession() if (anon) return; - if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) + if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNSUPPORTED_CERTIFICATE); throw protocol_error("Unsupported certificate type"); + } err = gnutls_certificate_verify_peers2(session, &status); if (err != 0) { vlog.error("Server certificate verification failed: %s", gnutls_strerror(err)); + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Server certificate verification()", err); } @@ -346,13 +322,17 @@ void CSecurityTLS::checkSession() GNUTLS_CRT_X509, &status_str, 0); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Failed to get certificate error description", err); + } error = (const char*)status_str.data; gnutls_free(status_str.data); + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw protocol_error( core::format("Invalid server certificate: %s", error.c_str())); } @@ -361,8 +341,10 @@ void CSecurityTLS::checkSession() GNUTLS_CRT_X509, &status_str, 0); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Failed to get certificate error description", err); + } vlog.info("Server certificate errors: %s", status_str.data); @@ -372,16 +354,21 @@ void CSecurityTLS::checkSession() /* Process overridable errors later */ cert_list = gnutls_certificate_get_peers(session, &cert_list_size); - if (!cert_list_size) + if (!cert_list_size) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNSUPPORTED_CERTIFICATE); throw protocol_error("Empty certificate chain"); + } /* Process only server's certificate, not issuer's certificate */ gnutls_x509_crt_t crt; gnutls_x509_crt_init(&crt); err = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, err); throw rdr::tls_error("Failed to decode server certificate", err); + } if (gnutls_x509_crt_check_hostname(crt, client->getServerName()) == 0) { vlog.info("Server certificate doesn't match given server name"); @@ -420,12 +407,15 @@ void CSecurityTLS::checkSession() if ((known != GNUTLS_E_NO_CERTIFICATE_FOUND) && (known != GNUTLS_E_CERTIFICATE_KEY_MISMATCH)) { + gnutls_alert_send_appropriate(session, known); throw rdr::tls_error("Could not load known hosts database", known); } err = gnutls_x509_crt_print(crt, GNUTLS_CRT_PRINT_ONELINE, &info); - if (err != GNUTLS_E_SUCCESS) + if (err != GNUTLS_E_SUCCESS) { + gnutls_alert_send_appropriate(session, known); throw rdr::tls_error("Could not find certificate to display", err); + } len = strlen((char*)info.data); for (size_t i = 0; i < len - 1; i++) { @@ -456,8 +446,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unknown certificate issuer", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNKNOWN_CA); throw auth_cancelled(); + } status &= ~(GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND | @@ -478,8 +471,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Certificate is not yet valid", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_NOT_ACTIVATED; } @@ -498,8 +494,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Expired certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_EXPIRED; } @@ -518,14 +517,19 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Insecure certificate algorithm", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_INSECURE_ALGORITHM; } if (status != 0) { vlog.error("Unhandled certificate problems: 0x%x", status); + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw std::logic_error("Unhandled certificate problems"); } @@ -544,8 +548,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Certificate hostname mismatch", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } } } else if (known == GNUTLS_E_CERTIFICATE_KEY_MISMATCH) { std::string text; @@ -571,8 +578,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_UNKNOWN_CA); throw auth_cancelled(); + } status &= ~(GNUTLS_CERT_INVALID | GNUTLS_CERT_SIGNER_NOT_FOUND | @@ -594,8 +604,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_NOT_ACTIVATED; } @@ -615,8 +628,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_EXPIRED; } @@ -636,14 +652,19 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } status &= ~GNUTLS_CERT_INSECURE_ALGORITHM; } if (status != 0) { vlog.error("Unhandled certificate problems: 0x%x", status); + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw std::logic_error("Unhandled certificate problems"); } @@ -663,8 +684,11 @@ void CSecurityTLS::checkSession() if (!cc->showMsgBox(MsgBoxFlags::M_YESNO, "Unexpected server certificate", - text.c_str())) + text.c_str())) { + gnutls_alert_send(session, GNUTLS_AL_FATAL, + GNUTLS_A_BAD_CERTIFICATE); throw auth_cancelled(); + } } } diff --git a/common/rfb/CSecurityTLS.h b/common/rfb/CSecurityTLS.h index 9b70366d..51b7dac1 100644 --- a/common/rfb/CSecurityTLS.h +++ b/common/rfb/CSecurityTLS.h @@ -2,6 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,8 +35,7 @@ namespace rdr { class InStream; class OutStream; - class TLSInStream; - class TLSOutStream; + class TLSSocket; } namespace rfb { @@ -63,8 +63,7 @@ namespace rfb { gnutls_certificate_credentials_t cert_cred; bool anon; - rdr::TLSInStream* tlsis; - rdr::TLSOutStream* tlsos; + rdr::TLSSocket* tlssock; rdr::InStream* rawis; rdr::OutStream* rawos; diff --git a/common/rfb/ClientParams.cxx b/common/rfb/ClientParams.cxx index 972b89e8..514b0b4e 100644 --- a/common/rfb/ClientParams.cxx +++ b/common/rfb/ClientParams.cxx @@ -24,6 +24,7 @@ #include <stdexcept> +#include <core/LogWriter.h> #include <core/string.h> #include <rfb/encodings.h> @@ -35,6 +36,8 @@ using namespace rfb; +static core::LogWriter vlog("ClientParams"); + ClientParams::ClientParams() : majorVersion(0), minorVersion(0), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), @@ -72,8 +75,14 @@ void ClientParams::setDimensions(int width, int height) void ClientParams::setDimensions(int width, int height, const ScreenSet& layout) { - if (!layout.validate(width, height)) + if (!layout.validate(width, height)) { + char buffer[2048]; + vlog.debug("Invalid screen layout for %dx%d:", width, height); + layout.print(buffer, sizeof(buffer)); + vlog.debug("%s", buffer); + throw std::invalid_argument("Attempted to configure an invalid screen layout"); + } width_ = width; height_ = height; @@ -247,4 +256,4 @@ bool ClientParams::supportsExtendedMouseButtons() const if (supportsEncoding(pseudoEncodingExtendedMouseButtons)) return true; return false; -}
\ No newline at end of file +} diff --git a/common/rfb/DecodeManager.cxx b/common/rfb/DecodeManager.cxx index 94908f86..48181f94 100644 --- a/common/rfb/DecodeManager.cxx +++ b/common/rfb/DecodeManager.cxx @@ -24,7 +24,6 @@ #include <string.h> #include <core/LogWriter.h> -#include <core/Mutex.h> #include <core/Region.h> #include <core/string.h> @@ -48,11 +47,7 @@ DecodeManager::DecodeManager(CConnection *conn_) : memset(stats, 0, sizeof(stats)); - queueMutex = new core::Mutex(); - producerCond = new core::Condition(queueMutex); - consumerCond = new core::Condition(queueMutex); - - cpuCount = core::Thread::getSystemCPUCount(); + cpuCount = std::thread::hardware_concurrency(); if (cpuCount == 0) { vlog.error("Unable to determine the number of CPU cores on this system"); cpuCount = 1; @@ -90,10 +85,6 @@ DecodeManager::~DecodeManager() freeBuffers.pop_back(); } - delete consumerCond; - delete producerCond; - delete queueMutex; - for (Decoder* decoder : decoders) delete decoder; } @@ -125,29 +116,25 @@ bool DecodeManager::decodeRect(const core::Rect& r, int encoding, decoder = decoders[encoding]; // Wait for an available memory buffer - queueMutex->lock(); + std::unique_lock<std::mutex> lock(queueMutex); // FIXME: Should we return and let other things run here? while (freeBuffers.empty()) - producerCond->wait(); + producerCond.wait(lock); // Don't pop the buffer in case we throw an exception // whilst reading bufferStream = freeBuffers.front(); - queueMutex->unlock(); + lock.unlock(); // First check if any thread has encountered a problem throwThreadException(); // Read the rect bufferStream->clear(); - try { - if (!decoder->readRect(r, conn->getInStream(), conn->server, bufferStream)) - return false; - } catch (std::exception& e) { - throw std::runtime_error(core::format("Error reading rect: %s", e.what())); - } + if (!decoder->readRect(r, conn->getInStream(), conn->server, bufferStream)) + return false; stats[encoding].rects++; stats[encoding].bytes += 12 + bufferStream->length(); @@ -170,7 +157,7 @@ bool DecodeManager::decodeRect(const core::Rect& r, int encoding, bufferStream->length(), conn->server, &entry->affectedRegion); - queueMutex->lock(); + lock.lock(); // The workers add buffers to the end so it's safe to assume // the front is still the same buffer @@ -180,21 +167,21 @@ bool DecodeManager::decodeRect(const core::Rect& r, int encoding, // We only put a single entry on the queue so waking a single // thread is sufficient - consumerCond->signal(); + consumerCond.notify_one(); - queueMutex->unlock(); + lock.unlock(); return true; } void DecodeManager::flush() { - queueMutex->lock(); + std::unique_lock<std::mutex> lock(queueMutex); while (!workQueue.empty()) - producerCond->wait(); + producerCond.wait(lock); - queueMutex->unlock(); + lock.unlock(); throwThreadException(); } @@ -242,7 +229,7 @@ void DecodeManager::logStats() void DecodeManager::setThreadException() { - core::AutoMutex a(queueMutex); + const std::lock_guard<std::mutex> lock(queueMutex); if (threadException) return; @@ -252,7 +239,7 @@ void DecodeManager::setThreadException() void DecodeManager::throwThreadException() { - core::AutoMutex a(queueMutex); + const std::lock_guard<std::mutex> lock(queueMutex); if (!threadException) return; @@ -266,7 +253,7 @@ void DecodeManager::throwThreadException() } DecodeManager::DecodeThread::DecodeThread(DecodeManager* manager_) - : manager(manager_), stopRequested(false) + : manager(manager_), thread(nullptr), stopRequested(false) { start(); } @@ -274,25 +261,35 @@ DecodeManager::DecodeThread::DecodeThread(DecodeManager* manager_) DecodeManager::DecodeThread::~DecodeThread() { stop(); - wait(); + if (thread != nullptr) { + thread->join(); + delete thread; + } +} + +void DecodeManager::DecodeThread::start() +{ + assert(thread == nullptr); + + thread = new std::thread(&DecodeThread::worker, this); } void DecodeManager::DecodeThread::stop() { - core::AutoMutex a(manager->queueMutex); + const std::lock_guard<std::mutex> lock(manager->queueMutex); - if (!isRunning()) + if (thread == nullptr) return; stopRequested = true; // We can't wake just this thread, so wake everyone - manager->consumerCond->broadcast(); + manager->consumerCond.notify_all(); } void DecodeManager::DecodeThread::worker() { - manager->queueMutex->lock(); + std::unique_lock<std::mutex> lock(manager->queueMutex); while (!stopRequested) { DecodeManager::QueueEntry *entry; @@ -301,14 +298,14 @@ void DecodeManager::DecodeThread::worker() entry = findEntry(); if (entry == nullptr) { // Wait and try again - manager->consumerCond->wait(); + manager->consumerCond.wait(lock); continue; } // This is ours now entry->active = true; - manager->queueMutex->unlock(); + lock.unlock(); // Do the actual decoding try { @@ -321,7 +318,7 @@ void DecodeManager::DecodeThread::worker() assert(false); } - manager->queueMutex->lock(); + lock.lock(); // Remove the entry from the queue and give back the memory buffer manager->freeBuffers.push_back(entry->bufferStream); @@ -329,14 +326,12 @@ void DecodeManager::DecodeThread::worker() delete entry; // Wake the main thread in case it is waiting for a memory buffer - manager->producerCond->signal(); + manager->producerCond.notify_one(); // This rect might have been blocking multiple other rects, so // wake up every worker thread if (manager->workQueue.size() > 1) - manager->consumerCond->broadcast(); + manager->consumerCond.notify_all(); } - - manager->queueMutex->unlock(); } DecodeManager::QueueEntry* DecodeManager::DecodeThread::findEntry() diff --git a/common/rfb/DecodeManager.h b/common/rfb/DecodeManager.h index 95d3ceca..146bf8ae 100644 --- a/common/rfb/DecodeManager.h +++ b/common/rfb/DecodeManager.h @@ -19,17 +19,17 @@ #ifndef __RFB_DECODEMANAGER_H__ #define __RFB_DECODEMANAGER_H__ +#include <condition_variable> #include <exception> #include <list> +#include <mutex> +#include <thread> #include <core/Region.h> -#include <core/Thread.h> #include <rfb/encodings.h> namespace core { - class Condition; - class Mutex; struct Rect; } @@ -86,25 +86,27 @@ namespace rfb { std::list<rdr::MemOutStream*> freeBuffers; std::list<QueueEntry*> workQueue; - core::Mutex* queueMutex; - core::Condition* producerCond; - core::Condition* consumerCond; + std::mutex queueMutex; + std::condition_variable producerCond; + std::condition_variable consumerCond; private: - class DecodeThread : public core::Thread { + class DecodeThread { public: DecodeThread(DecodeManager* manager); ~DecodeThread(); + void start(); void stop(); protected: - void worker() override; + void worker(); DecodeManager::QueueEntry* findEntry(); private: DecodeManager* manager; + std::thread* thread; bool stopRequested; }; diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index 7ec70e69..6a63fa6f 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -278,6 +278,13 @@ void EncodeManager::pruneLosslessRefresh(const core::Region& limits) pendingRefreshRegion.assign_intersect(limits); } +void EncodeManager::forceRefresh(const core::Region& req) +{ + lossyRegion.assign_union(req); + if (!recentChangeTimer.isStarted()) + pendingRefreshRegion.assign_union(req); +} + void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, const RenderedCursor* renderedCursor) { diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h index 959c13d6..4ce6d0ce 100644 --- a/common/rfb/EncodeManager.h +++ b/common/rfb/EncodeManager.h @@ -54,6 +54,8 @@ namespace rfb { void pruneLosslessRefresh(const core::Region& limits); + void forceRefresh(const core::Region& req); + void writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, const RenderedCursor* renderedCursor); diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index c269fac0..c698b991 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -154,8 +154,6 @@ bool SConnection::processVersionMsg() client.majorVersion,client.minorVersion); } - versionReceived(); - std::list<uint8_t> secTypes; std::list<uint8_t>::iterator i; secTypes = security.GetEnabledSecTypes(); @@ -346,6 +344,8 @@ bool SConnection::accessCheck(AccessRights ar) const void SConnection::setEncodings(int nEncodings, const int32_t* encodings) { int i; + bool firstFence, firstContinuousUpdates, firstLEDState, + firstQEMUKeyEvent, firstExtMouseButtonsEvent; preferredEncoding = encodingRaw; for (i = 0;i < nEncodings;i++) { @@ -355,7 +355,26 @@ void SConnection::setEncodings(int nEncodings, const int32_t* encodings) } } - SMsgHandler::setEncodings(nEncodings, encodings); + firstFence = !client.supportsFence(); + firstContinuousUpdates = !client.supportsContinuousUpdates(); + firstLEDState = !client.supportsLEDState(); + firstQEMUKeyEvent = !client.supportsEncoding(pseudoEncodingQEMUKeyEvent); + firstExtMouseButtonsEvent = !client.supportsEncoding(pseudoEncodingExtendedMouseButtons); + + client.setEncodings(nEncodings, encodings); + + supportsLocalCursor(); + + if (client.supportsFence() && firstFence) + supportsFence(); + if (client.supportsContinuousUpdates() && firstContinuousUpdates) + supportsContinuousUpdates(); + if (client.supportsLEDState() && firstLEDState) + supportsLEDState(); + if (client.supportsEncoding(pseudoEncodingQEMUKeyEvent) && firstQEMUKeyEvent) + writer()->writeQEMUKeyEvent(); + if (client.supportsEncoding(pseudoEncodingExtendedMouseButtons) && firstExtMouseButtonsEvent) + writer()->writeExtendedMouseButtonsSupport(); if (client.supportsEncoding(pseudoEncodingExtendedClipboard)) { uint32_t sizes[] = { 0 }; @@ -375,9 +394,54 @@ void SConnection::clientCutText(const char* str) clientClipboard = str; hasRemoteClipboard = true; + if (!accessCheck(AccessCutText)) + return; + handleClipboardAnnounce(true); } +void SConnection::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) +{ + int i; + + vlog.debug("Got client clipboard capabilities:"); + for (i = 0;i < 16;i++) { + if (flags & (1 << i)) { + const char *type; + + switch (1 << i) { + case clipboardUTF8: + type = "Plain text"; + break; + case clipboardRTF: + type = "Rich text"; + break; + case clipboardHTML: + type = "HTML"; + break; + case clipboardDIB: + type = "Images"; + break; + case clipboardFiles: + type = "Files"; + break; + default: + vlog.debug(" Unknown format 0x%x", 1 << i); + continue; + } + + if (lengths[i] == 0) + vlog.debug(" %s (only notify)", type); + else { + vlog.debug(" %s (automatically send up to %s)", + type, core::iecPrefix(lengths[i], "B").c_str()); + } + } + } + + client.setClipboardCaps(flags, lengths); +} + void SConnection::handleClipboardRequest(uint32_t flags) { if (!(flags & rfb::clipboardUTF8)) { @@ -388,6 +452,8 @@ void SConnection::handleClipboardRequest(uint32_t flags) vlog.debug("Ignoring unexpected clipboard request"); return; } + if (!accessCheck(AccessCutText)) + return; handleClipboardRequest(); } @@ -403,10 +469,15 @@ void SConnection::handleClipboardNotify(uint32_t flags) if (flags & rfb::clipboardUTF8) { hasLocalClipboard = false; + if (!accessCheck(AccessCutText)) + return; handleClipboardAnnounce(true); } else { + if (!accessCheck(AccessCutText)) + return; handleClipboardAnnounce(false); } + } void SConnection::handleClipboardProvide(uint32_t flags, @@ -426,21 +497,26 @@ void SConnection::handleClipboardProvide(uint32_t flags, clientClipboard = core::convertLF((const char*)data[0], lengths[0]); hasRemoteClipboard = true; + if (!accessCheck(AccessCutText)) + return; + // FIXME: Should probably verify that this data was actually requested handleClipboardData(clientClipboard.c_str()); } -void SConnection::supportsQEMUKeyEvent() +void SConnection::supportsLocalCursor() +{ +} + +void SConnection::supportsFence() { - writer()->writeQEMUKeyEvent(); } -void SConnection::supportsExtendedMouseButtons() +void SConnection::supportsContinuousUpdates() { - writer()->writeExtendedMouseButtonsSupport(); } -void SConnection::versionReceived() +void SConnection::supportsLEDState() { } @@ -502,7 +578,7 @@ void SConnection::close(const char* /*reason*/) void SConnection::setPixelFormat(const PixelFormat& pf) { - SMsgHandler::setPixelFormat(pf); + client.setPF(pf); readyForSetColourMapEntries = true; if (!pf.trueColour) writeFakeColourMap(); @@ -551,6 +627,9 @@ void SConnection::handleClipboardData(const char* /*data*/) void SConnection::requestClipboard() { + if (!accessCheck(AccessCutText)) + return; + if (hasRemoteClipboard) { handleClipboardData(clientClipboard.c_str()); return; @@ -563,6 +642,9 @@ void SConnection::requestClipboard() void SConnection::announceClipboard(bool available) { + if (!accessCheck(AccessCutText)) + return; + hasLocalClipboard = available; unsolicitedClipboardAttempt = false; @@ -589,6 +671,9 @@ void SConnection::announceClipboard(bool available) void SConnection::sendClipboardData(const char* data) { + if (!accessCheck(AccessCutText)) + return; + if (client.supportsEncoding(pseudoEncodingExtendedClipboard) && (client.clipboardFlags() & rfb::clipboardProvide)) { // FIXME: This conversion magic should be in SMsgWriter diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 0f4de5a5..a90b37ca 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -87,6 +87,59 @@ namespace rfb { // cleanup of the SConnection object by the server virtual void close(const char* reason); + // requestClipboard() will result in a request to the client to + // transfer its clipboard data. A call to handleClipboardData() + // will be made once the data is available. + virtual void requestClipboard(); + + // announceClipboard() informs the client of changes to the + // clipboard on the server. The client may later request the + // clipboard data via handleClipboardRequest(). + virtual void announceClipboard(bool available); + + // sendClipboardData() transfers the clipboard data to the client + // and should be called whenever the client has requested the + // clipboard via handleClipboardRequest(). + virtual void sendClipboardData(const char* data); + + // getAccessRights() returns the access rights of a SConnection to the server. + AccessRights getAccessRights() { return accessRights; } + + // setAccessRights() allows a security package to limit the access rights + // of a SConnection to the server. How the access rights are treated + // is up to the derived class. + virtual void setAccessRights(AccessRights ar); + virtual bool accessCheck(AccessRights ar) const; + + // authenticated() returns true if the client has authenticated + // successfully. + bool authenticated() { return (state_ == RFBSTATE_INITIALISATION || + state_ == RFBSTATE_NORMAL); } + + SMsgReader* reader() { return reader_; } + SMsgWriter* writer() { return writer_; } + + rdr::InStream* getInStream() { return is; } + rdr::OutStream* getOutStream() { return os; } + + enum stateEnum { + RFBSTATE_UNINITIALISED, + RFBSTATE_PROTOCOL_VERSION, + RFBSTATE_SECURITY_TYPE, + RFBSTATE_SECURITY, + RFBSTATE_SECURITY_FAILURE, + RFBSTATE_QUERYING, + RFBSTATE_INITIALISATION, + RFBSTATE_NORMAL, + RFBSTATE_CLOSING, + RFBSTATE_INVALID + }; + + stateEnum state() { return state_; } + + int32_t getPreferredEncoding() { return preferredEncoding; } + + protected: // Overridden from SMsgHandler @@ -94,23 +147,38 @@ namespace rfb { void clientCutText(const char* str) override; + void handleClipboardCaps(uint32_t flags, + const uint32_t* lengths) override; void handleClipboardRequest(uint32_t flags) override; void handleClipboardPeek() override; void handleClipboardNotify(uint32_t flags) override; void handleClipboardProvide(uint32_t flags, const size_t* lengths, const uint8_t* const* data) override; - void supportsQEMUKeyEvent() override; - - virtual void supportsExtendedMouseButtons() override; - - // Methods to be overridden in a derived class - // versionReceived() indicates that the version number has just been read - // from the client. The version will already have been "cooked" - // to deal with unknown/bogus viewer protocol numbers. - virtual void versionReceived(); + // supportsLocalCursor() is called whenever the status of + // cp.supportsLocalCursor has changed. At the moment this happens on a + // setEncodings message, but in the future this may be due to a message + // specially for this purpose. + virtual void supportsLocalCursor(); + + // supportsFence() is called the first time we detect support for fences + // in the client. A fence message should be sent at this point to notify + // the client of server support. + virtual void supportsFence(); + + // supportsContinuousUpdates() is called the first time we detect that + // the client wants the continuous updates extension. A + // EndOfContinuousUpdates message should be sent back to the client at + // this point if it is supported. + virtual void supportsContinuousUpdates(); + + // supportsLEDState() is called the first time we detect that the + // client supports the LED state extension. A LEDState message + // should be sent back to the client to inform it of the current + // server state. + virtual void supportsLEDState(); // authSuccess() is called when authentication has succeeded. virtual void authSuccess(); @@ -166,62 +234,6 @@ namespace rfb { // client received the request. virtual void handleClipboardData(const char* data); - - // Other methods - - // requestClipboard() will result in a request to the client to - // transfer its clipboard data. A call to handleClipboardData() - // will be made once the data is available. - virtual void requestClipboard(); - - // announceClipboard() informs the client of changes to the - // clipboard on the server. The client may later request the - // clipboard data via handleClipboardRequest(). - virtual void announceClipboard(bool available); - - // sendClipboardData() transfers the clipboard data to the client - // and should be called whenever the client has requested the - // clipboard via handleClipboardRequest(). - virtual void sendClipboardData(const char* data); - - // getAccessRights() returns the access rights of a SConnection to the server. - AccessRights getAccessRights() { return accessRights; } - - // setAccessRights() allows a security package to limit the access rights - // of a SConnection to the server. How the access rights are treated - // is up to the derived class. - virtual void setAccessRights(AccessRights ar); - virtual bool accessCheck(AccessRights ar) const; - - // authenticated() returns true if the client has authenticated - // successfully. - bool authenticated() { return (state_ == RFBSTATE_INITIALISATION || - state_ == RFBSTATE_NORMAL); } - - SMsgReader* reader() { return reader_; } - SMsgWriter* writer() { return writer_; } - - rdr::InStream* getInStream() { return is; } - rdr::OutStream* getOutStream() { return os; } - - enum stateEnum { - RFBSTATE_UNINITIALISED, - RFBSTATE_PROTOCOL_VERSION, - RFBSTATE_SECURITY_TYPE, - RFBSTATE_SECURITY, - RFBSTATE_SECURITY_FAILURE, - RFBSTATE_QUERYING, - RFBSTATE_INITIALISATION, - RFBSTATE_NORMAL, - RFBSTATE_CLOSING, - RFBSTATE_INVALID - }; - - stateEnum state() { return state_; } - - int32_t getPreferredEncoding() { return preferredEncoding; } - - protected: // failConnection() prints a message to the log, sends a connection // failed message to the client (if possible) and throws an // Exception. diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx deleted file mode 100644 index 9eb5ae08..00000000 --- a/common/rfb/SMsgHandler.cxx +++ /dev/null @@ -1,176 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2019 Pierre Ossman for Cendio AB - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <core/LogWriter.h> -#include <core/string.h> - -#include <rfb/SMsgHandler.h> -#include <rfb/ScreenSet.h> -#include <rfb/clipboardTypes.h> -#include <rfb/encodings.h> - -using namespace rfb; - -static core::LogWriter vlog("SMsgHandler"); - -SMsgHandler::SMsgHandler() -{ -} - -SMsgHandler::~SMsgHandler() -{ -} - -void SMsgHandler::clientInit(bool /*shared*/) -{ -} - -void SMsgHandler::setPixelFormat(const PixelFormat& pf) -{ - client.setPF(pf); -} - -void SMsgHandler::setEncodings(int nEncodings, const int32_t* encodings) -{ - bool firstFence, firstContinuousUpdates, firstLEDState, - firstQEMUKeyEvent, firstExtMouseButtonsEvent; - - firstFence = !client.supportsFence(); - firstContinuousUpdates = !client.supportsContinuousUpdates(); - firstLEDState = !client.supportsLEDState(); - firstQEMUKeyEvent = !client.supportsEncoding(pseudoEncodingQEMUKeyEvent); - firstExtMouseButtonsEvent = !client.supportsEncoding(pseudoEncodingExtendedMouseButtons); - - client.setEncodings(nEncodings, encodings); - - supportsLocalCursor(); - - if (client.supportsFence() && firstFence) - supportsFence(); - if (client.supportsContinuousUpdates() && firstContinuousUpdates) - supportsContinuousUpdates(); - if (client.supportsLEDState() && firstLEDState) - supportsLEDState(); - if (client.supportsEncoding(pseudoEncodingQEMUKeyEvent) && firstQEMUKeyEvent) - supportsQEMUKeyEvent(); - if (client.supportsEncoding(pseudoEncodingExtendedMouseButtons) && firstExtMouseButtonsEvent) - supportsExtendedMouseButtons(); -} - -void SMsgHandler::keyEvent(uint32_t /*keysym*/, uint32_t /*keycode*/, - bool /*down*/) -{ -} - -void SMsgHandler::pointerEvent(const core::Point& /*pos*/, - uint16_t /*buttonMask*/) -{ -} - -void SMsgHandler::clientCutText(const char* /*str*/) -{ -} - -void SMsgHandler::handleClipboardCaps(uint32_t flags, const uint32_t* lengths) -{ - int i; - - vlog.debug("Got client clipboard capabilities:"); - for (i = 0;i < 16;i++) { - if (flags & (1 << i)) { - const char *type; - - switch (1 << i) { - case clipboardUTF8: - type = "Plain text"; - break; - case clipboardRTF: - type = "Rich text"; - break; - case clipboardHTML: - type = "HTML"; - break; - case clipboardDIB: - type = "Images"; - break; - case clipboardFiles: - type = "Files"; - break; - default: - vlog.debug(" Unknown format 0x%x", 1 << i); - continue; - } - - if (lengths[i] == 0) - vlog.debug(" %s (only notify)", type); - else { - vlog.debug(" %s (automatically send up to %s)", - type, core::iecPrefix(lengths[i], "B").c_str()); - } - } - } - - client.setClipboardCaps(flags, lengths); -} - -void SMsgHandler::handleClipboardRequest(uint32_t /*flags*/) -{ -} - -void SMsgHandler::handleClipboardPeek() -{ -} - -void SMsgHandler::handleClipboardNotify(uint32_t /*flags*/) -{ -} - -void SMsgHandler::handleClipboardProvide(uint32_t /*flags*/, - const size_t* /*lengths*/, - const uint8_t* const* /*data*/) -{ -} - -void SMsgHandler::supportsLocalCursor() -{ -} - -void SMsgHandler::supportsFence() -{ -} - -void SMsgHandler::supportsContinuousUpdates() -{ -} - -void SMsgHandler::supportsLEDState() -{ -} - -void SMsgHandler::supportsQEMUKeyEvent() -{ -} - -void SMsgHandler::supportsExtendedMouseButtons() -{ -}
\ No newline at end of file diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index f5f5b769..d14a21c0 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -27,80 +27,45 @@ #include <rfb/ClientParams.h> -namespace rdr { class InStream; } - namespace rfb { class SMsgHandler { public: - SMsgHandler(); - virtual ~SMsgHandler(); + virtual ~SMsgHandler() {} - // The following methods are called as corresponding messages are read. A - // derived class should override these methods as desired. Note that for - // the setPixelFormat(), setEncodings() and clipboardCaps() methods, a - // derived class must call on to SMsgHandler's methods. + // The following methods are called as corresponding messages are + // read. A derived class must override these methods. - virtual void clientInit(bool shared); + virtual void clientInit(bool shared) = 0; - virtual void setPixelFormat(const PixelFormat& pf); - virtual void setEncodings(int nEncodings, const int32_t* encodings); + virtual void setPixelFormat(const PixelFormat& pf) = 0; + virtual void setEncodings(int nEncodings, + const int32_t* encodings) = 0; virtual void framebufferUpdateRequest(const core::Rect& r, bool incremental) = 0; virtual void setDesktopSize(int fb_width, int fb_height, const ScreenSet& layout) = 0; - virtual void fence(uint32_t flags, unsigned len, const uint8_t data[]) = 0; + virtual void fence(uint32_t flags, unsigned len, + const uint8_t data[]) = 0; virtual void enableContinuousUpdates(bool enable, - int x, int y, int w, int h) = 0; + int x, int y, + int w, int h) = 0; virtual void keyEvent(uint32_t keysym, uint32_t keycode, - bool down); + bool down) = 0; virtual void pointerEvent(const core::Point& pos, - uint16_t buttonMask); + uint16_t buttonMask) = 0; - virtual void clientCutText(const char* str); + virtual void clientCutText(const char* str) = 0; virtual void handleClipboardCaps(uint32_t flags, - const uint32_t* lengths); - virtual void handleClipboardRequest(uint32_t flags); - virtual void handleClipboardPeek(); - virtual void handleClipboardNotify(uint32_t flags); + const uint32_t* lengths) = 0; + virtual void handleClipboardRequest(uint32_t flags) = 0; + virtual void handleClipboardPeek() = 0; + virtual void handleClipboardNotify(uint32_t flags) = 0; virtual void handleClipboardProvide(uint32_t flags, const size_t* lengths, - const uint8_t* const* data); - - // supportsLocalCursor() is called whenever the status of - // cp.supportsLocalCursor has changed. At the moment this happens on a - // setEncodings message, but in the future this may be due to a message - // specially for this purpose. - virtual void supportsLocalCursor(); - - // supportsFence() is called the first time we detect support for fences - // in the client. A fence message should be sent at this point to notify - // the client of server support. - virtual void supportsFence(); - - // supportsContinuousUpdates() is called the first time we detect that - // the client wants the continuous updates extension. A - // EndOfContinuousUpdates message should be sent back to the client at - // this point if it is supported. - virtual void supportsContinuousUpdates(); - - // supportsLEDState() is called the first time we detect that the - // client supports the LED state extension. A LEDState message - // should be sent back to the client to inform it of the current - // server state. - virtual void supportsLEDState(); - - // supportsQEMUKeyEvent() is called the first time we detect that the - // client wants the QEMU Extended Key Event extension. The default - // handler will send a pseudo-rect back, signalling server support. - virtual void supportsQEMUKeyEvent(); - - // supportsExtendedMouseButtons() is called the first time we detect that the - // client supports sending 16 bit mouse button state. This lets us pass more button - // states between server and client. - virtual void supportsExtendedMouseButtons(); + const uint8_t* const* data) = 0; ClientParams client; }; diff --git a/common/rfb/SSecurityPlain.cxx b/common/rfb/SSecurityPlain.cxx index 06631f81..4fa63250 100644 --- a/common/rfb/SSecurityPlain.cxx +++ b/common/rfb/SSecurityPlain.cxx @@ -113,8 +113,9 @@ bool SSecurityPlain::processMsg() password[plen] = 0; username[ulen] = 0; plen = 0; - if (!valid->validate(sc, username, password)) - throw auth_error("Authentication failed"); + std::string msg = "Authentication failed"; + if (!valid->validate(sc, username, password, msg)) + throw auth_error(msg); } return true; diff --git a/common/rfb/SSecurityPlain.h b/common/rfb/SSecurityPlain.h index f2bc3483..4c030455 100644 --- a/common/rfb/SSecurityPlain.h +++ b/common/rfb/SSecurityPlain.h @@ -29,14 +29,20 @@ namespace rfb { class PasswordValidator { public: - bool validate(SConnection* sc, const char *username, const char *password) - { return validUser(username) ? validateInternal(sc, username, password) : false; } + bool validate(SConnection* sc, + const char *username, + const char *password, + std::string &msg) + { return validUser(username) ? validateInternal(sc, username, password, msg) : false; } static core::StringListParameter plainUsers; virtual ~PasswordValidator() { } protected: - virtual bool validateInternal(SConnection* sc, const char *username, const char *password)=0; + virtual bool validateInternal(SConnection* sc, + const char *username, + const char *password, + std::string &msg) = 0; static bool validUser(const char* username); }; diff --git a/common/rfb/SSecurityRSAAES.cxx b/common/rfb/SSecurityRSAAES.cxx index 6afb52dd..405005ab 100644 --- a/common/rfb/SSecurityRSAAES.cxx +++ b/common/rfb/SSecurityRSAAES.cxx @@ -583,9 +583,10 @@ void SSecurityRSAAES::verifyUserPass() #elif !defined(__APPLE__) UnixPasswordValidator *valid = new UnixPasswordValidator(); #endif - if (!valid->validate(sc, username, password)) { + std::string msg = "Authentication failed"; + if (!valid->validate(sc, username, password, msg)) { delete valid; - throw auth_error("Authentication failed"); + throw auth_error(msg); } delete valid; #else diff --git a/common/rfb/SSecurityTLS.cxx b/common/rfb/SSecurityTLS.cxx index 2e173771..17497b8e 100644 --- a/common/rfb/SSecurityTLS.cxx +++ b/common/rfb/SSecurityTLS.cxx @@ -2,7 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team - * Copyright (C) 2012-2021 Pierre Ossman for Cendio AB + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,8 +37,7 @@ #include <rfb/Exception.h> #include <rdr/TLSException.h> -#include <rdr/TLSInStream.h> -#include <rdr/TLSOutStream.h> +#include <rdr/TLSSocket.h> #include <gnutls/x509.h> @@ -72,7 +71,7 @@ static core::LogWriter vlog("TLS"); SSecurityTLS::SSecurityTLS(SConnection* sc_, bool _anon) : SSecurity(sc_), session(nullptr), anon_cred(nullptr), - cert_cred(nullptr), anon(_anon), tlsis(nullptr), tlsos(nullptr), + cert_cred(nullptr), anon(_anon), tlssock(nullptr), rawis(nullptr), rawos(nullptr) { int ret; @@ -88,32 +87,13 @@ SSecurityTLS::SSecurityTLS(SConnection* sc_, bool _anon) void SSecurityTLS::shutdown() { - if (tlsos) { - try { - if (tlsos->hasBufferedData()) { - tlsos->cork(false); - tlsos->flush(); - if (tlsos->hasBufferedData()) - vlog.error("Failed to flush remaining socket data on close"); - } - } catch (std::exception& e) { - vlog.error("Failed to flush remaining socket data on close: %s", e.what()); - } - } - - if (session) { - int ret; - // FIXME: We can't currently wait for the response, so we only send - // our close and hope for the best - ret = gnutls_bye(session, GNUTLS_SHUT_WR); - if ((ret != GNUTLS_E_SUCCESS) && (ret != GNUTLS_E_INVALID_SESSION)) - vlog.error("TLS shutdown failed: %s", gnutls_strerror(ret)); - } + if (tlssock) + tlssock->shutdown(); #if defined (SSECURITYTLS__USE_DEPRECATED_DH) if (dh_params) { gnutls_dh_params_deinit(dh_params); - dh_params = 0; + dh_params = nullptr; } #endif @@ -133,13 +113,9 @@ void SSecurityTLS::shutdown() rawos = nullptr; } - if (tlsis) { - delete tlsis; - tlsis = nullptr; - } - if (tlsos) { - delete tlsos; - tlsos = nullptr; + if (tlssock) { + delete tlssock; + tlssock = nullptr; } if (session) { @@ -185,56 +161,46 @@ bool SSecurityTLS::processMsg() os->writeU8(1); os->flush(); - // Create these early as they set up the push/pull functions - // for GnuTLS - tlsis = new rdr::TLSInStream(is, session); - tlsos = new rdr::TLSOutStream(os, session); + tlssock = new rdr::TLSSocket(is, os, session); rawis = is; rawos = os; } - err = gnutls_handshake(session); - if (err != GNUTLS_E_SUCCESS) { - if (!gnutls_error_is_fatal(err)) { - vlog.debug("Deferring completion of TLS handshake: %s", gnutls_strerror(err)); + try { + if (!tlssock->handshake()) return false; - } - vlog.error("TLS Handshake failed: %s", gnutls_strerror (err)); + } catch (std::exception&) { shutdown(); - throw rdr::tls_error("TLS Handshake failed", err); + throw; } vlog.debug("TLS handshake completed with %s", gnutls_session_get_desc(session)); - sc->setStreams(tlsis, tlsos); + sc->setStreams(&tlssock->inStream(), &tlssock->outStream()); return true; } void SSecurityTLS::setParams() { - static const char kx_anon_priority[] = ":+ANON-ECDH:+ANON-DH"; + static const char kx_anon_priority[] = "+ANON-ECDH:+ANON-DH"; int ret; // Custom priority string specified? if (strcmp(Security::GnuTLSPriority, "") != 0) { - char *prio; + std::string prio; const char *err; - prio = new char[strlen(Security::GnuTLSPriority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, Security::GnuTLSPriority); - if (anon) - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = (const char*)Security::GnuTLSPriority; + if (anon) { + prio += ":"; + prio += kx_anon_priority; + } + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); @@ -244,30 +210,22 @@ void SSecurityTLS::setParams() const char *err; #if GNUTLS_VERSION_NUMBER >= 0x030603 - // gnutls_set_default_priority_appends() expects a normal priority string that - // doesn't start with ":". - ret = gnutls_set_default_priority_append(session, kx_anon_priority + 1, &err, 0); + ret = gnutls_set_default_priority_append(session, kx_anon_priority, &err, 0); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); throw rdr::tls_error("gnutls_set_default_priority_append()", ret); } #else + std::string prio; + // We don't know what the system default priority is, so we guess // it's what upstream GnuTLS has - static const char gnutls_default_priority[] = "NORMAL"; - char *prio; - - prio = new char[strlen(gnutls_default_priority) + - strlen(kx_anon_priority) + 1]; - - strcpy(prio, gnutls_default_priority); - strcat(prio, kx_anon_priority); - - ret = gnutls_priority_set_direct(session, prio, &err); - - delete [] prio; + prio = "NORMAL"; + prio += ":"; + prio += kx_anon_priority; + ret = gnutls_priority_set_direct(session, prio.c_str(), &err); if (ret != GNUTLS_E_SUCCESS) { if (ret == GNUTLS_E_INVALID_REQUEST) vlog.error("GnuTLS priority syntax error at: %s", err); diff --git a/common/rfb/SSecurityTLS.h b/common/rfb/SSecurityTLS.h index 7e80117c..61eec2a8 100644 --- a/common/rfb/SSecurityTLS.h +++ b/common/rfb/SSecurityTLS.h @@ -2,6 +2,7 @@ * Copyright (C) 2004 Red Hat Inc. * Copyright (C) 2005 Martin Koegler * Copyright (C) 2010 TigerVNC Team + * Copyright 2012-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,8 +42,7 @@ namespace rdr { class InStream; class OutStream; - class TLSInStream; - class TLSOutStream; + class TLSSocket; } namespace rfb { @@ -72,8 +72,7 @@ namespace rfb { bool anon; - rdr::TLSInStream* tlsis; - rdr::TLSOutStream* tlsos; + rdr::TLSSocket* tlssock; rdr::InStream* rawis; rdr::OutStream* rawos; diff --git a/common/rfb/ServerParams.cxx b/common/rfb/ServerParams.cxx index e4691e19..4b8f6136 100644 --- a/common/rfb/ServerParams.cxx +++ b/common/rfb/ServerParams.cxx @@ -24,6 +24,7 @@ #include <stdexcept> +#include <core/LogWriter.h> #include <core/string.h> #include <rfb/ledStates.h> @@ -33,6 +34,8 @@ using namespace rfb; +static core::LogWriter vlog("ServerParams"); + ServerParams::ServerParams() : majorVersion(0), minorVersion(0), supportsQEMUKeyEvent(false), @@ -67,8 +70,14 @@ void ServerParams::setDimensions(int width, int height) void ServerParams::setDimensions(int width, int height, const ScreenSet& layout) { - if (!layout.validate(width, height)) + if (!layout.validate(width, height)) { + char buffer[2048]; + vlog.debug("Invalid screen layout for %dx%d:", width, height); + layout.print(buffer, sizeof(buffer)); + vlog.debug("%s", buffer); + throw std::invalid_argument("Attempted to configure an invalid screen layout"); + } width_ = width; height_ = height; diff --git a/common/rfb/UnixPasswordValidator.cxx b/common/rfb/UnixPasswordValidator.cxx index 36b8dd7a..8239463a 100644 --- a/common/rfb/UnixPasswordValidator.cxx +++ b/common/rfb/UnixPasswordValidator.cxx @@ -22,24 +22,119 @@ #include <config.h> #endif +#include <assert.h> +#include <string.h> +#include <security/pam_appl.h> + #include <core/Configuration.h> +#include <core/LogWriter.h> #include <rfb/UnixPasswordValidator.h> -#include <rfb/pam.h> using namespace rfb; +static core::LogWriter vlog("UnixPasswordValidator"); + static core::StringParameter pamService ("PAMService", "Service name for PAM password validation", "vnc"); core::AliasParameter pam_service("pam_service", "Alias for PAMService", &pamService); -int do_pam_auth(const char *service, const char *username, - const char *password); +std::string UnixPasswordValidator::displayName; + +typedef struct +{ + const char *username; + const char *password; + std::string &msg; +} AuthData; + +#if defined(__sun) +static int pam_callback(int count, struct pam_message **in, + struct pam_response **out, void *ptr) +#else +static int pam_callback(int count, const struct pam_message **in, + struct pam_response **out, void *ptr) +#endif +{ + int i; + AuthData *auth = (AuthData *) ptr; + struct pam_response *resp = + (struct pam_response *) malloc (sizeof (struct pam_response) * count); + + if (!resp && count) + return PAM_CONV_ERR; + + for (i = 0; i < count; i++) { + resp[i].resp_retcode = PAM_SUCCESS; + switch (in[i]->msg_style) { + case PAM_TEXT_INFO: + vlog.info("%s info: %s", (const char *) pamService, in[i]->msg); + auth->msg = in[i]->msg; + resp[i].resp = nullptr; + break; + case PAM_ERROR_MSG: + vlog.error("%s error: %s", (const char *) pamService, in[i]->msg); + auth->msg = in[i]->msg; + resp[i].resp = nullptr; + break; + case PAM_PROMPT_ECHO_ON: /* Send Username */ + resp[i].resp = strdup(auth->username); + break; + case PAM_PROMPT_ECHO_OFF: /* Send Password */ + resp[i].resp = strdup(auth->password); + break; + default: + free(resp); + return PAM_CONV_ERR; + } + } -bool UnixPasswordValidator::validateInternal(SConnection * /*sc*/, + *out = resp; + return PAM_SUCCESS; +} + +bool UnixPasswordValidator::validateInternal(SConnection * /* sc */, const char *username, - const char *password) + const char *password, + std::string &msg) { - return do_pam_auth(pamService, username, password); + int ret; + AuthData auth = { username, password, msg }; + struct pam_conv conv = { + pam_callback, + &auth + }; + pam_handle_t *pamh = nullptr; + ret = pam_start(pamService, username, &conv, &pamh); + if (ret != PAM_SUCCESS) { + /* Can't call pam_strerror() here because the content of pamh undefined */ + vlog.error("pam_start(%s) failed: %d", (const char *) pamService, ret); + return false; + } +#ifdef PAM_XDISPLAY + /* At this point, displayName should never be empty */ + assert(displayName.length() > 0); + /* Pass the display name to PAM modules but PAM_XDISPLAY may not be + * recognized by modules built with old versions of PAM */ + ret = pam_set_item(pamh, PAM_XDISPLAY, displayName.c_str()); + if (ret != PAM_SUCCESS && ret != PAM_BAD_ITEM) { + vlog.error("pam_set_item(PAM_XDISPLAY) failed: %d (%s)", ret, pam_strerror(pamh, ret)); + goto error; + } +#endif + ret = pam_authenticate(pamh, 0); + if (ret != PAM_SUCCESS) { + vlog.error("pam_authenticate() failed: %d (%s)", ret, pam_strerror(pamh, ret)); + goto error; + } + ret = pam_acct_mgmt(pamh, 0); + if (ret != PAM_SUCCESS) { + vlog.error("pam_acct_mgmt() failed: %d (%s)", ret, pam_strerror(pamh, ret)); + goto error; + } + return true; +error: + pam_end(pamh, ret); + return false; } diff --git a/common/rfb/UnixPasswordValidator.h b/common/rfb/UnixPasswordValidator.h index 4d623d6c..a2cc89c5 100644 --- a/common/rfb/UnixPasswordValidator.h +++ b/common/rfb/UnixPasswordValidator.h @@ -26,9 +26,19 @@ namespace rfb { class UnixPasswordValidator: public PasswordValidator { + public: + static void setDisplayName(const std::string& display) { + displayName = display; + } + protected: - bool validateInternal(SConnection * sc, const char *username, - const char *password) override; + bool validateInternal(SConnection *sc, + const char *username, + const char *password, + std::string &msg) override; + + private: + static std::string displayName; }; } diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 0b3537ad..2d77fae6 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -314,8 +314,6 @@ void VNCSConnectionST::requestClipboardOrClose() { try { if (state() != RFBSTATE_NORMAL) return; - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; requestClipboard(); } catch(std::exception& e) { close(e.what()); @@ -326,8 +324,6 @@ void VNCSConnectionST::announceClipboardOrClose(bool available) { try { if (state() != RFBSTATE_NORMAL) return; - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::sendCutText) return; announceClipboard(available); } catch(std::exception& e) { close(e.what()); @@ -338,8 +334,6 @@ void VNCSConnectionST::sendClipboardDataOrClose(const char* data) { try { if (state() != RFBSTATE_NORMAL) return; - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::sendCutText) return; sendClipboardData(data); } catch(std::exception& e) { close(e.what()); @@ -467,6 +461,7 @@ void VNCSConnectionST::setPixelFormat(const PixelFormat& pf) pf.print(buffer, 256); vlog.info("Client pixel format %s", buffer); setCursor(); + encodeManager.forceRefresh(server->getPixelBuffer()->getRect()); } void VNCSConnectionST::pointerEvent(const core::Point& pos, @@ -476,7 +471,6 @@ void VNCSConnectionST::pointerEvent(const core::Point& pos, idleTimer.start(core::secsToMillis(rfb::Server::idleTimeout)); pointerEventTime = time(nullptr); if (!accessCheck(AccessPtrEvents)) return; - if (!rfb::Server::acceptPointerEvents) return; pointerEventPos = pos; server->pointerEvent(this, pointerEventPos, buttonMask); } @@ -509,6 +503,8 @@ void VNCSConnectionST::keyEvent(uint32_t keysym, uint32_t keycode, bool down) { if (rfb::Server::idleTimeout) idleTimer.start(core::secsToMillis(rfb::Server::idleTimeout)); if (!accessCheck(AccessKeyEvents)) return; + // FIXME: This check isn't strictly needed, but we get a lot of + // confusing debug logging without it if (!rfb::Server::acceptKeyEvents) return; if (down) @@ -662,8 +658,7 @@ void VNCSConnectionST::setDesktopSize(int fb_width, int fb_height, layout.print(buffer, sizeof(buffer)); vlog.debug("%s", buffer); - if (!accessCheck(AccessSetDesktopSize) || - !rfb::Server::acceptSetDesktopSize) { + if (!accessCheck(AccessSetDesktopSize)) { vlog.debug("Rejecting unauthorized framebuffer resize request"); result = resultProhibited; } else { @@ -744,21 +739,16 @@ void VNCSConnectionST::enableContinuousUpdates(bool enable, void VNCSConnectionST::handleClipboardRequest() { - if (!accessCheck(AccessCutText)) return; server->handleClipboardRequest(this); } void VNCSConnectionST::handleClipboardAnnounce(bool available) { - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; server->handleClipboardAnnounce(this, available); } void VNCSConnectionST::handleClipboardData(const char* data) { - if (!accessCheck(AccessCutText)) return; - if (!rfb::Server::acceptCutText) return; server->handleClipboardData(this, data); } diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index cce04164..77d652b9 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -364,6 +364,9 @@ void VNCServerST::setScreenLayout(const ScreenSet& layout) void VNCServerST::requestClipboard() { + if (!rfb::Server::acceptCutText) + return; + if (clipboardClient == nullptr) { slog.debug("Got request for client clipboard but no client currently owns the clipboard"); return; @@ -378,6 +381,9 @@ void VNCServerST::announceClipboard(bool available) clipboardRequestors.clear(); + if (!rfb::Server::sendCutText) + return; + for (ci = clients.begin(); ci != clients.end(); ++ci) (*ci)->announceClipboardOrClose(available); } @@ -386,6 +392,9 @@ void VNCServerST::sendClipboardData(const char* data) { std::list<VNCSConnectionST*>::iterator ci; + if (!rfb::Server::sendCutText) + return; + if (strchr(data, '\r') != nullptr) throw std::invalid_argument("Invalid carriage return in clipboard data"); @@ -478,6 +487,9 @@ void VNCServerST::setLEDState(unsigned int state) void VNCServerST::keyEvent(uint32_t keysym, uint32_t keycode, bool down) { + if (!rfb::Server::acceptKeyEvents) + return; + if (rfb::Server::maxIdleTime) idleTimer.start(core::secsToMillis(rfb::Server::maxIdleTime)); @@ -500,6 +512,10 @@ void VNCServerST::pointerEvent(VNCSConnectionST* client, uint16_t buttonMask) { time_t now = time(nullptr); + + if (!rfb::Server::acceptPointerEvents) + return; + if (rfb::Server::maxIdleTime) idleTimer.start(core::secsToMillis(rfb::Server::maxIdleTime)); @@ -529,9 +545,11 @@ void VNCServerST::handleClipboardRequest(VNCSConnectionST* client) void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client, bool available) { - if (available) + if (available) { + if (!rfb::Server::acceptCutText) + return; clipboardClient = client; - else { + } else { if (client != clipboardClient) return; clipboardClient = nullptr; @@ -542,6 +560,8 @@ void VNCServerST::handleClipboardAnnounce(VNCSConnectionST* client, void VNCServerST::handleClipboardData(VNCSConnectionST* client, const char* data) { + if (!rfb::Server::acceptCutText) + return; if (client != clipboardClient) { slog.debug("Ignoring unexpected clipboard data"); return; @@ -556,6 +576,11 @@ unsigned int VNCServerST::setDesktopSize(VNCSConnectionST* requester, unsigned int result; std::list<VNCSConnectionST*>::iterator ci; + if (!rfb::Server::acceptSetDesktopSize) { + slog.debug("Rejecting unauthorized framebuffer resize request"); + return resultProhibited; + } + // We can't handle a framebuffer larger than this, so don't let a // client set one (see PixelBuffer.cxx) if ((fb_width > 16384) || (fb_height > 16384)) { diff --git a/common/rfb/WinPasswdValidator.cxx b/common/rfb/WinPasswdValidator.cxx index 84832e81..a6281950 100644 --- a/common/rfb/WinPasswdValidator.cxx +++ b/common/rfb/WinPasswdValidator.cxx @@ -30,7 +30,8 @@ using namespace rfb; // This method will only work for Windows NT, 2000, and XP (and possibly Vista) bool WinPasswdValidator::validateInternal(rfb::SConnection* /*sc*/, const char* username, - const char* password) + const char* password, + std::string & /* msg */) { HANDLE handle; diff --git a/common/rfb/WinPasswdValidator.h b/common/rfb/WinPasswdValidator.h index 340a6234..993cafea 100644 --- a/common/rfb/WinPasswdValidator.h +++ b/common/rfb/WinPasswdValidator.h @@ -21,6 +21,7 @@ #ifndef __RFB_WINPASSWDVALIDATOR_H__ #define __RFB_WINPASSWDVALIDATOR_H__ +#include <string> #include <rfb/SSecurityPlain.h> namespace rfb @@ -30,7 +31,10 @@ namespace rfb WinPasswdValidator() {}; virtual ~WinPasswdValidator() {}; protected: - bool validateInternal(SConnection *sc, const char* username, const char* password) override; + bool validateInternal(SConnection *sc, + const char *username, + const char *password, + std::string &msg) override; }; } diff --git a/common/rfb/pam.c b/common/rfb/pam.c deleted file mode 100644 index f9e5ce74..00000000 --- a/common/rfb/pam.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2006 Martin Koegler - * Copyright (C) 2010 TigerVNC Team - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdlib.h> -#include <string.h> -#include <security/pam_appl.h> - -#include <rfb/pam.h> - -typedef struct -{ - const char *username; - const char *password; -} AuthData; - -#if defined(__sun) -static int pam_callback(int count, struct pam_message **in, - struct pam_response **out, void *ptr) -#else -static int pam_callback(int count, const struct pam_message **in, - struct pam_response **out, void *ptr) -#endif -{ - int i; - AuthData *auth = (AuthData *) ptr; - struct pam_response *resp = - (struct pam_response *) malloc (sizeof (struct pam_response) * count); - - if (!resp && count) - return PAM_CONV_ERR; - - for (i = 0; i < count; i++) { - resp[i].resp_retcode = PAM_SUCCESS; - switch (in[i]->msg_style) { - case PAM_TEXT_INFO: - case PAM_ERROR_MSG: - resp[i].resp = 0; - break; - case PAM_PROMPT_ECHO_ON: /* Send Username */ - resp[i].resp = strdup(auth->username); - break; - case PAM_PROMPT_ECHO_OFF: /* Send Password */ - resp[i].resp = strdup(auth->password); - break; - default: - free(resp); - return PAM_CONV_ERR; - } - } - - *out = resp; - return PAM_SUCCESS; -} - - -int do_pam_auth(const char *service, const char *username, const char *password) -{ - int ret; - AuthData auth = { username, password }; - struct pam_conv conv = { - pam_callback, - &auth - }; - pam_handle_t *h = 0; - ret = pam_start(service, username, &conv, &h); - if (ret == PAM_SUCCESS) - ret = pam_authenticate(h, 0); - if (ret == PAM_SUCCESS) - ret = pam_acct_mgmt(h, 0); - pam_end(h, ret); - - return ret == PAM_SUCCESS ? 1 : 0; -} - diff --git a/common/rfb/pam.h b/common/rfb/pam.h deleted file mode 100644 index d378d19c..00000000 --- a/common/rfb/pam.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2006 Martin Koegler - * Copyright (C) 2010 TigerVNC Team - * - * 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. - */ - -#ifndef __RFB_PAM_H__ -#define __RFB_PAM_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -int do_pam_auth(const char *service, const char *username, const char *password); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/contrib/packages/deb/ubuntu-focal/debian/rules b/contrib/packages/deb/ubuntu-focal/debian/rules index 0245a065..92150cd3 100644 --- a/contrib/packages/deb/ubuntu-focal/debian/rules +++ b/contrib/packages/deb/ubuntu-focal/debian/rules @@ -55,6 +55,12 @@ config-stamp: xorg-source-stamp # Add here commands to configure the package. cmake -G"Unix Makefiles" \ -DBUILD_STATIC=off \ + -DENABLE_NLS=ON \ + -DENABLE_H264=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DENABLE_SYSTEMD=ON \ + -DBUILD_VIEWER=ON \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_LIBEXECDIR:PATH=lib/$(DEB_HOST_MULTIARCH) \ -DCMAKE_INSTALL_UNITDIR:PATH=/lib/systemd/system @@ -126,7 +132,7 @@ build-arch-stamp: # Add here command to compile/build the package. # Build first things. # Build Xvnc - make $(NUMJOBS) LDFLAGS="-lpng" + make $(NUMJOBS) LDFLAGS="-lpng" VERBOSE=1 make $(NUMJOBS) -C unix/xserver touch build-arch-stamp diff --git a/contrib/packages/deb/ubuntu-jammy/debian/rules b/contrib/packages/deb/ubuntu-jammy/debian/rules index 1b4fd6f8..33e4523f 100644 --- a/contrib/packages/deb/ubuntu-jammy/debian/rules +++ b/contrib/packages/deb/ubuntu-jammy/debian/rules @@ -55,6 +55,12 @@ config-stamp: xorg-source-stamp # Add here commands to configure the package. cmake -G"Unix Makefiles" \ -DBUILD_STATIC=off \ + -DENABLE_NLS=ON \ + -DENABLE_H264=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DENABLE_SYSTEMD=ON \ + -DBUILD_VIEWER=ON \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_LIBEXECDIR:PATH=lib/$(DEB_HOST_MULTIARCH) \ -DCMAKE_INSTALL_UNITDIR:PATH=/lib/systemd/system @@ -126,7 +132,7 @@ build-arch-stamp: # Add here command to compile/build the package. # Build first things. # Build Xvnc - make $(NUMJOBS) LDFLAGS="-lpng" + make $(NUMJOBS) LDFLAGS="-lpng" VERBOSE=1 make $(NUMJOBS) -C unix/xserver touch build-arch-stamp diff --git a/contrib/packages/deb/ubuntu-noble/debian/rules b/contrib/packages/deb/ubuntu-noble/debian/rules index 1b4fd6f8..33e4523f 100644 --- a/contrib/packages/deb/ubuntu-noble/debian/rules +++ b/contrib/packages/deb/ubuntu-noble/debian/rules @@ -55,6 +55,12 @@ config-stamp: xorg-source-stamp # Add here commands to configure the package. cmake -G"Unix Makefiles" \ -DBUILD_STATIC=off \ + -DENABLE_NLS=ON \ + -DENABLE_H264=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DENABLE_SYSTEMD=ON \ + -DBUILD_VIEWER=ON \ -DCMAKE_INSTALL_PREFIX:PATH=/usr \ -DCMAKE_INSTALL_LIBEXECDIR:PATH=lib/$(DEB_HOST_MULTIARCH) \ -DCMAKE_INSTALL_UNITDIR:PATH=/lib/systemd/system @@ -126,7 +132,7 @@ build-arch-stamp: # Add here command to compile/build the package. # Build first things. # Build Xvnc - make $(NUMJOBS) LDFLAGS="-lpng" + make $(NUMJOBS) LDFLAGS="-lpng" VERBOSE=1 make $(NUMJOBS) -C unix/xserver touch build-arch-stamp diff --git a/contrib/packages/rpm/el8/SPECS/tigervnc.spec b/contrib/packages/rpm/el8/SPECS/tigervnc.spec index 307e26f6..9fe7563e 100644 --- a/contrib/packages/rpm/el8/SPECS/tigervnc.spec +++ b/contrib/packages/rpm/el8/SPECS/tigervnc.spec @@ -33,7 +33,8 @@ BuildRequires: xorg-x11-util-macros, xorg-x11-xtrans-devel, libXtst-devel BuildRequires: xorg-x11-font-utils BuildRequires: libXfont2-devel # SELinux -BuildRequires: libselinux-devel, selinux-policy-devel, systemd +BuildRequires: libselinux-devel, selinux-policy-devel +BuildRequires: systemd-devel # TigerVNC 1.4.x requires fltk 1.3.3 for keyboard handling support # See https://github.com/TigerVNC/tigervnc/issues/8, also bug #1208814 @@ -139,7 +140,13 @@ export CFLAGS="$RPM_OPT_FLAGS -fpic" %endif export CXXFLAGS="$CFLAGS -std=c++11" -%cmake +%cmake \ + -DENABLE_NLS=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DENABLE_SELINUX=ON \ + -DENABLE_SYSTEMD=ON \ + -DBUILD_VIEWER=ON %cmake_build diff --git a/contrib/packages/rpm/el9/SPECS/tigervnc.spec b/contrib/packages/rpm/el9/SPECS/tigervnc.spec index 5d120bc0..bfab2bf1 100644 --- a/contrib/packages/rpm/el9/SPECS/tigervnc.spec +++ b/contrib/packages/rpm/el9/SPECS/tigervnc.spec @@ -32,7 +32,8 @@ BuildRequires: pixman-devel, libdrm-devel, mesa-libgbm-devel BuildRequires: xorg-x11-util-macros, xorg-x11-xtrans-devel, libXtst-devel BuildRequires: libXfont2-devel # SELinux -BuildRequires: libselinux-devel, selinux-policy-devel, systemd +BuildRequires: libselinux-devel, selinux-policy-devel +BuildRequires: systemd-devel # TigerVNC 1.4.x requires fltk 1.3.3 for keyboard handling support # See https://github.com/TigerVNC/tigervnc/issues/8, also bug #1208814 @@ -138,7 +139,13 @@ export CFLAGS="$RPM_OPT_FLAGS -fpic" %endif export CXXFLAGS="$CFLAGS -std=c++11" -%cmake +%cmake \ + -DENABLE_NLS=ON \ + -DENABLE_GNUTLS=ON \ + -DENABLE_NETTLE=ON \ + -DENABLE_SELINUX=ON \ + -DENABLE_SYSTEMD=ON \ + -DBUILD_VIEWER=ON %cmake_build diff --git a/java/com/tigervnc/vncviewer/MANIFEST.MF b/java/com/tigervnc/vncviewer/MANIFEST.MF index 9e282655..5f67f81c 100644 --- a/java/com/tigervnc/vncviewer/MANIFEST.MF +++ b/java/com/tigervnc/vncviewer/MANIFEST.MF @@ -1,5 +1,5 @@ Manifest-Version: 1.0 Main-Class: com.tigervnc.vncviewer.VncViewer -Application-Name: TigerVNC viewer +Application-Name: TigerVNC Permissions: all-permissions Codebase: * diff --git a/java/com/tigervnc/vncviewer/OptionsDialog.java b/java/com/tigervnc/vncviewer/OptionsDialog.java index ccf27327..9c442cbc 100644 --- a/java/com/tigervnc/vncviewer/OptionsDialog.java +++ b/java/com/tigervnc/vncviewer/OptionsDialog.java @@ -177,7 +177,7 @@ class OptionsDialog extends Dialog { @SuppressWarnings({"rawtypes","unchecked"}) public OptionsDialog() { super(true); - setTitle("VNC viewer options"); + setTitle("TigerVNC options"); setResizable(false); getContentPane().setLayout( diff --git a/java/com/tigervnc/vncviewer/ServerDialog.java b/java/com/tigervnc/vncviewer/ServerDialog.java index 01f91db2..9c92698e 100644 --- a/java/com/tigervnc/vncviewer/ServerDialog.java +++ b/java/com/tigervnc/vncviewer/ServerDialog.java @@ -47,7 +47,7 @@ class ServerDialog extends Dialog implements Runnable { super(true); this.vncServerName = vncServerName; setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); - setTitle("VNC viewer: Connection details"); + setTitle("TigerVNC"); setResizable(false); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { @@ -258,7 +258,7 @@ class ServerDialog extends Dialog implements Runnable { JOptionPane op = new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null, options, options[1]); - JDialog dlg = op.createDialog(this, "TigerVNC Viewer"); + JDialog dlg = op.createDialog(this, "TigerVNC"); dlg.setIconImage(VncViewer.frameIcon); dlg.setAlwaysOnTop(true); dlg.setVisible(true); diff --git a/java/com/tigervnc/vncviewer/Viewport.java b/java/com/tigervnc/vncviewer/Viewport.java index 0f614162..3c6f623e 100644 --- a/java/com/tigervnc/vncviewer/Viewport.java +++ b/java/com/tigervnc/vncviewer/Viewport.java @@ -647,7 +647,7 @@ class Viewport extends JPanel implements ActionListener { this, ID.OPTIONS, EnumSet.noneOf(MENU.class)); menu_add(contextMenu, "Connection info...", KeyEvent.VK_I, this, ID.INFO, EnumSet.noneOf(MENU.class)); - menu_add(contextMenu, "About TigerVNC viewer...", KeyEvent.VK_T, + menu_add(contextMenu, "About TigerVNC...", KeyEvent.VK_T, this, ID.ABOUT, EnumSet.of(MENU.DIVIDER)); menu_add(contextMenu, "Dismiss menu", KeyEvent.VK_M, diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java index ea2c2125..2a372b98 100644 --- a/java/com/tigervnc/vncviewer/VncViewer.java +++ b/java/com/tigervnc/vncviewer/VncViewer.java @@ -361,7 +361,7 @@ public class VncViewer implements Runnable { JOptionPane op = new JOptionPane(msg, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, VncViewer.logoIcon, options); - JDialog dlg = op.createDialog(parent, "About TigerVNC viewer for Java"); + JDialog dlg = op.createDialog(parent, "About TigerVNC"); dlg.setIconImage(VncViewer.frameIcon); dlg.setAlwaysOnTop(true); dlg.setVisible(true); @@ -380,7 +380,7 @@ public class VncViewer implements Runnable { void reportException(java.lang.Exception e) { String title, msg = e.getMessage(); int msgType = JOptionPane.ERROR_MESSAGE; - title = "TigerVNC viewer : Error"; + title = "TigerVNC : Error"; e.printStackTrace(); JOptionPane.showMessageDialog(null, msg, title, msgType); } diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt index 7d316e78..03fb2632 100644 --- a/po/CMakeLists.txt +++ b/po/CMakeLists.txt @@ -23,6 +23,7 @@ if (GETTEXT_XGETTEXT_EXECUTABLE) "--directory=${PROJECT_SOURCE_DIR}" "--output=${CMAKE_CURRENT_SOURCE_DIR}/tigervnc.pot" --default-domain=tigervnc + --from-code=UTF-8 --keyword=_ --keyword=p_:1c,2 --keyword=N_ @@ -4,13 +4,13 @@ # PuppyRus linux team <www.puppyrus.org>. # Constantin Kaplinsky <const@tightvnc.com>, 2011. # Pavel Maryanov <acid@jack.kiev.ua>, 2016. -# Yuri Kozlov <yuray@komyakino.ru>, 2016, 2017, 2018, 2019, 2021, 2023. +# Yuri Kozlov <yuray@komyakino.ru>, 2016, 2017, 2018, 2019, 2021, 2023, 2025. msgid "" msgstr "" -"Project-Id-Version: tigervnc 1.12.90\n" +"Project-Id-Version: tigervnc 1.14.90\n" "Report-Msgid-Bugs-To: tigervnc-devel@googlegroups.com\n" -"POT-Creation-Date: 2022-12-15 16:35+0100\n" -"PO-Revision-Date: 2023-10-20 18:09+0300\n" +"POT-Creation-Date: 2025-01-14 16:15+0100\n" +"PO-Revision-Date: 2025-03-26 21:38+0300\n" "Last-Translator: Yuri Kozlov <yuray@komyakino.ru>\n" "Language-Team: Russian <gnu@d07.ru>\n" "Language: ru_UA\n" @@ -19,14 +19,14 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Bugs: Report translation errors to the Language-Team address.\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Lokalize 22.12.3\n" +"X-Generator: Lokalize 24.12.0\n" -#: vncviewer/CConn.cxx:103 +#: vncviewer/CConn.cxx:102 #, c-format msgid "Connected to socket %s" msgstr "Подключён к Ñокету %s" -#: vncviewer/CConn.cxx:110 +#: vncviewer/CConn.cxx:109 #, c-format msgid "Connected to host %s port %d" msgstr "Подключён к компьютеру %s, порт %d" @@ -42,112 +42,128 @@ msgstr "" "\n" "%s" -#: vncviewer/CConn.cxx:159 +#: vncviewer/CConn.cxx:151 #, c-format msgid "Desktop name: %.80s" msgstr "Ð˜Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°: %.80s" -#: vncviewer/CConn.cxx:164 +#: vncviewer/CConn.cxx:154 #, c-format msgid "Host: %.80s port: %d" msgstr "Компьютер: %.80s порт: %d" -#: vncviewer/CConn.cxx:169 +#: vncviewer/CConn.cxx:158 #, c-format msgid "Size: %d x %d" msgstr "Размер: %d x %d" -#: vncviewer/CConn.cxx:177 +#: vncviewer/CConn.cxx:165 #, c-format msgid "Pixel format: %s" msgstr "Формат пикÑелей: %s" -#: vncviewer/CConn.cxx:184 +#: vncviewer/CConn.cxx:170 #, c-format msgid "(server default %s)" msgstr "(Ñервер по умолчанию %s)" -#: vncviewer/CConn.cxx:189 +#: vncviewer/CConn.cxx:173 #, c-format msgid "Requested encoding: %s" msgstr "Запрошено кодирование: %s" -#: vncviewer/CConn.cxx:194 +#: vncviewer/CConn.cxx:177 #, c-format msgid "Last used encoding: %s" msgstr "ИÑпользуетÑÑ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ðµ: %s" -#: vncviewer/CConn.cxx:199 +#: vncviewer/CConn.cxx:181 #, c-format msgid "Line speed estimate: %d kbit/s" msgstr "СкороÑть ÑоединениÑ: %d кбит/Ñ" -#: vncviewer/CConn.cxx:204 +#: vncviewer/CConn.cxx:185 #, c-format msgid "Protocol version: %d.%d" msgstr "ВерÑÐ¸Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð°: %d.%d" -#: vncviewer/CConn.cxx:209 +#: vncviewer/CConn.cxx:189 #, c-format msgid "Security method: %s" msgstr "Метод защиты: %s" -#: vncviewer/CConn.cxx:270 vncviewer/CConn.cxx:272 +#: vncviewer/CConn.cxx:250 vncviewer/CConn.cxx:252 msgid "The connection was dropped by the server before the session could be established." msgstr "Соединение прервано Ñервером до уÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑеанÑа." -#: vncviewer/CConn.cxx:332 +#: vncviewer/CConn.cxx:262 +#, c-format +msgid "Authentication failed: %s" +msgstr "Ошибка аутентификации: %s" + +#: vncviewer/CConn.cxx:263 +#, c-format +msgid "" +"Failed to authenticate with the server. Reason given by the server:\n" +"\n" +"%s" +msgstr "" +"Ошибка аутентификации на Ñервере. Причина, Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ð¾Ñ‚ Ñервера:\n" +"\n" +"%s" + +#: vncviewer/CConn.cxx:335 #, c-format msgid "SetDesktopSize failed: %d" msgstr "Ошибка SetDesktopSize: %d" -#: vncviewer/CConn.cxx:404 +#: vncviewer/CConn.cxx:408 msgid "Invalid SetColourMapEntries from server!" msgstr "С Ñервера получен недопуÑтимый SetColourMapEntries" -#: vncviewer/CConn.cxx:512 +#: vncviewer/CConn.cxx:516 #, c-format msgid "Throughput %d kbit/s - changing to quality %d" msgstr "ПропуÑÐºÐ½Ð°Ñ ÑпоÑобноÑть %d кбит/Ñ. УÑтановлено качеÑтво %d" -#: vncviewer/CConn.cxx:534 +#: vncviewer/CConn.cxx:538 #, c-format msgid "Throughput %d kbit/s - full color is now enabled" msgstr "ПропуÑÐºÐ½Ð°Ñ ÑпоÑобноÑть %d кбит/Ñ â€” включено полноцветное отображение" -#: vncviewer/CConn.cxx:537 +#: vncviewer/CConn.cxx:541 #, c-format msgid "Throughput %d kbit/s - full color is now disabled" msgstr "ПропуÑÐºÐ½Ð°Ñ ÑпоÑобноÑть %d кбит/Ñ â€” полноцветное отображение выключено" -#: vncviewer/CConn.cxx:563 +#: vncviewer/CConn.cxx:567 #, c-format msgid "Using pixel format %s" msgstr "ИÑпользуетÑÑ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ пикÑелей %s" -#: vncviewer/DesktopWindow.cxx:145 +#: vncviewer/DesktopWindow.cxx:146 msgid "Invalid geometry specified!" msgstr "Указан недопуÑтимый размер Ñкрана." -#: vncviewer/DesktopWindow.cxx:166 +#: vncviewer/DesktopWindow.cxx:167 msgid "Reducing window size to fit on current monitor" msgstr "УменьшаетÑÑ Ñ€Ð°Ð·Ð¼ÐµÑ€ окна, чтобы помеÑтитьÑÑ Ð² текущем мониторе" -#: vncviewer/DesktopWindow.cxx:648 +#: vncviewer/DesktopWindow.cxx:646 msgid "Adjusting window size to avoid accidental full-screen request" msgstr "ПодгонÑетÑÑ Ñ€Ð°Ð·Ð¼ÐµÑ€ окна, чтобы избегать Ñлучайных запроÑов Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð² полный Ñкран" -#: vncviewer/DesktopWindow.cxx:696 +#: vncviewer/DesktopWindow.cxx:694 #, c-format msgid "Press %s to open the context menu" msgstr "Ðажмите %s, чтобы открыть контекÑтное меню" -#: vncviewer/DesktopWindow.cxx:1083 vncviewer/DesktopWindow.cxx:1091 -#: vncviewer/DesktopWindow.cxx:1111 +#: vncviewer/DesktopWindow.cxx:1094 vncviewer/DesktopWindow.cxx:1102 +#: vncviewer/DesktopWindow.cxx:1122 msgid "Failure grabbing keyboard" msgstr "Ðе удалоÑÑŒ перехватить клавиатуру" -#: vncviewer/DesktopWindow.cxx:1401 +#: vncviewer/DesktopWindow.cxx:1411 msgid "Invalid screen layout computed for resize request!" msgstr "Ð”Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на изменение размера раÑÑчитан недопуÑтимый макет Ñкрана." @@ -155,251 +171,307 @@ msgstr "Ð”Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа на изменение размера раÑÑчРmsgid "Invalid state for 3 button emulation" msgstr "Ðекорректное ÑоÑтоÑние Ð´Ð»Ñ ÑмулÑÑ†Ð¸Ñ Ñредней кнопки" +#: vncviewer/KeyboardWin32.cxx:242 +#, c-format +msgid "No scan code for extended virtual key 0x%02x" +msgstr "Ðет Ñкан-кода Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ виртуальной клавиши 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:244 +#, c-format +msgid "No scan code for virtual key 0x%02x" +msgstr "Ðет Ñкан-кода Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ клавиши 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:250 +#, c-format +msgid "Invalid scan code 0x%02x" +msgstr "Ðекорректный Ñкан-код 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:262 +#, c-format +msgid "No symbol for extended virtual key 0x%02x" +msgstr "Ðет Ñимвола Ð´Ð»Ñ Ñ€Ð°Ñширенной виртуальной клавиши 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:264 +#, c-format +msgid "No symbol for virtual key 0x%02x" +msgstr "Ðет Ñимвола Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ клавиши 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:423 +#, c-format +msgid "Failed to update keyboard LED state: %lu" +msgstr "Ðе удалоÑÑŒ обновить ÑоÑтоÑние LED клавиатуры: %lu" + +#: vncviewer/KeyboardX11.cxx:104 +#, c-format +msgid "No symbol for key code %d (in the current state)" +msgstr "Ðет Ñимвола Ð´Ð»Ñ ÐºÐ¾Ð´Ð° клавиши %d (в текущем ÑоÑтоÑнии)" + +#: vncviewer/KeyboardX11.cxx:129 +#, c-format +msgid "Failed to get keyboard LED state: %d" +msgstr "Ðе удалоÑÑŒ получить ÑоÑтоÑние LED клавиатуры: %d" + +#: vncviewer/KeyboardX11.cxx:174 +msgid "Failed to update keyboard LED state" +msgstr "Ðе удалоÑÑŒ обновить ÑоÑтоÑние LED клавиатуры" + #: vncviewer/MonitorIndicesParameter.cxx:52 -#: vncviewer/MonitorIndicesParameter.cxx:105 +#: vncviewer/MonitorIndicesParameter.cxx:100 msgid "Failed to get system monitor configuration" msgstr "Ðе удалоÑÑŒ получить наÑтройки ÑиÑтемного монитора" -#: vncviewer/MonitorIndicesParameter.cxx:83 +#: vncviewer/MonitorIndicesParameter.cxx:79 #, c-format msgid "Invalid configuration specified for %s" msgstr "Задано недопуÑтимое значение Ð´Ð»Ñ %s" -#: vncviewer/MonitorIndicesParameter.cxx:91 +#: vncviewer/MonitorIndicesParameter.cxx:86 #, c-format msgid "Monitor index %d does not exist" msgstr "Монитор Ñ Ð¸Ð½Ð´ÐµÐºÑом %d не ÑущеÑтвует" -#: vncviewer/MonitorIndicesParameter.cxx:169 -#: vncviewer/MonitorIndicesParameter.cxx:189 +#: vncviewer/MonitorIndicesParameter.cxx:162 +#: vncviewer/MonitorIndicesParameter.cxx:182 #, c-format msgid "Invalid monitor index '%s'" msgstr "Ðекорректный Ð¸Ð½Ð´ÐµÐºÑ Ð¼Ð¾Ð½Ð¸Ñ‚Ð¾Ñ€Ð° «%s»" -#: vncviewer/MonitorIndicesParameter.cxx:177 +#: vncviewer/MonitorIndicesParameter.cxx:170 #, c-format msgid "Unexpected character '%c'" msgstr "Ðеожиданный Ñимвол «%c»" -#: vncviewer/OptionsDialog.cxx:63 -msgid "VNC Viewer: Connection Options" -msgstr "VNC Viewer: параметры ÑоединениÑ" +#: vncviewer/OptionsDialog.cxx:64 +msgid "TigerVNC options" +msgstr "Параметры TigerVNC" -#: vncviewer/OptionsDialog.cxx:89 vncviewer/ServerDialog.cxx:108 -#: vncviewer/vncviewer.cxx:417 +#: vncviewer/OptionsDialog.cxx:97 vncviewer/ServerDialog.cxx:107 +#: vncviewer/vncviewer.cxx:397 msgid "Cancel" msgstr "Отмена" -#: vncviewer/OptionsDialog.cxx:94 vncviewer/vncviewer.cxx:416 +#: vncviewer/OptionsDialog.cxx:102 vncviewer/vncviewer.cxx:396 msgid "OK" msgstr "ОК" -#: vncviewer/OptionsDialog.cxx:484 +#: vncviewer/OptionsDialog.cxx:514 msgid "Compression" msgstr "Сжатие" -#: vncviewer/OptionsDialog.cxx:501 +#: vncviewer/OptionsDialog.cxx:530 msgid "Auto select" msgstr "ÐвтоматичеÑкий выбор" -#: vncviewer/OptionsDialog.cxx:516 +#: vncviewer/OptionsDialog.cxx:541 msgid "Preferred encoding" msgstr "Вид кодированиÑ" -#: vncviewer/OptionsDialog.cxx:574 +#: vncviewer/OptionsDialog.cxx:602 msgid "Color level" msgstr "Глубина цвета" -#: vncviewer/OptionsDialog.cxx:585 +#: vncviewer/OptionsDialog.cxx:614 msgid "Full" msgstr "ПолнаÑ" -#: vncviewer/OptionsDialog.cxx:592 +#: vncviewer/OptionsDialog.cxx:621 msgid "Medium" msgstr "СреднÑÑ" -#: vncviewer/OptionsDialog.cxx:599 +#: vncviewer/OptionsDialog.cxx:628 msgid "Low" msgstr "ÐизкаÑ" -#: vncviewer/OptionsDialog.cxx:606 +#: vncviewer/OptionsDialog.cxx:635 msgid "Very low" msgstr "Очень низкаÑ" -#: vncviewer/OptionsDialog.cxx:624 +#: vncviewer/OptionsDialog.cxx:657 msgid "Custom compression level:" msgstr "Уровень ÑжатиÑ:" -#: vncviewer/OptionsDialog.cxx:630 +#: vncviewer/OptionsDialog.cxx:664 msgid "level (0=fast, 9=best)" msgstr "уровень (0=быÑтрое, 9=лучшее)" -#: vncviewer/OptionsDialog.cxx:637 +#: vncviewer/OptionsDialog.cxx:671 msgid "Allow JPEG compression:" msgstr "Разрешить Ñжатие JPEG:" -#: vncviewer/OptionsDialog.cxx:643 +#: vncviewer/OptionsDialog.cxx:678 msgid "quality (0=poor, 9=best)" msgstr "качеÑтво (0=наихудшее, 9=наилучшее)" -#: vncviewer/OptionsDialog.cxx:654 +#: vncviewer/OptionsDialog.cxx:689 msgid "Security" msgstr "БезопаÑноÑть" -#: vncviewer/OptionsDialog.cxx:676 +#: vncviewer/OptionsDialog.cxx:703 msgid "Encryption" msgstr "Шифрование" -#: vncviewer/OptionsDialog.cxx:687 vncviewer/OptionsDialog.cxx:750 -#: vncviewer/OptionsDialog.cxx:854 +#: vncviewer/OptionsDialog.cxx:715 vncviewer/OptionsDialog.cxx:782 +#: vncviewer/OptionsDialog.cxx:905 msgid "None" msgstr "Ðет" -#: vncviewer/OptionsDialog.cxx:694 +#: vncviewer/OptionsDialog.cxx:722 msgid "TLS with anonymous certificates" msgstr "TLS Ñ Ð°Ð½Ð¾Ð½Ð¸Ð¼Ð½Ñ‹Ð¼Ð¸ Ñертификатами" -#: vncviewer/OptionsDialog.cxx:700 +#: vncviewer/OptionsDialog.cxx:728 msgid "TLS with X509 certificates" msgstr "TLS Ñ Ñертификатами X509" -#: vncviewer/OptionsDialog.cxx:707 +#: vncviewer/OptionsDialog.cxx:735 msgid "Path to X509 CA certificate" msgstr "Путь к Ñертификату X509 CA" -#: vncviewer/OptionsDialog.cxx:714 +#: vncviewer/OptionsDialog.cxx:742 msgid "Path to X509 CRL file" msgstr "Путь к файлу X509 CRL" -#: vncviewer/OptionsDialog.cxx:739 +#: vncviewer/OptionsDialog.cxx:770 msgid "Authentication" msgstr "ÐвторизациÑ" -#: vncviewer/OptionsDialog.cxx:756 +#: vncviewer/OptionsDialog.cxx:788 msgid "Standard VNC (insecure without encryption)" msgstr "Стандартный VNC (без защиты и шифрованиÑ)" -#: vncviewer/OptionsDialog.cxx:762 +#: vncviewer/OptionsDialog.cxx:794 msgid "Username and password (insecure without encryption)" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ пароль (без защиты и шифрованиÑ)" -#: vncviewer/OptionsDialog.cxx:781 +#: vncviewer/OptionsDialog.cxx:822 msgid "Input" msgstr "Ввод" -#: vncviewer/OptionsDialog.cxx:794 +#: vncviewer/OptionsDialog.cxx:835 msgid "View only (ignore mouse and keyboard)" msgstr "Только проÑмотр (не перехватывать мышь и клавиатуру)" -#: vncviewer/OptionsDialog.cxx:801 +#: vncviewer/OptionsDialog.cxx:842 msgid "Mouse" msgstr "Мышь" -#: vncviewer/OptionsDialog.cxx:815 +#: vncviewer/OptionsDialog.cxx:854 msgid "Emulate middle mouse button" msgstr "Ðмулировать Ñреднюю кнопку мыши" -#: vncviewer/OptionsDialog.cxx:821 -msgid "Show dot when no cursor" -msgstr "Показывать точку при отÑутÑтвии курÑора" +#: vncviewer/OptionsDialog.cxx:860 +msgid "Show local cursor when not provided by server" +msgstr "Показывать локальный курÑор, когда он не предоÑтавлÑетÑÑ Ñервером" -#: vncviewer/OptionsDialog.cxx:835 +#: vncviewer/OptionsDialog.cxx:865 +msgid "Cursor type" +msgstr "Тип курÑора" + +#: vncviewer/OptionsDialog.cxx:867 +msgid "Dot" +msgstr "Dot" + +#: vncviewer/OptionsDialog.cxx:868 +msgid "System" +msgstr "System" + +#: vncviewer/OptionsDialog.cxx:888 msgid "Keyboard" msgstr "Клавиатура" -#: vncviewer/OptionsDialog.cxx:849 +#: vncviewer/OptionsDialog.cxx:900 msgid "Pass system keys directly to server (full screen)" msgstr "ОтправлÑть ÑÐ¾Ñ‡ÐµÑ‚Ð°Ð½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ñˆ (Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð³Ð¾ Ñкрана)" -#: vncviewer/OptionsDialog.cxx:852 +#: vncviewer/OptionsDialog.cxx:903 msgid "Menu key" msgstr "Вызов меню:" -#: vncviewer/OptionsDialog.cxx:871 +#: vncviewer/OptionsDialog.cxx:926 msgid "Clipboard" msgstr "Буфер обмена" -#: vncviewer/OptionsDialog.cxx:885 +#: vncviewer/OptionsDialog.cxx:938 msgid "Accept clipboard from server" msgstr "Принимать буфер обмена Ñ Ñервера" -#: vncviewer/OptionsDialog.cxx:893 +#: vncviewer/OptionsDialog.cxx:946 msgid "Also set primary selection" msgstr "Также принимать мышиный буфер" -#: vncviewer/OptionsDialog.cxx:900 +#: vncviewer/OptionsDialog.cxx:953 msgid "Send clipboard to server" msgstr "ОтправлÑть буфер обмена на Ñервер" -#: vncviewer/OptionsDialog.cxx:908 +#: vncviewer/OptionsDialog.cxx:961 msgid "Send primary selection as clipboard" msgstr "ОтправлÑть мышиный буфер туда же, куда и буфер обмена" -#: vncviewer/OptionsDialog.cxx:927 +#: vncviewer/OptionsDialog.cxx:982 msgid "Display" msgstr "Ðкран" -#: vncviewer/OptionsDialog.cxx:941 +#: vncviewer/OptionsDialog.cxx:996 msgid "Display mode" msgstr "Режим Ñкрана" -#: vncviewer/OptionsDialog.cxx:956 +#: vncviewer/OptionsDialog.cxx:1009 msgid "Windowed" msgstr "Оконный режим" -#: vncviewer/OptionsDialog.cxx:964 +#: vncviewer/OptionsDialog.cxx:1017 msgid "Full screen on current monitor" msgstr "ПолноÑкранный режим на текущем мониторе" -#: vncviewer/OptionsDialog.cxx:972 +#: vncviewer/OptionsDialog.cxx:1025 msgid "Full screen on all monitors" msgstr "ПолноÑкранный режим на вÑех мониторах" -#: vncviewer/OptionsDialog.cxx:980 +#: vncviewer/OptionsDialog.cxx:1033 msgid "Full screen on selected monitor(s)" msgstr "ПолноÑкранный режим на выбранных мониторах" -#: vncviewer/OptionsDialog.cxx:1007 -msgid "Misc." +#: vncviewer/OptionsDialog.cxx:1062 +msgid "Miscellaneous" msgstr "Разное" -#: vncviewer/OptionsDialog.cxx:1015 +#: vncviewer/OptionsDialog.cxx:1070 msgid "Shared (don't disconnect other viewers)" msgstr "СовмеÑÑ‚Ð½Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð° (не отключать других клиентов)" -#: vncviewer/OptionsDialog.cxx:1021 +#: vncviewer/OptionsDialog.cxx:1076 msgid "Ask to reconnect on connection errors" msgstr "Запрашивать о переподключении при ошибках ÑоединениÑ" -#: vncviewer/ServerDialog.cxx:58 -msgid "VNC Viewer: Connection Details" -msgstr "VNC Viewer: Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñоединении" +#: vncviewer/ServerDialog.cxx:63 +msgid "VNC viewer: Connection details" +msgstr "VNC viewer: Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñоединении" -#: vncviewer/ServerDialog.cxx:65 vncviewer/ServerDialog.cxx:70 +#: vncviewer/ServerDialog.cxx:73 msgid "VNC server:" msgstr "Сервер VNC:" -#: vncviewer/ServerDialog.cxx:81 +#: vncviewer/ServerDialog.cxx:80 msgid "Options..." msgstr "Параметры" -#: vncviewer/ServerDialog.cxx:86 +#: vncviewer/ServerDialog.cxx:84 msgid "Load..." msgstr "Загрузить" -#: vncviewer/ServerDialog.cxx:91 -msgid "Save As..." -msgstr "Сохранить" +#: vncviewer/ServerDialog.cxx:88 +msgid "Save as..." +msgstr "Сохранить как…" -#: vncviewer/ServerDialog.cxx:103 +#: vncviewer/ServerDialog.cxx:102 msgid "About..." msgstr "О программе" -#: vncviewer/ServerDialog.cxx:113 +#: vncviewer/ServerDialog.cxx:111 msgid "Connect" msgstr "Войти" -#: vncviewer/ServerDialog.cxx:145 +#: vncviewer/ServerDialog.cxx:147 #, c-format msgid "" "Unable to load the server history:\n" @@ -410,15 +482,15 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:173 vncviewer/ServerDialog.cxx:212 +#: vncviewer/ServerDialog.cxx:176 vncviewer/ServerDialog.cxx:216 msgid "TigerVNC configuration (*.tigervnc)" msgstr "ÐаÑтройки TigerVNC (*.tigervnc)" -#: vncviewer/ServerDialog.cxx:174 +#: vncviewer/ServerDialog.cxx:177 msgid "Select a TigerVNC configuration file" msgstr "Выбрать файл наÑтроек TigerVNC" -#: vncviewer/ServerDialog.cxx:196 vncviewer/vncviewer.cxx:552 +#: vncviewer/ServerDialog.cxx:199 vncviewer/vncviewer.cxx:517 #, c-format msgid "" "Unable to load the specified configuration file:\n" @@ -429,24 +501,24 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:213 +#: vncviewer/ServerDialog.cxx:217 msgid "Save the TigerVNC configuration to file" msgstr "Сохранить наÑтройки TigerVNC в файл" -#: vncviewer/ServerDialog.cxx:239 +#: vncviewer/ServerDialog.cxx:243 #, c-format msgid "%s already exists. Do you want to overwrite?" msgstr "Файл «%s» уже ÑущеÑтвует, перезапиÑать?" -#: vncviewer/ServerDialog.cxx:240 vncviewer/vncviewer.cxx:414 +#: vncviewer/ServerDialog.cxx:244 vncviewer/vncviewer.cxx:394 msgid "No" msgstr "Ðет" -#: vncviewer/ServerDialog.cxx:240 +#: vncviewer/ServerDialog.cxx:244 msgid "Overwrite" msgstr "ПерезапиÑать" -#: vncviewer/ServerDialog.cxx:256 +#: vncviewer/ServerDialog.cxx:260 #, c-format msgid "" "Unable to save the specified configuration file:\n" @@ -457,7 +529,7 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:290 +#: vncviewer/ServerDialog.cxx:294 #, c-format msgid "" "Unable to save the default configuration:\n" @@ -468,7 +540,7 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:303 +#: vncviewer/ServerDialog.cxx:306 #, c-format msgid "" "Unable to save the server history:\n" @@ -479,202 +551,142 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:320 vncviewer/ServerDialog.cxx:387 -#: vncviewer/parameters.cxx:635 vncviewer/parameters.cxx:740 -#: vncviewer/vncviewer.cxx:459 -msgid "Could not obtain the home directory path" -msgstr "Ðевозможно получить путь к домашнему каталогу" +#: vncviewer/ServerDialog.cxx:351 vncviewer/ServerDialog.cxx:429 +#: vncviewer/vncviewer.cxx:580 +msgid "Could not determine VNC state directory path" +msgstr "Ðевозможно определить путь к каталогу ÑоÑтоÑÐ½Ð¸Ñ VNC" -#: vncviewer/ServerDialog.cxx:333 vncviewer/ServerDialog.cxx:396 -#: vncviewer/parameters.cxx:646 vncviewer/parameters.cxx:753 +#: vncviewer/ServerDialog.cxx:363 vncviewer/ServerDialog.cxx:437 +#: vncviewer/parameters.cxx:671 vncviewer/parameters.cxx:752 #, c-format -msgid "Could not open \"%s\": %s" -msgstr "Ðевозможно открыть «%s»: %s" +msgid "Could not open \"%s\"" +msgstr "Ðевозможно открыть «%s»" -#: vncviewer/ServerDialog.cxx:348 vncviewer/ServerDialog.cxx:356 -#: vncviewer/parameters.cxx:767 vncviewer/parameters.cxx:773 -#: vncviewer/parameters.cxx:804 vncviewer/parameters.cxx:833 -#: vncviewer/parameters.cxx:839 +#: vncviewer/ServerDialog.cxx:378 vncviewer/ServerDialog.cxx:387 +#: vncviewer/parameters.cxx:766 vncviewer/parameters.cxx:773 +#: vncviewer/parameters.cxx:807 vncviewer/parameters.cxx:837 +#: vncviewer/parameters.cxx:844 #, c-format -msgid "Failed to read line %d in file %s: %s" -msgstr "Ðе удалоÑÑŒ прочитать Ñтроку %d из файла %s: %s" +msgid "Failed to read line %d in file \"%s\"" +msgstr "Ðе удалоÑÑŒ прочитать Ñтроку %d из файла «%s»" -#: vncviewer/ServerDialog.cxx:357 vncviewer/parameters.cxx:774 +#: vncviewer/ServerDialog.cxx:390 vncviewer/parameters.cxx:776 msgid "Line too long" msgstr "Строка Ñлишком длиннаÑ" -#: vncviewer/UserDialog.cxx:98 +#: vncviewer/UserDialog.cxx:123 msgid "Opening password file failed" msgstr "Ðе удалоÑÑŒ открыть файл Ñ Ð¿Ð°Ñ€Ð¾Ð»ÐµÐ¼" -#: vncviewer/UserDialog.cxx:118 +#: vncviewer/UserDialog.cxx:143 msgid "VNC authentication" msgstr "ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ VNC" -#: vncviewer/UserDialog.cxx:125 +#: vncviewer/UserDialog.cxx:150 msgid "This connection is secure" msgstr "Ðто Ñоединение защищено" -#: vncviewer/UserDialog.cxx:129 +#: vncviewer/UserDialog.cxx:154 msgid "This connection is not secure" msgstr "Ðто Ñоединение не защищено" -#: vncviewer/UserDialog.cxx:146 +#: vncviewer/UserDialog.cxx:176 msgid "Username:" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ:" -#: vncviewer/UserDialog.cxx:159 +#: vncviewer/UserDialog.cxx:189 msgid "Password:" msgstr "Пароль:" -#: vncviewer/UserDialog.cxx:198 -msgid "Authentication cancelled" -msgstr "ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¾Ñ‚Ð¼ÐµÐ½ÐµÐ½Ð°" - -#: vncviewer/Viewport.cxx:391 -#, c-format -msgid "Failed to update keyboard LED state: %lu" -msgstr "Ðе удалоÑÑŒ обновить ÑоÑтоÑние LED клавиатуры: %lu" - -#: vncviewer/Viewport.cxx:397 vncviewer/Viewport.cxx:403 -#, c-format -msgid "Failed to update keyboard LED state: %d" -msgstr "Ðе удалоÑÑŒ обновить ÑоÑтоÑние LED клавиатуры: %d" - -#: vncviewer/Viewport.cxx:433 -msgid "Failed to update keyboard LED state" -msgstr "Ðе удалоÑÑŒ обновить ÑоÑтоÑние LED клавиатуры" - -#: vncviewer/Viewport.cxx:460 vncviewer/Viewport.cxx:468 -#: vncviewer/Viewport.cxx:485 -#, c-format -msgid "Failed to get keyboard LED state: %d" -msgstr "Ðе удалоÑÑŒ получить ÑоÑтоÑние LED клавиатуры: %d" - -#: vncviewer/Viewport.cxx:849 -msgid "No key code specified on key press" -msgstr "Ðе задан код клавиши при нажатии" - -#: vncviewer/Viewport.cxx:1008 -#, c-format -msgid "No scan code for extended virtual key 0x%02x" -msgstr "Ðет Ñкан-кода Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ виртуальной клавиши 0x%02x" - -#: vncviewer/Viewport.cxx:1010 -#, c-format -msgid "No scan code for virtual key 0x%02x" -msgstr "Ðет Ñкан-кода Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ клавиши 0x%02x" +#: vncviewer/UserDialog.cxx:197 +msgid "Keep password for reconnect" +msgstr "Сохранить пароль Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ" -#: vncviewer/Viewport.cxx:1016 -#, c-format -msgid "Invalid scan code 0x%02x" -msgstr "Ðекорректный Ñкан-код 0x%02x" - -#: vncviewer/Viewport.cxx:1046 -#, c-format -msgid "No symbol for extended virtual key 0x%02x" -msgstr "Ðет Ñимвола Ð´Ð»Ñ Ñ€Ð°Ñширенной виртуальной клавиши 0x%02x" - -#: vncviewer/Viewport.cxx:1048 -#, c-format -msgid "No symbol for virtual key 0x%02x" -msgstr "Ðет Ñимвола Ð´Ð»Ñ Ð²Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð¾Ð¹ клавиши 0x%02x" - -#: vncviewer/Viewport.cxx:1154 -#, c-format -msgid "No symbol for key code 0x%02x (in the current state)" -msgstr "Ðет Ñимвола Ð´Ð»Ñ ÐºÐ¾Ð´Ð° клавиши 0x%02x (в текущем ÑоÑтоÑнии)" - -#: vncviewer/Viewport.cxx:1187 -#, c-format -msgid "No symbol for key code %d (in the current state)" -msgstr "Ðет Ñимвола Ð´Ð»Ñ ÐºÐ¾Ð´Ð° клавиши %d (в текущем ÑоÑтоÑнии)" - -#: vncviewer/Viewport.cxx:1247 +#: vncviewer/Viewport.cxx:695 msgctxt "ContextMenu|" -msgid "Dis&connect" +msgid "Disconn&ect" msgstr "От&ключитьÑÑ" -#: vncviewer/Viewport.cxx:1250 +#: vncviewer/Viewport.cxx:698 msgctxt "ContextMenu|" msgid "&Full screen" msgstr "Полный &Ñкран" -#: vncviewer/Viewport.cxx:1253 +#: vncviewer/Viewport.cxx:701 msgctxt "ContextMenu|" msgid "Minimi&ze" msgstr "&Свернуть" -#: vncviewer/Viewport.cxx:1255 +#: vncviewer/Viewport.cxx:703 msgctxt "ContextMenu|" msgid "Resize &window to session" msgstr "Изменить &размер окна" -#: vncviewer/Viewport.cxx:1260 +#: vncviewer/Viewport.cxx:708 msgctxt "ContextMenu|" msgid "&Ctrl" msgstr "&Ctrl" -#: vncviewer/Viewport.cxx:1263 +#: vncviewer/Viewport.cxx:711 msgctxt "ContextMenu|" msgid "&Alt" msgstr "&Alt" -#: vncviewer/Viewport.cxx:1269 +#: vncviewer/Viewport.cxx:717 #, c-format msgctxt "ContextMenu|" msgid "Send %s" msgstr "Отправить %s" -#: vncviewer/Viewport.cxx:1275 +#: vncviewer/Viewport.cxx:724 msgctxt "ContextMenu|" msgid "Send Ctrl-Alt-&Del" msgstr "Отправить Ctrl-Alt-&Del" -#: vncviewer/Viewport.cxx:1278 +#: vncviewer/Viewport.cxx:727 msgctxt "ContextMenu|" msgid "&Refresh screen" msgstr "&Обновить Ñкран" -#: vncviewer/Viewport.cxx:1281 +#: vncviewer/Viewport.cxx:730 msgctxt "ContextMenu|" msgid "&Options..." msgstr "&Параметры…" -#: vncviewer/Viewport.cxx:1283 +#: vncviewer/Viewport.cxx:732 msgctxt "ContextMenu|" msgid "Connection &info..." msgstr "Сведен&Ð¸Ñ Ð¾ Ñоединении…" -#: vncviewer/Viewport.cxx:1285 +#: vncviewer/Viewport.cxx:734 msgctxt "ContextMenu|" msgid "About &TigerVNC viewer..." msgstr "О &TigerVNC…" -#: vncviewer/Viewport.cxx:1374 +#: vncviewer/Viewport.cxx:830 msgid "VNC connection info" msgstr "Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ Ñоединении VNC" -#: vncviewer/Win32TouchHandler.cxx:47 +#: vncviewer/Win32TouchHandler.cxx:48 msgid "Window is registered for touch instead of gestures" msgstr "Ð”Ð»Ñ Ð¾ÐºÐ½Ð° зарегиÑтрировано управление прикоÑновениÑми, а не жеÑтами" -#: vncviewer/Win32TouchHandler.cxx:81 +#: vncviewer/Win32TouchHandler.cxx:83 #, c-format msgid "Failed to set gesture configuration (error 0x%x)" msgstr "Ðе удалоÑÑŒ задать параметры жеÑтов (ошибка 0x%x)" -#: vncviewer/Win32TouchHandler.cxx:93 +#: vncviewer/Win32TouchHandler.cxx:95 #, c-format msgid "Failed to get gesture information (error 0x%x)" msgstr "Ðе удалоÑÑŒ получить информацию о жеÑтах (ошибка 0x%x)" -#: vncviewer/Win32TouchHandler.cxx:358 +#: vncviewer/Win32TouchHandler.cxx:360 #, c-format msgid "Invalid mouse button %d, must be a number between 1 and 7." msgstr "ÐÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÐ° мыши %d: должно быть чиÑло от 1 до 7." -#: vncviewer/Win32TouchHandler.cxx:423 +#: vncviewer/Win32TouchHandler.cxx:425 #, c-format msgid "Unhandled key 0x%x - can't generate keyboard event." msgstr "ÐÐµÐ¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚Ð°Ð½Ð½Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÐ° 0x%x: невозможно Ñгенерировать Ñобытие клавиатуры." @@ -700,7 +712,6 @@ msgid "Failure grabbing device %i" msgstr "Ðе удалоÑÑŒ перехватить уÑтройÑтво %i" #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:13 -#: vncviewer/vncviewer.cxx:406 vncviewer/vncviewer.desktop.in.in:3 msgid "TigerVNC Viewer" msgstr "TigerVNC Viewer" @@ -718,87 +729,92 @@ msgid "TigerVNC is a high-speed version of VNC based on the RealVNC 4 and X.org msgstr "TigerVNC — выÑокоÑкороÑÑ‚Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ VNC, в оÑнове лежит код RealVNC 4 и X.org. TigerVNC начиналаÑÑŒ как разработка Ñледующего Ð¿Ð¾ÐºÐ¾Ð»ÐµÐ½Ð¸Ñ TightVNC Ð´Ð»Ñ Ð¿Ð»Ð°Ñ‚Ñ„Ð¾Ñ€Ð¼ Unix и Linux, но отделилаÑÑŒ от родительÑкого проекта в начале 2009 года Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы TightVNC Ñмогла ÑфокуÑироватьÑÑ Ð½Ð° платформе Windows. TigerVNC поддерживает вариант ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Tight, который был значительно уÑкорен Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ кодека libjpeg-turbo JPEG." #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:33 -msgid "TigerVNC Viewer connection to a CentOS machine" -msgstr "Подключение TigerVNC Viewer к машине CentOS" +msgid "TigerVNC viewer connection to a CentOS machine" +msgstr "Подключение TigerVNC viewer к машине CentOS" #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:37 -msgid "TigerVNC Viewer connection to a macOS machine" -msgstr "Подключение TigerVNC Viewer к машине macOS" +msgid "TigerVNC viewer connection to a macOS machine" +msgstr "Подключение TigerVNC viewer к машине macOS" #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:41 -msgid "TigerVNC Viewer connection to a Windows machine" -msgstr "Подключение TigerVNC Viewer к машине Windows" - -#: vncviewer/parameters.cxx:308 vncviewer/parameters.cxx:333 -#: vncviewer/parameters.cxx:350 vncviewer/parameters.cxx:390 -#: vncviewer/parameters.cxx:410 +msgid "TigerVNC viewer connection to a Windows machine" +msgstr "Подключение TigerVNC viewer к машине Windows" + +#. developer_name tag deprecated with Appstream 1.0 +#: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:46 +#: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:48 +msgid "The TigerVNC team" +msgstr "Команда TigerVNC" + +#: vncviewer/parameters.cxx:319 vncviewer/parameters.cxx:344 +#: vncviewer/parameters.cxx:361 vncviewer/parameters.cxx:401 +#: vncviewer/parameters.cxx:421 msgid "The name of the parameter is too large" msgstr "Ðазвание параметра Ñлишком длинное" -#: vncviewer/parameters.cxx:312 vncviewer/parameters.cxx:317 -#: vncviewer/parameters.cxx:368 +#: vncviewer/parameters.cxx:323 vncviewer/parameters.cxx:328 +#: vncviewer/parameters.cxx:379 msgid "The parameter is too large" msgstr "Параметр Ñлишком длинный" -#: vncviewer/parameters.cxx:375 vncviewer/parameters.cxx:696 -#: vncviewer/parameters.cxx:818 +#: vncviewer/parameters.cxx:386 vncviewer/parameters.cxx:712 +#: vncviewer/parameters.cxx:822 msgid "Invalid format or too large value" msgstr "ÐедопуÑтимый формат или Ñлишком большое значение" -#: vncviewer/parameters.cxx:429 vncviewer/parameters.cxx:460 +#: vncviewer/parameters.cxx:440 vncviewer/parameters.cxx:473 msgid "Failed to create registry key" msgstr "Ðе удалоÑÑŒ Ñоздать ключ рееÑтра" -#: vncviewer/parameters.cxx:448 vncviewer/parameters.cxx:503 -#: vncviewer/parameters.cxx:545 vncviewer/parameters.cxx:612 +#: vncviewer/parameters.cxx:461 vncviewer/parameters.cxx:528 +#: vncviewer/parameters.cxx:571 vncviewer/parameters.cxx:638 msgid "Failed to close registry key" msgstr "Ðе удалоÑÑŒ закрыть ключ рееÑтра" -#: vncviewer/parameters.cxx:466 vncviewer/parameters.cxx:483 -#: vncviewer/parameters.cxx:654 vncviewer/parameters.cxx:664 -#: vncviewer/parameters.cxx:675 +#: vncviewer/parameters.cxx:479 vncviewer/parameters.cxx:506 +#: vncviewer/parameters.cxx:680 vncviewer/parameters.cxx:692 #, c-format msgid "Failed to save \"%s\": %s" msgstr "Ðе удалоÑÑŒ Ñохранить «%s»: %s" -#: vncviewer/parameters.cxx:479 vncviewer/parameters.cxx:567 -#: vncviewer/parameters.cxx:677 vncviewer/parameters.cxx:714 -msgid "Unknown parameter type" -msgstr "ÐеизвеÑтный тип Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð°" - -#: vncviewer/parameters.cxx:496 +#: vncviewer/parameters.cxx:489 vncviewer/parameters.cxx:520 #, c-format msgid "Failed to remove \"%s\": %s" msgstr "Ðе удалоÑÑŒ удалить «%s»: %s" -#: vncviewer/parameters.cxx:518 vncviewer/parameters.cxx:590 +#: vncviewer/parameters.cxx:544 vncviewer/parameters.cxx:616 msgid "Failed to open registry key" msgstr "Ðе удалоÑÑŒ открыть ключ рееÑтра" -#: vncviewer/parameters.cxx:535 +#: vncviewer/parameters.cxx:561 #, c-format msgid "Failed to read server history entry %d: %s" msgstr "Ðе удалоÑÑŒ прочитать Ñлемент иÑтории Ñервера %d: %s" -#: vncviewer/parameters.cxx:571 vncviewer/parameters.cxx:601 +#: vncviewer/parameters.cxx:597 vncviewer/parameters.cxx:627 #, c-format msgid "Failed to read parameter \"%s\": %s" msgstr "Ðе удалоÑÑŒ прочитать параметр «%s»: %s" -#: vncviewer/parameters.cxx:655 vncviewer/parameters.cxx:666 +#: vncviewer/parameters.cxx:661 vncviewer/parameters.cxx:740 +#: vncviewer/vncviewer.cxx:546 +msgid "Could not determine VNC config directory path" +msgstr "Ðевозможно определить путь к каталогу наÑтроек VNC" + +#: vncviewer/parameters.cxx:682 vncviewer/parameters.cxx:694 msgid "Could not encode parameter" msgstr "Ðе удалоÑÑŒ закодировать параметр" -#: vncviewer/parameters.cxx:783 +#: vncviewer/parameters.cxx:785 #, c-format msgid "Configuration file %s is in an invalid format" msgstr "ÐедопуÑтимый формат файла конфигурации %s" -#: vncviewer/parameters.cxx:805 +#: vncviewer/parameters.cxx:809 msgid "Invalid format" msgstr "ÐедопуÑтимый формат" -#: vncviewer/parameters.cxx:840 +#: vncviewer/parameters.cxx:846 msgid "Unknown parameter" msgstr "ÐеизвеÑтный параметр" @@ -822,40 +838,40 @@ msgstr "Ðе удалоÑÑŒ Ñоздать обработчик прикоÑно msgid "Couldn't attach event handler to window (error 0x%x)" msgstr "Ðевозможно прикрепить обработчик Ñобытий к окну (ошибка 0x%x)" -#: vncviewer/touch.cxx:212 +#: vncviewer/touch.cxx:215 msgid "Failed to get event data for X Input event" msgstr "Ðе удалоÑÑŒ получить данные о Ñобытии Ð´Ð»Ñ ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ X Input" -#: vncviewer/touch.cxx:225 +#: vncviewer/touch.cxx:228 msgid "X Input event for unknown window" msgstr "Событие X Input Ð´Ð»Ñ Ð½ÐµÐ¸Ð·Ð²ÐµÑтного окна" -#: vncviewer/touch.cxx:251 +#: vncviewer/touch.cxx:254 msgid "X Input extension not available." msgstr "РаÑширение X Input недоÑтупно." -#: vncviewer/touch.cxx:258 +#: vncviewer/touch.cxx:261 msgid "X Input 2 (or newer) is not available." msgstr "РаÑширение X Input 2 (или новее) недоÑтупно." -#: vncviewer/touch.cxx:263 +#: vncviewer/touch.cxx:266 msgid "X Input 2.2 (or newer) is not available. Touch gestures will not be supported." msgstr "РаÑширение X Input 2.2 (или новее) недоÑтупно. ПрикоÑÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¶ÐµÑтов не будут поддерживатьÑÑ." -#: vncviewer/vncviewer.cxx:107 +#: vncviewer/vncviewer.cxx:104 #, c-format msgid "" -"TigerVNC Viewer v%s\n" +"TigerVNC viewer v%s\n" "Built on: %s\n" -"Copyright (C) 1999-%d TigerVNC Team and many others (see README.rst)\n" +"Copyright (C) 1999-%d TigerVNC team and many others (see README.rst)\n" "See https://www.tigervnc.org for information on TigerVNC." msgstr "" -"TigerVNC Viewer v%s\n" +"TigerVNC viewer v%s\n" "Сборка от: %s\n" -"Copyright (C) 1999-%d, TigerVNC Team и многие другие (Ñм. README.rst)\n" +"Copyright (C) 1999-%d, команда TigerVNC и многие другие (Ñм. README.rst)\n" "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ TigerVNC на Ñайте https://www.tigervnc.org" -#: vncviewer/vncviewer.cxx:161 +#: vncviewer/vncviewer.cxx:158 #, c-format msgid "" "An unexpected error occurred when communicating with the server:\n" @@ -866,15 +882,15 @@ msgstr "" "\n" "%s" -#: vncviewer/vncviewer.cxx:177 +#: vncviewer/vncviewer.cxx:174 msgid "About TigerVNC Viewer" msgstr "О TigerVNC viewer" -#: vncviewer/vncviewer.cxx:198 +#: vncviewer/vncviewer.cxx:195 msgid "Internal FLTK error. Exiting." msgstr "ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° FLTK. Выход." -#: vncviewer/vncviewer.cxx:217 +#: vncviewer/vncviewer.cxx:214 #, c-format msgid "" "%s\n" @@ -885,79 +901,182 @@ msgstr "" "\n" "ПопытатьÑÑ Ð¿ÐµÑ€ÐµÐ¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒÑÑ?" -#: vncviewer/vncviewer.cxx:248 vncviewer/vncviewer.cxx:260 +#: vncviewer/vncviewer.cxx:245 vncviewer/vncviewer.cxx:257 #, c-format msgid "Error starting new TigerVNC Viewer: %s" msgstr "Ðе удалоÑÑŒ запуÑтить новый TigerVNC Viewer: %s" -#: vncviewer/vncviewer.cxx:269 +#: vncviewer/vncviewer.cxx:266 #, c-format -msgid "Termination signal %d has been received. TigerVNC Viewer will now exit." -msgstr "Получен Ñигнал Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ %d. TigerVNC Viewer будет закрыт." +msgid "Termination signal %d has been received. TigerVNC viewer will now exit." +msgstr "Получен Ñигнал Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ %d. TigerVNC viewer будет закрыт." -#: vncviewer/vncviewer.cxx:415 +#: vncviewer/vncviewer.cxx:391 vncviewer/vncviewer.desktop.in.in:3 +msgid "TigerVNC viewer" +msgstr "TigerVNC viewer" + +#: vncviewer/vncviewer.cxx:395 msgid "Yes" msgstr "Да" -#: vncviewer/vncviewer.cxx:418 +#: vncviewer/vncviewer.cxx:398 msgid "Close" msgstr "Закрыть" -#: vncviewer/vncviewer.cxx:423 +#: vncviewer/vncviewer.cxx:403 msgid "About" msgstr "О программе" -#: vncviewer/vncviewer.cxx:426 +#: vncviewer/vncviewer.cxx:406 msgid "Hide" msgstr "Скрыть" -#: vncviewer/vncviewer.cxx:429 +#: vncviewer/vncviewer.cxx:409 msgid "Quit" msgstr "Выход" -#: vncviewer/vncviewer.cxx:433 +#: vncviewer/vncviewer.cxx:413 msgid "Services" msgstr "Службы" -#: vncviewer/vncviewer.cxx:434 -msgid "Hide Others" +#: vncviewer/vncviewer.cxx:414 +msgid "Hide others" msgstr "Скрыть прочее" -#: vncviewer/vncviewer.cxx:435 -msgid "Show All" -msgstr "Показать вÑе" +#: vncviewer/vncviewer.cxx:415 +msgid "Show all" +msgstr "Показать вÑÑ‘" -#: vncviewer/vncviewer.cxx:444 +#: vncviewer/vncviewer.cxx:424 msgctxt "SysMenu|" msgid "&File" msgstr "&Файл" -#: vncviewer/vncviewer.cxx:447 +#: vncviewer/vncviewer.cxx:427 msgctxt "SysMenu|File|" msgid "&New Connection" msgstr "&Ðовое Ñоединение" -#: vncviewer/vncviewer.cxx:463 +#: vncviewer/vncviewer.cxx:450 #, c-format -msgid "Could not create VNC home directory: %s" -msgstr "Ðе удалоÑÑŒ Ñоздать домашний каталог VNC: %s" +msgid "" +"\n" +"Usage: %s [parameters] [host][:displayNum]\n" +" %s [parameters] [host][::port]\n" +" %s [parameters] [unix socket]\n" +" %s [parameters] -listen [port]\n" +" %s [parameters] [.tigervnc file]\n" +msgstr "" +"\n" +"ИÑпользование: %s [параметры] [узел][:номерДиÑплеиÑ]\n" +" %s [параметры] [узел][::порт]\n" +" %s [параметры] [Ñокет unix]\n" +" %s [параметры] -listen [порт]\n" +" %s [параметры] [файл .tigervnc]\n" -#: vncviewer/vncviewer.cxx:562 +#: vncviewer/vncviewer.cxx:465 +#, c-format +msgid "" +"\n" +"Options:\n" +"\n" +" -display Xdisplay - Specifies the X display for the viewer window\n" +" -geometry geometry - Initial position of the main VNC viewer window. See the\n" +" man page for details.\n" +msgstr "" +"\n" +"Параметры:\n" +"\n" +" -display Xdisplay - Задать X-диÑплей Ð´Ð»Ñ Ð¾ÐºÐ½Ð° программы\n" +" -geometry Ð³ÐµÐ¾Ð¼ÐµÑ‚Ñ€Ð¸Ñ - Ðачальное положение оÑновного окна VNC.\n" +" ПодробноÑти в Ñправочной Ñтранице.\n" + +#: vncviewer/vncviewer.cxx:472 +#, c-format +msgid "" +"\n" +"Parameters can be turned on with -<param> or off with -<param>=0\n" +"Parameters which take a value can be specified as -<param> <value>\n" +"Other valid forms are <param>=<value> -<param>=<value> --<param>=<value>\n" +"Parameter names are case-insensitive. The parameters are:\n" +"\n" +msgstr "" +"\n" +"Параметры включаютÑÑ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸ÐµÐ¼ в виде -<параметр> или \n" +"отключаютÑÑ Ð² виде -<параметр>=0\n" +"Параметры Ñо значениÑми можно задавать в виде -<параметр> <значение>\n" +"Также допуÑкаетÑÑ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚ <параметр>=<значение> -<параметр>=<значение>\n" +"--<параметр>=<значение>\n" +"Имена параметров не чувÑтвительны к региÑтру. Возможные параметры:\n" +"\n" + +#: vncviewer/vncviewer.cxx:527 msgid "FullScreenAllMonitors is deprecated, set FullScreenMode to 'all' instead" msgstr "FullScreenAllMonitors уÑтарел, задайте FullScreenMode Ñо значением «all»" +#: vncviewer/vncviewer.cxx:532 +msgid "DotWhenNoCursor is deprecated, set AlwaysCursor to 1 and CursorType to 'Dot' instead" +msgstr "DotWhenNoCursor уÑтарел, вмеÑто него иÑпользуйте AlwaysCursor равным 1 и CursorType равным «Dot»" + +#: vncviewer/vncviewer.cxx:553 +msgid "~/.vnc is deprecated, please consult 'man vncviewer' for paths to migrate to." +msgstr "Каталог ~/.vnc уÑтарел, по «man vncviewer» можно найти информацию об изменениÑÑ… в путÑÑ…." + +#: vncviewer/vncviewer.cxx:557 +#, c-format +msgid "%%APPDATA%%\\vnc is deprecated, please switch to the %%APPDATA%%\\TigerVNC location." +msgstr "%%APPDATA%%\\vnc уÑтарел, иÑпользуйте %%APPDATA%%\\TigerVNC." + +#: vncviewer/vncviewer.cxx:562 +#, c-format +msgid "Could not create VNC config directory \"%s\": %s" +msgstr "Ðевозможно Ñоздать каталог наÑтроек VNC «%s»: %s" + +#: vncviewer/vncviewer.cxx:568 +msgid "Could not determine VNC data directory path" +msgstr "Ðевозможно определить путь к каталогу данных VNC" + +#: vncviewer/vncviewer.cxx:574 +#, c-format +msgid "Could not create VNC data directory \"%s\": %s" +msgstr "Ðевозможно Ñоздать каталог данных VNC «%s»: %s" + +#: vncviewer/vncviewer.cxx:586 +#, c-format +msgid "Could not create VNC state directory \"%s\": %s" +msgstr "Ðевозможно Ñоздать каталог ÑоÑтоÑÐ½Ð¸Ñ VNC «%s»: %s" + +#: vncviewer/vncviewer.cxx:703 +#, c-format +msgid "%s: Unrecognized option '%s'\n" +msgstr "%s: ÐераÑпознанный параметр «%s»\n" + +#: vncviewer/vncviewer.cxx:705 vncviewer/vncviewer.cxx:713 +#, c-format +msgid "See '%s --help' for more information.\n" +msgstr "По команде «%s --help» можно получить дополнительную информацию.\n" + +#: vncviewer/vncviewer.cxx:712 +#, c-format +msgid "%s: Extra argument '%s'\n" +msgstr "%s: Лишний аргумент «%s»\n" + #. TRANSLATORS: "Parameters" are command line arguments, or settings #. from a file or the Windows registry. -#: vncviewer/vncviewer.cxx:768 vncviewer/vncviewer.cxx:769 +#: vncviewer/vncviewer.cxx:748 vncviewer/vncviewer.cxx:749 msgid "Parameters -listen and -via are incompatible" msgstr "Параметры -listen и -via неÑовмеÑтимы" -#: vncviewer/vncviewer.cxx:783 +#: vncviewer/vncviewer.cxx:763 +msgid "Unable to listen for incoming connections" +msgstr "Ðевозможно проÑлушивать входÑщие ÑоединениÑ" + +#: vncviewer/vncviewer.cxx:765 #, c-format msgid "Listening on port %d" msgstr "ПроÑлушиваетÑÑ Ð¿Ð¾Ñ€Ñ‚ %d" -#: vncviewer/vncviewer.cxx:816 +#: vncviewer/vncviewer.cxx:794 #, c-format msgid "" "Failure waiting for incoming VNC connection:\n" @@ -968,10 +1087,44 @@ msgstr "" "\n" "%s" +#: vncviewer/vncviewer.cxx:815 +#, c-format +msgid "" +"Failure setting up encrypted tunnel:\n" +"\n" +"%s" +msgstr "" +"Ошибка при Ñоздании шифрованного туннелÑ:\n" +"\n" +"%s" + #: vncviewer/vncviewer.desktop.in.in:4 -msgid "Remote Desktop Viewer" +msgid "Remote desktop viewer" msgstr "ПроÑмотр удалённых рабочих Ñтолов" +#~ msgid "VNC Viewer: Connection Options" +#~ msgstr "VNC Viewer: параметры ÑоединениÑ" + +#~ msgid "Show dot when no cursor" +#~ msgstr "Показывать точку при отÑутÑтвии курÑора" + +#~ msgid "Misc." +#~ msgstr "Разное" + +#, c-format +#~ msgid "Failed to update keyboard LED state: %d" +#~ msgstr "Ðе удалоÑÑŒ обновить ÑоÑтоÑние LED клавиатуры: %d" + +#~ msgid "No key code specified on key press" +#~ msgstr "Ðе задан код клавиши при нажатии" + +#, c-format +#~ msgid "No symbol for key code 0x%02x (in the current state)" +#~ msgstr "Ðет Ñимвола Ð´Ð»Ñ ÐºÐ¾Ð´Ð° клавиши 0x%02x (в текущем ÑоÑтоÑнии)" + +#~ msgid "Unknown parameter type" +#~ msgstr "ÐеизвеÑтный тип Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€Ð°" + #~ msgid "Failed to get monitor name because X11 RandR could not be found" #~ msgstr "Ðе удалоÑÑŒ получить Ð¸Ð¼Ñ Ð¼Ð¾Ð½Ð¸Ñ‚Ð¾Ñ€Ð°, так как не найден X11 RandR" @@ -1,14 +1,14 @@ # Serbian translation for tigervnc. # Copyright © 2020 the TigerVNC Team (msgids) # This file is distributed under the same license as the tigervnc package. -# МироÑлав Ðиколић <miroslavnikolic@rocketmail.com>, 2016-2024. +# МироÑлав Ðиколић <miroslavnikolic@rocketmail.com>, 2016-2025. # msgid "" msgstr "" -"Project-Id-Version: tigervnc-1.13.90\n" +"Project-Id-Version: tigervnc-1.14.90\n" "Report-Msgid-Bugs-To: tigervnc-devel@googlegroups.com\n" -"POT-Creation-Date: 2024-06-20 15:01+0200\n" -"PO-Revision-Date: 2024-12-15 19:41+0100\n" +"POT-Creation-Date: 2025-01-14 16:15+0100\n" +"PO-Revision-Date: 2025-05-18 09:05+0200\n" "Last-Translator: МироÑлав Ðиколић <miroslavnikolic@rocketmail.com>\n" "Language-Team: Serbian <(nothing)>\n" "Language: sr\n" @@ -19,17 +19,17 @@ msgstr "" "X-Bugs: Report translation errors to the Language-Team address.\n" "X-Generator: Poedit 3.5\n" -#: vncviewer/CConn.cxx:99 +#: vncviewer/CConn.cxx:102 #, c-format msgid "Connected to socket %s" msgstr "Повезан на прикључницу „%s“" -#: vncviewer/CConn.cxx:106 +#: vncviewer/CConn.cxx:109 #, c-format msgid "Connected to host %s port %d" msgstr "Повезан Ñа домаћином „%s“ прикључник %d" -#: vncviewer/CConn.cxx:111 +#: vncviewer/CConn.cxx:114 #, c-format msgid "" "Failed to connect to \"%s\":\n" @@ -40,85 +40,101 @@ msgstr "" "\n" "%s" -#: vncviewer/CConn.cxx:155 +#: vncviewer/CConn.cxx:151 #, c-format msgid "Desktop name: %.80s" msgstr "Ðазив радне површи: %.80s" -#: vncviewer/CConn.cxx:160 +#: vncviewer/CConn.cxx:154 #, c-format msgid "Host: %.80s port: %d" msgstr "Домаћин: %.80s прикључник: %d" -#: vncviewer/CConn.cxx:165 +#: vncviewer/CConn.cxx:158 #, c-format msgid "Size: %d x %d" msgstr "Величина: %d x %d" -#: vncviewer/CConn.cxx:173 +#: vncviewer/CConn.cxx:165 #, c-format msgid "Pixel format: %s" msgstr "Формат пикÑела: %s" -#: vncviewer/CConn.cxx:180 +#: vncviewer/CConn.cxx:170 #, c-format msgid "(server default %s)" msgstr "(оÑновно на Ñерверу %s)" -#: vncviewer/CConn.cxx:185 +#: vncviewer/CConn.cxx:173 #, c-format msgid "Requested encoding: %s" msgstr "Затражено кодирање: %s" -#: vncviewer/CConn.cxx:190 +#: vncviewer/CConn.cxx:177 #, c-format msgid "Last used encoding: %s" msgstr "ПоÑледње коришћено кодирање: %s" -#: vncviewer/CConn.cxx:195 +#: vncviewer/CConn.cxx:181 #, c-format msgid "Line speed estimate: %d kbit/s" msgstr "Процењена брзина линије: %d kbit/s" -#: vncviewer/CConn.cxx:200 +#: vncviewer/CConn.cxx:185 #, c-format msgid "Protocol version: %d.%d" msgstr "Издања протокола: %d.%d" -#: vncviewer/CConn.cxx:205 +#: vncviewer/CConn.cxx:189 #, c-format msgid "Security method: %s" msgstr "Метода безбедноÑти: %s" -#: vncviewer/CConn.cxx:266 vncviewer/CConn.cxx:268 +#: vncviewer/CConn.cxx:250 vncviewer/CConn.cxx:252 msgid "The connection was dropped by the server before the session could be established." msgstr "Сервер је одбацио везу пре него ли је ÑеÑија могла да Ñе уÑпоÑтави." -#: vncviewer/CConn.cxx:326 +#: vncviewer/CConn.cxx:262 +#, c-format +msgid "Authentication failed: %s" +msgstr "Потврђивање идентитета није уÑпело: %s" + +#: vncviewer/CConn.cxx:263 +#, c-format +msgid "" +"Failed to authenticate with the server. Reason given by the server:\n" +"\n" +"%s" +msgstr "" +"ÐиÑам уÑпео да потврдим идентитет Ñа Ñервером. Разлог који је дао Ñервер:\n" +"\n" +"%s" + +#: vncviewer/CConn.cxx:335 #, c-format msgid "SetDesktopSize failed: %d" msgstr "ÐеуÑпело подешавање величине радне површи: %d" -#: vncviewer/CConn.cxx:399 +#: vncviewer/CConn.cxx:408 msgid "Invalid SetColourMapEntries from server!" msgstr "ÐеиÑправни уноÑи подешавања мапе боје Ñа Ñервера!" -#: vncviewer/CConn.cxx:507 +#: vncviewer/CConn.cxx:516 #, c-format msgid "Throughput %d kbit/s - changing to quality %d" msgstr "ПропуÑноÑÑ‚ је %d kbit/s — мењам на квалитет %d" -#: vncviewer/CConn.cxx:529 +#: vncviewer/CConn.cxx:538 #, c-format msgid "Throughput %d kbit/s - full color is now enabled" msgstr "ПропуÑноÑÑ‚ је %d kbit/s — пуна боја је Ñада омогућена" -#: vncviewer/CConn.cxx:532 +#: vncviewer/CConn.cxx:541 #, c-format msgid "Throughput %d kbit/s - full color is now disabled" msgstr "ПропуÑноÑÑ‚ је %d kbit/s — пуна боја је Ñада онемогућена" -#: vncviewer/CConn.cxx:558 +#: vncviewer/CConn.cxx:567 #, c-format msgid "Using pixel format %s" msgstr "КориÑтим формат пикÑела %s" @@ -131,21 +147,21 @@ msgstr "Ðаведена је неиÑправна геометрија!" msgid "Reducing window size to fit on current monitor" msgstr "Смањујем величину прозора да Ñтане на текући монитор" -#: vncviewer/DesktopWindow.cxx:648 +#: vncviewer/DesktopWindow.cxx:646 msgid "Adjusting window size to avoid accidental full-screen request" msgstr "Прилагођавам величину прозора да би Ñе избегли Ñлучајни захтеви за целим екраном" -#: vncviewer/DesktopWindow.cxx:696 +#: vncviewer/DesktopWindow.cxx:694 #, c-format msgid "Press %s to open the context menu" msgstr "ПритиÑните „%s“ да отворите приручни изборник" -#: vncviewer/DesktopWindow.cxx:1097 vncviewer/DesktopWindow.cxx:1105 -#: vncviewer/DesktopWindow.cxx:1125 +#: vncviewer/DesktopWindow.cxx:1094 vncviewer/DesktopWindow.cxx:1102 +#: vncviewer/DesktopWindow.cxx:1122 msgid "Failure grabbing keyboard" msgstr "ÐеуÑпех хватања таÑтатуре" -#: vncviewer/DesktopWindow.cxx:1415 +#: vncviewer/DesktopWindow.cxx:1411 msgid "Invalid screen layout computed for resize request!" msgstr "Прорачунат је неодговарајући раÑпоред екрана за захтев промене величине!" @@ -153,251 +169,307 @@ msgstr "Прорачунат је неодговарајући раÑпоред msgid "Invalid state for 3 button emulation" msgstr "ÐеиÑправно Ñтање за опонашање 3 дугмета" +#: vncviewer/KeyboardWin32.cxx:242 +#, c-format +msgid "No scan code for extended virtual key 0x%02x" +msgstr "Ðема шифре прегледа за проширени виртуелни кључ 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:244 +#, c-format +msgid "No scan code for virtual key 0x%02x" +msgstr "Ðема шифре прегледа за виртуелни кључ 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:250 +#, c-format +msgid "Invalid scan code 0x%02x" +msgstr "ÐеиÑправан код Ñкенирања 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:262 +#, c-format +msgid "No symbol for extended virtual key 0x%02x" +msgstr "Ðема Ñимбола за проширени виртуелни кључ 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:264 +#, c-format +msgid "No symbol for virtual key 0x%02x" +msgstr "Ðема Ñимбола за виртуелни кључ 0x%02x" + +#: vncviewer/KeyboardWin32.cxx:423 +#, c-format +msgid "Failed to update keyboard LED state: %lu" +msgstr "ÐиÑам уÑпео да оÑвежим Ñтање диоде таÑтатуре: %lu" + +#: vncviewer/KeyboardX11.cxx:104 +#, c-format +msgid "No symbol for key code %d (in the current state)" +msgstr "Ðема Ñимбола за шифру кључа %d (у текућем Ñтању)" + +#: vncviewer/KeyboardX11.cxx:129 +#, c-format +msgid "Failed to get keyboard LED state: %d" +msgstr "ÐиÑам уÑпео да добавим Ñтање диоде таÑтатуре: %d" + +#: vncviewer/KeyboardX11.cxx:174 +msgid "Failed to update keyboard LED state" +msgstr "ÐиÑам уÑпео да оÑвежим Ñтање диоде таÑтатуре" + #: vncviewer/MonitorIndicesParameter.cxx:52 -#: vncviewer/MonitorIndicesParameter.cxx:102 +#: vncviewer/MonitorIndicesParameter.cxx:100 msgid "Failed to get system monitor configuration" msgstr "ÐиÑам уÑпео да добавим подешавање монитора ÑиÑтема" -#: vncviewer/MonitorIndicesParameter.cxx:80 +#: vncviewer/MonitorIndicesParameter.cxx:79 #, c-format msgid "Invalid configuration specified for %s" msgstr "ÐеиÑправно подешавање је наведено за „%s“" -#: vncviewer/MonitorIndicesParameter.cxx:88 +#: vncviewer/MonitorIndicesParameter.cxx:86 #, c-format msgid "Monitor index %d does not exist" msgstr "Ð˜Ð½Ð´ÐµÐºÑ Ð¼Ð¾Ð½Ð¸Ñ‚Ð¾Ñ€Ð° %d не поÑтоји" -#: vncviewer/MonitorIndicesParameter.cxx:166 -#: vncviewer/MonitorIndicesParameter.cxx:186 +#: vncviewer/MonitorIndicesParameter.cxx:162 +#: vncviewer/MonitorIndicesParameter.cxx:182 #, c-format msgid "Invalid monitor index '%s'" msgstr "ÐеиÑправан Ð¸Ð½Ð´ÐµÐºÑ Ð¼Ð¾Ð½Ð¸Ñ‚Ð¾Ñ€Ð° „%s“" -#: vncviewer/MonitorIndicesParameter.cxx:174 +#: vncviewer/MonitorIndicesParameter.cxx:170 #, c-format msgid "Unexpected character '%c'" msgstr "Ðеочекивани знак „%c“" #: vncviewer/OptionsDialog.cxx:64 -msgid "TigerVNC Options" +msgid "TigerVNC options" msgstr "Опције ТигарВÐЦ-а" -#: vncviewer/OptionsDialog.cxx:97 vncviewer/ServerDialog.cxx:102 -#: vncviewer/vncviewer.cxx:395 +#: vncviewer/OptionsDialog.cxx:97 vncviewer/ServerDialog.cxx:107 +#: vncviewer/vncviewer.cxx:397 msgid "Cancel" msgstr "Откажи" -#: vncviewer/OptionsDialog.cxx:102 vncviewer/vncviewer.cxx:394 +#: vncviewer/OptionsDialog.cxx:102 vncviewer/vncviewer.cxx:396 msgid "OK" msgstr "У реду" -#: vncviewer/OptionsDialog.cxx:502 +#: vncviewer/OptionsDialog.cxx:514 msgid "Compression" msgstr "Сажимање" -#: vncviewer/OptionsDialog.cxx:518 +#: vncviewer/OptionsDialog.cxx:530 msgid "Auto select" msgstr "Сам изабери" -#: vncviewer/OptionsDialog.cxx:529 +#: vncviewer/OptionsDialog.cxx:541 msgid "Preferred encoding" msgstr "Жељено кодирање" -#: vncviewer/OptionsDialog.cxx:590 +#: vncviewer/OptionsDialog.cxx:602 msgid "Color level" msgstr "Ðиво боје" -#: vncviewer/OptionsDialog.cxx:602 +#: vncviewer/OptionsDialog.cxx:614 msgid "Full" msgstr "Пуна" -#: vncviewer/OptionsDialog.cxx:609 +#: vncviewer/OptionsDialog.cxx:621 msgid "Medium" msgstr "Средња" -#: vncviewer/OptionsDialog.cxx:616 +#: vncviewer/OptionsDialog.cxx:628 msgid "Low" msgstr "Слаба" -#: vncviewer/OptionsDialog.cxx:623 +#: vncviewer/OptionsDialog.cxx:635 msgid "Very low" msgstr "Врло Ñлаба" -#: vncviewer/OptionsDialog.cxx:645 +#: vncviewer/OptionsDialog.cxx:657 msgid "Custom compression level:" msgstr "Произвољни ниво Ñажимања:" -#: vncviewer/OptionsDialog.cxx:652 +#: vncviewer/OptionsDialog.cxx:664 msgid "level (0=fast, 9=best)" msgstr "ниво (0=брзо, 9=најбоље)" -#: vncviewer/OptionsDialog.cxx:659 +#: vncviewer/OptionsDialog.cxx:671 msgid "Allow JPEG compression:" msgstr "Дозволи ЈПЕГ Ñажимање:" -#: vncviewer/OptionsDialog.cxx:666 +#: vncviewer/OptionsDialog.cxx:678 msgid "quality (0=poor, 9=best)" msgstr "квалитет (0=лош, 9=најбољи)" -#: vncviewer/OptionsDialog.cxx:677 +#: vncviewer/OptionsDialog.cxx:689 msgid "Security" msgstr "БезбедноÑÑ‚" -#: vncviewer/OptionsDialog.cxx:691 +#: vncviewer/OptionsDialog.cxx:703 msgid "Encryption" msgstr "Шифровање" -#: vncviewer/OptionsDialog.cxx:703 vncviewer/OptionsDialog.cxx:770 -#: vncviewer/OptionsDialog.cxx:876 +#: vncviewer/OptionsDialog.cxx:715 vncviewer/OptionsDialog.cxx:782 +#: vncviewer/OptionsDialog.cxx:905 msgid "None" msgstr "Ðишта" -#: vncviewer/OptionsDialog.cxx:710 +#: vncviewer/OptionsDialog.cxx:722 msgid "TLS with anonymous certificates" msgstr "ТЛС Ñа анонимним уверењима" -#: vncviewer/OptionsDialog.cxx:716 +#: vncviewer/OptionsDialog.cxx:728 msgid "TLS with X509 certificates" msgstr "ТЛС Ñа X509 уверењима" -#: vncviewer/OptionsDialog.cxx:723 +#: vncviewer/OptionsDialog.cxx:735 msgid "Path to X509 CA certificate" msgstr "Путања до X509 уверења" -#: vncviewer/OptionsDialog.cxx:730 +#: vncviewer/OptionsDialog.cxx:742 msgid "Path to X509 CRL file" msgstr "Путања до X509 ЦРЛ датотеке" -#: vncviewer/OptionsDialog.cxx:758 +#: vncviewer/OptionsDialog.cxx:770 msgid "Authentication" msgstr "Потврђивање идентитета" -#: vncviewer/OptionsDialog.cxx:776 +#: vncviewer/OptionsDialog.cxx:788 msgid "Standard VNC (insecure without encryption)" msgstr "Стандардни Ð’ÐЦ (неÑигурно без шифровања)" -#: vncviewer/OptionsDialog.cxx:782 +#: vncviewer/OptionsDialog.cxx:794 msgid "Username and password (insecure without encryption)" msgstr "КориÑник и лозинка (неÑигурно без шифровања)" -#: vncviewer/OptionsDialog.cxx:805 +#: vncviewer/OptionsDialog.cxx:822 msgid "Input" msgstr "Улаз" -#: vncviewer/OptionsDialog.cxx:818 +#: vncviewer/OptionsDialog.cxx:835 msgid "View only (ignore mouse and keyboard)" msgstr "Само преглед (занемари миша и таÑтатуру)" -#: vncviewer/OptionsDialog.cxx:825 +#: vncviewer/OptionsDialog.cxx:842 msgid "Mouse" msgstr "Миш" -#: vncviewer/OptionsDialog.cxx:837 +#: vncviewer/OptionsDialog.cxx:854 msgid "Emulate middle mouse button" msgstr "Опонашај Ñредње дугме миша" -#: vncviewer/OptionsDialog.cxx:843 -msgid "Show dot when no cursor" -msgstr "Прикажи тачку када нема курзора" +#: vncviewer/OptionsDialog.cxx:860 +msgid "Show local cursor when not provided by server" +msgstr "Прикажи локални курзор када га Ñервер не доÑтави" + +#: vncviewer/OptionsDialog.cxx:865 +msgid "Cursor type" +msgstr "Ð’Ñ€Ñта курзора" + +#: vncviewer/OptionsDialog.cxx:867 +msgid "Dot" +msgstr "Тачка" + +#: vncviewer/OptionsDialog.cxx:868 +msgid "System" +msgstr "СиÑтем" -#: vncviewer/OptionsDialog.cxx:859 +#: vncviewer/OptionsDialog.cxx:888 msgid "Keyboard" msgstr "ТаÑтатура" -#: vncviewer/OptionsDialog.cxx:871 +#: vncviewer/OptionsDialog.cxx:900 msgid "Pass system keys directly to server (full screen)" msgstr "ПроÑледи ÑиÑтемÑке кључеве директно на Ñервер (пун екран)" -#: vncviewer/OptionsDialog.cxx:874 +#: vncviewer/OptionsDialog.cxx:903 msgid "Menu key" msgstr "ТаÑтер изборника" -#: vncviewer/OptionsDialog.cxx:895 +#: vncviewer/OptionsDialog.cxx:926 msgid "Clipboard" msgstr "ОÑтава" -#: vncviewer/OptionsDialog.cxx:907 +#: vncviewer/OptionsDialog.cxx:938 msgid "Accept clipboard from server" msgstr "Прихвати оÑтаву Ñа Ñервера" -#: vncviewer/OptionsDialog.cxx:915 +#: vncviewer/OptionsDialog.cxx:946 msgid "Also set primary selection" msgstr "Такође поÑтави први избор" -#: vncviewer/OptionsDialog.cxx:922 +#: vncviewer/OptionsDialog.cxx:953 msgid "Send clipboard to server" msgstr "Пошаљи оÑтаву на Ñервер" -#: vncviewer/OptionsDialog.cxx:930 +#: vncviewer/OptionsDialog.cxx:961 msgid "Send primary selection as clipboard" msgstr "Пошаљи први избор као оÑтаву" -#: vncviewer/OptionsDialog.cxx:951 +#: vncviewer/OptionsDialog.cxx:982 msgid "Display" msgstr "Приказ" -#: vncviewer/OptionsDialog.cxx:965 +#: vncviewer/OptionsDialog.cxx:996 msgid "Display mode" msgstr "Режим приказа" -#: vncviewer/OptionsDialog.cxx:978 +#: vncviewer/OptionsDialog.cxx:1009 msgid "Windowed" msgstr "Упрозорен" -#: vncviewer/OptionsDialog.cxx:986 +#: vncviewer/OptionsDialog.cxx:1017 msgid "Full screen on current monitor" msgstr "Цео екран на текућем монитору" -#: vncviewer/OptionsDialog.cxx:994 +#: vncviewer/OptionsDialog.cxx:1025 msgid "Full screen on all monitors" msgstr "Цео екран на Ñвим мониторима" -#: vncviewer/OptionsDialog.cxx:1002 +#: vncviewer/OptionsDialog.cxx:1033 msgid "Full screen on selected monitor(s)" msgstr "Цео екран на изабраном монитору" -#: vncviewer/OptionsDialog.cxx:1031 +#: vncviewer/OptionsDialog.cxx:1062 msgid "Miscellaneous" msgstr "Разно" -#: vncviewer/OptionsDialog.cxx:1039 +#: vncviewer/OptionsDialog.cxx:1070 msgid "Shared (don't disconnect other viewers)" msgstr "Дељено (не прекидај везу другим прегледачима)" -#: vncviewer/OptionsDialog.cxx:1045 +#: vncviewer/OptionsDialog.cxx:1076 msgid "Ask to reconnect on connection errors" msgstr "Питај за поновно повезивање при грешкама везе" -#: vncviewer/ServerDialog.cxx:58 -msgid "VNC Viewer: Connection Details" +#: vncviewer/ServerDialog.cxx:63 +msgid "VNC viewer: Connection details" msgstr "Ð’ÐЦ прегледач: ПојединоÑти повезивања" -#: vncviewer/ServerDialog.cxx:68 +#: vncviewer/ServerDialog.cxx:73 msgid "VNC server:" msgstr "Ð’ÐЦ Ñервер:" -#: vncviewer/ServerDialog.cxx:75 +#: vncviewer/ServerDialog.cxx:80 msgid "Options..." msgstr "МогућноÑти..." -#: vncviewer/ServerDialog.cxx:79 +#: vncviewer/ServerDialog.cxx:84 msgid "Load..." msgstr "Учитавам..." -#: vncviewer/ServerDialog.cxx:83 -msgid "Save As..." +#: vncviewer/ServerDialog.cxx:88 +msgid "Save as..." msgstr "Сачувај као..." -#: vncviewer/ServerDialog.cxx:97 +#: vncviewer/ServerDialog.cxx:102 msgid "About..." msgstr "О програму..." -#: vncviewer/ServerDialog.cxx:106 +#: vncviewer/ServerDialog.cxx:111 msgid "Connect" msgstr "Повежи Ñе" -#: vncviewer/ServerDialog.cxx:143 +#: vncviewer/ServerDialog.cxx:147 #, c-format msgid "" "Unable to load the server history:\n" @@ -408,15 +480,15 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:172 vncviewer/ServerDialog.cxx:212 +#: vncviewer/ServerDialog.cxx:176 vncviewer/ServerDialog.cxx:216 msgid "TigerVNC configuration (*.tigervnc)" msgstr "Подешавање ТиграВÐЦ (*.tigervnc)" -#: vncviewer/ServerDialog.cxx:173 +#: vncviewer/ServerDialog.cxx:177 msgid "Select a TigerVNC configuration file" msgstr "Изаберите датотеку подешавања ТиграВÐЦ" -#: vncviewer/ServerDialog.cxx:195 vncviewer/vncviewer.cxx:515 +#: vncviewer/ServerDialog.cxx:199 vncviewer/vncviewer.cxx:517 #, c-format msgid "" "Unable to load the specified configuration file:\n" @@ -427,24 +499,24 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:213 +#: vncviewer/ServerDialog.cxx:217 msgid "Save the TigerVNC configuration to file" msgstr "Сачувајте подешавање ТиграВÐЦ у датотеку" -#: vncviewer/ServerDialog.cxx:239 +#: vncviewer/ServerDialog.cxx:243 #, c-format msgid "%s already exists. Do you want to overwrite?" msgstr "„%s“ већ поÑтоји. Желите ли да је препишете?" -#: vncviewer/ServerDialog.cxx:240 vncviewer/vncviewer.cxx:392 +#: vncviewer/ServerDialog.cxx:244 vncviewer/vncviewer.cxx:394 msgid "No" msgstr "Ðе" -#: vncviewer/ServerDialog.cxx:240 +#: vncviewer/ServerDialog.cxx:244 msgid "Overwrite" msgstr "Препиши" -#: vncviewer/ServerDialog.cxx:256 +#: vncviewer/ServerDialog.cxx:260 #, c-format msgid "" "Unable to save the specified configuration file:\n" @@ -455,7 +527,7 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:290 +#: vncviewer/ServerDialog.cxx:294 #, c-format msgid "" "Unable to save the default configuration:\n" @@ -466,7 +538,7 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:303 +#: vncviewer/ServerDialog.cxx:306 #, c-format msgid "" "Unable to save the server history:\n" @@ -477,205 +549,147 @@ msgstr "" "\n" "%s" -#: vncviewer/ServerDialog.cxx:320 vncviewer/ServerDialog.cxx:386 -msgid "Could not obtain the state directory path" -msgstr "Ðе могу да набавим путању директоријума Ñтања" +#: vncviewer/ServerDialog.cxx:351 vncviewer/ServerDialog.cxx:429 +#: vncviewer/vncviewer.cxx:580 +msgid "Could not determine VNC state directory path" +msgstr "Ðе могу да одредим путању фаÑцикле Ð’ÐЦ Ñтања" -#: vncviewer/ServerDialog.cxx:332 vncviewer/ServerDialog.cxx:394 -#: vncviewer/parameters.cxx:644 vncviewer/parameters.cxx:750 +#: vncviewer/ServerDialog.cxx:363 vncviewer/ServerDialog.cxx:437 +#: vncviewer/parameters.cxx:671 vncviewer/parameters.cxx:752 #, c-format -msgid "Could not open \"%s\": %s" -msgstr "Ðе могу да отворим „%s“: %s" +msgid "Could not open \"%s\"" +msgstr "Ðе могу да отворим „%s“" -#: vncviewer/ServerDialog.cxx:347 vncviewer/ServerDialog.cxx:355 -#: vncviewer/parameters.cxx:764 vncviewer/parameters.cxx:770 -#: vncviewer/parameters.cxx:801 vncviewer/parameters.cxx:830 -#: vncviewer/parameters.cxx:836 +#: vncviewer/ServerDialog.cxx:378 vncviewer/ServerDialog.cxx:387 +#: vncviewer/parameters.cxx:766 vncviewer/parameters.cxx:773 +#: vncviewer/parameters.cxx:807 vncviewer/parameters.cxx:837 +#: vncviewer/parameters.cxx:844 #, c-format -msgid "Failed to read line %d in file %s: %s" -msgstr "ÐиÑам уÑпео да прочитам %d. ред у датотеци „%s“: %s" +msgid "Failed to read line %d in file \"%s\"" +msgstr "ÐиÑам уÑпео да прочитам %d. ред у датотеци „%s“" -#: vncviewer/ServerDialog.cxx:356 vncviewer/parameters.cxx:771 +#: vncviewer/ServerDialog.cxx:390 vncviewer/parameters.cxx:776 msgid "Line too long" msgstr "Ред је предуг" -#: vncviewer/UserDialog.cxx:99 +#: vncviewer/UserDialog.cxx:123 msgid "Opening password file failed" msgstr "Отварање датотеке лозинке није уÑпело" -#: vncviewer/UserDialog.cxx:118 +#: vncviewer/UserDialog.cxx:143 msgid "VNC authentication" msgstr "Потврђивање идентитета Ð’ÐЦ-а" -#: vncviewer/UserDialog.cxx:125 +#: vncviewer/UserDialog.cxx:150 msgid "This connection is secure" msgstr "Ова веза је безбедна" -#: vncviewer/UserDialog.cxx:129 +#: vncviewer/UserDialog.cxx:154 msgid "This connection is not secure" msgstr "Ова веза није безбедна" -#: vncviewer/UserDialog.cxx:151 +#: vncviewer/UserDialog.cxx:176 msgid "Username:" msgstr "КориÑник:" -#: vncviewer/UserDialog.cxx:164 +#: vncviewer/UserDialog.cxx:189 msgid "Password:" msgstr "Лозинка:" -#: vncviewer/UserDialog.cxx:207 -msgid "Authentication cancelled" -msgstr "Потврђивање идентитета је отказано" - -#: vncviewer/Viewport.cxx:390 -#, c-format -msgid "Failed to update keyboard LED state: %lu" -msgstr "ÐиÑам уÑпео да оÑвежим Ñтање диоде таÑтатуре: %lu" - -#: vncviewer/Viewport.cxx:396 vncviewer/Viewport.cxx:402 -#, c-format -msgid "Failed to update keyboard LED state: %d" -msgstr "ÐиÑам уÑпео да оÑвежим Ñтање диоде таÑтатуре: %d" - -#: vncviewer/Viewport.cxx:432 -msgid "Failed to update keyboard LED state" -msgstr "ÐиÑам уÑпео да оÑвежим Ñтање диоде таÑтатуре" - -#: vncviewer/Viewport.cxx:459 vncviewer/Viewport.cxx:467 -#: vncviewer/Viewport.cxx:484 -#, c-format -msgid "Failed to get keyboard LED state: %d" -msgstr "ÐиÑам уÑпео да добавим Ñтање диоде таÑтатуре: %d" - -#: vncviewer/Viewport.cxx:839 -msgid "No key code specified on key press" -msgstr "Ðије наведен код таÑтера на притиÑак иÑтог" - -#: vncviewer/Viewport.cxx:990 -#, c-format -msgid "No scan code for extended virtual key 0x%02x" -msgstr "Ðема шифре прегледа за проширени виртуелни кључ 0x%02x" +#: vncviewer/UserDialog.cxx:197 +msgid "Keep password for reconnect" +msgstr "Задржи лозинку за поновно повезивање" -#: vncviewer/Viewport.cxx:992 -#, c-format -msgid "No scan code for virtual key 0x%02x" -msgstr "Ðема шифре прегледа за виртуелни кључ 0x%02x" - -#: vncviewer/Viewport.cxx:998 -#, c-format -msgid "Invalid scan code 0x%02x" -msgstr "ÐеиÑправан код Ñкенирања 0x%02x" - -#: vncviewer/Viewport.cxx:1028 -#, c-format -msgid "No symbol for extended virtual key 0x%02x" -msgstr "Ðема Ñимбола за проширени виртуелни кључ 0x%02x" - -#: vncviewer/Viewport.cxx:1030 -#, c-format -msgid "No symbol for virtual key 0x%02x" -msgstr "Ðема Ñимбола за виртуелни кључ 0x%02x" - -#: vncviewer/Viewport.cxx:1136 -#, c-format -msgid "No symbol for key code 0x%02x (in the current state)" -msgstr "Ðема Ñимбола за шифру кључа 0x%02x (у текућем Ñтању)" - -#: vncviewer/Viewport.cxx:1169 -#, c-format -msgid "No symbol for key code %d (in the current state)" -msgstr "Ðема Ñимбола за шифру кључа %d (у текућем Ñтању)" - -#: vncviewer/Viewport.cxx:1229 +#: vncviewer/Viewport.cxx:695 msgctxt "ContextMenu|" msgid "Disconn&ect" msgstr "Пре&кини везу" -#: vncviewer/Viewport.cxx:1232 +#: vncviewer/Viewport.cxx:698 msgctxt "ContextMenu|" msgid "&Full screen" msgstr "&Пун екран" -#: vncviewer/Viewport.cxx:1235 +#: vncviewer/Viewport.cxx:701 msgctxt "ContextMenu|" msgid "Minimi&ze" msgstr "&Умањи" -#: vncviewer/Viewport.cxx:1237 +#: vncviewer/Viewport.cxx:703 msgctxt "ContextMenu|" msgid "Resize &window to session" msgstr "&Величина прозора на ÑеÑију" -#: vncviewer/Viewport.cxx:1242 +#: vncviewer/Viewport.cxx:708 msgctxt "ContextMenu|" msgid "&Ctrl" msgstr "&Ктрл" -#: vncviewer/Viewport.cxx:1245 +#: vncviewer/Viewport.cxx:711 msgctxt "ContextMenu|" msgid "&Alt" msgstr "&Ðлт" -#: vncviewer/Viewport.cxx:1251 +#: vncviewer/Viewport.cxx:717 #, c-format msgctxt "ContextMenu|" msgid "Send %s" msgstr "Пошаљи „%s“" -#: vncviewer/Viewport.cxx:1257 +#: vncviewer/Viewport.cxx:724 msgctxt "ContextMenu|" msgid "Send Ctrl-Alt-&Del" msgstr "Пошаљи Ктрл-Ðлт-&Дел" -#: vncviewer/Viewport.cxx:1260 +#: vncviewer/Viewport.cxx:727 msgctxt "ContextMenu|" msgid "&Refresh screen" msgstr "&ОÑвежи екран" -#: vncviewer/Viewport.cxx:1263 +#: vncviewer/Viewport.cxx:730 msgctxt "ContextMenu|" msgid "&Options..." msgstr "&МогућноÑти..." -#: vncviewer/Viewport.cxx:1265 +#: vncviewer/Viewport.cxx:732 msgctxt "ContextMenu|" msgid "Connection &info..." msgstr "Подаци о &вези..." -#: vncviewer/Viewport.cxx:1267 +#: vncviewer/Viewport.cxx:734 msgctxt "ContextMenu|" msgid "About &TigerVNC viewer..." msgstr "О &програму..." -#: vncviewer/Viewport.cxx:1356 +#: vncviewer/Viewport.cxx:830 msgid "VNC connection info" msgstr "Подаци о Ð’ÐЦ вези" -#: vncviewer/Win32TouchHandler.cxx:47 +#: vncviewer/Win32TouchHandler.cxx:48 msgid "Window is registered for touch instead of gestures" msgstr "Прозор је региÑтрован за додир умеÑто покрета" -#: vncviewer/Win32TouchHandler.cxx:82 +#: vncviewer/Win32TouchHandler.cxx:83 #, c-format msgid "Failed to set gesture configuration (error 0x%x)" msgstr "ÐиÑам уÑпео да поÑтавим подешавање покрета (грешка 0x%x)" -#: vncviewer/Win32TouchHandler.cxx:94 +#: vncviewer/Win32TouchHandler.cxx:95 #, c-format msgid "Failed to get gesture information (error 0x%x)" msgstr "ÐиÑам уÑпео да добавим информације покрета (грешка 0x%x)" -#: vncviewer/Win32TouchHandler.cxx:359 +#: vncviewer/Win32TouchHandler.cxx:360 #, c-format msgid "Invalid mouse button %d, must be a number between 1 and 7." msgstr "ÐеиÑправно дугме миша %d, мора бити број између 1 и 7." -#: vncviewer/Win32TouchHandler.cxx:424 +#: vncviewer/Win32TouchHandler.cxx:425 #, c-format msgid "Unhandled key 0x%x - can't generate keyboard event." msgstr "Ðеобрадиви таÑтер 0x%x – не могу да Ñтворим догађај таÑтатуре." -#: vncviewer/XInputTouchHandler.cxx:102 vncviewer/touch.cxx:108 +#: vncviewer/XInputTouchHandler.cxx:102 vncviewer/touch.cxx:107 #, c-format msgid "Unable to get X Input 2 event mask for window 0x%08lx" msgstr "Ðе могу да добавим маÑку „X Input 2“ догађаја за прозор 0x%08lx" @@ -685,7 +699,7 @@ msgstr "Ðе могу да добавим маÑку „X Input 2“ догађРmsgid "Window 0x%08lx has no X Input 2 event mask" msgstr "Прозор 0x%08lx нема маÑку „X Input 2“ догађаја" -#: vncviewer/XInputTouchHandler.cxx:112 vncviewer/touch.cxx:115 +#: vncviewer/XInputTouchHandler.cxx:112 vncviewer/touch.cxx:114 #, c-format msgid "Window 0x%08lx has more than one X Input 2 event mask" msgstr "Прозор 0x%08lx има више од једне маÑке „X Input 2“ догађаја" @@ -696,7 +710,6 @@ msgid "Failure grabbing device %i" msgstr "ÐеуÑпех хватања уређаја %i" #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:13 -#: vncviewer/vncviewer.cxx:389 vncviewer/vncviewer.desktop.in.in:3 msgid "TigerVNC Viewer" msgstr "Прегледач ТигарВÐЦ" @@ -714,145 +727,146 @@ msgid "TigerVNC is a high-speed version of VNC based on the RealVNC 4 and X.org msgstr "ТигарВÐЦ великобрзинÑко издање Ð’ÐЦ-а заÑновано на оÑновама „RealVNC“-у 4 и „X.org“ кода. ТигарВÐЦ је започео као развојно залагање Ñледеће генерације за „TightVNC“ на ÐˆÑƒÐ½Ð¸ÐºÑ Ð¸ Ð›Ð¸Ð½ÑƒÐºÑ Ð¿Ð»Ð°Ñ‚Ñ„Ð¾Ñ€Ð¼Ð°Ð¼Ð°, али Ñе издваја из Ñвог родитељÑког пројекта у раним 2009 тако да Ñе „TightVNC“ може фокуÑирати на Виндоуз платформама. ТигарВÐЦ подржава варијанту „Tight“ кодирања тако да је поприлично убрзан коришћењем „libjpeg-turbo“ ЈПЕГ кодека." #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:33 -msgid "TigerVNC Viewer connection to a CentOS machine" +msgid "TigerVNC viewer connection to a CentOS machine" msgstr "Веза ТигарВÐЦ прегледача Ñа CentOS рачунаром" #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:37 -msgid "TigerVNC Viewer connection to a macOS machine" +msgid "TigerVNC viewer connection to a macOS machine" msgstr "Веза ТигарВÐЦ прегледача Ñа macOS рачунаром" #: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:41 -msgid "TigerVNC Viewer connection to a Windows machine" +msgid "TigerVNC viewer connection to a Windows machine" msgstr "Веза ТигарВÐЦ прегледача Ñа Виндоуз рачунаром" -#: vncviewer/parameters.cxx:307 vncviewer/parameters.cxx:332 -#: vncviewer/parameters.cxx:349 vncviewer/parameters.cxx:389 -#: vncviewer/parameters.cxx:409 +#. developer_name tag deprecated with Appstream 1.0 +#: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:46 +#: vncviewer/org.tigervnc.vncviewer.metainfo.xml.in:48 +msgid "The TigerVNC team" +msgstr "Тим ТигарВÐЦ-а" + +#: vncviewer/parameters.cxx:319 vncviewer/parameters.cxx:344 +#: vncviewer/parameters.cxx:361 vncviewer/parameters.cxx:401 +#: vncviewer/parameters.cxx:421 msgid "The name of the parameter is too large" msgstr "Ðазив параметра је превелик" -#: vncviewer/parameters.cxx:311 vncviewer/parameters.cxx:316 -#: vncviewer/parameters.cxx:367 +#: vncviewer/parameters.cxx:323 vncviewer/parameters.cxx:328 +#: vncviewer/parameters.cxx:379 msgid "The parameter is too large" msgstr "Параметар је превелик" -#: vncviewer/parameters.cxx:374 vncviewer/parameters.cxx:694 -#: vncviewer/parameters.cxx:815 +#: vncviewer/parameters.cxx:386 vncviewer/parameters.cxx:712 +#: vncviewer/parameters.cxx:822 msgid "Invalid format or too large value" msgstr "ÐеиÑправан Ð·Ð°Ð¿Ð¸Ñ Ð¸Ð»Ð¸ предуга вредноÑÑ‚" -#: vncviewer/parameters.cxx:428 vncviewer/parameters.cxx:459 +#: vncviewer/parameters.cxx:440 vncviewer/parameters.cxx:473 msgid "Failed to create registry key" msgstr "ÐиÑам уÑпео да направим кључ региÑтра" -#: vncviewer/parameters.cxx:447 vncviewer/parameters.cxx:502 -#: vncviewer/parameters.cxx:544 vncviewer/parameters.cxx:611 +#: vncviewer/parameters.cxx:461 vncviewer/parameters.cxx:528 +#: vncviewer/parameters.cxx:571 vncviewer/parameters.cxx:638 msgid "Failed to close registry key" msgstr "ÐиÑам уÑпео да затворим кључ региÑтра" -#: vncviewer/parameters.cxx:465 vncviewer/parameters.cxx:482 -#: vncviewer/parameters.cxx:652 vncviewer/parameters.cxx:662 -#: vncviewer/parameters.cxx:673 +#: vncviewer/parameters.cxx:479 vncviewer/parameters.cxx:506 +#: vncviewer/parameters.cxx:680 vncviewer/parameters.cxx:692 #, c-format msgid "Failed to save \"%s\": %s" msgstr "ÐиÑам уÑпео да Ñачувам „%s“: %s" -#: vncviewer/parameters.cxx:478 vncviewer/parameters.cxx:566 -#: vncviewer/parameters.cxx:675 vncviewer/parameters.cxx:712 -msgid "Unknown parameter type" -msgstr "Ðепозната врÑта параметра" - -#: vncviewer/parameters.cxx:495 +#: vncviewer/parameters.cxx:489 vncviewer/parameters.cxx:520 #, c-format msgid "Failed to remove \"%s\": %s" msgstr "ÐиÑам уÑпео да уклоним „%s“: %s" -#: vncviewer/parameters.cxx:517 vncviewer/parameters.cxx:589 +#: vncviewer/parameters.cxx:544 vncviewer/parameters.cxx:616 msgid "Failed to open registry key" msgstr "ÐиÑам уÑпео да отворим кључ региÑтра" -#: vncviewer/parameters.cxx:534 +#: vncviewer/parameters.cxx:561 #, c-format msgid "Failed to read server history entry %d: %s" msgstr "ÐиÑам уÑпео да прочитам ÑƒÐ½Ð¾Ñ Ð¸Ñторијата Ñервера %d: %s" -#: vncviewer/parameters.cxx:570 vncviewer/parameters.cxx:600 +#: vncviewer/parameters.cxx:597 vncviewer/parameters.cxx:627 #, c-format msgid "Failed to read parameter \"%s\": %s" msgstr "ÐиÑам уÑпео да прочитам параметар „%s“: %s" -#: vncviewer/parameters.cxx:634 vncviewer/parameters.cxx:738 -msgid "Could not obtain the config directory path" -msgstr "Ðе могу да набавим путању директоријума подешавања" +#: vncviewer/parameters.cxx:661 vncviewer/parameters.cxx:740 +#: vncviewer/vncviewer.cxx:546 +msgid "Could not determine VNC config directory path" +msgstr "Ðе могу да одредим путању фаÑцикле Ð’ÐЦ подешавања" -#: vncviewer/parameters.cxx:653 vncviewer/parameters.cxx:664 +#: vncviewer/parameters.cxx:682 vncviewer/parameters.cxx:694 msgid "Could not encode parameter" msgstr "Ðе могу да кодирам параметар" -#: vncviewer/parameters.cxx:780 +#: vncviewer/parameters.cxx:785 #, c-format msgid "Configuration file %s is in an invalid format" msgstr "Датотека подешавања „%s“ је у неиÑправном запиÑу" -#: vncviewer/parameters.cxx:802 +#: vncviewer/parameters.cxx:809 msgid "Invalid format" msgstr "ÐеиÑправан запиÑ" -#: vncviewer/parameters.cxx:837 +#: vncviewer/parameters.cxx:846 msgid "Unknown parameter" msgstr "Ðепознат параметар" -#: vncviewer/touch.cxx:76 +#: vncviewer/touch.cxx:75 #, c-format msgid "Got message (0x%x) for an unhandled window" msgstr "Добих поруку (0x%x) за неруковани прозор" -#: vncviewer/touch.cxx:139 vncviewer/touch.cxx:161 +#: vncviewer/touch.cxx:138 vncviewer/touch.cxx:160 #, c-format msgid "Invalid window 0x%08lx specified for pointer grab" msgstr "ÐеиÑправан прозор 0x%08lx је наведен за хватање показивача" -#: vncviewer/touch.cxx:184 vncviewer/touch.cxx:185 +#: vncviewer/touch.cxx:183 vncviewer/touch.cxx:184 #, c-format msgid "Failed to create touch handler: %s" msgstr "ÐиÑам уÑпео да Ñтворим руковаоца додира: %s" -#: vncviewer/touch.cxx:189 +#: vncviewer/touch.cxx:188 #, c-format msgid "Couldn't attach event handler to window (error 0x%x)" msgstr "Ðе могу да приложим руковаоца догађајем прозору (грешка 0x%x)" -#: vncviewer/touch.cxx:216 +#: vncviewer/touch.cxx:215 msgid "Failed to get event data for X Input event" msgstr "ÐиÑам уÑпео да добавим податке догађаја за „X Input“ догађај" -#: vncviewer/touch.cxx:229 +#: vncviewer/touch.cxx:228 msgid "X Input event for unknown window" msgstr "„X Input“ догађај за непознати прозор" -#: vncviewer/touch.cxx:255 +#: vncviewer/touch.cxx:254 msgid "X Input extension not available." msgstr "„X Input“ проширење није доÑтупно." -#: vncviewer/touch.cxx:262 +#: vncviewer/touch.cxx:261 msgid "X Input 2 (or newer) is not available." msgstr "„X Input 2“ (или новије) није доÑтупно." -#: vncviewer/touch.cxx:267 +#: vncviewer/touch.cxx:266 msgid "X Input 2.2 (or newer) is not available. Touch gestures will not be supported." msgstr "„X Input 2“ (или новије) није доÑтупно. Покрети додира неће бити подржани." #: vncviewer/vncviewer.cxx:104 #, c-format msgid "" -"TigerVNC Viewer v%s\n" +"TigerVNC viewer v%s\n" "Built on: %s\n" -"Copyright (C) 1999-%d TigerVNC Team and many others (see README.rst)\n" +"Copyright (C) 1999-%d TigerVNC team and many others (see README.rst)\n" "See https://www.tigervnc.org for information on TigerVNC." msgstr "" "Прегледач ТигарВÐЦ и%s\n" "Изграђен: %s\n" -"ÐуторÑка права © 1999-%d Тим Тигра Ð’ÐЦ-а и многи други (видите „README.rst“)\n" +"ÐуторÑка права © 1999-%d Тим ТигарВÐЦ-а и многи други (видите „README.rst“)\n" "ПоÑетите „https://www.tigervnc.org“ да Ñазнате више о програму." #: vncviewer/vncviewer.cxx:158 @@ -892,95 +906,173 @@ msgstr "Грешка покретања новог примерка програ #: vncviewer/vncviewer.cxx:266 #, c-format -msgid "Termination signal %d has been received. TigerVNC Viewer will now exit." +msgid "Termination signal %d has been received. TigerVNC viewer will now exit." msgstr "Примљен је Ñигнал за окончавање %d. Програм ће Ñада изаћи." -#: vncviewer/vncviewer.cxx:393 +#: vncviewer/vncviewer.cxx:391 vncviewer/vncviewer.desktop.in.in:3 +msgid "TigerVNC viewer" +msgstr "Прегледач ТигарВÐЦ" + +#: vncviewer/vncviewer.cxx:395 msgid "Yes" msgstr "Да" -#: vncviewer/vncviewer.cxx:396 +#: vncviewer/vncviewer.cxx:398 msgid "Close" msgstr "Затвори" -#: vncviewer/vncviewer.cxx:401 +#: vncviewer/vncviewer.cxx:403 msgid "About" msgstr "О програму" -#: vncviewer/vncviewer.cxx:404 +#: vncviewer/vncviewer.cxx:406 msgid "Hide" msgstr "Сакриј" -#: vncviewer/vncviewer.cxx:407 +#: vncviewer/vncviewer.cxx:409 msgid "Quit" msgstr "Изађи" -#: vncviewer/vncviewer.cxx:411 +#: vncviewer/vncviewer.cxx:413 msgid "Services" msgstr "УÑлуге" -#: vncviewer/vncviewer.cxx:412 -msgid "Hide Others" +#: vncviewer/vncviewer.cxx:414 +msgid "Hide others" msgstr "Сакриј оÑтале" -#: vncviewer/vncviewer.cxx:413 -msgid "Show All" +#: vncviewer/vncviewer.cxx:415 +msgid "Show all" msgstr "Прикажи Ñве" -#: vncviewer/vncviewer.cxx:422 +#: vncviewer/vncviewer.cxx:424 msgctxt "SysMenu|" msgid "&File" msgstr "&Датотека" -#: vncviewer/vncviewer.cxx:425 +#: vncviewer/vncviewer.cxx:427 msgctxt "SysMenu|File|" msgid "&New Connection" msgstr "&Ðова веза" -#: vncviewer/vncviewer.cxx:525 +#: vncviewer/vncviewer.cxx:450 +#, c-format +msgid "" +"\n" +"Usage: %s [parameters] [host][:displayNum]\n" +" %s [parameters] [host][::port]\n" +" %s [parameters] [unix socket]\n" +" %s [parameters] -listen [port]\n" +" %s [parameters] [.tigervnc file]\n" +msgstr "" +"\n" +"Коришћење: %s [parametri] [domaćin][:displayNum]\n" +" %s [parametri] [domaćin][::port]\n" +" %s [parametri] [prikljuÄnica linuksa]\n" +" %s [parametri] -listen [prikquÄnik]\n" +" %s [parametri] [.tigervnc datteka]\n" + +#: vncviewer/vncviewer.cxx:465 +#, c-format +msgid "" +"\n" +"Options:\n" +"\n" +" -display Xdisplay - Specifies the X display for the viewer window\n" +" -geometry geometry - Initial position of the main VNC viewer window. See the\n" +" man page for details.\n" +msgstr "" +"\n" +"Опције:\n" +"\n" +" -display Xdisplay – Ðаводи X приказ за прозор прегледача\n" +" -geometry geometry – почетни почожај главног прозора VNC прегледача. Вифите\n" +" Ñтраницу упутÑтва за више о томе.\n" + +#: vncviewer/vncviewer.cxx:472 +#, c-format +msgid "" +"\n" +"Parameters can be turned on with -<param> or off with -<param>=0\n" +"Parameters which take a value can be specified as -<param> <value>\n" +"Other valid forms are <param>=<value> -<param>=<value> --<param>=<value>\n" +"Parameter names are case-insensitive. The parameters are:\n" +"\n" +msgstr "" +"\n" +"Параметри Ñе могу укључити Ñа „-<param>“ или иÑкључити Ñа „-<param>=0“\n" +"Параметри који имају вредноÑÑ‚ Ñе могу навеÑти као „-<param> <vrednost>“\n" +"Други иÑправни облици Ñу „<param>=<vrednost> -<param>=<vrednost> --<param>=<vrednost>“\n" +"Ðазиви параметара ниÑу оÑетљиви на величину Ñлова. Параметри Ñу:\n" +"\n" + +#: vncviewer/vncviewer.cxx:527 msgid "FullScreenAllMonitors is deprecated, set FullScreenMode to 'all' instead" msgstr "„FullScreenAllMonitors“ је заÑтарело, поÑтавите „FullScreenMode“ на „all“" -#: vncviewer/vncviewer.cxx:721 +#: vncviewer/vncviewer.cxx:532 +msgid "DotWhenNoCursor is deprecated, set AlwaysCursor to 1 and CursorType to 'Dot' instead" +msgstr "„DotWhenNoCursor“ је заÑтарело, поÑтавите „AlwaysCursor“ на 1 и „CursorType“ на „Dot“ (тачка)" + +#: vncviewer/vncviewer.cxx:553 msgid "~/.vnc is deprecated, please consult 'man vncviewer' for paths to migrate to." msgstr "„~/.vnc“ је заÑтарело, погледајте „man vncviewer“ за путање за преÑељење." -#: vncviewer/vncviewer.cxx:725 +#: vncviewer/vncviewer.cxx:557 #, c-format msgid "%%APPDATA%%\\vnc is deprecated, please switch to the %%APPDATA%%\\TigerVNC location." msgstr "„%%APPDATA%%\\vnc“ је заÑтарело, пређите на „%%APPDATA%%\\TigerVNC“." -#: vncviewer/vncviewer.cxx:730 +#: vncviewer/vncviewer.cxx:562 #, c-format -msgid "Could not create VNC config directory: %s" -msgstr "Ðе могу да направим фаÑциклу подешавања Ð’ÐЦ-а: %s" +msgid "Could not create VNC config directory \"%s\": %s" +msgstr "Ðе могу да направим фаÑциклу подешавања Ð’ÐЦ-а „%s“: %s" -#: vncviewer/vncviewer.cxx:735 +#: vncviewer/vncviewer.cxx:568 +msgid "Could not determine VNC data directory path" +msgstr "Ðе могу да одредим путању фаÑцикле Ð’ÐЦ података" + +#: vncviewer/vncviewer.cxx:574 +#, c-format +msgid "Could not create VNC data directory \"%s\": %s" +msgstr "Ðе могу да направим фаÑциклу података Ð’ÐЦ-а „%s“: %s" + +#: vncviewer/vncviewer.cxx:586 #, c-format -msgid "Could not create VNC data directory: %s" -msgstr "Ðе могу да направим фаÑциклу података Ð’ÐЦ-а: %s" +msgid "Could not create VNC state directory \"%s\": %s" +msgstr "Ðе могу да направим фаÑциклу Ñтања Ð’ÐЦ-а „%s“: %s" -#: vncviewer/vncviewer.cxx:740 +#: vncviewer/vncviewer.cxx:703 #, c-format -msgid "Could not create VNC state directory: %s" -msgstr "Ðе могу да направим фаÑциклу Ñтања Ð’ÐЦ-а: %s" +msgid "%s: Unrecognized option '%s'\n" +msgstr "%s: Ðепозната опција „%s“\n" + +#: vncviewer/vncviewer.cxx:705 vncviewer/vncviewer.cxx:713 +#, c-format +msgid "See '%s --help' for more information.\n" +msgstr "Видите „%s --help“ за више информација.\n" + +#: vncviewer/vncviewer.cxx:712 +#, c-format +msgid "%s: Extra argument '%s'\n" +msgstr "%s: Додатни аргумент „%s“\n" #. TRANSLATORS: "Parameters" are command line arguments, or settings #. from a file or the Windows registry. -#: vncviewer/vncviewer.cxx:755 vncviewer/vncviewer.cxx:756 +#: vncviewer/vncviewer.cxx:748 vncviewer/vncviewer.cxx:749 msgid "Parameters -listen and -via are incompatible" msgstr "Параметри „-listen“ и „-via“ ниÑу ÑаглаÑни" -#: vncviewer/vncviewer.cxx:770 +#: vncviewer/vncviewer.cxx:763 msgid "Unable to listen for incoming connections" msgstr "Ðе могу да оÑлушкујем долазне везе" -#: vncviewer/vncviewer.cxx:772 +#: vncviewer/vncviewer.cxx:765 #, c-format msgid "Listening on port %d" msgstr "ОÑлушкујем на прикључнику %d" -#: vncviewer/vncviewer.cxx:805 +#: vncviewer/vncviewer.cxx:794 #, c-format msgid "" "Failure waiting for incoming VNC connection:\n" @@ -991,10 +1083,38 @@ msgstr "" "\n" "%s" +#: vncviewer/vncviewer.cxx:815 +#, c-format +msgid "" +"Failure setting up encrypted tunnel:\n" +"\n" +"%s" +msgstr "" +"ÐеуÑпех поÑтављања шифрованог тунела:\n" +"\n" +"%s" + #: vncviewer/vncviewer.desktop.in.in:4 -msgid "Remote Desktop Viewer" +msgid "Remote desktop viewer" msgstr "Прегледач удаљених радних површи" +#~ msgid "Show dot when no cursor" +#~ msgstr "Прикажи тачку када нема курзора" + +#, c-format +#~ msgid "Failed to update keyboard LED state: %d" +#~ msgstr "ÐиÑам уÑпео да оÑвежим Ñтање диоде таÑтатуре: %d" + +#~ msgid "No key code specified on key press" +#~ msgstr "Ðије наведен код таÑтера на притиÑак иÑтог" + +#, c-format +#~ msgid "No symbol for key code 0x%02x (in the current state)" +#~ msgstr "Ðема Ñимбола за шифру кључа 0x%02x (у текућем Ñтању)" + +#~ msgid "Unknown parameter type" +#~ msgstr "Ðепозната врÑта параметра" + #~ msgid "VNC Viewer: Connection Options" #~ msgstr "Ð’ÐЦ прегледач: МогућноÑти повезивања" diff --git a/release/CMakeLists.txt b/release/CMakeLists.txt index 6cb14de0..02491a2b 100644 --- a/release/CMakeLists.txt +++ b/release/CMakeLists.txt @@ -15,18 +15,18 @@ endif() configure_file(tigervnc.iss.in tigervnc.iss) -add_custom_target(installer - iscc -o. ${INST_DEFS} -F${CMAKE_PROJECT_NAME}${INST_SUFFIX}-${VERSION} tigervnc.iss - DEPENDS vncviewer - SOURCES ${CMAKE_CURRENT_BINARY_DIR}/tigervnc.iss) +add_custom_command(OUTPUT ${CMAKE_PROJECT_NAME}${INST_SUFFIX}-${VERSION}.exe + COMMAND iscc -o. ${INST_DEFS} -F${CMAKE_PROJECT_NAME}${INST_SUFFIX}-${VERSION} tigervnc.iss + DEPENDS vncviewer tigervnc.iss) +add_custom_target(installer DEPENDS ${CMAKE_PROJECT_NAME}${INST_SUFFIX}-${VERSION}.exe) if(BUILD_WINVNC) configure_file(winvnc.iss.in winvnc.iss) - add_custom_target(winvnc_installer - iscc -o. ${INST_DEFS} -F${CMAKE_PROJECT_NAME}${INST_SUFFIX}-winvnc-${VERSION} winvnc.iss - DEPENDS winvnc4 wm_hooks vncconfig - SOURCES ${CMAKE_CURRENT_BINARY_DIR}/winvnc.iss) + add_custom_command(OUTPUT ${CMAKE_PROJECT_NAME}${INST_SUFFIX}-winvnc-${VERSION}.exe + COMMAND iscc -o. ${INST_DEFS} -F${CMAKE_PROJECT_NAME}${INST_SUFFIX}-winvnc-${VERSION} winvnc.iss + DEPENDS winvnc4 wm_hooks vncconfig winvnc.iss) + add_custom_target(winvnc_installer DEPENDS ${CMAKE_PROJECT_NAME}${INST_SUFFIX}-winvnc-${VERSION}.exe) endif() endif() # WIN32 @@ -41,9 +41,10 @@ if(APPLE) configure_file(makemacapp.in makemacapp) configure_file(Info.plist.in Info.plist) -add_custom_target(dmg sh makemacapp - DEPENDS vncviewer - SOURCES makemacapp) +add_custom_command(OUTPUT TigerVNC-${VERSION}.dmg + COMMAND sh makemacapp + DEPENDS vncviewer makemacapp Info.plist) +add_custom_target(dmg DEPENDS TigerVNC-${VERSION}.dmg) endif() # APPLE @@ -56,16 +57,16 @@ if(UNIX) configure_file(maketarball.in maketarball) -set(TARBALL_DEPENDS vncviewer vncpasswd vncconfig) if(BUILD_JAVA) - set(TARBALL_DEPENDS ${TARBALL_DEPENDS} java) + set(TARBALL_JAVA_DEPENDENCY java) endif() -add_custom_target(tarball bash maketarball - DEPENDS ${TARBALL_DEPENDS}) +set(PACKAGE_FILE ${CMAKE_PROJECT_NAME}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-${VERSION}.tar.gz) +add_custom_command(OUTPUT ${PACKAGE_FILE} + COMMAND bash maketarball + DEPENDS maketarball vncviewer vncpasswd vncconfig ${TARBALL_JAVA_DEPENDENCY}) -add_custom_target(servertarball bash maketarball server - DEPENDS ${TARBALL_DEPENDS}) +add_custom_target(tarball DEPENDS ${PACKAGE_FILE}) endif() #UNIX diff --git a/release/Info.plist.in b/release/Info.plist.in index 3f166bd0..9be17e5e 100644 --- a/release/Info.plist.in +++ b/release/Info.plist.in @@ -5,13 +5,13 @@ <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleDisplayName</key> - <string>TigerVNC viewer</string> + <string>TigerVNC</string> <key>CFBundleExecutable</key> - <string>TigerVNC viewer</string> + <string>vncviewer</string> <key>NSHighResolutionCapable</key> <false/> <key>CFBundleGetInfoString</key> - <string>@VERSION@, Copyright © 1998-2025 [many holders]</string> + <string>@VERSION@, Copyright (C) 1999-2025 TigerVNC team and many others (see README.rst)</string> <key>CFBundleIconFile</key> <string>tigervnc.icns</string> <key>CFBundleIdentifier</key> @@ -19,9 +19,9 @@ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleLongVersionString</key> - <string>TigerVNC viewer @VERSION@</string> + <string>TigerVNC @VERSION@</string> <key>CFBundleName</key> - <string>TigerVNC viewer</string> + <string>TigerVNC</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> @@ -31,6 +31,6 @@ <key>LSRequiresCarbon</key> <true/> <key>NSHumanReadableCopyright</key> - <string>Copyright © 1998-2025 [many holders]</string> + <string>Copyright (C) 1999-2025 TigerVNC team and many others (see README.rst)</string> </dict> </plist> diff --git a/release/makemacapp.in b/release/makemacapp.in index 0827715c..43441b8b 100644 --- a/release/makemacapp.in +++ b/release/makemacapp.in @@ -29,25 +29,23 @@ BUILD=@BUILD@ SRCDIR=@CMAKE_SOURCE_DIR@ BINDIR=@CMAKE_BINARY_DIR@ -cd $BINDIR - if [ -f $PACKAGE_NAME.dmg ]; then rm -f $PACKAGE_NAME.dmg fi umask 022 TMPDIR=`mktemp -d /tmp/$PACKAGE_NAME-build.XXXXXX` -APPROOT="$TMPDIR/dmg/TigerVNC viewer $VERSION.app" +APPROOT="$TMPDIR/dmg/TigerVNC.app" mkdir -p "$APPROOT/Contents/MacOS" mkdir -p "$APPROOT/Contents/Resources" -install -m 755 vncviewer/vncviewer "$APPROOT/Contents/MacOS/TigerVNC viewer" +install -m 755 $BINDIR/vncviewer/vncviewer "$APPROOT/Contents/MacOS/" install -m 644 $SRCDIR/media/icons/tigervnc.icns "$APPROOT/Contents/Resources/" -install -m 644 release/Info.plist "$APPROOT/Contents/" +install -m 644 $BINDIR/release/Info.plist "$APPROOT/Contents/" for lang in `cat "$SRCDIR/po/LINGUAS"`; do mkdir -p "$APPROOT/Contents/Resources/locale/$lang/LC_MESSAGES" - install -m 644 po/$lang.mo \ + install -m 644 $BINDIR/po/$lang.mo \ "$APPROOT/Contents/Resources/locale/$lang/LC_MESSAGES/tigervnc.mo" done diff --git a/release/maketarball.in b/release/maketarball.in index 56618934..108de92c 100644 --- a/release/maketarball.in +++ b/release/maketarball.in @@ -28,13 +28,6 @@ if [[ $CFLAGS = *-m32* ]]; then CPU=i686 fi PACKAGE_FILE=$PACKAGE_NAME-$OS-$CPU-$VERSION.tar.gz -SERVER=0 - -if [ $# -gt 0 ]; then - if [ "$1" = "server" ]; then - SERVER=1 - fi -fi cd $BINDIR @@ -47,13 +40,6 @@ mkdir -p $OUTDIR/bin mkdir -p $OUTDIR/man/man1 make DESTDIR=$TMPDIR/inst install -if [ $SERVER = 1 ]; then - install -m 755 ./xorg.build/bin/Xvnc $OUTDIR/bin/ - install -m 644 ./xorg.build/man/man1/Xvnc.1 $OUTDIR/man/man1/Xvnc.1 - install -m 644 ./xorg.build/man/man1/Xserver.1 $OUTDIR/man/man1/Xserver.1 - mkdir -p $OUTDIR/lib/dri/ - install -m 755 ./xorg.build/lib/dri/swrast_dri.so $OUTDIR/lib/dri/ -fi pushd $TMPDIR/inst tar cfz ../$PACKAGE_FILE . diff --git a/release/tigervnc.iss.in b/release/tigervnc.iss.in index de4ee317..519d232f 100644 --- a/release/tigervnc.iss.in +++ b/release/tigervnc.iss.in @@ -5,7 +5,7 @@ ArchitecturesInstallIn64BitMode=x64 AppName=TigerVNC AppVerName=TigerVNC @VERSION@ (@BUILD@) AppVersion=@VERSION@ -AppPublisher=TigerVNC project +AppPublisher=TigerVNC team AppPublisherURL=https://tigervnc.org DefaultDirName={pf}\TigerVNC DefaultGroupName=TigerVNC @@ -25,8 +25,8 @@ Source: "@CMAKE_SOURCE_DIR@\LICENCE.TXT"; DestDir: "{app}"; Flags: ignoreversion #for {LINGUAS = FileOpen("@CMAKE_SOURCE_DIR@\po\LINGUAS"); !FileEof(LINGUAS); ""} AddLanguage [Icons] -Name: "{group}\TigerVNC Viewer"; FileName: "{app}\vncviewer.exe"; -Name: "{group}\Listening TigerVNC Viewer"; FileName: "{app}\vncviewer.exe"; Parameters: "-listen"; +Name: "{group}\TigerVNC"; FileName: "{app}\vncviewer.exe"; +Name: "{group}\Listening TigerVNC"; FileName: "{app}\vncviewer.exe"; Parameters: "-listen"; Name: "{group}\License"; FileName: "write.exe"; Parameters: "LICENCE.TXT"; WorkingDir: "{app}"; Flags: "useapppaths" Name: "{group}\Read Me"; FileName: "write.exe"; Parameters: "README.rst"; WorkingDir: "{app}"; Flags: "useapppaths" diff --git a/release/winvnc.iss.in b/release/winvnc.iss.in index 773aa175..2b002983 100644 --- a/release/winvnc.iss.in +++ b/release/winvnc.iss.in @@ -5,7 +5,7 @@ ArchitecturesInstallIn64BitMode=x64 AppName=TigerVNC server AppVerName=TigerVNC server v@VERSION@ (@BUILD@) AppVersion=@VERSION@ -AppPublisher=TigerVNC project +AppPublisher=TigerVNC team AppPublisherURL=https://tigervnc.org DefaultDirName={pf}\TigerVNC server DefaultGroupName=TigerVNC server diff --git a/tests/perf/CMakeLists.txt b/tests/perf/CMakeLists.txt index 38f874d4..54a1ad89 100644 --- a/tests/perf/CMakeLists.txt +++ b/tests/perf/CMakeLists.txt @@ -27,8 +27,8 @@ if (BUILD_VIEWER) target_sources(fbperf PRIVATE ${CMAKE_SOURCE_DIR}/vncviewer/Surface_X11.cxx) endif() target_include_directories(fbperf SYSTEM PUBLIC ${FLTK_INCLUDE_DIR}) - target_include_directories(fbperf SYSTEM PUBLIC ${GETTEXT_INCLUDE_DIR}) - target_link_libraries(fbperf test_util core rfb ${FLTK_LIBRARIES} ${GETTEXT_LIBRARIES}) + target_include_directories(fbperf SYSTEM PUBLIC ${Intl_INCLUDE_DIR}) + target_link_libraries(fbperf test_util core rfb ${FLTK_LIBRARIES} ${Intl_LIBRARIES}) if(WIN32) target_link_libraries(fbperf msimg32) endif() diff --git a/tests/perf/decperf.cxx b/tests/perf/decperf.cxx index 4d2f151a..2c7efa3b 100644 --- a/tests/perf/decperf.cxx +++ b/tests/perf/decperf.cxx @@ -67,8 +67,6 @@ public: ~CConn(); void initDone() override; - void setCursor(int, int, const core::Point&, const uint8_t*) override; - void setCursorPos(const core::Point&) override; void framebufferUpdateStart() override; void framebufferUpdateEnd() override; void setColourMapEntries(int, int, uint16_t*) override; @@ -140,14 +138,6 @@ void CConn::initDone() server.height())); } -void CConn::setCursor(int, int, const core::Point&, const uint8_t*) -{ -} - -void CConn::setCursorPos(const core::Point&) -{ -} - void CConn::framebufferUpdateStart() { CConnection::framebufferUpdateStart(); diff --git a/tests/perf/encperf.cxx b/tests/perf/encperf.cxx index 302e7eed..0da54603 100644 --- a/tests/perf/encperf.cxx +++ b/tests/perf/encperf.cxx @@ -98,8 +98,6 @@ public: void initDone() override {}; void resizeFramebuffer() override; - void setCursor(int, int, const core::Point&, const uint8_t*) override; - void setCursorPos(const core::Point&) override; void framebufferUpdateStart() override; void framebufferUpdateEnd() override; bool dataRect(const core::Rect&, int) override; @@ -141,6 +139,10 @@ public: void setDesktopSize(int fb_width, int fb_height, const rfb::ScreenSet& layout) override; + void keyEvent(uint32_t keysym, uint32_t keycode, bool down) override; + void pointerEvent(const core::Point& pos, + uint16_t buttonMask) override; + protected: DummyOutStream *out; Manager *manager; @@ -194,7 +196,7 @@ CConn::CConn(const char *filename) sc = new SConn(); sc->client.setPF((bool)translate ? fbPF : pf); - sc->setEncodings(sizeof(encodings) / sizeof(*encodings), encodings); + ((rfb::SMsgHandler*)sc)->setEncodings(sizeof(encodings) / sizeof(*encodings), encodings); } CConn::~CConn() @@ -219,14 +221,6 @@ void CConn::resizeFramebuffer() setFramebuffer(pb); } -void CConn::setCursor(int, int, const core::Point&, const uint8_t*) -{ -} - -void CConn::setCursorPos(const core::Point&) -{ -} - void CConn::framebufferUpdateStart() { CConnection::framebufferUpdateStart(); @@ -349,6 +343,14 @@ void SConn::setDesktopSize(int, int, const rfb::ScreenSet&) { } +void SConn::keyEvent(uint32_t, uint32_t, bool) +{ +} + +void SConn::pointerEvent(const core::Point&, uint16_t) +{ +} + struct stats { double decodeTime; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 0709766b..1e40a645 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -36,11 +36,15 @@ add_executable(pixelformat pixelformat.cxx) target_link_libraries(pixelformat rfb GTest::gtest_main) gtest_discover_tests(pixelformat) +add_executable(shortcuthandler shortcuthandler.cxx ../../vncviewer/ShortcutHandler.cxx) +target_link_libraries(shortcuthandler core ${Intl_LIBRARIES} GTest::gtest_main) +gtest_discover_tests(shortcuthandler) + add_executable(unicode unicode.cxx) target_link_libraries(unicode core GTest::gtest_main) gtest_discover_tests(unicode) add_executable(emulatemb emulatemb.cxx ../../vncviewer/EmulateMB.cxx) -target_include_directories(emulatemb SYSTEM PUBLIC ${GETTEXT_INCLUDE_DIR}) -target_link_libraries(emulatemb core ${GETTEXT_LIBRARIES} GTest::gtest_main) +target_include_directories(emulatemb SYSTEM PUBLIC ${Intl_INCLUDE_DIR}) +target_link_libraries(emulatemb core ${Intl_LIBRARIES} GTest::gtest_main) gtest_discover_tests(emulatemb) diff --git a/tests/unit/parameters.cxx b/tests/unit/parameters.cxx index fb240c91..e120f988 100644 --- a/tests/unit/parameters.cxx +++ b/tests/unit/parameters.cxx @@ -530,6 +530,14 @@ TEST(IntListParameter, strings) strings.setParam("9,\n10,\t11,\t12"); data = {9, 10, 11, 12}; EXPECT_EQ(strings, data); + + strings.setParam(""); + data = {}; + EXPECT_EQ(strings, data); + + strings.setParam(" "); + data = {}; + EXPECT_EQ(strings, data); } TEST(IntListParameter, minmax) @@ -650,6 +658,18 @@ TEST(StringListParameter, strings) strings.setParam("9,\n10,\t11,\t12"); data = {"9", "10", "11", "12"}; EXPECT_EQ(strings, data); + + strings.setParam(""); + data = {}; + EXPECT_EQ(strings, data); + + strings.setParam(" "); + data = {}; + EXPECT_EQ(strings, data); + + strings.setParam("a, , b"); + data = {"a", "", "b"}; + EXPECT_EQ(strings, data); } TEST(StringListParameter, null) @@ -733,6 +753,14 @@ TEST(EnumListParameter, strings) strings.setParam("b,\na,\tc,\tb"); data = {"b", "a", "c", "b"}; EXPECT_EQ(strings, data); + + strings.setParam(""); + data = {}; + EXPECT_EQ(strings, data); + + strings.setParam(" "); + data = {}; + EXPECT_EQ(strings, data); } TEST(EnumListParameter, validation) diff --git a/tests/unit/shortcuthandler.cxx b/tests/unit/shortcuthandler.cxx new file mode 100644 index 00000000..aa005155 --- /dev/null +++ b/tests/unit/shortcuthandler.cxx @@ -0,0 +1,607 @@ +/* Copyright 2021-2025 Pierre Ossman for Cendio AB + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtest/gtest.h> + +#define XK_LATIN1 +#define XK_MISCELLANY +#include <rfb/keysymdef.h> + +#include "ShortcutHandler.h" + +TEST(ShortcutHandler, noModifiers) +{ + ShortcutHandler handler; + + handler.setModifiers(0); + + EXPECT_EQ(handler.handleKeyPress(1, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Hyper_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, singleArmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyUnarm); +} + +TEST(ShortcutHandler, singleDualArmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyUnarm); +} + +TEST(ShortcutHandler, singleShortcut) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleRightShortcut) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleDualShortcut) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleShortcutReordered) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); +} + +TEST(ShortcutHandler, singleDualShortcutReordered) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_R), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleShortcutRepeated) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleShortcutMultipleKeys) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(3, XK_b), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(4, XK_c), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleWedgeNormal) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_b), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, singleWedgeModifier) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, singleWedgeModifierArmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, singleWedgeModifierFiring) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, singleUnwedge) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control); + + handler.handleKeyPress(1, XK_Shift_L); + handler.handleKeyPress(2, XK_Control_L); + handler.handleKeyRelease(1); + handler.handleKeyRelease(2); + + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiArmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyUnarm); +} + +TEST(ShortcutHandler, multiRearmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyUnarm); +} + +TEST(ShortcutHandler, multiFailedArm) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, multiDualArmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyUnarm); +} + +TEST(ShortcutHandler, multiShortcut) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiRightShortcut) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiDualShortcut) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Alt_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(6, XK_Shift_R), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(7, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(7), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(6), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiShortcutReordered) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); +} + +TEST(ShortcutHandler, multiDualShortcutReordered) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(7, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_R), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyPress(4, XK_Alt_R), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyPress(6, XK_Shift_R), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(6), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(7), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiShortcutRepeated) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiShortcutMultipleKeys) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(5, XK_b), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(6, XK_c), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(6), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiWedgeNormal) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_b), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, multiWedgeModifier) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Super_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, multiWedgeArming) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(1, XK_b), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_a), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, multiWedgeModifierArming) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Super_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, multiWedgeModifierArmed) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Super_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyNormal); +} + +TEST(ShortcutHandler, multiWedgeModifierFiring) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + EXPECT_EQ(handler.handleKeyPress(1, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(2, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyPress(5, XK_Super_L), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(1), ShortcutHandler::KeyIgnore); +} + +TEST(ShortcutHandler, multiUnwedge) +{ + ShortcutHandler handler; + + handler.setModifiers(ShortcutHandler::Control | + ShortcutHandler::Shift | + ShortcutHandler::Alt); + + handler.handleKeyPress(1, XK_Super_L); + handler.handleKeyPress(2, XK_Control_L); + handler.handleKeyPress(3, XK_Alt_L); + handler.handleKeyPress(4, XK_Shift_L); + handler.handleKeyRelease(1); + handler.handleKeyRelease(2); + handler.handleKeyRelease(3); + handler.handleKeyRelease(4); + + EXPECT_EQ(handler.handleKeyPress(2, XK_Control_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(3, XK_Alt_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(4, XK_Shift_L), ShortcutHandler::KeyNormal); + EXPECT_EQ(handler.handleKeyPress(5, XK_a), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(5), ShortcutHandler::KeyShortcut); + EXPECT_EQ(handler.handleKeyRelease(4), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(3), ShortcutHandler::KeyIgnore); + EXPECT_EQ(handler.handleKeyRelease(2), ShortcutHandler::KeyIgnore); +} + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/unix/vncpasswd/CMakeLists.txt b/unix/vncpasswd/CMakeLists.txt index 6ed4adaf..2acc9288 100644 --- a/unix/vncpasswd/CMakeLists.txt +++ b/unix/vncpasswd/CMakeLists.txt @@ -5,7 +5,8 @@ target_include_directories(vncpasswd PUBLIC ${CMAKE_SOURCE_DIR}/common) target_link_libraries(vncpasswd core tx rfb) if(PWQUALITY_FOUND) - target_link_libraries(vncpasswd pwquality) + target_include_directories(vncpasswd SYSTEM PRIVATE ${PWQUALITY_INCLUDE_DIRS}) + target_link_libraries(vncpasswd ${PWQUALITY_LIBRARIES}) endif() install(TARGETS vncpasswd DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) diff --git a/unix/vncserver/CMakeLists.txt b/unix/vncserver/CMakeLists.txt index ae69dc09..ed259c22 100644 --- a/unix/vncserver/CMakeLists.txt +++ b/unix/vncserver/CMakeLists.txt @@ -1,5 +1,8 @@ add_executable(vncsession vncsession.c) -target_link_libraries(vncsession ${PAM_LIBS} ${SELINUX_LIBS}) +target_include_directories(vncsession SYSTEM PRIVATE ${PAM_INCLUDE_DIRS}) +target_include_directories(vncsession SYSTEM PRIVATE ${SELINUX_INCLUDE_DIRS}) +target_link_libraries(vncsession ${PAM_LIBRARIES}) +target_link_libraries(vncsession ${SELINUX_LIBRARIES}) configure_file(vncserver@.service.in vncserver@.service @ONLY) configure_file(vncsession-start.in vncsession-start @ONLY) diff --git a/unix/vncserver/selinux/vncsession.te b/unix/vncserver/selinux/vncsession.te index 4dbf687e..2ce4fc81 100644 --- a/unix/vncserver/selinux/vncsession.te +++ b/unix/vncserver/selinux/vncsession.te @@ -34,17 +34,13 @@ allow vnc_session_t self:capability { chown dac_override dac_read_search fowner allow vnc_session_t self:process { getcap setexec setrlimit setsched }; allow vnc_session_t self:fifo_file rw_fifo_file_perms; -optional_policy(` - gen_require(` - type sysctl_fs_t; - ') - allow vnc_session_t sysctl_fs_t:dir search; - allow vnc_session_t sysctl_fs_t:file { getattr open read }; -') - allow vnc_session_t vnc_session_var_run_t:file manage_file_perms; files_pid_filetrans(vnc_session_t, vnc_session_var_run_t, file) +# Allow access to /proc/sys/fs/nr_open +# Needed when the nofile limit is set to unlimited. +kernel_read_fs_sysctls(vnc_session_t) + # Allowed to create ~/.local optional_policy(` gnome_filetrans_home_content(vnc_session_t) diff --git a/unix/vncserver/vncsession.c b/unix/vncserver/vncsession.c index 0830e81a..79683ff9 100644 --- a/unix/vncserver/vncsession.c +++ b/unix/vncserver/vncsession.c @@ -134,7 +134,7 @@ begin_daemon(void) static void finish_daemon(void) { - write(daemon_pipe_fd, "+", 1); + if (write(daemon_pipe_fd, "+", 1) == -1) {} close(daemon_pipe_fd); daemon_pipe_fd = -1; } @@ -545,8 +545,12 @@ run_script(const char *username, const char *display, char **envp) switch_user(pwent->pw_name, pwent->pw_uid, pwent->pw_gid); - if (chdir(pwent->pw_dir) == -1) - chdir("/"); + if (chdir(pwent->pw_dir) == -1) { + syslog(LOG_CRIT, "chdir(\"%s\") failed: %s", pwent->pw_dir, strerror(errno)); + // fallback to "/" + if (chdir("/") == -1) + syslog(LOG_CRIT, "chdir(\"%s\") failed: %s", "/", strerror(errno)); + } close_fds(); diff --git a/unix/x0vncserver/CMakeLists.txt b/unix/x0vncserver/CMakeLists.txt index 763f2de2..4ea09dd8 100644 --- a/unix/x0vncserver/CMakeLists.txt +++ b/unix/x0vncserver/CMakeLists.txt @@ -23,8 +23,9 @@ target_include_directories(x0vncserver PUBLIC ${CMAKE_SOURCE_DIR}/common) target_link_libraries(x0vncserver core tx rfb network rdr unixcommon) # systemd support (socket activation) -if (LIBSYSTEMD_FOUND) - target_link_libraries(x0vncserver ${LIBSYSTEMD_LIBRARIES}) +if (SYSTEMD_FOUND) + target_include_directories(x0vncserver SYSTEM PRIVATE ${SYSTEMD_INCLUDE_DIRS}) + target_link_libraries(x0vncserver ${SYSTEMD_LIBRARIES}) endif() if(X11_XTest_LIB) diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx index b42c38df..b8b631aa 100644 --- a/unix/x0vncserver/x0vncserver.cxx +++ b/unix/x0vncserver/x0vncserver.cxx @@ -36,8 +36,10 @@ #include <core/LogWriter.h> #include <core/Timer.h> +#include <rdr/FdInStream.h> #include <rdr/FdOutStream.h> +#include <rfb/UnixPasswordValidator.h> #include <rfb/VNCServerST.h> #include <network/TcpSocket.h> @@ -334,12 +336,14 @@ int main(int argc, char** argv) exit(1); } + const char *displayName = XDisplayName(displayname); if (!(dpy = XOpenDisplay(displayname))) { // FIXME: Why not vlog.error(...)? fprintf(stderr,"%s: Unable to open display \"%s\"\r\n", - programName, XDisplayName(displayname)); + programName, displayName); exit(1); } + rfb::UnixPasswordValidator::setDisplayName(displayName); signal(SIGHUP, CleanupSignalHandler); signal(SIGINT, CleanupSignalHandler); @@ -359,6 +363,8 @@ int main(int argc, char** argv) rfb::VNCServerST server(desktopName, &desktop); + FileTcpFilter fileTcpFilter(hostsFile); + if (createSystemdListeners(&listeners) > 0) { // When systemd is in charge of listeners, do not listen to anything else vlog.info("Listening on systemd sockets"); @@ -387,7 +393,6 @@ int main(int argc, char** argv) (int)rfbport); } - FileTcpFilter fileTcpFilter(hostsFile); if (strlen(hostsFile) != 0) for (network::SocketListener* listener : listeners) listener->setFilter(&fileTcpFilter); @@ -420,15 +425,10 @@ int main(int argc, char** argv) server.getSockets(&sockets); int clients_connected = 0; for (i = sockets.begin(); i != sockets.end(); i++) { - if ((*i)->isShutdown()) { - server.removeSocket(*i); - delete (*i); - } else { - FD_SET((*i)->getFd(), &rfds); - if ((*i)->outStream().hasBufferedData()) - FD_SET((*i)->getFd(), &wfds); - clients_connected++; - } + FD_SET((*i)->getFd(), &rfds); + if ((*i)->outStream().hasBufferedData()) + FD_SET((*i)->getFd(), &wfds); + clients_connected++; } if (!clients_connected) @@ -493,6 +493,29 @@ int main(int argc, char** argv) server.processSocketReadEvent(*i); if (FD_ISSET((*i)->getFd(), &wfds)) server.processSocketWriteEvent(*i); + + // Do a graceful close by waiting for the peer to close their + // end + if ((*i)->isShutdown()) { + bool done; + + done = false; + while (true) { + try { + (*i)->inStream().skip((*i)->inStream().avail()); + if (!(*i)->inStream().hasData(1)) + break; + } catch (std::exception&) { + done = true; + break; + } + } + + if (done) { + server.removeSocket(*i); + delete (*i); + } + } } if (desktop.isRunning() && sched.goodTimeToPoll()) { diff --git a/unix/xserver/hw/vnc/RFBGlue.cc b/unix/xserver/hw/vnc/RFBGlue.cc index b7616298..f217906a 100644 --- a/unix/xserver/hw/vnc/RFBGlue.cc +++ b/unix/xserver/hw/vnc/RFBGlue.cc @@ -32,6 +32,8 @@ #include <network/TcpSocket.h> +#include <rfb/UnixPasswordValidator.h> + #include "RFBGlue.h" // Loggers used by C code must be created here @@ -132,31 +134,9 @@ const char* vncGetParamDesc(const char *name) return param->getDescription(); } -int vncIsParamBool(const char *name) -{ - core::VoidParameter* param; - core::BoolParameter* bparam; - - param = core::Configuration::getParam(name); - if (param == nullptr) - return false; - - bparam = dynamic_cast<core::BoolParameter*>(param); - if (bparam == nullptr) - return false; - - return true; -} - int vncGetParamCount(void) { - int count; - - count = 0; - for (core::VoidParameter *param: *core::Configuration::global()) - count++; - - return count; + return core::Configuration::global()->size(); } char *vncGetParamList(void) @@ -256,3 +236,10 @@ int vncIsValidUTF8(const char* str, size_t bytes) return 0; } } + +void vncSetDisplayName(const char *displayNumStr) +{ + std::string displayName(":"); + displayName += displayNumStr; + rfb::UnixPasswordValidator::setDisplayName(displayName); +} diff --git a/unix/xserver/hw/vnc/RFBGlue.h b/unix/xserver/hw/vnc/RFBGlue.h index 926f49c6..86304ad5 100644 --- a/unix/xserver/hw/vnc/RFBGlue.h +++ b/unix/xserver/hw/vnc/RFBGlue.h @@ -38,7 +38,6 @@ void vncLogDebug(const char *name, const char *format, ...) int vncSetParam(const char *name, const char *value); char* vncGetParam(const char *name); const char* vncGetParamDesc(const char *name); -int vncIsParamBool(const char *name); int vncGetParamCount(void); char *vncGetParamList(void); @@ -56,6 +55,8 @@ char* vncUTF8ToLatin1(const char* src, size_t bytes); int vncIsValidUTF8(const char* str, size_t bytes); +void vncSetDisplayName(const char *displayNumStr); + #ifdef __cplusplus } #endif diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index d88ef874..1a7a06db 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -40,6 +40,7 @@ #include <core/Configuration.h> #include <core/LogWriter.h> +#include <rdr/FdInStream.h> #include <rdr/FdOutStream.h> #include <network/Socket.h> @@ -363,6 +364,31 @@ bool XserverDesktop::handleSocketEvent(int fd, if (write) sockserv->processSocketWriteEvent(*i); + // Do a graceful close by waiting for the peer to close their end + if ((*i)->isShutdown()) { + bool done; + + done = false; + while (true) { + try { + (*i)->inStream().skip((*i)->inStream().avail()); + if (!(*i)->inStream().hasData(1)) + break; + } catch (std::exception&) { + done = true; + break; + } + } + + if (done) { + vlog.debug("Client gone, sock %d",fd); + vncRemoveNotifyFd(fd); + sockserv->removeSocket(*i); + vncClientGone(fd); + delete (*i); + } + } + return true; } @@ -380,16 +406,8 @@ void XserverDesktop::blockHandler(int* timeout) server->getSockets(&sockets); for (i = sockets.begin(); i != sockets.end(); i++) { int fd = (*i)->getFd(); - if ((*i)->isShutdown()) { - vlog.debug("Client gone, sock %d",fd); - vncRemoveNotifyFd(fd); - server->removeSocket(*i); - vncClientGone(fd); - delete (*i); - } else { - /* Update existing NotifyFD to listen for write (or not) */ - vncSetNotifyFd(fd, screenIndex, true, (*i)->outStream().hasBufferedData()); - } + /* Update existing NotifyFD to listen for write (or not) */ + vncSetNotifyFd(fd, screenIndex, true, (*i)->outStream().hasBufferedData()); } // We are responsible for propagating mouse movement between clients diff --git a/unix/xserver/hw/vnc/vncModule.c b/unix/xserver/hw/vnc/vncModule.c index 5f0886a3..bff317b5 100644 --- a/unix/xserver/hw/vnc/vncModule.c +++ b/unix/xserver/hw/vnc/vncModule.c @@ -50,7 +50,7 @@ ExtensionModule vncExt = static XF86ModuleVersionInfo vncVersRec = { "vnc", - "TigerVNC project", + "TigerVNC", MODINFOSTRING1, MODINFOSTRING2, VENDOR_RELEASE, diff --git a/unix/xserver/hw/vnc/xvnc.c b/unix/xserver/hw/vnc/xvnc.c index 0b8e028f..5cf673aa 100644 --- a/unix/xserver/hw/vnc/xvnc.c +++ b/unix/xserver/hw/vnc/xvnc.c @@ -110,7 +110,6 @@ static VncScreenInfo vncScreenInfo = { static Bool vncPixmapDepths[33]; static Bool Render = TRUE; -static Bool displaySpecified = FALSE; static char displayNumStr[16]; static int vncVerbose = 0; @@ -176,15 +175,20 @@ ddxGiveUp(enum ExitCode error) vncFreeFramebufferMemory(&vncScreenInfo.fb); } +#if XORG_OLDER_THAN(1, 21, 1) void AbortDDX(enum ExitCode error) { ddxGiveUp(error); } +#endif void OsVendorInit(void) { + /* At this point, display has been set, so we can use it to + * initialize UnixPasswordValidator */ + vncSetDisplayName(display); } void @@ -276,14 +280,16 @@ ddxProcessArgument(int argc, char *argv[], int i) } if (argv[i][0] == ':') - displaySpecified = TRUE; + return 0; +#if XORG_OLDER_THAN(1, 21, 1) #define CHECK_FOR_REQUIRED_ARGUMENTS(num) \ if (((i + num) >= argc) || (!argv[i + num])) { \ ErrorF("Required argument to %s not specified\n", argv[i]); \ UseMsg(); \ FatalError("Required argument to %s not specified\n", argv[i]); \ } +#endif if (strcmp(argv[i], "-pixdepths") == 0) { /* -pixdepths list-of-depth */ int depth, ret = 1; @@ -382,7 +388,7 @@ ddxProcessArgument(int argc, char *argv[], int i) dup2(nullfd, 2); close(nullfd); - if (!displaySpecified) { + if (!explicit_display) { int port = vncGetSocketPort(vncInetdSock); int displayNum = port - 5900; @@ -396,9 +402,9 @@ ddxProcessArgument(int argc, char *argv[], int i) FatalError ("Xvnc error: No free display number for -inetd\n"); } - - display = displayNumStr; sprintf(displayNumStr, "%d", displayNum); + display = displayNumStr; + explicit_display = TRUE; } return 1; @@ -442,30 +448,11 @@ ddxProcessArgument(int argc, char *argv[], int i) } if (!strcmp(argv[i], "-showconfig") || !strcmp(argv[i], "-version")) { - /* Already shown at start */ + vncPrintBanner(); exit(0); } - /* We need to resolve an ambiguity for booleans */ - if (argv[i][0] == '-' && i + 1 < argc && vncIsParamBool(&argv[i][1])) { - if ((strcasecmp(argv[i + 1], "0") == 0) || - (strcasecmp(argv[i + 1], "1") == 0) || - (strcasecmp(argv[i + 1], "true") == 0) || - (strcasecmp(argv[i + 1], "false") == 0) || - (strcasecmp(argv[i + 1], "yes") == 0) || - (strcasecmp(argv[i + 1], "no") == 0)) { - vncSetParam(&argv[i][1], argv[i + 1]); - return 2; - } - } - - int ret; - - ret = vncHandleParamArg(argc, argv, i); - if (ret != 0) - return ret; - - return 0; + return vncHandleParamArg(argc, argv, i); } static Bool @@ -761,12 +748,13 @@ vncRandRModeGet(int width, int height) xRRModeInfo modeInfo; char name[100]; RRModePtr mode; +#ifdef HAVE_LIBXCVT + struct libxcvt_mode_info *cvtMode; +#endif memset(&modeInfo, 0, sizeof(modeInfo)); #ifdef HAVE_LIBXCVT - struct libxcvt_mode_info *cvtMode; - cvtMode = libxcvt_gen_mode_info(width, height, 60.0, false, false); modeInfo.width = cvtMode->hdisplay; @@ -899,7 +887,7 @@ vncRandRCreateScreenOutputs(int scrIdx, int extraOutputs) /* Creating and modifying modes, used by XserverDesktop and init here */ int -vncRandRCanCreateModes() +vncRandRCanCreateModes(void) { return 1; } @@ -1166,8 +1154,11 @@ InitOutput(ScreenInfo * scrInfo, int argc, char **argv) int i; int NumFormats = 0; - if (serverGeneration == 1) + if (serverGeneration == 1) { + vncPrintBanner(); + LoadExtensionList(vncExtensions, ARRAY_SIZE(vncExtensions), TRUE); + } #if XORG_AT_LEAST(1, 20, 0) xorgGlxCreateVendor(); @@ -1224,11 +1215,13 @@ DDXRingBell(int percent, int pitch, int duration) vncBell(); } +#if XORG_OLDER_THAN(1, 21, 1) Bool LegalModifier(unsigned int key, DeviceIntPtr pDev) { return TRUE; } +#endif void ProcessInputEvents(void) @@ -1259,7 +1252,5 @@ vncClientGone(int fd) int main(int argc, char *argv[], char *envp[]) { - vncPrintBanner(); - return dix_main(argc, argv, envp); } diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx index e567939c..da99c8f1 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -30,6 +30,7 @@ #include <core/LogWriter.h> #include <core/Timer.h> #include <core/string.h> +#include <core/time.h> #include <rdr/FdInStream.h> #include <rdr/FdOutStream.h> @@ -76,12 +77,12 @@ static const rfb::PixelFormat mediumColourPF(8, 8, false, true, // Time new bandwidth estimates are weighted against (in ms) static const unsigned bpsEstimateWindow = 1000; -CConn::CConn(const char* vncServerName, network::Socket* socket=nullptr) - : serverPort(0), desktop(nullptr), updateCount(0), pixelCount(0), +CConn::CConn() + : serverPort(0), sock(nullptr), desktop(nullptr), + updateCount(0), pixelCount(0), lastServerEncoding((unsigned int)-1), bpsEstimate(20000000) { setShared(::shared); - sock = socket; supportsLocalCursor = true; supportsCursorPosition = true; @@ -94,6 +95,61 @@ CConn::CConn(const char* vncServerName, network::Socket* socket=nullptr) if (!noJpeg) setQualityLevel(::qualityLevel); + OptionsDialog::addCallback(handleOptions, this); +} + +CConn::~CConn() +{ + close(); + + OptionsDialog::removeCallback(handleOptions); + Fl::remove_timeout(handleUpdateTimeout, this); + + if (desktop) + delete desktop; + + if (sock) { + struct timeval now; + + sock->shutdown(); + + // Do a graceful close by waiting for the peer (up to 250 ms) + // FIXME: should do this asynchronously + gettimeofday(&now, nullptr); + while (core::msSince(&now) < 250) { + bool done; + + done = false; + while (true) { + try { + sock->inStream().skip(sock->inStream().avail()); + if (!sock->inStream().hasData(1)) + break; + } catch (std::exception&) { + done = true; + break; + } + } + + if (done) + break; + + #ifdef WIN32 + Sleep(10); + #else + usleep(10000); + #endif + } + + Fl::remove_fd(sock->getFd()); + + delete sock; + } +} + +void CConn::connect(const char* vncServerName, network::Socket* socket) +{ + sock = socket; if(sock == nullptr) { try { #ifndef WIN32 @@ -124,23 +180,6 @@ CConn::CConn(const char* vncServerName, network::Socket* socket=nullptr) setStreams(&sock->inStream(), &sock->outStream()); initialiseProtocol(); - - OptionsDialog::addCallback(handleOptions, this); -} - -CConn::~CConn() -{ - close(); - - OptionsDialog::removeCallback(handleOptions); - Fl::remove_timeout(handleUpdateTimeout, this); - - if (desktop) - delete desktop; - - if (sock) - Fl::remove_fd(sock->getFd()); - delete sock; } std::string CConn::connectionInfo() @@ -166,11 +205,6 @@ std::string CConn::connectionInfo() infoText += core::format(_("Pixel format: %s"), pfStr); infoText += "\n"; - // TRANSLATORS: Similar to the earlier "Pixel format" string - serverPF.print(pfStr, 100); - infoText += core::format(_("(server default %s)"), pfStr); - infoText += "\n"; - infoText += core::format(_("Requested encoding: %s"), rfb::encodingName(getPreferredEncoding())); infoText += "\n"; @@ -305,17 +339,12 @@ void CConn::initDone() if (server.beforeVersion(3, 8) && autoSelect) fullColour.setParam(true); - serverPF = server.pf(); - - desktop = new DesktopWindow(server.width(), server.height(), - server.name(), serverPF, this); + desktop = new DesktopWindow(server.width(), server.height(), this); fullColourPF = desktop->getPreferredPF(); // Force a switch to the format and encoding we'd like + updateEncoding(); updatePixelFormat(); - int encNum = rfb::encodingNum(::preferredEncoding.getValueStr().c_str()); - if (encNum != -1) - setPreferredEncoding(encNum); } void CConn::setExtendedDesktopSize(unsigned reason, unsigned result, @@ -332,7 +361,7 @@ void CConn::setExtendedDesktopSize(unsigned reason, unsigned result, void CConn::setName(const char* name) { CConnection::setName(name); - desktop->setName(name); + desktop->updateCaption(); } // framebufferUpdateStart() is called at the beginning of an update. @@ -385,18 +414,15 @@ void CConn::framebufferUpdateEnd() desktop->updateWindow(); // Compute new settings based on updated bandwidth values - if (autoSelect) - autoSelectFormatAndEncoding(); + if (autoSelect) { + updateEncoding(); + updateQualityLevel(); + updatePixelFormat(); + } } // The rest of the callbacks are fairly self-explanatory... -void CConn::setColourMapEntries(int /*firstColour*/, int /*nColours*/, - uint16_t* /*rgbs*/) -{ - vlog.error(_("Invalid SetColourMapEntries from server!")); -} - void CConn::bell() { fl_beep(); @@ -420,7 +446,9 @@ bool CConn::dataRect(const core::Rect& r, int encoding) void CConn::setCursor(int width, int height, const core::Point& hotspot, const uint8_t* data) { - desktop->setCursor(width, height, hotspot, data); + CConnection::setCursor(width, height, hotspot, data); + + desktop->setCursor(); } void CConn::setCursorPos(const core::Point& pos) @@ -428,20 +456,6 @@ void CConn::setCursorPos(const core::Point& pos) desktop->setCursorPos(pos); } -void CConn::fence(uint32_t flags, unsigned len, const uint8_t data[]) -{ - CMsgHandler::fence(flags, len, data); - - if (flags & rfb::fenceFlagRequest) { - // We handle everything synchronously so we trivially honor these modes - flags = flags & (rfb::fenceFlagBlockBefore | - rfb::fenceFlagBlockAfter); - - writer()->writeFence(flags, len, data); - return; - } -} - void CConn::setLEDState(unsigned int state) { CConnection::setLEDState(state); @@ -472,44 +486,59 @@ void CConn::resizeFramebuffer() desktop->resizeFramebuffer(server.width(), server.height()); } -// autoSelectFormatAndEncoding() chooses the format and encoding appropriate -// to the connection speed: -// -// First we wait for at least one second of bandwidth measurement. -// -// Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality, -// which should be perceptually lossless. -// -// If the bandwidth is below that, we choose a more lossy JPEG quality. -// -// If the bandwidth drops below 256 Kbps, we switch to palette mode. -// -// Note: The system here is fairly arbitrary and should be replaced -// with something more intelligent at the server end. -// -void CConn::autoSelectFormatAndEncoding() +void CConn::updateEncoding() { - bool newFullColour = fullColour; - int newQualityLevel = ::qualityLevel; + int encNum; - // Always use Tight - setPreferredEncoding(rfb::encodingTight); + if (autoSelect) + encNum = rfb::encodingTight; + else + encNum = rfb::encodingNum(::preferredEncoding.getValueStr().c_str()); + + if (encNum != -1) + setPreferredEncoding(encNum); +} + +void CConn::updateCompressLevel() +{ + if (customCompressLevel) + setCompressLevel(::compressLevel); + else + setCompressLevel(-1); +} + +void CConn::updateQualityLevel() +{ + int newQualityLevel; + + if (noJpeg) + newQualityLevel = -1; + else if (!autoSelect) + newQualityLevel = ::qualityLevel; + else { + // Above 16Mbps (i.e. LAN), we choose the second highest JPEG + // quality, which should be perceptually lossless. If the bandwidth + // is below that, we choose a more lossy JPEG quality. - // Select appropriate quality level - if (!noJpeg) { if (bpsEstimate > 16000000) newQualityLevel = 8; else newQualityLevel = 6; - if (newQualityLevel != ::qualityLevel) { + if (newQualityLevel != getQualityLevel()) { vlog.info(_("Throughput %d kbit/s - changing to quality %d"), (int)(bpsEstimate/1000), newQualityLevel); - ::qualityLevel.setParam(newQualityLevel); - setQualityLevel(newQualityLevel); } } + setQualityLevel(newQualityLevel); +} + +void CConn::updatePixelFormat() +{ + bool useFullColour; + rfb::PixelFormat pf; + if (server.beforeVersion(3, 8)) { // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with // cursors "asynchronously". If this happens in the middle of a @@ -520,28 +549,23 @@ void CConn::autoSelectFormatAndEncoding() // old servers. return; } - - // Select best color level - newFullColour = (bpsEstimate > 256000); - if (newFullColour != fullColour) { - if (newFullColour) - vlog.info(_("Throughput %d kbit/s - full color is now enabled"), - (int)(bpsEstimate/1000)); - else - vlog.info(_("Throughput %d kbit/s - full color is now disabled"), - (int)(bpsEstimate/1000)); - fullColour.setParam(newFullColour); - updatePixelFormat(); - } -} -// requestNewUpdate() requests an update from the server, having set the -// format and encoding appropriately. -void CConn::updatePixelFormat() -{ - rfb::PixelFormat pf; + useFullColour = fullColour; + + // If the bandwidth drops below 256 Kbps, we switch to palette mode. + if (autoSelect) { + useFullColour = (bpsEstimate > 256000); + if (useFullColour != (server.pf() == fullColourPF)) { + if (useFullColour) + vlog.info(_("Throughput %d kbit/s - full color is now enabled"), + (int)(bpsEstimate/1000)); + else + vlog.info(_("Throughput %d kbit/s - full color is now disabled"), + (int)(bpsEstimate/1000)); + } + } - if (fullColour) { + if (useFullColour) { pf = fullColourPF; } else { if (lowColourLevel == 0) @@ -552,37 +576,21 @@ void CConn::updatePixelFormat() pf = mediumColourPF; } - char str[256]; - pf.print(str, 256); - vlog.info(_("Using pixel format %s"),str); - setPF(pf); + if (pf != server.pf()) { + char str[256]; + pf.print(str, 256); + vlog.info(_("Using pixel format %s"),str); + setPF(pf); + } } void CConn::handleOptions(void *data) { CConn *self = (CConn*)data; - // Checking all the details of the current set of encodings is just - // a pain. Assume something has changed, as resending the encoding - // list is cheap. Avoid overriding what the auto logic has selected - // though. - if (!autoSelect) { - int encNum = rfb::encodingNum(::preferredEncoding.getValueStr().c_str()); - - if (encNum != -1) - self->setPreferredEncoding(encNum); - } - - if (customCompressLevel) - self->setCompressLevel(::compressLevel); - else - self->setCompressLevel(-1); - - if (!noJpeg && !autoSelect) - self->setQualityLevel(::qualityLevel); - else - self->setQualityLevel(-1); - + self->updateEncoding(); + self->updateCompressLevel(); + self->updateQualityLevel(); self->updatePixelFormat(); } diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h index 5fc2650c..bc30d9b7 100644 --- a/vncviewer/CConn.h +++ b/vncviewer/CConn.h @@ -33,15 +33,19 @@ class DesktopWindow; class CConn : public rfb::CConnection { public: - CConn(const char* vncServerName, network::Socket* sock); + CConn(); ~CConn(); + void connect(const char* vncServerName, network::Socket* sock=nullptr); + std::string connectionInfo(); unsigned getUpdateCount(); unsigned getPixelCount(); unsigned getPosition(); +protected: + // Callback when socket is ready (or broken) static void socketEvent(FL_SOCKET fd, void *data); @@ -63,9 +67,6 @@ public: void setName(const char* name) override; - void setColourMapEntries(int firstColour, int nColours, - uint16_t* rgbs) override; - void bell() override; void framebufferUpdateStart() override; @@ -76,9 +77,6 @@ public: const uint8_t* data) override; void setCursorPos(const core::Point& pos) override; - void fence(uint32_t flags, unsigned len, - const uint8_t data[]) override; - void setLEDState(unsigned int state) override; void handleClipboardRequest() override; @@ -89,7 +87,9 @@ private: void resizeFramebuffer() override; - void autoSelectFormatAndEncoding(); + void updateEncoding(); + void updateCompressLevel(); + void updateQualityLevel(); void updatePixelFormat(); static void handleOptions(void *data); @@ -106,7 +106,6 @@ private: unsigned updateCount; unsigned pixelCount; - rfb::PixelFormat serverPF; rfb::PixelFormat fullColourPF; int lastServerEncoding; diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt index d32ad2ea..1ca6675d 100644 --- a/vncviewer/CMakeLists.txt +++ b/vncviewer/CMakeLists.txt @@ -6,13 +6,13 @@ add_executable(vncviewer fltk/Fl_Monitor_Arrangement.cxx fltk/Fl_Navigation.cxx fltk/theme.cxx - menukey.cxx BaseTouchHandler.cxx CConn.cxx DesktopWindow.cxx EmulateMB.cxx UserDialog.cxx ServerDialog.cxx + ShortcutHandler.cxx Surface.cxx OptionsDialog.cxx PlatformPixelBuffer.cxx @@ -49,10 +49,10 @@ else() endif() target_include_directories(vncviewer SYSTEM PUBLIC ${FLTK_INCLUDE_DIR}) -target_include_directories(vncviewer SYSTEM PUBLIC ${GETTEXT_INCLUDE_DIR}) +target_include_directories(vncviewer SYSTEM PUBLIC ${Intl_INCLUDE_DIR}) target_include_directories(vncviewer PUBLIC ${CMAKE_SOURCE_DIR}/common) target_link_libraries(vncviewer core rfb network rdr) -target_link_libraries(vncviewer ${FLTK_LIBRARIES} ${GETTEXT_LIBRARIES}) +target_link_libraries(vncviewer ${FLTK_LIBRARIES} ${Intl_LIBRARIES}) if(WIN32) target_link_libraries(vncviewer msimg32) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 4a15117a..831bb107 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -26,6 +26,8 @@ #include <assert.h> #include <stdio.h> #include <string.h> +#include <time.h> +#include <unistd.h> #include <sys/time.h> #include <core/LogWriter.h> @@ -73,20 +75,21 @@ static int edge_scroll_size_y = 96; // default: roughly 60 fps for smooth motion #define EDGE_SCROLL_SECONDS_PER_FRAME 0.016666 +// Time before we show an overlay tip again +const time_t OVERLAY_REPEAT_TIMEOUT = 600; + static core::LogWriter vlog("DesktopWindow"); // Global due to http://www.fltk.org/str.php?L2177 and the similar // issue for Fl::event_dispatch. static std::set<DesktopWindow *> instances; -DesktopWindow::DesktopWindow(int w, int h, const char *name, - const rfb::PixelFormat& serverPF, - CConn* cc_) - : Fl_Window(w, h), cc(cc_), offscreen(nullptr), overlay(nullptr), +DesktopWindow::DesktopWindow(int w, int h, CConn* cc_) + : Fl_Window(w, h), cc(cc_), offscreen(nullptr), firstUpdate(true), delayedFullscreen(false), sentDesktopSize(false), pendingRemoteResize(false), lastResize({0, 0}), - keyboardGrabbed(false), mouseGrabbed(false), + keyboardGrabbed(false), mouseGrabbed(false), regrabOnFocus(false), statsLastUpdates(0), statsLastPixels(0), statsLastPosition(0), statsGraph(nullptr) { @@ -97,7 +100,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, group->resizable(nullptr); resizable(group); - viewport = new Viewport(w, h, serverPF, cc); + viewport = new Viewport(w, h, cc); // Position will be adjusted later hscroll = new Fl_Scrollbar(0, 0, 0, 0); @@ -110,7 +113,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, callback(handleClose, this); - setName(name); + updateCaption(); OptionsDialog::addCallback(handleOptions, this); @@ -229,8 +232,16 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, Fl::add_timeout(0, handleStatsTimeout, this); } - // Show hint about menu key - Fl::add_timeout(0.5, menuOverlay, this); + // Show hint about menu shortcut + unsigned modifierMask; + + modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + if (modifierMask) + addOverlayTip(_("Press %sM to open the context menu"), + ShortcutHandler::modifierPrefix(modifierMask)); // By default we get a slight delay when we warp the pointer, something // we don't want or we'll get jerky movement @@ -251,17 +262,18 @@ DesktopWindow::~DesktopWindow() // Unregister all timeouts in case they get a change tro trigger // again later when this object is already gone. - Fl::remove_timeout(handleGrab, this); Fl::remove_timeout(handleResizeTimeout, this); Fl::remove_timeout(handleFullscreenTimeout, this); Fl::remove_timeout(handleEdgeScroll, this); Fl::remove_timeout(handleStatsTimeout, this); - Fl::remove_timeout(menuOverlay, this); Fl::remove_timeout(updateOverlay, this); OptionsDialog::removeCallback(handleOptions); - delete overlay; + while (!overlays.empty()) { + delete overlays.front().surface; + overlays.pop_front(); + } delete offscreen; delete statsGraph; @@ -284,46 +296,50 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF() } -void DesktopWindow::setName(const char *name) +void DesktopWindow::updateCaption() { - char windowNameStr[100]; + const size_t maxLen = 100; + std::string windowName; const char *labelFormat; size_t maxNameSize; - char truncatedName[sizeof(windowNameStr)]; + std::string name; - labelFormat = "%s - TigerVNC"; + // FIXME: All of this consideres bytes, not characters + + if (keyboardGrabbed) + labelFormat = _("%s - TigerVNC (keyboard grabbed)"); + else + labelFormat = _("%s - TigerVNC"); // Ignore the length of '%s' since it is // a format marker which won't take up space - maxNameSize = sizeof(windowNameStr) - 1 - strlen(labelFormat) + 2; - - if (maxNameSize > strlen(name)) { - // Guaranteed to fit, no need to truncate - strcpy(truncatedName, name); - } else if (maxNameSize <= strlen("...")) { - // Even an ellipsis won't fit - truncatedName[0] = '\0'; - } else { - int offset; + maxNameSize = maxLen - strlen(labelFormat) + 2; + + name = cc->server.name(); - // We need to truncate, add an ellipsis - offset = maxNameSize - strlen("..."); - strncpy(truncatedName, name, sizeof(truncatedName)); - strcpy(truncatedName + offset, "..."); + if (name.size() > maxNameSize) { + if (maxNameSize <= strlen("...")) { + // Even an ellipsis won't fit + name.clear(); + } + else { + int offset; + + // We need to truncate, add an ellipsis + offset = maxNameSize - strlen("..."); + name.resize(offset); + name += "..."; + } } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" - if (snprintf(windowNameStr, sizeof(windowNameStr), labelFormat, - truncatedName) >= (int)sizeof(windowNameStr)) { - // This is just to shut up the compiler, as we've already made sure - // we won't truncate anything - } + windowName = core::format(labelFormat, name.c_str()); #pragma GCC diagnostic pop - copy_label(windowNameStr); + copy_label(windowName.c_str()); } @@ -410,11 +426,9 @@ void DesktopWindow::setDesktopSizeDone(unsigned result) } -void DesktopWindow::setCursor(int width, int height, - const core::Point& hotspot, - const uint8_t* data) +void DesktopWindow::setCursor() { - viewport->setCursor(width, height, hotspot, data); + viewport->setCursor(); } @@ -536,9 +550,12 @@ void DesktopWindow::draw() } // Overlay (if active) - if (overlay) { + if (!overlays.empty()) { int ox, oy, ow, oh; int sx, sy, sw, sh; + struct Overlay overlay; + + overlay = overlays.front(); // Make sure it's properly seen by adjusting it relative to the // primary screen rather than the entire window @@ -576,18 +593,20 @@ void DesktopWindow::draw() sw = w(); } - ox = X = sx + (sw - overlay->width()) / 2; + ox = X = sx + (sw - overlay.surface->width()) / 2; oy = Y = sy + 50; - ow = overlay->width(); - oh = overlay->height(); + ow = overlay.surface->width(); + oh = overlay.surface->height(); fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh); if ((ow != 0) && (oh != 0)) { if (offscreen) - overlay->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha); + overlay.surface->blend(offscreen, ox - X, oy - Y, + ox, oy, ow, oh, overlay.alpha); else - overlay->blend(ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha); + overlay.surface->blend(ox - X, oy - Y, + ox, oy, ow, oh, overlay.alpha); } } @@ -701,34 +720,55 @@ void DesktopWindow::resize(int x, int y, int w, int h) repositionWidgets(); } - - // Some systems require a grab after the window size has been changed. - // Otherwise they might hold on to displays, resulting in them being unusable. - maybeGrabKeyboard(); } - -void DesktopWindow::menuOverlay(void* data) +void DesktopWindow::addOverlayTip(const char* text, ...) { - DesktopWindow *self; + va_list ap; + char textbuf[1024]; - self = (DesktopWindow*)data; + std::map<std::string, time_t>::iterator iter; + + va_start(ap, text); + vsnprintf(textbuf, sizeof(textbuf), text, ap); + textbuf[sizeof(textbuf)-1] = '\0'; + va_end(ap); - // Empty string means None, for backward compatibility - if ((menuKey != "") && (menuKey != "None")) { - self->setOverlay(_("Press %s to open the context menu"), - menuKey.getValueStr().c_str()); + // Purge all old entries + for (iter = overlayTimes.begin(); iter != overlayTimes.end(); ) { + if ((time(nullptr) - iter->second) >= OVERLAY_REPEAT_TIMEOUT) + overlayTimes.erase(iter++); + else + iter++; } + + // Recently shown? + if (overlayTimes.count(textbuf) > 0) + return; + + overlayTimes[textbuf] = time(nullptr); + + addOverlay(textbuf); } -void DesktopWindow::setOverlay(const char* text, ...) +void DesktopWindow::addOverlayError(const char* text, ...) { - const Fl_Fontsize fontsize = 16; - const int margin = 10; - va_list ap; char textbuf[1024]; + va_start(ap, text); + vsnprintf(textbuf, sizeof(textbuf), text, ap); + textbuf[sizeof(textbuf)-1] = '\0'; + va_end(ap); + + addOverlay(textbuf); +} + +void DesktopWindow::addOverlay(const char *text) +{ + const Fl_Fontsize fontsize = 16; + const int margin = 10; + Fl_Image_Surface *surface; Fl_RGB_Image* imageText; @@ -742,13 +782,7 @@ void DesktopWindow::setOverlay(const char* text, ...) unsigned char* a; const unsigned char* b; - delete overlay; - Fl::remove_timeout(updateOverlay, this); - - va_start(ap, text); - vsnprintf(textbuf, sizeof(textbuf), text, ap); - textbuf[sizeof(textbuf)-1] = '\0'; - va_end(ap); + struct Overlay overlay; #if !defined(WIN32) && !defined(__APPLE__) // FLTK < 1.3.5 crashes if fl_gc is unset @@ -758,7 +792,7 @@ void DesktopWindow::setOverlay(const char* text, ...) fl_font(FL_HELVETICA, fontsize); w = 0; - fl_measure(textbuf, w, h); + fl_measure(text, w, h); // Margins w += margin * 2 * 2; @@ -771,7 +805,7 @@ void DesktopWindow::setOverlay(const char* text, ...) fl_font(FL_HELVETICA, fontsize); fl_color(FL_WHITE); - fl_draw(textbuf, 0, 0, w, h, FL_ALIGN_CENTER); + fl_draw(text, 0, 0, w, h, FL_ALIGN_CENTER); imageText = surface->image(); delete surface; @@ -807,39 +841,53 @@ void DesktopWindow::setOverlay(const char* text, ...) delete imageText; - overlay = new Surface(image); - overlayAlpha = 0; - gettimeofday(&overlayStart, nullptr); + overlay.surface = new Surface(image); + overlay.alpha = 0; + memset(&overlay.start, 0, sizeof(overlay.start)); + overlays.push_back(overlay); delete image; delete [] buffer; - Fl::add_timeout(1.0/60, updateOverlay, this); + if (overlays.size() == 1) + Fl::add_timeout(0.5, updateOverlay, this); } void DesktopWindow::updateOverlay(void *data) { DesktopWindow *self; + struct Overlay* overlay; unsigned elapsed; self = (DesktopWindow*)data; - elapsed = core::msSince(&self->overlayStart); + if (self->overlays.empty()) + return; + + overlay = &self->overlays.front(); + + if (overlay->start.tv_sec == 0) + gettimeofday(&overlay->start, nullptr); + + elapsed = core::msSince(&overlay->start); if (elapsed < 500) { - self->overlayAlpha = (unsigned)255 * elapsed / 500; + overlay->alpha = (unsigned)255 * elapsed / 500; Fl::add_timeout(1.0/60, updateOverlay, self); } else if (elapsed < 3500) { - self->overlayAlpha = 255; + overlay->alpha = 255; Fl::add_timeout(3.0, updateOverlay, self); } else if (elapsed < 4000) { - self->overlayAlpha = (unsigned)255 * (4000 - elapsed) / 500; + overlay->alpha = (unsigned)255 * (4000 - elapsed) / 500; Fl::add_timeout(1.0/60, updateOverlay, self); } else { - delete self->overlay; - self->overlay = nullptr; + delete overlay->surface; + self->overlays.pop_front(); + if (!self->overlays.empty()) + Fl::add_timeout(0.5, updateOverlay, self); } + // FIXME: Only damage relevant area self->damage(FL_DAMAGE_USER1); } @@ -853,10 +901,36 @@ int DesktopWindow::handle(int event) // Update scroll bars repositionWidgets(); - if (fullscreen_active()) - maybeGrabKeyboard(); - else - ungrabKeyboard(); + // Show how to get out of full screen + if (fullscreen_active()) { + unsigned modifierMask; + + modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + if (modifierMask) + addOverlayTip(_("Press %sEnter to leave full-screen mode"), + ShortcutHandler::modifierPrefix(modifierMask)); + } + +#ifdef __APPLE__ + // Complain to the user if we won't have permission to grab keyboard + if (fullscreenSystemKeys && fullscreen_active()) { + // FIXME: There is some race during initial full screen where we + // fail to give focus to the popup, but we can work around + // it using a timer + Fl::add_timeout(0, [](void*) { cocoa_is_trusted(true); }, nullptr); + } +#endif + + // Automatically toggle keyboard grab? + if (fullscreenSystemKeys) { + if (fullscreen_active()) + grabKeyboard(); + else + ungrabKeyboard(); + } // The window manager respected our full screen request, so stop // waiting and delaying the session resize @@ -925,6 +999,17 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win) if ((event == FL_MOVE) && (win == nullptr)) return 0; +#if !defined(WIN32) && !defined(__APPLE__) + // FLTK passes through the fake grab focus events that can cause us + // to end up in an infinite loop + // https://github.com/fltk/fltk/issues/295 + if ((event == FL_FOCUS) || (event == FL_UNFOCUS)) { + const XFocusChangeEvent* xfocus = &fl_xevent->xfocus; + if ((xfocus->mode == NotifyGrab) || (xfocus->mode == NotifyUngrab)) + return 0; + } +#endif + ret = Fl::handle_(event, win); // This is hackish and the result of the dodgy focus handling in FLTK. @@ -937,16 +1022,21 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win) if (dw) { switch (event) { // Focus might not stay with us just because we have grabbed the - // keyboard. E.g. we might have sub windows, or we're not using - // all monitors and the user clicked on another application. - // Make sure we update our grabs with the focus changes. + // keyboard. E.g. we might have sub windows, or the user clicked on + // another application. Make sure we update our grabs with the focus + // changes. case FL_FOCUS: - dw->maybeGrabKeyboard(); + if (dw->regrabOnFocus || + (fullscreenSystemKeys && dw->fullscreen_active())) + dw->grabKeyboard(); + dw->regrabOnFocus = false; break; case FL_UNFOCUS: - if (fullscreenSystemKeys) { - dw->ungrabKeyboard(); - } + // If the grab is active when we lose focus, the user likely wants + // the grab to remain once we regain focus + if (dw->keyboardGrabbed) + dw->regrabOnFocus = true; + dw->ungrabKeyboard(); break; case FL_SHOW: @@ -987,14 +1077,6 @@ int DesktopWindow::fltkHandle(int event) // not be resized to cover the new screen. A timer makes sense // also on other systems, to make sure that whatever desktop // environment has a chance to deal with things before we do. - // Please note that when using FullscreenSystemKeys on macOS, the - // display configuration cannot be changed: macOS will not detect - // added or removed screens and there will be no - // FL_SCREEN_CONFIGURATION_CHANGED event. This is by design: - // "When you capture a display, you have exclusive use of the - // display. Other applications and system services are not allowed - // to use the display or change its configuration. In addition, - // they are not notified of display changes" Fl::remove_timeout(reconfigureFullscreen); Fl::add_timeout(0.5, reconfigureFullscreen); } @@ -1070,43 +1152,13 @@ void DesktopWindow::fullscreen_on() } } -#ifdef __APPLE__ - // This is a workaround for a bug in FLTK, see: https://github.com/fltk/fltk/pull/277 - int savedLevel = -1; - if (shown()) - savedLevel = cocoa_get_level(this); -#endif + fullscreen_screens(top, bottom, left, right); -#ifdef __APPLE__ - // This is a workaround for a bug in FLTK, see: https://github.com/fltk/fltk/pull/277 - if (savedLevel != -1) { - if (cocoa_get_level(this) != savedLevel) - cocoa_set_level(this, savedLevel); - } -#endif if (!fullscreen_active()) fullscreen(); } -#if !defined(WIN32) && !defined(__APPLE__) -Bool eventIsFocusWithSerial(Display* /*display*/, XEvent *event, - XPointer arg) -{ - unsigned long serial; - - serial = *(unsigned long*)arg; - - if (event->xany.serial != serial) - return False; - - if ((event->type != FocusIn) && (event->type != FocusOut)) - return False; - - return True; -} -#endif - bool DesktopWindow::hasFocus() { Fl_Widget* focus; @@ -1121,66 +1173,66 @@ bool DesktopWindow::hasFocus() return focus->window() == this; } -void DesktopWindow::maybeGrabKeyboard() -{ - if (fullscreenSystemKeys && fullscreen_active() && hasFocus()) - grabKeyboard(); -} - void DesktopWindow::grabKeyboard() { + unsigned modifierMask; + // Grabbing the keyboard is fairly safe as FLTK reroutes events to the // correct widget regardless of which low level window got the system // event. // FIXME: Push this stuff into FLTK. + if (keyboardGrabbed) + return; + + if (!hasFocus()) + return; + #if defined(WIN32) int ret; ret = win32_enable_lowlevel_keyboard(fl_xid(this)); if (ret != 0) { - vlog.error(_("Failure grabbing keyboard")); + vlog.error(_("Failure grabbing control of the keyboard")); + addOverlayError(_("Failure grabbing control of the keyboard")); return; } #elif defined(__APPLE__) - int ret; - - ret = cocoa_capture_displays(this); - if (ret != 0) { - vlog.error(_("Failure grabbing keyboard")); + bool ret; + + ret = cocoa_tap_keyboard(); + if (!ret) { + vlog.error(_("Failure grabbing control of the keyboard")); + addOverlayError(_("Failure grabbing control of the keyboard")); return; } #else int ret; - XEvent xev; - unsigned long serial; - - serial = XNextRequest(fl_display); - ret = XGrabKeyboard(fl_display, fl_xid(this), True, GrabModeAsync, GrabModeAsync, CurrentTime); if (ret) { if (ret == AlreadyGrabbed) { - // It seems like we can race with the WM in some cases. - // Try again in a bit. - if (!Fl::has_timeout(handleGrab, this)) - Fl::add_timeout(0.500, handleGrab, this); - } else { - vlog.error(_("Failure grabbing keyboard")); + // It seems like we can race with the WM in some cases, e.g. when + // the WM holds the keyboard as part of handling Alt+Tab. + // Repeat the request a few times and see if we get it... + for (int attempt = 0; attempt < 5; attempt++) { + usleep(100000); + // Also throttle based on how busy the X server is + XSync(fl_display, False); + ret = XGrabKeyboard(fl_display, fl_xid(this), True, + GrabModeAsync, GrabModeAsync, CurrentTime); + if (ret != AlreadyGrabbed) + break; + } } - return; - } - // Xorg 1.20+ generates FocusIn/FocusOut even when there is no actual - // change of focus. This causes us to get stuck in an endless loop - // grabbing and ungrabbing the keyboard. Avoid this by filtering out - // any focus events generated by XGrabKeyboard(). - XSync(fl_display, False); - while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial, - (XPointer)&serial) == True) { - vlog.debug("Ignored synthetic focus event cause by grab change"); + if (ret) { + vlog.error(_("Failure grabbing control of the keyboard")); + addOverlayError(_("Failure grabbing control of the keyboard")); + return; + } } #endif @@ -1188,39 +1240,37 @@ void DesktopWindow::grabKeyboard() if (contains(Fl::belowmouse())) grabPointer(); + + updateCaption(); + + modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + if (modifierMask) + addOverlayTip(_("Press %s to release keyboard control from the session"), + ShortcutHandler::modifierPrefix(modifierMask, true)); } void DesktopWindow::ungrabKeyboard() { - Fl::remove_timeout(handleGrab, this); - keyboardGrabbed = false; ungrabPointer(); + updateCaption(); + #if defined(WIN32) win32_disable_lowlevel_keyboard(fl_xid(this)); #elif defined(__APPLE__) - cocoa_release_displays(this); + cocoa_untap_keyboard(); #else // FLTK has a grab so lets not mess with it if (Fl::grab()) return; - XEvent xev; - unsigned long serial; - - serial = XNextRequest(fl_display); - XUngrabKeyboard(fl_display, CurrentTime); - - // See grabKeyboard() - XSync(fl_display, False); - while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial, - (XPointer)&serial) == True) { - vlog.debug("Ignored synthetic focus event cause by grab change"); - } #endif } @@ -1251,16 +1301,6 @@ void DesktopWindow::ungrabPointer() } -void DesktopWindow::handleGrab(void *data) -{ - DesktopWindow *self = (DesktopWindow*)data; - - assert(self); - - self->maybeGrabKeyboard(); -} - - #define _NET_WM_STATE_ADD 1 /* add/set property */ void DesktopWindow::maximizeWindow() { @@ -1584,11 +1624,6 @@ void DesktopWindow::handleOptions(void *data) { DesktopWindow *self = (DesktopWindow*)data; - if (fullscreenSystemKeys) - self->maybeGrabKeyboard(); - else - self->ungrabKeyboard(); - // Call fullscreen_on even if active since it handles // fullScreenMode if (fullScreen) diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 69729ff8..ca4cf53a 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB + * Copyright 2011-2025 Pierre Ossman <ossman@cendio.se> for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,9 @@ #ifndef __DESKTOPWINDOW_H__ #define __DESKTOPWINDOW_H__ +#include <list> #include <map> +#include <string> #include <sys/time.h> @@ -37,8 +39,7 @@ class Fl_Scrollbar; class DesktopWindow : public Fl_Window { public: - DesktopWindow(int w, int h, const char *name, - const rfb::PixelFormat& serverPF, CConn* cc_); + DesktopWindow(int w, int h, CConn* cc_); ~DesktopWindow(); // Most efficient format (from DesktopWindow's point of view) @@ -48,7 +49,7 @@ public: void updateWindow(); // Updated session title - void setName(const char *name); + void updateCaption(); // Resize the current framebuffer, but retain the contents void resizeFramebuffer(int new_w, int new_h); @@ -57,8 +58,7 @@ public: void setDesktopSizeDone(unsigned result); // New image for the locally rendered cursor - void setCursor(int width, int height, const core::Point& hotspot, - const uint8_t* data); + void setCursor(); // Server-provided cursor position void setCursorPos(const core::Point& pos); @@ -80,11 +80,16 @@ public: void fullscreen_on(); -private: - static void menuOverlay(void *data); + // Grab keyboard events from desktop environment + void grabKeyboard(); + void ungrabKeyboard(); - void setOverlay(const char *text, ...) +private: + void addOverlayTip(const char *text, ...) + __attribute__((__format__ (__printf__, 2, 3))); + void addOverlayError(const char *text, ...) __attribute__((__format__ (__printf__, 2, 3))); + void addOverlay(const char *text); static void updateOverlay(void *data); static int fltkDispatch(int event, Fl_Window *win); @@ -92,14 +97,9 @@ private: bool hasFocus(); - void maybeGrabKeyboard(); - void grabKeyboard(); - void ungrabKeyboard(); void grabPointer(); void ungrabPointer(); - static void handleGrab(void *data); - void maximizeWindow(); static void handleResizeTimeout(void *data); @@ -125,9 +125,15 @@ private: Fl_Scrollbar *hscroll, *vscroll; Viewport *viewport; Surface *offscreen; - Surface *overlay; - unsigned char overlayAlpha; - struct timeval overlayStart; + + struct Overlay { + Surface *surface; + unsigned char alpha; + struct timeval start; + }; + + std::list<Overlay> overlays; + std::map<std::string, time_t> overlayTimes; bool firstUpdate; bool delayedFullscreen; @@ -139,6 +145,8 @@ private: bool keyboardGrabbed; bool mouseGrabbed; + bool regrabOnFocus; + struct statsEntry { unsigned ups; unsigned pps; diff --git a/vncviewer/Keyboard.h b/vncviewer/Keyboard.h index 81360252..aeab4e71 100644 --- a/vncviewer/Keyboard.h +++ b/vncviewer/Keyboard.h @@ -21,6 +21,8 @@ #include <stdint.h> +#include <list> + class KeyboardHandler { public: @@ -35,7 +37,10 @@ public: Keyboard(KeyboardHandler* handler_) : handler(handler_) {}; virtual ~Keyboard() {}; + virtual bool isKeyboardReset(const void* event) { (void)event; return false; } + virtual bool handleEvent(const void* event) = 0; + virtual std::list<uint32_t> translateToKeySyms(int systemKeyCode) = 0; virtual void reset() {}; diff --git a/vncviewer/KeyboardMacOS.h b/vncviewer/KeyboardMacOS.h index 0901664b..033c8539 100644 --- a/vncviewer/KeyboardMacOS.h +++ b/vncviewer/KeyboardMacOS.h @@ -23,10 +23,8 @@ #ifdef __OBJC__ @class NSEvent; -@class NSString; #else class NSEvent; -class NSString; #endif class KeyboardMacOS : public Keyboard @@ -35,22 +33,21 @@ public: KeyboardMacOS(KeyboardHandler* handler); virtual ~KeyboardMacOS(); + bool isKeyboardReset(const void* event) override; + bool handleEvent(const void* event) override; + std::list<uint32_t> translateToKeySyms(int systemKeyCode) override; unsigned getLEDState() override; void setLEDState(unsigned state) override; - // Special helper on macOS - static bool isKeyboardSync(const void* event); - protected: bool isKeyboardEvent(const NSEvent* nsevent); bool isKeyPress(const NSEvent* nsevent); uint32_t translateSystemKeyCode(int systemKeyCode); unsigned getSystemKeyCode(const NSEvent* nsevent); - NSString* keyTranslate(unsigned keyCode, unsigned modifierFlags); - uint32_t translateEventKeysym(const NSEvent* nsevent); + uint32_t translateToKeySym(unsigned keyCode, unsigned modifierFlags); int openHID(unsigned int* ioc); int getModifierLockState(int modifier, bool* on); diff --git a/vncviewer/KeyboardMacOS.mm b/vncviewer/KeyboardMacOS.mm index 9ef2fc93..599612ec 100644 --- a/vncviewer/KeyboardMacOS.mm +++ b/vncviewer/KeyboardMacOS.mm @@ -22,6 +22,8 @@ #include <assert.h> +#include <algorithm> + #import <Cocoa/Cocoa.h> #import <Carbon/Carbon.h> @@ -54,7 +56,7 @@ extern const unsigned int code_map_osx_to_qnum_len; static core::LogWriter vlog("KeyboardMacOS"); -static const int kvk_map[][2] = { +static const unsigned kvk_map[][2] = { { kVK_Return, XK_Return }, { kVK_Tab, XK_Tab }, { kVK_Space, XK_space }, @@ -140,6 +142,25 @@ KeyboardMacOS::~KeyboardMacOS() { } +bool KeyboardMacOS::isKeyboardReset(const void* event) +{ + const NSEvent* nsevent = (const NSEvent*)event; + + assert(event); + + // If we get a NSFlagsChanged event with key code 0 then this isn't + // an actual keyboard event but rather the system trying to sync up + // modifier state after it has stolen input for some reason (e.g. + // Cmd+Tab) + + if ([nsevent type] != NSFlagsChanged) + return false; + if ([nsevent keyCode] != 0) + return false; + + return true; +} + bool KeyboardMacOS::handleEvent(const void* event) { const NSEvent* nsevent = (NSEvent*)event; @@ -155,10 +176,25 @@ bool KeyboardMacOS::handleEvent(const void* event) if (isKeyPress(nsevent)) { uint32_t keyCode; uint32_t keySym; + unsigned modifiers; keyCode = translateSystemKeyCode(systemKeyCode); - keySym = translateEventKeysym(nsevent); + // We want a "normal" symbol out of the event, which basically means + // we only respect the shift and alt/altgr modifiers. Cocoa can help + // us if we only wanted shift, but as we also want alt/altgr, we'll + // have to do some lookup ourselves. This matches our behaviour on + // other platforms. + + modifiers = 0; + if ([nsevent modifierFlags] & NSAlphaShiftKeyMask) + modifiers |= alphaLock; + if ([nsevent modifierFlags] & NSShiftKeyMask) + modifiers |= shiftKey; + if ([nsevent modifierFlags] & NSAlternateKeyMask) + modifiers |= optionKey; + + keySym = translateToKeySym([nsevent keyCode], modifiers); if (keySym == NoSymbol) { vlog.error(_("No symbol for key code 0x%02x (in the current state)"), systemKeyCode); @@ -177,6 +213,51 @@ bool KeyboardMacOS::handleEvent(const void* event) return true; } +std::list<uint32_t> KeyboardMacOS::translateToKeySyms(int systemKeyCode) +{ + std::list<uint32_t> keySyms; + unsigned mods; + + uint32_t ks; + + // Start with no modifiers + ks = translateToKeySym(systemKeyCode, 0); + if (ks != NoSymbol) + keySyms.push_back(ks); + + // Next just a single modifier at a time + for (mods = cmdKey; mods <= controlKey; mods <<= 1) { + std::list<uint32_t>::const_iterator iter; + + ks = translateToKeySym(systemKeyCode, mods); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + // Finally everything + for (mods = cmdKey; mods < (controlKey << 1); mods += cmdKey) { + std::list<uint32_t>::const_iterator iter; + + ks = translateToKeySym(systemKeyCode, mods); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + return keySyms; +} + unsigned KeyboardMacOS::getLEDState() { unsigned state; @@ -225,25 +306,6 @@ void KeyboardMacOS::setLEDState(unsigned state) // No support for Scroll Lock // } -bool KeyboardMacOS::isKeyboardSync(const void* event) -{ - const NSEvent* nsevent = (const NSEvent*)event; - - assert(event); - - // If we get a NSFlagsChanged event with key code 0 then this isn't - // an actual keyboard event but rather the system trying to sync up - // modifier state after it has stolen input for some reason (e.g. - // Cmd+Tab) - - if ([nsevent type] != NSFlagsChanged) - return false; - if ([nsevent keyCode] != 0) - return false; - - return true; -} - bool KeyboardMacOS::isKeyboardEvent(const NSEvent* nsevent) { switch ([nsevent type]) { @@ -251,7 +313,7 @@ bool KeyboardMacOS::isKeyboardEvent(const NSEvent* nsevent) case NSKeyUp: return true; case NSFlagsChanged: - if (isKeyboardSync(nsevent)) + if (isKeyboardReset(nsevent)) return false; return true; default: @@ -339,30 +401,35 @@ uint32_t KeyboardMacOS::translateSystemKeyCode(int systemKeyCode) return code_map_osx_to_qnum[systemKeyCode]; } -NSString* KeyboardMacOS::keyTranslate(unsigned keyCode, - unsigned modifierFlags) +uint32_t KeyboardMacOS::translateToKeySym(unsigned keyCode, + unsigned modifierFlags) { const UCKeyboardLayout *layout; OSStatus err; - layout = nullptr; - TISInputSourceRef keyboard; CFDataRef uchr; + UInt32 dead_state; + UniCharCount max_len, actual_len; + UniChar string[255]; + + // Start with keys that either don't generate a symbol, or + // generate the same symbol as some other key. + for (size_t i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) { + if (keyCode == kvk_map[i][0]) + return kvk_map[i][1]; + } + keyboard = TISCopyCurrentKeyboardLayoutInputSource(); uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); if (uchr == nullptr) - return nil; + return NoSymbol; layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); if (layout == nullptr) - return nil; - - UInt32 dead_state; - UniCharCount max_len, actual_len; - UniChar string[255]; + return NoSymbol; dead_state = 0; max_len = sizeof(string)/sizeof(*string); @@ -373,10 +440,12 @@ NSString* KeyboardMacOS::keyTranslate(unsigned keyCode, LMGetKbdType(), 0, &dead_state, max_len, &actual_len, string); if (err != noErr) - return nil; + return NoSymbol; // Dead key? if (dead_state != 0) { + unsigned combining; + // We have no fool proof way of asking what dead key this is. // Assume we get a spacing equivalent if we press the // same key again, and try to deduce something from that. @@ -384,34 +453,28 @@ NSString* KeyboardMacOS::keyTranslate(unsigned keyCode, LMGetKbdType(), 0, &dead_state, max_len, &actual_len, string); if (err != noErr) - return nil; - } - - return [NSString stringWithCharacters:string length:actual_len]; -} - -uint32_t KeyboardMacOS::translateEventKeysym(const NSEvent* nsevent) -{ - UInt16 key_code; - size_t i; + return NoSymbol; - NSString *chars; - UInt32 modifiers; + // FIXME: Some dead keys are given as NBSP + combining character + if (actual_len != 1) + return NoSymbol; - key_code = [nsevent keyCode]; + combining = ucs2combining(string[0]); + if (combining == (unsigned)-1) + return NoSymbol; - // Start with keys that either don't generate a symbol, or - // generate the same symbol as some other key. - for (i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) { - if (key_code == kvk_map[i][0]) - return kvk_map[i][1]; + return ucs2keysym(combining); } + // Sanity check + if (actual_len != 1) + return NoSymbol; + // OS X always sends the same key code for the decimal key on the // num pad, but X11 wants different keysyms depending on if it should // be a comma or full stop. - if (key_code == 0x41) { - switch ([[nsevent charactersIgnoringModifiers] UTF8String][0]) { + if (keyCode == 0x41) { + switch (string[0]) { case ',': return XK_KP_Separator; case '.': @@ -421,33 +484,7 @@ uint32_t KeyboardMacOS::translateEventKeysym(const NSEvent* nsevent) } } - // We want a "normal" symbol out of the event, which basically means - // we only respect the shift and alt/altgr modifiers. Cocoa can help - // us if we only wanted shift, but as we also want alt/altgr, we'll - // have to do some lookup ourselves. This matches our behaviour on - // other platforms. - - modifiers = 0; - if ([nsevent modifierFlags] & NSAlphaShiftKeyMask) - modifiers |= alphaLock; - if ([nsevent modifierFlags] & NSShiftKeyMask) - modifiers |= shiftKey; - if ([nsevent modifierFlags] & NSAlternateKeyMask) - modifiers |= optionKey; - - chars = keyTranslate(key_code, modifiers); - if (chars == nil) - return NoSymbol; - - // FIXME: Some dead keys are given as NBSP + combining character - if ([chars length] != 1) - return NoSymbol; - - // Dead key? - if ([[nsevent characters] length] == 0) - return ucs2keysym(ucs2combining([chars characterAtIndex:0])); - - return ucs2keysym([chars characterAtIndex:0]); + return ucs2keysym(string[0]); } int KeyboardMacOS::openHID(unsigned int* ioc) diff --git a/vncviewer/KeyboardWin32.cxx b/vncviewer/KeyboardWin32.cxx index 76286217..095927f1 100644 --- a/vncviewer/KeyboardWin32.cxx +++ b/vncviewer/KeyboardWin32.cxx @@ -24,6 +24,8 @@ #include <assert.h> +#include <algorithm> + // Missing in at least some versions of MinGW #ifndef MAPVK_VK_TO_CHAR #define MAPVK_VK_TO_CHAR 2 @@ -139,6 +141,8 @@ static const UINT vkey_map[][3] = { { VK_MEDIA_STOP, NoSymbol, XF86XK_AudioStop }, { VK_MEDIA_PLAY_PAUSE, NoSymbol, XF86XK_AudioPlay }, { VK_LAUNCH_MAIL, NoSymbol, XF86XK_Mail }, + { VK_LAUNCH_MEDIA_SELECT, NoSymbol, XF86XK_AudioMedia }, + { VK_LAUNCH_APP1, NoSymbol, XF86XK_MyComputer }, { VK_LAUNCH_APP2, NoSymbol, XF86XK_Calculator }, }; @@ -203,6 +207,7 @@ bool KeyboardWin32::handleEvent(const void* event) bool isExtended; int systemKeyCode, keyCode; uint32_t keySym; + BYTE state[256]; vKey = msg->wParam; isExtended = (msg->lParam & (1 << 24)) != 0; @@ -257,7 +262,23 @@ bool KeyboardWin32::handleEvent(const void* event) keyCode = translateSystemKeyCode(systemKeyCode); - keySym = translateVKey(vKey, isExtended); + GetKeyboardState(state); + + // Pressing Ctrl wreaks havoc with the symbol lookup, so turn + // that off. But AltGr shows up as Ctrl+Alt in Windows, so keep + // Ctrl if Alt is active. + if (!(state[VK_LCONTROL] & 0x80) || !(state[VK_RMENU] & 0x80)) + state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; + + keySym = translateVKey(vKey, isExtended, state); + + if (keySym == NoSymbol) { + // Most Ctrl+Alt combinations will fail to produce a symbol, so + // try it again with Ctrl unconditionally disabled. + state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; + keySym = translateVKey(vKey, isExtended, state); + } + if (keySym == NoSymbol) { if (isExtended) vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey); @@ -355,6 +376,114 @@ bool KeyboardWin32::handleEvent(const void* event) return false; } +std::list<uint32_t> KeyboardWin32::translateToKeySyms(int systemKeyCode) +{ + unsigned vkey; + bool extended; + + std::list<uint32_t> keySyms; + unsigned mods; + + BYTE state[256]; + + uint32_t ks; + + UINT ch; + + extended = systemKeyCode & 0x80; + if (extended) + systemKeyCode = 0xe0 | (systemKeyCode & 0x7f); + + vkey = MapVirtualKey(systemKeyCode, MAPVK_VSC_TO_VK_EX); + if (vkey == 0) + return keySyms; + + // Start with no modifiers + memset(state, 0, sizeof(state)); + ks = translateVKey(vkey, extended, state); + if (ks != NoSymbol) + keySyms.push_back(ks); + + // Next just a single modifier at a time + for (mods = 1; mods < 16; mods <<= 1) { + std::list<uint32_t>::const_iterator iter; + + memset(state, 0, sizeof(state)); + if (mods & 0x1) + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + if (mods & 0x2) + state[VK_SHIFT] = state[VK_LSHIFT] = 0x80; + if (mods & 0x4) + state[VK_MENU] = state[VK_LMENU] = 0x80; + if (mods & 0x8) { + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + state[VK_MENU] = state[VK_RMENU] = 0x80; + } + + ks = translateVKey(vkey, extended, state); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + // Finally everything + for (mods = 0; mods < 16; mods++) { + std::list<uint32_t>::const_iterator iter; + + memset(state, 0, sizeof(state)); + if (mods & 0x1) + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + if (mods & 0x2) + state[VK_SHIFT] = state[VK_LSHIFT] = 0x80; + if (mods & 0x4) + state[VK_MENU] = state[VK_LMENU] = 0x80; + if (mods & 0x8) { + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + state[VK_MENU] = state[VK_RMENU] = 0x80; + } + + ks = translateVKey(vkey, extended, state); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + // As a final resort we use MapVirtualKey() as that gives us a Latin + // character even on non-Latin keyboards, which is useful for + // shortcuts + // + // FIXME: Can this give us anything but ASCII? + + ch = MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR); + if (ch != 0) { + if (ch & 0x80000000) + ch = ucs2combining(ch & 0xffff); + else + ch = ch & 0xffff; + + ks = ucs2keysym(ch); + if (ks != NoSymbol) { + std::list<uint32_t>::const_iterator iter; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter == keySyms.end()) + keySyms.push_back(ks); + } + } + + return keySyms; +} + void KeyboardWin32::reset() { altGrArmed = false; @@ -466,12 +595,12 @@ uint32_t KeyboardWin32::lookupVKeyMap(unsigned vkey, bool extended, return NoSymbol; } -uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended) +uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended, + const unsigned char state[256]) { HKL layout; WORD lang, primary_lang; - BYTE state[256]; int ret; WCHAR wstr[10]; @@ -525,25 +654,10 @@ uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended) // does what we want though. Unfortunately it keeps state, so // we have to be careful around dead characters. - GetKeyboardState(state); - - // Pressing Ctrl wreaks havoc with the symbol lookup, so turn - // that off. But AltGr shows up as Ctrl+Alt in Windows, so keep - // Ctrl if Alt is active. - if (!(state[VK_LCONTROL] & 0x80) || !(state[VK_RMENU] & 0x80)) - state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; - // FIXME: Multi character results, like U+0644 U+0627 // on Arabic layout ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); - if (ret == 0) { - // Most Ctrl+Alt combinations will fail to produce a symbol, so - // try it again with Ctrl unconditionally disabled. - state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; - ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); - } - if (ret == 1) return ucs2keysym(wstr[0]); diff --git a/vncviewer/KeyboardWin32.h b/vncviewer/KeyboardWin32.h index 336fe6da..ecab9268 100644 --- a/vncviewer/KeyboardWin32.h +++ b/vncviewer/KeyboardWin32.h @@ -28,6 +28,7 @@ public: virtual ~KeyboardWin32(); bool handleEvent(const void* event) override; + std::list<uint32_t> translateToKeySyms(int systemKeyCode) override; void reset() override; @@ -38,7 +39,8 @@ protected: uint32_t translateSystemKeyCode(int systemKeyCode); uint32_t lookupVKeyMap(unsigned vkey, bool extended, const UINT map[][3], size_t size); - uint32_t translateVKey(unsigned vkey, bool extended); + uint32_t translateVKey(unsigned vkey, bool extended, + const unsigned char state[256]); bool hasAltGr(); static void handleAltGrTimeout(void *data); diff --git a/vncviewer/KeyboardX11.cxx b/vncviewer/KeyboardX11.cxx index 973278fc..587d9fc5 100644 --- a/vncviewer/KeyboardX11.cxx +++ b/vncviewer/KeyboardX11.cxx @@ -22,6 +22,7 @@ #include <assert.h> +#include <algorithm> #include <stdexcept> #include <X11/XKBlib.h> @@ -87,6 +88,63 @@ KeyboardX11::~KeyboardX11() { } +struct GrabInfo { + Window window; + bool found; +}; + +static Bool is_same_window(Display*, XEvent* event, XPointer arg) +{ + GrabInfo* info = (GrabInfo*)arg; + + assert(info); + + // Focus is returned to our window + if ((event->type == FocusIn) && + (event->xfocus.window == info->window)) { + info->found = true; + } + + // Focus got stolen yet again + if ((event->type == FocusOut) && + (event->xfocus.window == info->window)) { + info->found = false; + } + + return False; +} + +bool KeyboardX11::isKeyboardReset(const void* event) +{ + const XEvent* xevent = (const XEvent*)event; + + assert(event); + + if (xevent->type == FocusOut) { + if (xevent->xfocus.mode == NotifyGrab) { + GrabInfo info; + XEvent dummy; + + // Something grabbed the keyboard, but we don't know if it was to + // ourselves or someone else + + // Make sure we have all the queued events from the X server + XSync(fl_display, False); + + // Check if we'll get the focus back right away + info.window = xevent->xfocus.window; + info.found = false; + XCheckIfEvent(fl_display, &dummy, is_same_window, (XPointer)&info); + if (info.found) + return false; + + return true; + } + } + + return false; +} + bool KeyboardX11::handleEvent(const void* event) { const XEvent *xevent = (const XEvent*)event; @@ -98,6 +156,10 @@ bool KeyboardX11::handleEvent(const void* event) char str; KeySym keysym; + // FLTK likes to use this instead of CurrentTime, so we need to keep + // it updated now that we steal this event + fl_event_time = xevent->xkey.time; + keycode = code_map_keycode_to_qnum[xevent->xkey.keycode]; XLookupString((XKeyEvent*)&xevent->xkey, &str, 1, &keysym, nullptr); @@ -109,6 +171,7 @@ bool KeyboardX11::handleEvent(const void* event) handler->handleKeyPress(xevent->xkey.keycode, keycode, keysym); return true; } else if (xevent->type == KeyRelease) { + fl_event_time = xevent->xkey.time; handler->handleKeyRelease(xevent->xkey.keycode); return true; } @@ -116,6 +179,31 @@ bool KeyboardX11::handleEvent(const void* event) return false; } +std::list<uint32_t> KeyboardX11::translateToKeySyms(int systemKeyCode) +{ + Status status; + XkbStateRec state; + std::list<uint32_t> keySyms; + unsigned char group; + + status = XkbGetState(fl_display, XkbUseCoreKbd, &state); + if (status != Success) + return keySyms; + + // Start with the currently used group + translateToKeySyms(systemKeyCode, state.group, &keySyms); + + // Then all other groups + for (group = 0; group < XkbNumKbdGroups; group++) { + if (group == state.group) + continue; + + translateToKeySyms(systemKeyCode, group, &keySyms); + } + + return keySyms; +} + unsigned KeyboardX11::getLEDState() { unsigned state; @@ -219,3 +307,40 @@ out: return mask; } + +void KeyboardX11::translateToKeySyms(int systemKeyCode, + unsigned char group, + std::list<uint32_t>* keySyms) +{ + unsigned int mods; + + // Start with no modifiers + translateToKeySyms(systemKeyCode, group, 0, keySyms); + + // Next just a single modifier at a time + for (mods = 1; mods < (Mod5Mask+1); mods <<= 1) + translateToKeySyms(systemKeyCode, group, mods, keySyms); + + // Finally everything + for (mods = 0; mods < (Mod5Mask<<1); mods++) + translateToKeySyms(systemKeyCode, group, mods, keySyms); +} + +void KeyboardX11::translateToKeySyms(int systemKeyCode, + unsigned char group, + unsigned char mods, + std::list<uint32_t>* keySyms) +{ + KeySym ks; + std::list<uint32_t>::const_iterator iter; + + ks = XkbKeycodeToKeysym(fl_display, systemKeyCode, group, mods); + if (ks == NoSymbol) + return; + + iter = std::find(keySyms->begin(), keySyms->end(), ks); + if (iter != keySyms->end()) + return; + + keySyms->push_back(ks); +} diff --git a/vncviewer/KeyboardX11.h b/vncviewer/KeyboardX11.h index ba9a88f9..b3b8d0a0 100644 --- a/vncviewer/KeyboardX11.h +++ b/vncviewer/KeyboardX11.h @@ -27,7 +27,10 @@ public: KeyboardX11(KeyboardHandler* handler); virtual ~KeyboardX11(); + bool isKeyboardReset(const void* event) override; + bool handleEvent(const void* event) override; + std::list<uint32_t> translateToKeySyms(int systemKeyCode) override; unsigned getLEDState() override; void setLEDState(unsigned state) override; @@ -36,6 +39,13 @@ protected: unsigned getModifierMask(uint32_t keysym); private: + void translateToKeySyms(int systemKeyCode, unsigned char group, + std::list<uint32_t>* keySyms); + void translateToKeySyms(int systemKeyCode, + unsigned char group, unsigned char mods, + std::list<uint32_t>* keySyms); + +private: int code_map_keycode_to_qnum[256]; }; diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index 9ff3285c..3ba6fba1 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -1,4 +1,4 @@ -/* Copyright 2011-2021 Pierre Ossman <ossman@cendio.se> for Cendio AB +/* Copyright 2011-2025 Pierre Ossman <ossman@cendio.se> for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include <stdlib.h> #include <list> +#include <core/string.h> + #include <rfb/encodings.h> #if defined(HAVE_GNUTLS) || defined(HAVE_NETTLE) @@ -35,8 +37,8 @@ #endif #include "OptionsDialog.h" +#include "ShortcutHandler.h" #include "i18n.h" -#include "menukey.h" #include "parameters.h" #include "fltk/layout.h" @@ -44,12 +46,18 @@ #include "fltk/Fl_Monitor_Arrangement.h" #include "fltk/Fl_Navigation.h" +#ifdef __APPLE__ +#include "cocoa.h" +#endif + #include <FL/Fl.H> +#include <FL/Fl_Box.H> #include <FL/Fl_Tabs.H> #include <FL/Fl_Button.H> #include <FL/Fl_Check_Button.H> #include <FL/Fl_Return_Button.H> #include <FL/Fl_Round_Button.H> +#include <FL/Fl_Toggle_Button.H> #include <FL/Fl_Int_Input.H> #include <FL/Fl_Choice.H> @@ -82,6 +90,7 @@ OptionsDialog::OptionsDialog() createCompressionPage(tx, ty, tw, th); createSecurityPage(tx, ty, tw, th); createInputPage(tx, ty, tw, th); + createShortcutsPage(tx, ty, tw, th); createDisplayPage(tx, ty, tw, th); createMiscPage(tx, ty, tw, th); } @@ -311,11 +320,19 @@ void OptionsDialog::loadOptions(void) #endif systemKeysCheckbox->value(fullscreenSystemKeys); - menuKeyChoice->value(0); + /* Keyboard shortcuts */ + unsigned modifierMask; + + modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); - for (int idx = 0; idx < getMenuKeySymbolCount(); idx++) - if (menuKey == getMenuKeySymbols()[idx].name) - menuKeyChoice->value(idx + 1); + ctrlButton->value(modifierMask & ShortcutHandler::Control); + shiftButton->value(modifierMask & ShortcutHandler::Shift); + altButton->value(modifierMask & ShortcutHandler::Alt); + superButton->value(modifierMask & ShortcutHandler::Super); + + handleModifier(nullptr, this); /* Display */ if (!fullScreen) { @@ -452,11 +469,23 @@ void OptionsDialog::storeOptions(void) #endif fullscreenSystemKeys.setParam(systemKeysCheckbox->value()); - if (menuKeyChoice->value() == 0) - menuKey.setParam("None"); - else { - menuKey.setParam(menuKeyChoice->text()); - } + /* Keyboard shortcuts */ + std::list<std::string> modifierList; + + if (ctrlButton->value()) + modifierList.push_back( + ShortcutHandler::modifierString(ShortcutHandler::Control)); + if (shiftButton->value()) + modifierList.push_back( + ShortcutHandler::modifierString(ShortcutHandler::Shift)); + if (altButton->value()) + modifierList.push_back( + ShortcutHandler::modifierString(ShortcutHandler::Alt)); + if (superButton->value()) + modifierList.push_back( + ShortcutHandler::modifierString(ShortcutHandler::Super)); + + shortcutModifiers.setParam(modifierList); /* Display */ if (windowedButton->value()) { @@ -879,21 +908,11 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th) tx += INDENT; ty += TIGHT_MARGIN; - systemKeysCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty, - CHECK_MIN_WIDTH, - CHECK_HEIGHT, - _("Pass system keys directly to server (full screen)"))); + systemKeysCheckbox = new Fl_Check_Button( + LBLRIGHT(tx, ty, CHECK_MIN_WIDTH, CHECK_HEIGHT, + _("Always send all keyboard input in full screen"))); + systemKeysCheckbox->callback(handleSystemKeys, this); ty += CHECK_HEIGHT + TIGHT_MARGIN; - - menuKeyChoice = new Fl_Choice(LBLLEFT(tx, ty, 150, CHOICE_HEIGHT, _("Menu key"))); - - fltk_menu_add(menuKeyChoice, _("None"), 0, nullptr, nullptr, FL_MENU_DIVIDER); - for (int idx = 0; idx < getMenuKeySymbolCount(); idx++) - fltk_menu_add(menuKeyChoice, getMenuKeySymbols()[idx].name, 0, nullptr, nullptr, 0); - - fltk_adjust_choice(menuKeyChoice); - - ty += CHOICE_HEIGHT + TIGHT_MARGIN; } ty -= TIGHT_MARGIN; @@ -962,6 +981,76 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th) } +void OptionsDialog::createShortcutsPage(int tx, int ty, int tw, int th) +{ + Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Keyboard shortcuts")); + + tx += OUTER_MARGIN; + ty += OUTER_MARGIN; + + Fl_Box *intro = new Fl_Box(tx, ty, tw - OUTER_MARGIN * 2, INPUT_HEIGHT); + intro->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE); + intro->label(_("Modifier keys for keyboard shortcuts:")); + + ty += INPUT_HEIGHT + INNER_MARGIN; + + int width; + + width = (tw - OUTER_MARGIN * 2 - INNER_MARGIN * 3) / 4; + + ctrlButton = new Fl_Toggle_Button(tx, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Ctrl")); + ctrlButton->selection_color(FL_SELECTION_COLOR); + ctrlButton->callback(handleModifier, this); + shiftButton = new Fl_Toggle_Button(tx + width + INNER_MARGIN, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Shift")); + shiftButton->selection_color(FL_SELECTION_COLOR); + shiftButton->callback(handleModifier, this); + altButton = new Fl_Toggle_Button(tx + width * 2 + INNER_MARGIN * 2, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Alt")); + altButton->selection_color(FL_SELECTION_COLOR); + altButton->callback(handleModifier, this); + superButton = new Fl_Toggle_Button(tx + width * 3 + INNER_MARGIN * 3, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Win")); + superButton->selection_color(FL_SELECTION_COLOR); + superButton->callback(handleModifier, this); + +#ifdef __APPLE__ + /* TRANSLATORS: This refers to the keyboard key */ + ctrlButton->label(_("⌃ Ctrl")); + /* TRANSLATORS: This refers to the keyboard key */ + shiftButton->label(_("⇧ Shift")); + /* TRANSLATORS: This refers to the keyboard key */ + altButton->label(_("⌥ Option")); + /* TRANSLATORS: This refers to the keyboard key */ + superButton->label(_("⌘ Cmd")); +#endif + + ty += BUTTON_HEIGHT + INNER_MARGIN; + + shortcutsText = new Fl_Box(tx, ty, tw - OUTER_MARGIN * 2, th - ty - OUTER_MARGIN); + shortcutsText->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_WRAP); + + group->end(); +} + + void OptionsDialog::createDisplayPage(int tx, int ty, int tw, int th) { Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Display")); @@ -1130,6 +1219,20 @@ void OptionsDialog::handleRSAAES(Fl_Widget* /*widget*/, void *data) } +void OptionsDialog::handleSystemKeys(Fl_Widget* /*widget*/, void* data) +{ +#ifdef __APPLE__ + OptionsDialog* dialog = (OptionsDialog*)data; + + // Pop up the access dialog if needed + if (dialog->systemKeysCheckbox->value()) + cocoa_is_trusted(true); +#else + (void)data; +#endif +} + + void OptionsDialog::handleClipboard(Fl_Widget* /*widget*/, void *data) { (void)data; @@ -1147,6 +1250,61 @@ void OptionsDialog::handleClipboard(Fl_Widget* /*widget*/, void *data) #endif } +void OptionsDialog::handleModifier(Fl_Widget* /*widget*/, void *data) +{ + OptionsDialog *dialog = (OptionsDialog*)data; + unsigned mask; + + mask = 0; + if (dialog->ctrlButton->value()) + mask |= ShortcutHandler::Control; + if (dialog->shiftButton->value()) + mask |= ShortcutHandler::Shift; + if (dialog->altButton->value()) + mask |= ShortcutHandler::Alt; + if (dialog->superButton->value()) + mask |= ShortcutHandler::Super; + + if (mask == 0) { + dialog->shortcutsText->copy_label( + _("All keyboard shortcuts are disabled.")); + } else { + char prefix[256]; + char prefix_noplus[256]; + + std::string label; + + strcpy(prefix, ShortcutHandler::modifierPrefix(mask)); + strcpy(prefix_noplus, ShortcutHandler::modifierPrefix(mask, true)); + + label += core::format( + _("To release keyboard control from the session, press %s."), + prefix_noplus); + label += "\n\n"; + + label += core::format( + _("To pass all keyboard input to the session, press %sG."), + prefix); + label += "\n\n"; + + label += core::format( + _("To toggle full-screen mode, press %sEnter."), prefix); + label += "\n\n"; + + label += core::format( + _("To open the session context menu, press %sM."), prefix); + label += "\n\n"; + + label += core::format( + _("To send a key combination that includes %s directly to the " + "session, press %sSpace, release the space bar without " + "releasing %s, and press the desired key."), + prefix_noplus, prefix, prefix_noplus); + + dialog->shortcutsText->copy_label(label.c_str()); + } +} + void OptionsDialog::handleFullScreenMode(Fl_Widget* /*widget*/, void *data) { OptionsDialog *dialog = (OptionsDialog*)data; diff --git a/vncviewer/OptionsDialog.h b/vncviewer/OptionsDialog.h index 86a1423a..daa9f3e8 100644 --- a/vncviewer/OptionsDialog.h +++ b/vncviewer/OptionsDialog.h @@ -24,9 +24,11 @@ #include <FL/Fl_Window.H> class Fl_Widget; +class Fl_Box; class Fl_Group; class Fl_Check_Button; class Fl_Round_Button; +class Fl_Toggle_Button; class Fl_Input; class Fl_Int_Input; class Fl_Choice; @@ -54,6 +56,7 @@ protected: void createCompressionPage(int tx, int ty, int tw, int th); void createSecurityPage(int tx, int ty, int tw, int th); void createInputPage(int tx, int ty, int tw, int th); + void createShortcutsPage(int tx, int ty, int tw, int th); void createDisplayPage(int tx, int ty, int tw, int th); void createMiscPage(int tx, int ty, int tw, int th); @@ -65,8 +68,12 @@ protected: static void handleX509(Fl_Widget *widget, void *data); static void handleRSAAES(Fl_Widget *widget, void *data); + static void handleSystemKeys(Fl_Widget *widget, void *data); + static void handleClipboard(Fl_Widget *widget, void *data); + static void handleModifier(Fl_Widget *widget, void *data); + static void handleFullScreenMode(Fl_Widget *widget, void *data); static void handleCancel(Fl_Widget *widget, void *data); @@ -120,7 +127,6 @@ protected: Fl_Choice *cursorTypeChoice; Fl_Group *keyboardGroup; Fl_Check_Button *systemKeysCheckbox; - Fl_Choice *menuKeyChoice; Fl_Group *clipboardGroup; Fl_Check_Button *acceptClipboardCheckbox; #if !defined(WIN32) && !defined(__APPLE__) @@ -131,6 +137,14 @@ protected: Fl_Check_Button *sendPrimaryCheckbox; #endif + /* Keyboard shortcuts */ + Fl_Toggle_Button *ctrlButton; + Fl_Toggle_Button *altButton; + Fl_Toggle_Button *shiftButton; + Fl_Toggle_Button *superButton; + + Fl_Box *shortcutsText; + /* Display */ Fl_Group *displayModeGroup; Fl_Round_Button *windowedButton; diff --git a/vncviewer/PlatformPixelBuffer.h b/vncviewer/PlatformPixelBuffer.h index 498b337f..2c8d3957 100644 --- a/vncviewer/PlatformPixelBuffer.h +++ b/vncviewer/PlatformPixelBuffer.h @@ -27,8 +27,8 @@ #endif #include <list> +#include <mutex> -#include <core/Mutex.h> #include <core/Region.h> #include <rfb/PixelBuffer.h> @@ -48,7 +48,7 @@ public: using rfb::FullFramePixelBuffer::height; protected: - core::Mutex mutex; + std::mutex mutex; core::Region damage; #if !defined(WIN32) && !defined(__APPLE__) diff --git a/vncviewer/ServerDialog.cxx b/vncviewer/ServerDialog.cxx index b7adabe7..3011e948 100644 --- a/vncviewer/ServerDialog.cxx +++ b/vncviewer/ServerDialog.cxx @@ -60,7 +60,7 @@ static core::LogWriter vlog("ServerDialog"); const char* SERVER_HISTORY="tigervnc.history"; ServerDialog::ServerDialog() - : Fl_Window(450, 0, _("VNC viewer: Connection details")) + : Fl_Window(450, 0, "TigerVNC") { int x, y, x2; Fl_Button *button; diff --git a/vncviewer/ShortcutHandler.cxx b/vncviewer/ShortcutHandler.cxx new file mode 100644 index 00000000..aa17a6d1 --- /dev/null +++ b/vncviewer/ShortcutHandler.cxx @@ -0,0 +1,275 @@ +/* Copyright 2021-2025 Pierre Ossman for Cendio AB + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define XK_MISCELLANY +#include <rfb/keysymdef.h> + +#include "ShortcutHandler.h" +#include "i18n.h" + +ShortcutHandler::ShortcutHandler() : + modifierMask(0), state(Idle) +{ +} + +void ShortcutHandler::setModifiers(unsigned mask) +{ + modifierMask = mask; + reset(); +} + +ShortcutHandler::KeyAction ShortcutHandler::handleKeyPress(int keyCode, + uint32_t keySym) +{ + unsigned modifier, pressedMask; + std::map<int, uint32_t>::const_iterator iter; + + pressedKeys[keyCode] = keySym; + + if (modifierMask == 0) + return KeyNormal; + + modifier = keySymToModifier(keySym); + + pressedMask = 0; + for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) + pressedMask |= keySymToModifier(iter->second); + + switch (state) { + case Idle: + case Arming: + case Rearming: + if (pressedMask == modifierMask) { + // All triggering modifier keys are pressed + state = Armed; + } if (modifier && ((modifier & modifierMask) == modifier)) { + // The new key is part of the triggering set + if (state == Idle) + state = Arming; + } else { + // The new key was something else + state = Wedged; + } + return KeyNormal; + case Armed: + if (modifier && ((modifier & modifierMask) == modifier)) { + // The new key is part of the triggering set + return KeyNormal; + } else if (modifier) { + // The new key is some other modifier + state = Wedged; + return KeyNormal; + } else { + // The new key was something else + state = Firing; + firedKeys.insert(keyCode); + return KeyShortcut; + } + break; + case Firing: + if (modifier) { + // The new key is a modifier (may or may not be part of the + // triggering set) + return KeyIgnore; + } else { + // The new key was something else + firedKeys.insert(keyCode); + return KeyShortcut; + } + default: + break; + } + + return KeyNormal; +} + +ShortcutHandler::KeyAction ShortcutHandler::handleKeyRelease(int keyCode) +{ + bool firedKey; + unsigned pressedMask; + std::map<int, uint32_t>::const_iterator iter; + KeyAction action; + + firedKey = firedKeys.count(keyCode) != 0; + + firedKeys.erase(keyCode); + pressedKeys.erase(keyCode); + + pressedMask = 0; + for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) + pressedMask |= keySymToModifier(iter->second); + + switch (state) { + case Arming: + action = KeyNormal; + break; + case Armed: + if (pressedKeys.empty()) + action = KeyUnarm; + else if (pressedMask == modifierMask) + action = KeyNormal; + else { + action = KeyNormal; + state = Rearming; + } + break; + case Rearming: + if (pressedKeys.empty()) + action = KeyUnarm; + else + action = KeyNormal; + break; + case Firing: + if (firedKey) + action = KeyShortcut; + else + action = KeyIgnore; + break; + default: + action = KeyNormal; + } + + if (pressedKeys.empty()) + state = Idle; + + return action; +} + +void ShortcutHandler::reset() +{ + state = Idle; + firedKeys.clear(); + pressedKeys.clear(); +} + +// Keep list of valid values in sync with shortcutModifiers +unsigned ShortcutHandler::parseModifier(const char* key) +{ + if (strcasecmp(key, "Ctrl") == 0) + return Control; + else if (strcasecmp(key, "Shift") == 0) + return Shift; + else if (strcasecmp(key, "Alt") == 0) + return Alt; + else if (strcasecmp(key, "Win") == 0) + return Super; + else if (strcasecmp(key, "Super") == 0) + return Super; + else if (strcasecmp(key, "Option") == 0) + return Alt; + else if (strcasecmp(key, "Cmd") == 0) + return Super; + else + return 0; +} + +const char* ShortcutHandler::modifierString(unsigned key) +{ + if (key == Control) + return "Ctrl"; + if (key == Shift) + return "Shift"; + if (key == Alt) + return "Alt"; + if (key == Super) + return "Super"; + + return ""; +} + +const char* ShortcutHandler::modifierPrefix(unsigned mask, + bool justPrefix) +{ + static char prefix[256]; + + prefix[0] = '\0'; + if (mask & Control) { +#ifdef __APPLE__ + strcat(prefix, "⌃"); +#else + strcat(prefix, _("Ctrl")); + strcat(prefix, "+"); +#endif + } + if (mask & Shift) { +#ifdef __APPLE__ + strcat(prefix, "⇧"); +#else + strcat(prefix, _("Shift")); + strcat(prefix, "+"); +#endif + } + if (mask & Alt) { +#ifdef __APPLE__ + strcat(prefix, "⌥"); +#else + strcat(prefix, _("Alt")); + strcat(prefix, "+"); +#endif + } + if (mask & Super) { +#ifdef __APPLE__ + strcat(prefix, "⌘"); +#else + strcat(prefix, _("Win")); + strcat(prefix, "+"); +#endif + } + + if (prefix[0] == '\0') + return ""; + + if (justPrefix) { +#ifndef __APPLE__ + prefix[strlen(prefix)-1] = '\0'; +#endif + return prefix; + } + +#ifdef __APPLE__ + strcat(prefix, "\xc2\xa0"); // U+00A0 NO-BREAK SPACE +#endif + + return prefix; +} + +unsigned ShortcutHandler::keySymToModifier(uint32_t keySym) +{ + switch (keySym) { + case XK_Control_L: + case XK_Control_R: + return Control; + case XK_Shift_L: + case XK_Shift_R: + return Shift; + case XK_Alt_L: + case XK_Alt_R: + return Alt; + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + return Super; + } + + return 0; +} diff --git a/vncviewer/ShortcutHandler.h b/vncviewer/ShortcutHandler.h new file mode 100644 index 00000000..bb6497a9 --- /dev/null +++ b/vncviewer/ShortcutHandler.h @@ -0,0 +1,79 @@ +/* Copyright 2021-2025 Pierre Ossman for Cendio AB + * + * 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. + */ + +#ifndef __SHORTCUTHANDLER__ +#define __SHORTCUTHANDLER__ + +#include <set> +#include <map> + +#include <stdint.h> + +class ShortcutHandler { +public: + ShortcutHandler(); + + void setModifiers(unsigned mask); + + enum KeyAction { + KeyNormal, + KeyUnarm, + KeyShortcut, + KeyIgnore, + }; + + KeyAction handleKeyPress(int keyCode, uint32_t keySym); + KeyAction handleKeyRelease(int keyCode); + + void reset(); + +public: + enum Modifier { + Control = (1<<0), + Shift = (1<<1), + Alt = (1<<2), + Super = (1<<3), + }; + + static unsigned parseModifier(const char* key); + static const char* modifierString(unsigned key); + + static const char* modifierPrefix(unsigned mask, + bool justPrefix=false); + +private: + unsigned keySymToModifier(uint32_t keySym); + +private: + unsigned modifierMask; + + enum State { + Idle, + Arming, + Armed, + Rearming, + Firing, + Wedged, + }; + State state; + + std::set<int> firedKeys; + std::map<int, uint32_t> pressedKeys; +}; + +#endif diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 175be172..03e6fb09 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2021 Pierre Ossman for Cendio AB + * Copyright 2011-2025 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,14 +31,21 @@ #include <core/string.h> #include <rfb/CMsgWriter.h> +#include <rfb/Cursor.h> +#include <rfb/KeysymStr.h> #include <rfb/ledStates.h> // FLTK can pull in the X11 headers on some systems #ifndef XK_VoidSymbol +#define XK_LATIN1 #define XK_MISCELLANY #include <rfb/keysymdef.h> #endif +#ifndef NoSymbol +#define NoSymbol 0 +#endif + #include "fltk/layout.h" #include "fltk/util.h" #include "Viewport.h" @@ -47,7 +54,6 @@ #include "DesktopWindow.h" #include "i18n.h" #include "parameters.h" -#include "menukey.h" #include "vncviewer.h" #include "PlatformPixelBuffer.h" @@ -76,7 +82,7 @@ static core::LogWriter vlog("Viewport"); // Menu constants enum { ID_DISCONNECT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE, - ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL, + ID_CTRL, ID_ALT, ID_CTRLALTDEL, ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT }; // Used for fake key presses from the menu @@ -87,10 +93,10 @@ static const int FAKE_DEL_KEY_CODE = 0x10003; // Used for fake key presses for lock key sync static const int FAKE_KEY_CODE = 0xffff; -Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc_) +Viewport::Viewport(int w, int h, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), - keyboard(nullptr), + keyboard(nullptr), shortcutBypass(false), shortcutActive(false), firstLEDState(true), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(nullptr), cursorIsBlank(false) @@ -129,12 +135,16 @@ Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc // reparenting to the current window works for most cases. window()->add(contextMenu); - setMenuKey(); + unsigned modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + shortcutHandler.setModifiers(modifierMask); OptionsDialog::addCallback(handleOptions, this); // Make sure we have an initial blank cursor set - setCursor(0, 0, {0, 0}, nullptr); + setCursor(); } @@ -191,10 +201,12 @@ static const char * dotcursor_xpm[] = { " ... ", " "}; -void Viewport::setCursor(int width, int height, - const core::Point& hotspot, - const uint8_t* data) +void Viewport::setCursor() { + int width, height; + core::Point hotspot; + const uint8_t* data; + int i; if (cursor) { @@ -203,6 +215,11 @@ void Viewport::setCursor(int width, int height, delete cursor; } + width = cc->server.cursor().width(); + height = cc->server.cursor().height(); + hotspot = cc->server.cursor().hotspot(); + data = cc->server.cursor().getBuffer(); + for (i = 0; i < width*height; i++) if (data[i*4 + 3] != 0) break; @@ -665,23 +682,122 @@ void Viewport::resetKeyboard() } keyboard->reset(); + + shortcutHandler.reset(); + shortcutBypass = false; + shortcutActive = false; + pressedKeys.clear(); } void Viewport::handleKeyPress(int systemKeyCode, uint32_t keyCode, uint32_t keySym) { - static bool menuRecursion = false; - - // Prevent recursion if the menu wants to send its own - // activation key. - if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) { - menuRecursion = true; - popupContextMenu(); - menuRecursion = false; - return; + pressedKeys.insert(systemKeyCode); + + // Possible keyboard shortcut? + + if (!shortcutBypass) { + ShortcutHandler::KeyAction action; + + action = shortcutHandler.handleKeyPress(systemKeyCode, keySym); + + if (action == ShortcutHandler::KeyIgnore) { + vlog.debug("Ignoring key press %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + return; + } + + if (action == ShortcutHandler::KeyShortcut) { + std::list<uint32_t> keySyms; + std::list<uint32_t>::const_iterator iter; + + // Modifiers can change the KeySym that's been resolved, so we + // need to check all possible KeySyms for this physical key, not + // just the current one + keySyms = keyboard->translateToKeySyms(systemKeyCode); + + // Then we pick the one that matches first + keySym = NoSymbol; + for (iter = keySyms.begin(); iter != keySyms.end(); iter++) { + bool found; + + switch (*iter) { + case XK_space: + case XK_G: + case XK_g: + case XK_M: + case XK_m: + case XK_KP_Enter: + case XK_Return: + keySym = *iter; + found = true; + break; + default: + found = false; + break; + } + + if (found) + break; + } + + vlog.debug("Detected shortcut %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + + // Special case which we need to handle first + if (keySym == XK_space) { + // If another shortcut has already fired, then we're too late as + // we've already released the modifier keys + if (!shortcutActive) { + shortcutBypass = true; + shortcutHandler.reset(); + } + return; + } + + shortcutActive = true; + + // The remote session won't see any more keys, so release the ones + // currently down + try { + cc->releaseAllKeys(); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection(_("An unexpected error occurred when communicating " + "with the server:\n\n%s"), e.what()); + } + + switch (keySym) { + case XK_G: + case XK_g: + ((DesktopWindow*)window())->grabKeyboard(); + break; + case XK_M: + case XK_m: + popupContextMenu(); + break; + case XK_KP_Enter: + case XK_Return: + if (window()->fullscreen_active()) { + fullScreen.setParam(false); + window()->fullscreen_off(); + } else { + fullScreen.setParam(true); + ((DesktopWindow*)window())->fullscreen_on(); + } + break; + default: + // Unknown/Unused keyboard shortcut + break; + } + + return; + } } + // Normal key, so send to server... + if (viewOnly) return; @@ -696,6 +812,54 @@ void Viewport::handleKeyPress(int systemKeyCode, void Viewport::handleKeyRelease(int systemKeyCode) { + pressedKeys.erase(systemKeyCode); + + if (pressedKeys.empty()) + shortcutActive = false; + + // Possible keyboard shortcut? + + if (!shortcutBypass) { + ShortcutHandler::KeyAction action; + + action = shortcutHandler.handleKeyRelease(systemKeyCode); + + if (action == ShortcutHandler::KeyIgnore) { + vlog.debug("Ignoring key release %d", systemKeyCode); + return; + } + + if (action == ShortcutHandler::KeyShortcut) { + vlog.debug("Shortcut release %d", systemKeyCode); + return; + } + + if (action == ShortcutHandler::KeyUnarm) { + DesktopWindow *win; + + vlog.debug("Detected shortcut to release grab"); + + try { + cc->releaseAllKeys(); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection(_("An unexpected error occurred when communicating " + "with the server:\n\n%s"), e.what()); + } + + win = dynamic_cast<DesktopWindow*>(window()); + assert(win); + win->ungrabKeyboard(); + + return; + } + } + + if (pressedKeys.empty()) + shortcutBypass = false; + + // Normal key, so send to server... + if (viewOnly) return; @@ -718,13 +882,11 @@ int Viewport::handleSystemEvent(void *event, void *data) if (!self->hasFocus()) return 0; -#ifdef __APPLE__ // Special event that means we temporarily lost some input - if (KeyboardMacOS::isKeyboardSync(event)) { + if (self->keyboard->isKeyboardReset(event)) { self->resetKeyboard(); return 1; } -#endif consumed = self->keyboard->handleEvent(event); if (consumed) @@ -760,16 +922,6 @@ void Viewport::initContextMenu() 0, nullptr, (void*)ID_ALT, FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0)); - if (menuKeySym) { - char sendMenuKey[64]; - snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), - menuKey.getValueStr().c_str()); - fltk_menu_add(contextMenu, sendMenuKey, 0, nullptr, (void*)ID_MENUKEY, 0); - fltk_menu_add(contextMenu, "Secret shortcut menu key", - menuKeyFLTK, nullptr, - (void*)ID_MENUKEY, FL_MENU_INVISIBLE); - } - fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"), 0, nullptr, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER); @@ -780,7 +932,7 @@ void Viewport::initContextMenu() 0, nullptr, (void*)ID_OPTIONS, 0); fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."), 0, nullptr, (void*)ID_INFO, 0); - fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."), + fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC..."), 0, nullptr, (void*)ID_ABOUT, 0); } #pragma GCC diagnostic pop @@ -803,11 +955,11 @@ void Viewport::popupContextMenu() window()->cursor(FL_CURSOR_DEFAULT); // FLTK also doesn't switch focus properly for menus - handle(FL_UNFOCUS); + Fl::handle(FL_UNFOCUS, window()); m = contextMenu->popup(); - handle(FL_FOCUS); + Fl::handle(FL_FOCUS, window()); // Back to our proper mouse pointer. if (Fl::belowmouse() == this) @@ -854,10 +1006,6 @@ void Viewport::popupContextMenu() handleKeyRelease(FAKE_ALT_KEY_CODE); menuAltKey = !menuAltKey; break; - case ID_MENUKEY: - handleKeyPress(FAKE_KEY_CODE, menuKeyCode, menuKeySym); - handleKeyRelease(FAKE_KEY_CODE); - break; case ID_CTRLALTDEL: handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); @@ -886,18 +1034,17 @@ void Viewport::popupContextMenu() } } - -void Viewport::setMenuKey() -{ - getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym); -} - - void Viewport::handleOptions(void *data) { Viewport *self = (Viewport*)data; + unsigned modifierMask; + + modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + self->shortcutHandler.setModifiers(modifierMask); - self->setMenuKey(); if (Fl::belowmouse() == self) self->showCursor(); } diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 0f606089..8e3f473e 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -26,6 +26,7 @@ #include "EmulateMB.h" #include "Keyboard.h" +#include "ShortcutHandler.h" class Fl_Menu_Button; class Fl_RGB_Image; @@ -39,7 +40,7 @@ class Viewport : public Fl_Widget, protected EmulateMB, protected KeyboardHandler { public: - Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_); + Viewport(int w, int h, CConn* cc_); ~Viewport(); // Most efficient format (from Viewport's point of view) @@ -49,8 +50,7 @@ public: void updateWindow(); // New image for the locally rendered cursor - void setCursor(int width, int height, const core::Point& hotspot, - const uint8_t* data); + void setCursor(); // Change client LED state void setLEDState(unsigned int state); @@ -100,8 +100,6 @@ private: void initContextMenu(); void popupContextMenu(); - void setMenuKey(); - static void handleOptions(void *data); private: @@ -113,6 +111,10 @@ private: uint16_t lastButtonMask; Keyboard* keyboard; + ShortcutHandler shortcutHandler; + bool shortcutBypass; + bool shortcutActive; + std::set<int> pressedKeys; bool firstLEDState; @@ -120,8 +122,6 @@ private: int clipboardSource; - uint32_t menuKeySym; - int menuKeyCode, menuKeyFLTK; Fl_Menu_Button *contextMenu; bool menuCtrlKey; diff --git a/vncviewer/Win32TouchHandler.cxx b/vncviewer/Win32TouchHandler.cxx index d1d81ae1..2777cca2 100644 --- a/vncviewer/Win32TouchHandler.cxx +++ b/vncviewer/Win32TouchHandler.cxx @@ -310,6 +310,9 @@ void Win32TouchHandler::fakeButtonEvent(bool press, int button, LPARAM lParam; int delta; + // Needed to silence false positive that this is used uninitialized + delta = 0; + switch (button) { case 1: // left mousebutton diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index b3a8326c..09db9a45 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -23,11 +23,10 @@ class Fl_Window; void cocoa_prevent_native_fullscreen(Fl_Window *win); -int cocoa_get_level(Fl_Window *win); -void cocoa_set_level(Fl_Window *win, int level); +bool cocoa_is_trusted(bool prompt=false); -int cocoa_capture_displays(Fl_Window *win); -void cocoa_release_displays(Fl_Window *win); +bool cocoa_tap_keyboard(); +void cocoa_untap_keyboard(); typedef struct CGColorSpace *CGColorSpaceRef; diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index 0675c429..4d9908dd 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -20,15 +20,19 @@ #include <config.h> #endif -#include <FL/Fl.H> +#include <assert.h> +#include <dlfcn.h> + #include <FL/Fl_Window.H> #include <FL/x.H> #import <Cocoa/Cocoa.h> +#import <ApplicationServices/ApplicationServices.h> -#include <core/Rect.h> +#include "cocoa.h" -static bool captured = false; +static CFMachPortRef event_tap; +static CFRunLoopSourceRef tap_source; void cocoa_prevent_native_fullscreen(Fl_Window *win) { @@ -40,102 +44,183 @@ void cocoa_prevent_native_fullscreen(Fl_Window *win) #endif } -int cocoa_get_level(Fl_Window *win) +bool cocoa_is_trusted(bool prompt) { - NSWindow *nsw; - nsw = (NSWindow*)fl_xid(win); - assert(nsw); - return [nsw level]; -} + CFStringRef keys[1]; + CFBooleanRef values[1]; + CFDictionaryRef options; -void cocoa_set_level(Fl_Window *win, int level) -{ - NSWindow *nsw; - nsw = (NSWindow*)fl_xid(win); - assert(nsw); - [nsw setLevel:level]; -} + Boolean trusted; -int cocoa_capture_displays(Fl_Window *win) -{ - NSWindow *nsw; - - nsw = (NSWindow*)fl_xid(win); - assert(nsw); +#if !defined(MAC_OS_X_VERSION_10_9) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9 + // FIXME: Raise system requirements so this isn't needed + void *lib; + typedef Boolean (*AXIsProcessTrustedWithOptionsRef)(CFDictionaryRef); + AXIsProcessTrustedWithOptionsRef AXIsProcessTrustedWithOptions; + CFStringRef kAXTrustedCheckOptionPrompt; - CGDisplayCount count; - CGDirectDisplayID displays[16]; + lib = dlopen(nullptr, 0); + if (lib == nullptr) + return false; - int sx, sy, sw, sh; - core::Rect windows_rect, screen_rect; + AXIsProcessTrustedWithOptions = + (AXIsProcessTrustedWithOptionsRef)dlsym(lib, "AXIsProcessTrustedWithOptions"); - windows_rect.setXYWH(win->x(), win->y(), win->w(), win->h()); + dlclose(lib); - if (CGGetActiveDisplayList(16, displays, &count) != kCGErrorSuccess) - return 1; + if (AXIsProcessTrustedWithOptions == nullptr) + return false; - if (count != (unsigned)Fl::screen_count()) - return 1; + kAXTrustedCheckOptionPrompt = CFSTR("AXTrustedCheckOptionPrompt"); +#endif - for (int i = 0; i < Fl::screen_count(); i++) { - Fl::screen_xywh(sx, sy, sw, sh, i); + keys[0] = kAXTrustedCheckOptionPrompt; + values[0] = prompt ? kCFBooleanTrue : kCFBooleanFalse; + options = CFDictionaryCreate(kCFAllocatorDefault, + (const void**)keys, + (const void**)values, 1, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (options == nullptr) + return false; + + trusted = AXIsProcessTrustedWithOptions(options); + CFRelease(options); + + // For some reason, the authentication popups isn't set as active and + // is hidden behind our window(s). Try to find it and manually switch + // to it. + if (!trusted && prompt) { + long long pid; + + pid = 0; + for (int attempt = 0; attempt < 5; attempt++) { + CFArrayRef windowList; + + windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, + kCGNullWindowID); + for (int i = 0; i < CFArrayGetCount(windowList); i++) { + CFDictionaryRef window; + CFStringRef owner; + CFNumberRef cfpid; + + window = (CFDictionaryRef)CFArrayGetValueAtIndex(windowList, i); + assert(window != nullptr); + owner = (CFStringRef)CFDictionaryGetValue(window, + kCGWindowOwnerName); + if (owner == nullptr) + continue; + + // FIXME: Unknown how stable this identifier is + CFStringRef authOwner = CFSTR("universalAccessAuthWarn"); + if (CFStringCompare(owner, authOwner, 0) != kCFCompareEqualTo) + continue; + + cfpid = (CFNumberRef)CFDictionaryGetValue(window, + kCGWindowOwnerPID); + if (cfpid == nullptr) + continue; + + CFNumberGetValue(cfpid, kCFNumberLongLongType, &pid); + break; + } + + CFRelease(windowList); + + if (pid != 0) + break; + + usleep(100000); + } - screen_rect.setXYWH(sx, sy, sw, sh); - if (screen_rect.enclosed_by(windows_rect)) { - if (CGDisplayCapture(displays[i]) != kCGErrorSuccess) - return 1; + if (pid != 0) { + NSRunningApplication* authApp; - } else { - // A display might have been captured with the previous - // monitor selection. In that case we don't want to keep - // it when its no longer inside the window_rect. - CGDisplayRelease(displays[i]); + authApp = [NSRunningApplication runningApplicationWithProcessIdentifier:pid]; + if (authApp != nil) { + // Seems to work fine even without yieldActivationToApplication, + // or NSApplicationActivateIgnoringOtherApps + [authApp activateWithOptions:0]; + } } } - captured = true; + return trusted; +} - if ([nsw level] == CGShieldingWindowLevel()) - return 0; +static CGEventRef cocoa_event_tap(CGEventTapProxy /*proxy*/, + CGEventType type, CGEventRef event, + void* /*refcon*/) +{ + ProcessSerialNumber psn; + OSErr err; + + // We should just be getting these events, but just in case + if ((type != kCGEventKeyDown) && + (type != kCGEventKeyUp) && + (type != kCGEventFlagsChanged)) + return event; + + // Redirect the event to us, no matter the original target + // (note that this will loop if kCGAnnotatedSessionEventTap is used) + err = GetCurrentProcess(&psn); + if (err != noErr) + return event; + + // FIXME: CGEventPostToPid() in macOS 10.11+ + CGEventPostToPSN(&psn, event); + + // Stop delivery to original target + return nullptr; +} - [nsw setLevel:CGShieldingWindowLevel()]; +bool cocoa_tap_keyboard() +{ + CGEventMask mask; - // We're not getting put in front of the shielding window in many - // cases on macOS 13, despite setLevel: being documented as also - // pushing the window to the front. So let's explicitly move it. - [nsw orderFront:nsw]; + if (event_tap != nullptr) + return true; - return 0; -} + if (!cocoa_is_trusted()) + return false; -void cocoa_release_displays(Fl_Window *win) -{ - NSWindow *nsw; - int newlevel; + mask = CGEventMaskBit(kCGEventKeyDown) | + CGEventMaskBit(kCGEventKeyUp) | + CGEventMaskBit(kCGEventFlagsChanged); - if (captured) - CGReleaseAllDisplays(); + // Cannot be kCGAnnotatedSessionEventTap as window manager intercepts + // before that (e.g. Ctrl+Up) + event_tap = CGEventTapCreate(kCGSessionEventTap, + kCGHeadInsertEventTap, + kCGEventTapOptionDefault, + mask, cocoa_event_tap, nullptr); + if (event_tap == nullptr) + return false; - captured = false; + tap_source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, + event_tap, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), tap_source, + kCFRunLoopCommonModes); - nsw = (NSWindow*)fl_xid(win); - assert(nsw); + return true; +} - // Someone else has already changed the level of this window - if ([nsw level] != CGShieldingWindowLevel()) +void cocoa_untap_keyboard() +{ + if (event_tap == nullptr) return; - // FIXME: Store the previous level somewhere so we don't have to hard - // code a level here. - if (win->fullscreen_active() && win->contains(Fl::focus())) - newlevel = NSStatusWindowLevel; - else - newlevel = NSNormalWindowLevel; - - // Only change if different as the level change also moves the window - // to the top of that level. - if ([nsw level] != newlevel) - [nsw setLevel:newlevel]; + // Need to explicitly disable the tap first, or we get a short delay + // where all events are dropped + CGEventTapEnable(event_tap, false); + + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), tap_source, + kCFRunLoopCommonModes); + CFRelease(tap_source); + tap_source = nullptr; + + CFRelease(event_tap); + event_tap = nullptr; } CGColorSpaceRef cocoa_win_color_space(Fl_Window *win) diff --git a/vncviewer/fltk/Fl_Navigation.cxx b/vncviewer/fltk/Fl_Navigation.cxx index d3117aae..be258ce5 100644 --- a/vncviewer/fltk/Fl_Navigation.cxx +++ b/vncviewer/fltk/Fl_Navigation.cxx @@ -31,6 +31,7 @@ #include <FL/Fl_Button.H> #include <FL/Fl_Scroll.H> +#include <FL/fl_draw.H> #include "Fl_Navigation.h" @@ -154,14 +155,21 @@ void Fl_Navigation::update_labels() for (i = 0;i < pages->children();i++) { Fl_Widget *page; Fl_Button *btn; + int w, h; page = pages->child(i); + w = labels->w() - page->labelsize() * 2; + fl_font(page->labelfont(), page->labelsize()); + fl_measure(page->label(), w, h); + h += page->labelsize() * 2; + btn = new Fl_Button(labels->x(), labels->y() + offset, - labels->w(), page->labelsize() * 3, + labels->w(), h, page->label()); btn->box(FL_FLAT_BOX); btn->type(FL_RADIO_BUTTON); + btn->align(btn->align() | FL_ALIGN_WRAP); btn->color(FL_BACKGROUND2_COLOR); btn->selection_color(FL_SELECTION_COLOR); btn->labelsize(page->labelsize()); @@ -171,7 +179,7 @@ void Fl_Navigation::update_labels() btn->callback(label_pressed, this); labels->add(btn); - offset += page->labelsize() * 3; + offset += h; } labels->size(labels->w(), offset); diff --git a/vncviewer/menukey.cxx b/vncviewer/menukey.cxx deleted file mode 100644 index 59e1daa1..00000000 --- a/vncviewer/menukey.cxx +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2011 Martin Koegler <mkoegler@auto.tuwien.ac.at> - * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <string.h> -#include <FL/Fl.H> - -// FLTK can pull in the X11 headers on some systems -#ifndef XK_VoidSymbol -#define XK_MISCELLANY -#include <rfb/keysymdef.h> -#endif - -#include "menukey.h" -#include "parameters.h" - -static const MenuKeySymbol menuSymbols[] = { - {"F1", FL_F + 1, 0x3b, XK_F1}, - {"F2", FL_F + 2, 0x3c, XK_F2}, - {"F3", FL_F + 3, 0x3d, XK_F3}, - {"F4", FL_F + 4, 0x3e, XK_F4}, - {"F5", FL_F + 5, 0x3f, XK_F5}, - {"F6", FL_F + 6, 0x40, XK_F6}, - {"F7", FL_F + 7, 0x41, XK_F7}, - {"F8", FL_F + 8, 0x42, XK_F8}, - {"F9", FL_F + 9, 0x43, XK_F9}, - {"F10", FL_F + 10, 0x44, XK_F10}, - {"F11", FL_F + 11, 0x57, XK_F11}, - {"F12", FL_F + 12, 0x58, XK_F12}, - {"Pause", FL_Pause, 0xc6, XK_Pause}, - {"Scroll_Lock", FL_Scroll_Lock, 0x46, XK_Scroll_Lock}, - {"Escape", FL_Escape, 0x01, XK_Escape}, - {"Insert", FL_Insert, 0xd2, XK_Insert}, - {"Delete", FL_Delete, 0xd3, XK_Delete}, - {"Home", FL_Home, 0xc7, XK_Home}, - {"Page_Up", FL_Page_Up, 0xc9, XK_Page_Up}, - {"Page_Down", FL_Page_Down, 0xd1, XK_Page_Down}, -}; - -int getMenuKeySymbolCount() -{ - return sizeof(menuSymbols)/sizeof(menuSymbols[0]); -} - -const MenuKeySymbol* getMenuKeySymbols() -{ - return menuSymbols; -} - -void getMenuKey(int *fltkcode, int *keycode, uint32_t *keysym) -{ - for(int i = 0; i < getMenuKeySymbolCount(); i++) { - if (menuKey == menuSymbols[i].name) { - *fltkcode = menuSymbols[i].fltkcode; - *keycode = menuSymbols[i].keycode; - *keysym = menuSymbols[i].keysym; - return; - } - } - - *fltkcode = 0; - *keycode = 0; - *keysym = 0; -} diff --git a/vncviewer/menukey.h b/vncviewer/menukey.h deleted file mode 100644 index 50106955..00000000 --- a/vncviewer/menukey.h +++ /dev/null @@ -1,34 +0,0 @@ -/* Copyright 2011 Martin Koegler <mkoegler@auto.tuwien.ac.at> - * - * 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. - */ -#ifndef __KEYSYM_H__ -#define __KEYSYM_H__ - -#include <stdint.h> - -typedef struct { - const char* name; - int fltkcode; - int keycode; - uint32_t keysym; -} MenuKeySymbol; - -void getMenuKey(int *fltkcode, int *keycode, uint32_t *keysym); -int getMenuKeySymbolCount(); -const MenuKeySymbol* getMenuKeySymbols(); - -#endif diff --git a/vncviewer/org.tigervnc.vncviewer.metainfo.xml.in b/vncviewer/org.tigervnc.vncviewer.metainfo.xml.in index 363c12fa..207f9707 100644 --- a/vncviewer/org.tigervnc.vncviewer.metainfo.xml.in +++ b/vncviewer/org.tigervnc.vncviewer.metainfo.xml.in @@ -10,7 +10,7 @@ <id>org.tigervnc.vncviewer</id> <metadata_license>FSFAP</metadata_license> <project_license>GPL-2.0-or-later</project_license> - <name>TigerVNC Viewer</name> + <name>TigerVNC</name> <summary>Connect to VNC server and display remote desktop</summary> <content_rating type="oars-1.1"/> <description> @@ -30,15 +30,15 @@ <launchable type="desktop-id">vncviewer.desktop</launchable> <screenshots> <screenshot type="default"> - <caption>TigerVNC viewer connection to a CentOS machine</caption> + <caption>TigerVNC connection to a CentOS machine</caption> <image>https://raw.githubusercontent.com/TigerVNC/tigervnc/741d3edbfab65eda6f033078bc06347fe244ea6a/vncviewer/metainfo/tigervnc-connection-linux.jpg</image> </screenshot> <screenshot> - <caption>TigerVNC viewer connection to a macOS machine</caption> + <caption>TigerVNC connection to a macOS machine</caption> <image>https://raw.githubusercontent.com/TigerVNC/tigervnc/741d3edbfab65eda6f033078bc06347fe244ea6a/vncviewer/metainfo/tigervnc-connection-macos.jpg</image> </screenshot> <screenshot> - <caption>TigerVNC viewer connection to a Windows machine</caption> + <caption>TigerVNC connection to a Windows machine</caption> <image>https://raw.githubusercontent.com/TigerVNC/tigervnc/741d3edbfab65eda6f033078bc06347fe244ea6a/vncviewer/metainfo/tigervnc-connection-windows.jpg</image> </screenshot> </screenshots> diff --git a/vncviewer/parameters.cxx b/vncviewer/parameters.cxx index 957838a7..d2000181 100644 --- a/vncviewer/parameters.cxx +++ b/vncviewer/parameters.cxx @@ -217,19 +217,21 @@ core::BoolParameter true); core::StringParameter display("display", - "Specifies the X display on which the VNC viewer window " + "Specifies the X display on which the TigerVNC window " "should appear.", ""); #endif -// Empty string means None, for backward compatibility -core::EnumParameter - menuKey("MenuKey", - "The key which brings up the popup menu", - {"", "None", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", - "F9", "F10", "F11", "F12", "Pause", "Scroll_Lock", "Escape", - "Insert", "Delete", "Home", "Page_Up", "Page_Down"}, - "F8"); +// Keep list of valid values in sync with ShortcutHandler +core::EnumListParameter + shortcutModifiers("ShortcutModifiers", + "The combination of modifier keys that triggers " + "special actions in the viewer instead of being " + "sent to the remote session. Possible values are a " + "combination of Ctrl, Shift, Alt, and Super.", + {"Ctrl", "Shift", "Alt", "Super", + "Win", "Option", "Cmd"}, + {"Ctrl", "Alt"}); core::BoolParameter fullscreenSystemKeys("FullscreenSystemKeys", @@ -282,8 +284,9 @@ static core::VoidParameter* parameterArray[] = { &sendPrimary, &setPrimary, #endif - &menuKey, - &fullscreenSystemKeys + &fullscreenSystemKeys, + /* Keyboard shortcuts */ + &shortcutModifiers, }; static core::VoidParameter* readOnlyParameterArray[] = { diff --git a/vncviewer/parameters.h b/vncviewer/parameters.h index 4dafdaa0..4dc30db6 100644 --- a/vncviewer/parameters.h +++ b/vncviewer/parameters.h @@ -73,7 +73,7 @@ extern core::BoolParameter sendPrimary; extern core::StringParameter display; #endif -extern core::EnumParameter menuKey; +extern core::EnumListParameter shortcutModifiers; extern core::BoolParameter fullscreenSystemKeys; extern core::BoolParameter alertOnFatalError; diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx index 29cf4ca0..324ec632 100644 --- a/vncviewer/vncviewer.cxx +++ b/vncviewer/vncviewer.cxx @@ -100,7 +100,7 @@ static const char *about_text() // encodings, so we need to make sure we get a fresh string every // time. snprintf(buffer, sizeof(buffer), - _("TigerVNC viewer v%s\n" + _("TigerVNC v%s\n" "Built on: %s\n" "Copyright (C) 1999-%d TigerVNC team and many others (see README.rst)\n" "See https://www.tigervnc.org for information on TigerVNC."), @@ -170,7 +170,7 @@ bool should_disconnect() void about_vncviewer() { - fl_message_title(_("About TigerVNC Viewer")); + fl_message_title(_("About TigerVNC")); fl_message("%s", about_text()); } @@ -181,7 +181,8 @@ static void mainloop(const char* vncserver, network::Socket* sock) exitMainloop = false; - cc = new CConn(vncserver, sock); + cc = new CConn(); + cc->connect(vncserver, sock); while (!exitMainloop) { int next_timer; @@ -241,7 +242,7 @@ static void new_connection_cb(Fl_Widget* /*widget*/, void* /*data*/) pid = fork(); if (pid == -1) { - vlog.error(_("Error starting new TigerVNC Viewer: %s"), strerror(errno)); + vlog.error(_("Error starting new connection: %s"), strerror(errno)); return; } @@ -253,7 +254,7 @@ static void new_connection_cb(Fl_Widget* /*widget*/, void* /*data*/) execvp(argv[0], (char * const *)argv); - vlog.error(_("Error starting new TigerVNC Viewer: %s"), strerror(errno)); + vlog.error(_("Error starting new connection: %s"), strerror(errno)); _exit(1); } #endif @@ -262,7 +263,7 @@ static void CleanupSignalHandler(int sig) { // CleanupSignalHandler allows C++ object cleanup to happen because it calls // exit() rather than the default which is to abort. - vlog.info(_("Termination signal %d has been received. TigerVNC viewer will now exit."), sig); + vlog.info(_("Termination signal %d has been received. TigerVNC will now exit."), sig); exit(1); } @@ -387,7 +388,7 @@ static void init_fltk() fl_message_hotspot(false); // Avoid empty titles for popups - fl_message_title_default(_("TigerVNC viewer")); + fl_message_title_default("TigerVNC"); // FLTK exposes these so that we can translate them. fl_no = _("No"); @@ -464,7 +465,7 @@ static void usage(const char *programName) fprintf(stderr, _("\n" "Options:\n\n" " -display Xdisplay - Specifies the X display for the viewer window\n" - " -geometry geometry - Initial position of the main VNC viewer window. See the\n" + " -geometry geometry - Initial position of the main TigerVNC window. See the\n" " man page for details.\n")); #endif @@ -607,7 +608,9 @@ createTunnel(const char *gatewayHost, const char *remoteHost, cmd2 = strdup(cmd); while ((percent = strchr(cmd2, '%')) != nullptr) *percent = '$'; - system(cmd2); + int res = system(cmd2); + if (res != 0) + fprintf(stderr, "Failed to create tunnel: '%s' returned %d\n", cmd2, res); free(cmd2); } diff --git a/vncviewer/vncviewer.desktop.in.in b/vncviewer/vncviewer.desktop.in.in index 1a91755c..705845d9 100644 --- a/vncviewer/vncviewer.desktop.in.in +++ b/vncviewer/vncviewer.desktop.in.in @@ -1,5 +1,5 @@ [Desktop Entry] -Name=TigerVNC viewer +Name=TigerVNC GenericName=Remote desktop viewer Comment=Connect to VNC server and display remote desktop Exec=@CMAKE_INSTALL_FULL_BINDIR@/vncviewer diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index 208858f9..9a8ad001 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -77,24 +77,40 @@ safely. Automatic selection can be turned off by setting the \fBAutoSelect\fP parameter to false, or from the options dialog. -.SH POPUP MENU -The viewer has a popup menu containing entries which perform various actions. -It is usually brought up by pressing F8, but this can be configured with the -MenuKey parameter. Actions which the popup menu can perform include: -.RS 2 -.IP * 2 -switching in and out of full-screen mode -.IP * -quitting the viewer -.IP * -generating key events, e.g. sending ctrl-alt-del -.IP * -accessing the options dialog and various other dialogs -.RE -.PP -By default, key presses in the popup menu get sent to the VNC server and -dismiss the popup. So to get an F8 through to the VNC server simply press it -twice. +.SH KEYBOARD SHORTCUTS + +The viewer can be controlled using certain key combinations, invoking +special actions instead of passing the keyboard events on to the remote +session. By default pressing Ctrl+Alt and something else will be +interpreted as a keyboard shortcut for the viewer, but this can be +changed witht the \fBShortcutModifiers\fP parameter. + +The possible keyboard shortcuts are: + +.TP +Ctrl+Alt +Releases control of the keyboard and allows system keys to be used +locally. +. +.TP +Ctrl+Alt+G +Grabs control of the keyboard and allows system keys (like Alt+Tab) to +be sent to the remote session. +. +.TP +Ctrl+Alt+Enter +Toggles full-screen mode. +. +.TP +Ctrl+Alt+M +Opens a popup menu that can perform various extra actions, such as +quitting the viewer or opening the options dialog. +. +.TP +Ctrl+Alt+Space +Temporarily bypasses the keyboard shortcuts, allowing the same key +combinations to be sent to the remote session. +. .SH FULL-SCREEN MODE A full-screen mode is supported. This is particularly useful when connecting @@ -144,7 +160,8 @@ The default is "Dot". . .TP .B \-CustomCompressLevel -Use custom compression level. Default if \fBCompressLevel\fP is specified. +Use custom compression level as specified by \fBCompressLevel\fP. Default is +off. . .TP .B \-DesktopSize \fIwidth\fPx\fIheight\fP @@ -154,7 +171,7 @@ the SetDesktopSize message then the screen will retain the original size. . .TP .B \-display \fIXdisplay\fP -Specifies the X display on which the VNC viewer window should appear. +Specifies the X display on which the TigerVNC window should appear. . .TP .B \-DotWhenNoCursor (DEPRECATED) @@ -196,12 +213,12 @@ The default is "1". . .TP .B \-FullscreenSystemKeys -Pass special keys (like Alt+Tab) directly to the server when in full-screen -mode. +Automatically grab all input from the keyboard when entering full-screen +and pass special keys (like Alt+Tab) directly to the server. . .TP .B \-geometry \fIgeometry\fP -Initial position of the main VNC viewer window. The format is +Initial position of the main TigerVNC window. The format is .B \fIwidth\fPx\fIheight\fP+\fIxoffset\fP+\fIyoffset\fP , where `+' signs can be replaced with `\-' signs to specify offsets from the right and/or from the bottom of the screen. Offsets are optional and the @@ -246,13 +263,6 @@ Default is \fB262144\fP. Maximize viewer window. . .TP -.B \-MenuKey \fIkeysym-name\fP -This option specifies the key which brings up the popup menu, or None to -disable the menu. The currently supported list is: F1, F2, F3, F4, F5, -F6, F7, F8, F9, F10, F11, F12, Pause, Scroll_Lock, Escape, Insert, -Delete, Home, Page_Up, Page_Down). Default is F8. -. -.TP .B \-NoJpeg Disable lossy JPEG compression in Tight encoding. Default is off. . @@ -319,6 +329,13 @@ normally closed. This option requests that they be left open, allowing you to share the desktop with someone already using it. . .TP +.B \-ShortcutModifiers \fIkeys\fP +The combination of modifier keys that triggers special actions in the +viewer instead of being sent to the remote session. Possible values are +a combination of \fBCtrl\fP, \fBShift\fP, \fBAlt\fP, and \fBSuper\fP. +Default is \fBCtrl,Alt\fP. +. +.TP .B \-UseIPv4 Use IPv4 for incoming and outgoing connections. Default is on. . diff --git a/vncviewer/vncviewer.rc.in b/vncviewer/vncviewer.rc.in index 43e44da3..375da7af 100644 --- a/vncviewer/vncviewer.rc.in +++ b/vncviewer/vncviewer.rc.in @@ -42,9 +42,9 @@ BEGIN BLOCK "080904b0" BEGIN VALUE "Comments", "\0" - VALUE "CompanyName", "TigerVNC project\0" - VALUE "FileDescription", "TigerVNC client\0" - VALUE "ProductName", "TigerVNC client\0" + VALUE "CompanyName", "TigerVNC team\0" + VALUE "FileDescription", "TigerVNC\0" + VALUE "ProductName", "TigerVNC\0" VALUE "FileVersion", "@RCVERSION@\0" VALUE "InternalName", "vncviewer\0" VALUE "LegalCopyright", "Copyright (C) 1999-2025 TigerVNC team and many others (see README.rst)\0" diff --git a/vncviewer/win32.c b/vncviewer/win32.c index b0a3813c..c649f783 100644 --- a/vncviewer/win32.c +++ b/vncviewer/win32.c @@ -27,39 +27,50 @@ static HANDLE thread; static DWORD thread_id; static HHOOK hook = 0; +static BYTE kbd_state[256]; static HWND target_wnd = 0; -static int is_system_hotkey(int vkCode) { - switch (vkCode) { - case VK_LWIN: - case VK_RWIN: - case VK_SNAPSHOT: - return 1; - case VK_TAB: - if (GetAsyncKeyState(VK_MENU) & 0x8000) - return 1; - break; - case VK_ESCAPE: - if (GetAsyncKeyState(VK_MENU) & 0x8000) - return 1; - if (GetAsyncKeyState(VK_CONTROL) & 0x8000) - return 1; - break; - } - return 0; -} - static LRESULT CALLBACK keyboard_hook(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { KBDLLHOOKSTRUCT* msgInfo = (KBDLLHOOKSTRUCT*)lParam; - // Grabbing everything seems to mess up some keyboard state that - // FLTK relies on, so just grab the keys that we normally cannot. - if (is_system_hotkey(msgInfo->vkCode)) { - PostMessage(target_wnd, wParam, msgInfo->vkCode, - (msgInfo->scanCode & 0xff) << 16 | - (msgInfo->flags & 0xff) << 24); + BYTE vkey; + BYTE scanCode; + BYTE flags; + + vkey = msgInfo->vkCode; + scanCode = msgInfo->scanCode; + flags = msgInfo->flags; + + // We get the low level vkeys here, but the application code + // expects this to have been translated to the generic ones + switch (vkey) { + case VK_LSHIFT: + case VK_RSHIFT: + vkey = VK_SHIFT; + // The extended bit is also always missing for right shift + flags &= ~0x01; + break; + case VK_LCONTROL: + case VK_RCONTROL: + vkey = VK_CONTROL; + break; + case VK_LMENU: + case VK_RMENU: + vkey = VK_MENU; + break; + } + + // If the key was pressed before the grab was activated, then we + // need to avoid intercepting the release event or Windows will get + // confused about the state of the key + if (((wParam == WM_KEYUP) || (wParam == WM_SYSKEYUP)) && + (kbd_state[msgInfo->vkCode] & 0x80)) { + kbd_state[msgInfo->vkCode] &= ~0x80; + } else { + PostMessage(target_wnd, wParam, vkey, + scanCode << 16 | flags << 24); return 1; } } @@ -76,6 +87,9 @@ static DWORD WINAPI keyboard_thread(LPVOID data) // Make sure a message queue is created PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE | PM_NOYIELD); + // We need to know which keys are currently pressed + GetKeyboardState(kbd_state); + hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook, GetModuleHandle(0), 0); // If something goes wrong then there is not much we can do. // Just sit around and wait for WM_QUIT... diff --git a/win/rfb_win32/RegConfig.cxx b/win/rfb_win32/RegConfig.cxx index 7d80871f..057882b9 100644 --- a/win/rfb_win32/RegConfig.cxx +++ b/win/rfb_win32/RegConfig.cxx @@ -93,17 +93,21 @@ void RegConfig::processEvent(HANDLE /*event*/) { } -RegConfigThread::RegConfigThread() : config(&eventMgr), thread_id(-1) { +RegConfigThread::RegConfigThread() : config(&eventMgr), thread(nullptr), + thread_id(-1) { } RegConfigThread::~RegConfigThread() { PostThreadMessage(thread_id, WM_QUIT, 0, 0); - wait(); + if (thread != nullptr) { + thread->join(); + delete thread; + } } bool RegConfigThread::start(const HKEY rootKey, const char* keyname) { if (config.setKey(rootKey, keyname)) { - Thread::start(); + thread = new std::thread(&RegConfigThread::worker, this); while (thread_id == (DWORD)-1) Sleep(0); return true; diff --git a/win/rfb_win32/RegConfig.h b/win/rfb_win32/RegConfig.h index 89e724f8..4a2f8ca8 100644 --- a/win/rfb_win32/RegConfig.h +++ b/win/rfb_win32/RegConfig.h @@ -24,7 +24,7 @@ #ifndef __RFB_WIN32_REG_CONFIG_H__ #define __RFB_WIN32_REG_CONFIG_H__ -#include <core/Thread.h> +#include <thread> #include <rfb_win32/Registry.h> #include <rfb_win32/EventManager.h> @@ -63,7 +63,7 @@ namespace rfb { RegKey key; }; - class RegConfigThread : core::Thread { + class RegConfigThread { public: RegConfigThread(); ~RegConfigThread(); @@ -71,9 +71,10 @@ namespace rfb { // Start the thread, reading from the specified key bool start(const HKEY rootkey, const char* keyname); protected: - void worker() override; + void worker(); EventManager eventMgr; RegConfig config; + std::thread* thread; DWORD thread_id; }; diff --git a/win/rfb_win32/SecurityPage.cxx b/win/rfb_win32/SecurityPage.cxx index f9321160..94a88492 100644 --- a/win/rfb_win32/SecurityPage.cxx +++ b/win/rfb_win32/SecurityPage.cxx @@ -123,9 +123,6 @@ SecurityPage::onOk() { bool vnc_loaded = false; list<uint32_t> secTypes; - /* Keep same priorities as in common/rfb/SecurityClient::secTypes */ - secTypes.push_back(secTypeVeNCrypt); - #ifdef HAVE_GNUTLS /* X509Plain */ if (authMethodEnabled(IDC_ENC_X509, IDC_AUTH_PLAIN)) { diff --git a/win/rfb_win32/WMHooks.cxx b/win/rfb_win32/WMHooks.cxx index 6b444166..e3285948 100644 --- a/win/rfb_win32/WMHooks.cxx +++ b/win/rfb_win32/WMHooks.cxx @@ -22,9 +22,10 @@ #include <config.h> #endif +#include <mutex> +#include <thread> + #include <core/LogWriter.h> -#include <core/Mutex.h> -#include <core/Thread.h> #include <rfb_win32/WMHooks.h> #include <rfb_win32/Service.h> @@ -115,21 +116,23 @@ error: } -class WMHooksThread : public Thread { +class WMHooksThread { public: - WMHooksThread() : active(true), thread_id(-1) { } + WMHooksThread() : active(true), thread(WMHooksThread::worker, this), + thread_id(-1) { } void stop(); DWORD getThreadId() { return thread_id; } protected: - void worker() override; + void worker(); protected: bool active; + std::thread thread; DWORD thread_id; }; static WMHooksThread* hook_mgr = nullptr; static std::list<WMHooks*> hooks; -static Mutex hook_mgr_lock; +static std::mutex hook_mgr_lock; static bool StartHookThread() { @@ -139,7 +142,6 @@ static bool StartHookThread() { return false; vlog.debug("Creating thread"); hook_mgr = new WMHooksThread(); - hook_mgr->start(); while (hook_mgr->getThreadId() == (DWORD)-1) Sleep(0); vlog.debug("Installing hooks"); @@ -167,7 +169,7 @@ static void StopHookThread() { static bool AddHook(WMHooks* hook) { vlog.debug("Adding hook"); - AutoMutex a(&hook_mgr_lock); + const std::lock_guard<std::mutex> lock(hook_mgr_lock); if (!StartHookThread()) return false; hooks.push_back(hook); @@ -177,7 +179,7 @@ static bool AddHook(WMHooks* hook) { static bool RemHook(WMHooks* hook) { { vlog.debug("Removing hook"); - AutoMutex a(&hook_mgr_lock); + const std::lock_guard<std::mutex> lock(hook_mgr_lock); hooks.remove(hook); } StopHookThread(); @@ -185,7 +187,7 @@ static bool RemHook(WMHooks* hook) { } static void NotifyHooksRegion(const Region& r) { - AutoMutex a(&hook_mgr_lock); + const std::lock_guard<std::mutex> lock(hook_mgr_lock); std::list<WMHooks*>::iterator i; for (i=hooks.begin(); i!=hooks.end(); i++) (*i)->NotifyHooksRegion(r); @@ -302,7 +304,7 @@ WMHooksThread::stop() { active = false; PostThreadMessage(thread_id, WM_QUIT, 0, 0); vlog.debug("Waiting for WMHooks thread"); - wait(); + thread.join(); } // -=- WMHooks class @@ -324,7 +326,7 @@ bool rfb::win32::WMHooks::setEvent(HANDLE ue) { bool rfb::win32::WMHooks::getUpdates(UpdateTracker* ut) { if (!updatesReady) return false; - AutoMutex a(&hook_mgr_lock); + const std::lock_guard<std::mutex> lock(hook_mgr_lock); updates.copyTo(ut); updates.clear(); updatesReady = false; @@ -376,12 +378,12 @@ static bool blockRealInputs(bool block_) { return block_ == blocking; } -static Mutex blockMutex; +static std::mutex blockMutex; static int blockCount = 0; bool rfb::win32::WMBlockInput::blockInputs(bool on) { if (active == on) return true; - AutoMutex a(&blockMutex); + const std::lock_guard<std::mutex> lock(blockMutex); int newCount = on ? blockCount+1 : blockCount-1; if (!blockRealInputs(newCount > 0)) return false; diff --git a/win/vncconfig/vncconfig.rc b/win/vncconfig/vncconfig.rc index f4b856dd..e8b50ed1 100644 --- a/win/vncconfig/vncconfig.rc +++ b/win/vncconfig/vncconfig.rc @@ -459,7 +459,7 @@ BEGIN BLOCK "080904b0" BEGIN VALUE "Comments", "\0" - VALUE "CompanyName", "TigerVNC project\0" + VALUE "CompanyName", "TigerVNC team\0" #ifdef WIN64 VALUE "FileDescription", "TigerVNC server configuration applet for Win64\0" VALUE "ProductName", "TigerVNC server configuration applet for Win64\0" diff --git a/win/winvnc/ControlPanel.cxx b/win/winvnc/ControlPanel.cxx index 6c593c45..9041d81f 100644 --- a/win/winvnc/ControlPanel.cxx +++ b/win/winvnc/ControlPanel.cxx @@ -31,9 +31,9 @@ void ControlPanel::initDialog() SendCommand(4, -1); } -bool ControlPanel::onCommand(int cmd) +bool ControlPanel::onCommand(int item, int /*cmd*/) { - switch (cmd) { + switch (item) { case IDC_PROPERTIES: SendMessage(m_hSTIcon, WM_COMMAND, ID_OPTIONS, 0); return false; @@ -122,7 +122,7 @@ BOOL ControlPanel::dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM /*lPara EndDialog(hwnd, 0); return TRUE; default: - return onCommand(LOWORD(wParam)); + return onCommand(LOWORD(wParam), HIWORD(wParam)); } } return FALSE; diff --git a/win/winvnc/ControlPanel.h b/win/winvnc/ControlPanel.h index 23aff0a5..3b994a59 100644 --- a/win/winvnc/ControlPanel.h +++ b/win/winvnc/ControlPanel.h @@ -26,7 +26,7 @@ namespace winvnc { }; virtual bool showDialog(); void initDialog() override; - virtual bool onCommand(int cmd); + bool onCommand(int item, int cmd) override; void UpdateListView(ListConnInfo* LCInfo); HWND GetHandle() {return handle;}; void SendCommand(DWORD command, int data); diff --git a/win/winvnc/QueryConnectDialog.cxx b/win/winvnc/QueryConnectDialog.cxx index 5d609898..1b03af5d 100644 --- a/win/winvnc/QueryConnectDialog.cxx +++ b/win/winvnc/QueryConnectDialog.cxx @@ -48,12 +48,21 @@ QueryConnectDialog::QueryConnectDialog(network::Socket* sock_, const char* userName_, VNCServerWin32* s) : Dialog(GetModuleHandle(nullptr)), - sock(sock_), peerIp(sock->getPeerAddress()), userName(userName_), + thread(nullptr), sock(sock_), peerIp(sock->getPeerAddress()), + userName(userName_?userName_:""), approve(false), server(s) { } +QueryConnectDialog::~QueryConnectDialog() +{ + if (thread != nullptr) { + thread->join(); + delete thread; + } +} + void QueryConnectDialog::startDialog() { - start(); + thread = new std::thread(&QueryConnectDialog::worker, this); } diff --git a/win/winvnc/QueryConnectDialog.h b/win/winvnc/QueryConnectDialog.h index 52a71ce2..102199af 100644 --- a/win/winvnc/QueryConnectDialog.h +++ b/win/winvnc/QueryConnectDialog.h @@ -21,7 +21,7 @@ #ifndef __WINVNC_QUERY_CONNECT_DIALOG_H__ #define __WINVNC_QUERY_CONNECT_DIALOG_H__ -#include <core/Thread.h> +#include <thread> #include <rfb_win32/Dialog.h> @@ -31,15 +31,16 @@ namespace winvnc { class VNCServerWin32; - class QueryConnectDialog : public core::Thread, rfb::win32::Dialog { + class QueryConnectDialog : rfb::win32::Dialog { public: QueryConnectDialog(network::Socket* sock, const char* userName, VNCServerWin32* s); + ~QueryConnectDialog(); virtual void startDialog(); network::Socket* getSock() {return sock;} bool isAccepted() const {return approve;} protected: // Thread methods - void worker() override; + void worker(); // Dialog methods (protected) void initDialog() override; @@ -48,6 +49,7 @@ namespace winvnc { // Custom internal methods void setCountdownLabel(); + std::thread* thread; int countdown; network::Socket* sock; std::string peerIp; diff --git a/win/winvnc/STrayIcon.cxx b/win/winvnc/STrayIcon.cxx index b8a57330..fc079e76 100644 --- a/win/winvnc/STrayIcon.cxx +++ b/win/winvnc/STrayIcon.cxx @@ -28,8 +28,6 @@ #include <core/Configuration.h> #include <core/LogWriter.h> -#include <core/Mutex.h> -#include <core/Thread.h> #include <rfb_win32/LaunchProcess.h> #include <rfb_win32/TrayIcon.h> @@ -217,7 +215,7 @@ public: case WM_SET_TOOLTIP: { - AutoMutex a(thread.lock); + const std::lock_guard<std::mutex> a(thread.lock); if (!thread.toolTip.empty()) setToolTip(thread.toolTip.c_str()); } @@ -239,12 +237,11 @@ protected: STrayIconThread::STrayIconThread(VNCServerWin32& sm, UINT inactiveIcon_, UINT activeIcon_, UINT dis_inactiveIcon_, UINT dis_activeIcon_, UINT menu_) -: thread_id(-1), windowHandle(nullptr), server(sm), +: thread(&STrayIconThread::worker, this), thread_id(-1), + windowHandle(nullptr), server(sm), inactiveIcon(inactiveIcon_), activeIcon(activeIcon_), dis_inactiveIcon(dis_inactiveIcon_), dis_activeIcon(dis_activeIcon_), menu(menu_), runTrayIcon(true) { - lock = new Mutex; - start(); while (thread_id == (DWORD)-1) Sleep(0); } @@ -252,7 +249,7 @@ STrayIconThread::STrayIconThread(VNCServerWin32& sm, UINT inactiveIcon_, UINT ac STrayIconThread::~STrayIconThread() { runTrayIcon = false; PostThreadMessage(thread_id, WM_QUIT, 0, 0); - delete lock; + thread.join(); } void STrayIconThread::worker() { @@ -277,7 +274,7 @@ void STrayIconThread::worker() { void STrayIconThread::setToolTip(const char* text) { if (!windowHandle) return; - AutoMutex a(lock); + const std::lock_guard<std::mutex> a(lock); toolTip = text; PostMessage(windowHandle, WM_SET_TOOLTIP, 0, 0); } diff --git a/win/winvnc/STrayIcon.h b/win/winvnc/STrayIcon.h index 0df8ecab..0398757c 100644 --- a/win/winvnc/STrayIcon.h +++ b/win/winvnc/STrayIcon.h @@ -19,14 +19,16 @@ #ifndef WINVNC_TRAYICON_H #define WINVNC_TRAYICON_H +#include <mutex> +#include <thread> + #include <winvnc/VNCServerWin32.h> #include <core/Configuration.h> -#include <core/Thread.h> namespace winvnc { - class STrayIconThread : core::Thread { + class STrayIconThread { public: STrayIconThread(VNCServerWin32& sm, UINT inactiveIcon, UINT activeIcon, UINT dis_inactiveIcon, UINT dis_activeIcon, UINT menu); @@ -39,9 +41,10 @@ namespace winvnc { friend class STrayIcon; protected: - void worker() override; + void worker(); - core::Mutex* lock; + std::mutex lock; + std::thread thread; DWORD thread_id; HWND windowHandle; std::string toolTip; diff --git a/win/winvnc/VNCServerWin32.cxx b/win/winvnc/VNCServerWin32.cxx index 4c81cec3..b09b6706 100644 --- a/win/winvnc/VNCServerWin32.cxx +++ b/win/winvnc/VNCServerWin32.cxx @@ -28,7 +28,6 @@ #include <winvnc/STrayIcon.h> #include <core/LogWriter.h> -#include <core/Mutex.h> #include <network/TcpSocket.h> @@ -73,11 +72,6 @@ VNCServerWin32::VNCServerWin32() config(&sockMgr), rfbSock(&sockMgr), trayIcon(nullptr), queryConnectDialog(nullptr) { - commandLock = new Mutex; - commandSig = new Condition(commandLock); - - runLock = new Mutex; - // Initialise the desktop desktop.setStatusLocation(&isDesktopStarted); desktop.setQueryConnectionHandler(this); @@ -99,15 +93,8 @@ VNCServerWin32::~VNCServerWin32() { desktop.setStatusLocation(nullptr); // Join the Accept/Reject dialog thread - if (queryConnectDialog) { - queryConnectDialog->wait(); + if (queryConnectDialog) delete queryConnectDialog; - } - - delete runLock; - - delete commandSig; - delete commandLock; } @@ -162,7 +149,7 @@ void VNCServerWin32::regConfigChanged() { int VNCServerWin32::run() { { - AutoMutex a(runLock); + const std::lock_guard<std::mutex> a(runLock); thread_id = GetCurrentThreadId(); runServer = true; } @@ -208,7 +195,7 @@ int VNCServerWin32::run() { } { - AutoMutex a(runLock); + const std::lock_guard<std::mutex> a(runLock); runServer = false; thread_id = (DWORD)-1; } @@ -217,7 +204,7 @@ int VNCServerWin32::run() { } void VNCServerWin32::stop() { - AutoMutex a(runLock); + const std::lock_guard<std::mutex> a(runLock); runServer = false; if (thread_id != (DWORD)-1) PostThreadMessage(thread_id, WM_QUIT, 0, 0); @@ -274,17 +261,17 @@ void VNCServerWin32::queryConnectionComplete() { bool VNCServerWin32::queueCommand(Command cmd, const void* data, int len, bool wait) { - AutoMutex a(commandLock); + std::unique_lock<std::mutex> lock(commandLock); while (command != NoCommand) - commandSig->wait(); + commandSig.wait(lock); command = cmd; commandData = data; commandDataLen = len; SetEvent(commandEvent); if (wait) { while (command != NoCommand) - commandSig->wait(); - commandSig->signal(); + commandSig.wait(lock); + commandSig.notify_one(); } return true; } @@ -295,7 +282,7 @@ void VNCServerWin32::processEvent(HANDLE event_) { if (event_ == commandEvent.h) { // If there is no command queued then return immediately { - AutoMutex a(commandLock); + const std::lock_guard<std::mutex> a(commandLock); if (command == NoCommand) return; } @@ -325,7 +312,6 @@ void VNCServerWin32::processEvent(HANDLE event_) { vncServer.approveConnection(queryConnectDialog->getSock(), queryConnectDialog->isAccepted(), "Connection rejected by user"); - queryConnectDialog->wait(); delete queryConnectDialog; queryConnectDialog = nullptr; break; @@ -336,9 +322,9 @@ void VNCServerWin32::processEvent(HANDLE event_) { // Clear the command and signal completion { - AutoMutex a(commandLock); + std::unique_lock<std::mutex> lock(commandLock); command = NoCommand; - commandSig->signal(); + commandSig.notify_one(); } } else if ((event_ == sessionEvent.h) || (event_ == desktop.getTerminateEvent())) { diff --git a/win/winvnc/VNCServerWin32.h b/win/winvnc/VNCServerWin32.h index 820ac905..656e2cfa 100644 --- a/win/winvnc/VNCServerWin32.h +++ b/win/winvnc/VNCServerWin32.h @@ -19,6 +19,9 @@ #ifndef __VNCSERVER_WIN32_H__ #define __VNCSERVER_WIN32_H__ +#include <condition_variable> +#include <mutex> + #include <winsock2.h> #include <network/TcpSocket.h> #include <rfb/VNCServerST.h> @@ -29,8 +32,6 @@ #include <winvnc/ManagedListener.h> namespace core { - class Mutex; - class Condition; class Thread; } @@ -106,15 +107,15 @@ namespace winvnc { Command command; const void* commandData; int commandDataLen; - core::Mutex* commandLock; - core::Condition* commandSig; + std::mutex commandLock; + std::condition_variable commandSig; rfb::win32::Handle commandEvent; rfb::win32::Handle sessionEvent; // VNCServerWin32 Server-internal state rfb::win32::SDisplay desktop; rfb::VNCServerST vncServer; - core::Mutex* runLock; + std::mutex runLock; DWORD thread_id; bool runServer; bool isDesktopStarted; diff --git a/win/winvnc/winvnc.rc b/win/winvnc/winvnc.rc index 0c756054..acaa0dbd 100644 --- a/win/winvnc/winvnc.rc +++ b/win/winvnc/winvnc.rc @@ -76,7 +76,7 @@ BEGIN BLOCK "080904b0" BEGIN VALUE "Comments", "\0" - VALUE "CompanyName", "TigerVNC project\0" + VALUE "CompanyName", "TigerVNC team\0" VALUE "FileDescription", "TigerVNC server\0" VALUE "ProductName", "TigerVNC server\0" VALUE "FileVersion", __RCVERSIONSTR diff --git a/win/wm_hooks/wm_hooks.rc b/win/wm_hooks/wm_hooks.rc index ae56b314..2bf38f3d 100644 --- a/win/wm_hooks/wm_hooks.rc +++ b/win/wm_hooks/wm_hooks.rc @@ -72,12 +72,12 @@ BEGIN BLOCK "080904b0" BEGIN VALUE "Comments", "\0" - VALUE "CompanyName", "TigerVNC project\0" + VALUE "CompanyName", "TigerVNC team\0" VALUE "FileDescription", "TigerVNC server hooking DLL\0" VALUE "ProductName", "TigerVNC server hooking DLL\0" VALUE "FileVersion", __RCVERSIONSTR VALUE "InternalName", "\0" - VALUE "LegalCopyright", "Copyright (C) 1999-2005 [many holders]\0" + VALUE "LegalCopyright", "Copyright (C) 1999-2025 TigerVNC team and many others (see README.rst)\0" VALUE "LegalTrademarks", "TigerVNC\0" VALUE "OriginalFilename", "wm_hooks.dll\0" VALUE "PrivateBuild", "\0" |