diff options
134 files changed, 4274 insertions, 2402 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 578c49c2..760cab7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,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 @@ -52,21 +60,28 @@ jobs: 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 @@ -103,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 @@ -113,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 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 27dac16f..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) @@ -114,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) @@ -158,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() @@ -218,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") @@ -278,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... @@ -308,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 @@ -349,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() @@ -386,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/CMakeLists.txt b/common/rfb/CMakeLists.txt index e37142f8..d7467421 100644 --- a/common/rfb/CMakeLists.txt +++ b/common/rfb/CMakeLists.txt @@ -67,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) @@ -76,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) @@ -93,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/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/DecodeManager.cxx b/common/rfb/DecodeManager.cxx index 05dfdc73..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,17 +116,17 @@ 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(); @@ -166,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 @@ -176,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(); } @@ -238,7 +229,7 @@ void DecodeManager::logStats() void DecodeManager::setThreadException() { - core::AutoMutex a(queueMutex); + const std::lock_guard<std::mutex> lock(queueMutex); if (threadException) return; @@ -248,7 +239,7 @@ void DecodeManager::setThreadException() void DecodeManager::throwThreadException() { - core::AutoMutex a(queueMutex); + const std::lock_guard<std::mutex> lock(queueMutex); if (!threadException) return; @@ -262,7 +253,7 @@ void DecodeManager::throwThreadException() } DecodeManager::DecodeThread::DecodeThread(DecodeManager* manager_) - : manager(manager_), stopRequested(false) + : manager(manager_), thread(nullptr), stopRequested(false) { start(); } @@ -270,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; @@ -297,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 { @@ -317,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); @@ -325,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/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/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/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 6c3fd051..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 diff --git a/contrib/packages/deb/ubuntu-jammy/debian/rules b/contrib/packages/deb/ubuntu-jammy/debian/rules index 9ed05508..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 diff --git a/contrib/packages/deb/ubuntu-noble/debian/rules b/contrib/packages/deb/ubuntu-noble/debian/rules index 9ed05508..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 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_ @@ -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/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/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 ddb24993..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; @@ -187,6 +186,9 @@ AbortDDX(enum ExitCode error) void OsVendorInit(void) { + /* At this point, display has been set, so we can use it to + * initialize UnixPasswordValidator */ + vncSetDisplayName(display); } void @@ -278,7 +280,7 @@ 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) \ @@ -386,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; @@ -400,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; @@ -446,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 @@ -1171,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(); @@ -1266,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 ba4876f2..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() @@ -322,7 +361,7 @@ void CConn::setExtendedDesktopSize(unsigned reason, unsigned result, void CConn::setName(const char* name) { CConnection::setName(name); - desktop->setName(); + desktop->updateCaption(); } // framebufferUpdateStart() is called at the beginning of an update. diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h index b45f58d7..bc30d9b7 100644 --- a/vncviewer/CConn.h +++ b/vncviewer/CConn.h @@ -33,9 +33,11 @@ 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(); 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 2ab6ec14..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,6 +75,9 @@ 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 @@ -80,11 +85,11 @@ static core::LogWriter vlog("DesktopWindow"); static std::set<DesktopWindow *> instances; DesktopWindow::DesktopWindow(int w, int h, CConn* cc_) - : Fl_Window(w, h), cc(cc_), offscreen(nullptr), overlay(nullptr), + : 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) { @@ -108,7 +113,7 @@ DesktopWindow::DesktopWindow(int w, int h, CConn* cc_) callback(handleClose, this); - setName(); + updateCaption(); OptionsDialog::addCallback(handleOptions, this); @@ -227,8 +232,16 @@ DesktopWindow::DesktopWindow(int w, int h, CConn* cc_) 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 @@ -249,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; @@ -282,7 +296,7 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF() } -void DesktopWindow::setName() +void DesktopWindow::updateCaption() { const size_t maxLen = 100; std::string windowName; @@ -292,7 +306,10 @@ void DesktopWindow::setName() // FIXME: All of this consideres bytes, not characters - labelFormat = "%s - TigerVNC"; + 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 @@ -533,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 @@ -573,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); } } @@ -698,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; - // Empty string means None, for backward compatibility - if ((menuKey != "") && (menuKey != "None")) { - self->setOverlay(_("Press %s to open the context menu"), - menuKey.getValueStr().c_str()); + va_start(ap, text); + vsnprintf(textbuf, sizeof(textbuf), text, ap); + textbuf[sizeof(textbuf)-1] = '\0'; + va_end(ap); + + // 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; @@ -739,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 @@ -755,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; @@ -768,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; @@ -804,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); } @@ -850,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 @@ -945,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: @@ -995,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); } @@ -1078,20 +1152,8 @@ 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(); @@ -1111,34 +1173,38 @@ 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 @@ -1148,14 +1214,25 @@ void DesktopWindow::grabKeyboard() 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; + } + } + + if (ret) { + vlog.error(_("Failure grabbing control of the keyboard")); + addOverlayError(_("Failure grabbing control of the keyboard")); + return; } - return; } #endif @@ -1163,21 +1240,31 @@ 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()) @@ -1214,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() { @@ -1547,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 19c41fe1..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> @@ -47,7 +49,7 @@ public: void updateWindow(); // Updated session title - void setName(); + void updateCaption(); // Resize the current framebuffer, but retain the contents void resizeFramebuffer(int new_w, int new_h); @@ -78,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); @@ -90,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); @@ -123,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; @@ -137,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 78c82787..aeab4e71 100644 --- a/vncviewer/Keyboard.h +++ b/vncviewer/Keyboard.h @@ -21,6 +21,8 @@ #include <stdint.h> +#include <list> + class KeyboardHandler { public: @@ -38,6 +40,7 @@ public: 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 d7626b67..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 @@ -38,6 +36,7 @@ public: 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; @@ -48,8 +47,7 @@ protected: 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 29bc74f6..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 }, @@ -174,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); @@ -196,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; @@ -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 8a91c2d0..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,32 @@ 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; @@ -95,9 +122,22 @@ bool KeyboardX11::isKeyboardReset(const void* event) if (xevent->type == FocusOut) { if (xevent->xfocus.mode == NotifyGrab) { - // Something grabbed the keyboard, but we don't know who. Might be - // us, but might be the window manager. Be cautious and assume the - // latter and report that the keyboard state was reset. + 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; } } @@ -116,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); @@ -127,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; } @@ -134,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; @@ -237,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 a4cbea04..b3b8d0a0 100644 --- a/vncviewer/KeyboardX11.h +++ b/vncviewer/KeyboardX11.h @@ -30,6 +30,7 @@ public: 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; @@ -38,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 2cffc7be..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 @@ -32,14 +32,20 @@ #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" @@ -48,7 +54,6 @@ #include "DesktopWindow.h" #include "i18n.h" #include "parameters.h" -#include "menukey.h" #include "vncviewer.h" #include "PlatformPixelBuffer.h" @@ -77,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 @@ -91,7 +96,7 @@ static const int FAKE_KEY_CODE = 0xffff; 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) @@ -130,7 +135,11 @@ Viewport::Viewport(int w, int h, 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); @@ -673,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; @@ -704,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; @@ -766,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); @@ -786,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 @@ -809,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) @@ -860,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); @@ -892,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 af74d390..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; @@ -99,8 +100,6 @@ private: void initContextMenu(); void popupContextMenu(); - void setMenuKey(); - static void handleOptions(void *data); private: @@ -112,6 +111,10 @@ private: uint16_t lastButtonMask; Keyboard* keyboard; + ShortcutHandler shortcutHandler; + bool shortcutBypass; + bool shortcutActive; + std::set<int> pressedKeys; bool firstLEDState; @@ -119,8 +122,6 @@ private: int clipboardSource; - uint32_t menuKeySym; - int menuKeyCode, menuKeyFLTK; Fl_Menu_Button *contextMenu; bool menuCtrlKey; 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 3db9bd64..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 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" |