Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:15.5:Update
gnome-remote-desktop
gnome-remote-desktop-41.3.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File gnome-remote-desktop-41.3.obscpio of Package gnome-remote-desktop
07070100000000000081A40000000000000000000000016293A07000000010000000000000000000000000000000000000002500000000gnome-remote-desktop-41.3/.gitignoreconfig.h build/ 07070100000001000041ED0000000000000000000000016293A07000000000000000000000000000000000000000000000002500000000gnome-remote-desktop-41.3/.gitlab-ci07070100000002000081A40000000000000000000000016293A070000009DA000000000000000000000000000000000000002900000000gnome-remote-desktop-41.3/.gitlab-ci.ymlinclude: - remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/8445ff7af2a68795afb98f486251f2ef8f90621c/templates/fedora.yml' stages: - prepare - build - test .gnome-remote-desktop.fedora:33@common: variables: FDO_DISTRIBUTION_VERSION: 34 BASE_TAG: '2021-08-06.1' FDO_UPSTREAM_REPO: GNOME/gnome-remote-desktop FDO_DISTRIBUTION_EXEC: | dnf -y update && dnf -y upgrade && dnf install -y 'dnf-command(builddep)' && # To build dnf install -y meson && dnf builddep -y gnome-remote-desktop && # To test dnf install -y 'pkgconfig(libvncclient)' && dnf remove -y pipewire0.2-devel pipewire0.2-libs && dnf install -y 'pkgconfig(libpipewire-0.3)' && dnf install -y 'pkgconfig(fuse3)' && dnf install -y 'pkgconfig(ffnvcodec)' && dnf install -y dbus-daemon xorg-x11-server-Xvfb python3-dbus \ python3-gobject gnome-settings-daemon mesa-dri-drivers \ xorg-x11-server-Xwayland mutter && dnf clean all .gnome-remote-desktop.fedora:33@x86_64: extends: .gnome-remote-desktop.fedora:33@common variables: FDO_DISTRIBUTION_TAG: "x86_64-${BASE_TAG}" default: # Cancel jobs if newer commits are pushed to the branch interruptible: true # Auto-retry jobs in case of infra failures retry: max: 1 when: - 'runner_system_failure' - 'stuck_or_timeout_failure' - 'scheduler_failure' - 'api_failure' build-fedora-container@x86_64: extends: - .fdo.container-build@fedora@x86_64 - .gnome-remote-desktop.fedora:33@x86_64 stage: prepare variables: GIT_STRATEGY: none build-gnome-remote-desktop: extends: - .fdo.distribution-image@fedora - .gnome-remote-desktop.fedora:33@x86_64 stage: build script: - meson . build -Ddebugtype=debugoptimized --werror - ninja -C build - ninja -C build install needs: - build-fedora-container@x86_64 artifacts: expire_in: 1 day paths: - build test-gnome-remote-desktop: extends: - .fdo.distribution-image@fedora - .gnome-remote-desktop.fedora:33@x86_64 stage: test dependencies: - build-gnome-remote-desktop variables: XDG_RUNTIME_DIR: "$CI_PROJECT_DIR/runtime-dir" GSETTINGS_SCHEMA_DIR: "$CI_PROJECT_DIR/build/src" script: - mkdir -m 700 $XDG_RUNTIME_DIR - glib-compile-schemas $GSETTINGS_SCHEMA_DIR - dbus-run-session -- ./.gitlab-ci/run-tests.sh needs: - build-gnome-remote-desktop 07070100000003000081ED0000000000000000000000016293A07000000261000000000000000000000000000000000000003E00000000gnome-remote-desktop-41.3/.gitlab-ci/install-meson-project.sh#!/bin/bash set -e if [[ $# -lt 3 ]]; then echo Usage: $0 [options] [repo-url] [commit] [subdir] echo Options: echo -Dkey=val exit 1 fi MESON_OPTIONS=() while [[ $1 =~ ^-D ]]; do MESON_OPTIONS+=( "$1" ) shift done REPO_URL="$1" TAG_OR_BRANCH="$2" SUBDIR="$3" COMMIT="$4" REPO_DIR="$(basename ${REPO_URL%.git})" git clone --depth 1 "$REPO_URL" -b "$TAG_OR_BRANCH" pushd "$REPO_DIR" pushd "$SUBDIR" if [ ! -z "$COMMIT" ]; then git fetch origin "$COMMIT" git checkout "$COMMIT" fi meson --prefix=/usr _build "${MESON_OPTIONS[@]}" ninja -C _build install popd popd rm -rf "$REPO_DIR" 07070100000004000081ED0000000000000000000000016293A0700000005B000000000000000000000000000000000000003200000000gnome-remote-desktop-41.3/.gitlab-ci/run-tests.sh#!/bin/sh set -x pipewire & meson test -C build --no-rebuild --verbose --no-stdsplit -t 10 07070100000005000081A40000000000000000000000016293A070000046AC000000000000000000000000000000000000002200000000gnome-remote-desktop-41.3/COPYING GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. 07070100000006000081A40000000000000000000000016293A0700000005B000000000000000000000000000000000000002100000000gnome-remote-desktop-41.3/READMEGNOME Remote Desktop ==================== Remote desktop daemon for GNOME using pipewire. 07070100000007000081A40000000000000000000000016293A07000000192000000000000000000000000000000000000002900000000gnome-remote-desktop-41.3/config.h.meson/* config.h. Generated by meson. */ /* Project version */ #mesondefine VERSION /* The prefix for our gettext translation domains. */ #mesondefine GETTEXT_PACKAGE /* Defined if RDP backend enabled */ #mesondefine HAVE_RDP /* Defined if VNC backend is enabled */ #mesondefine HAVE_VNC /* Defined if NVENC is available */ #mesondefine HAVE_NVENC /* Path of the data dir */ #mesondefine GRD_DATA_DIR 07070100000008000041ED0000000000000000000000016293A07000000000000000000000000000000000000000000000001F00000000gnome-remote-desktop-41.3/data07070100000009000081A40000000000000000000000016293A07000000A3B000000000000000000000000000000000000002600000000gnome-remote-desktop-41.3/data/READMEHow to produce the PTX instructions for CUDA kernels ==================================================== For the generation of the PTX instructions, the CUDA toolkit needs to be installed (See for this below in "Retrieving the CUDA toolkit"). Generation: ----------- When the CUDA toolkit is installed, and the current directory is `src`, generate the PTX instructions via: /opt/cuda/bin/./nvcc -arch=compute_30 -ptx grd-cuda-avc-utils.cu -o ../data/grd-cuda-avc-utils_30.ptx The nvcc path differ from OS to OS. In the case above, Archlinux is used, which uses the path `/opt/cuda/bin/nvcc` for nvcc. `-arch=compute_30` tells nvcc to generate instructions for GPUs with compute capability 3.0. CUDA GPUs with higher compute capability can also run CUDA kernels with lower compute capability. The CUDA kernel for gnome-remote-desktop uses compute capability 3.0, as compute capability 3.0 is the one of Kepler GPUs. Kepler GPUs are the first generation GPUs, that support NVENC. To remain compatible with these GPUs, generate the instructions for compute capability 3.0. Also append a suffix, like in the example above, for the compute capability of the generated PTX instructions. If a kernel might be more efficient with newer CUDA features, generate a kernel for the higher necessary compute capability and another one for older GPUs as fallback. Use then the CUDA functions to check the compute capability of the selected GPU at runtime to determine, which PTX instructions should be loaded. Retrieving the CUDA toolkit: ---------------------------- Retrieving the CUDA toolkit depends on the distribution. It should be noted, that the generation of PTX instructions for compute capability 3.0 was removed from the CUDA toolkit version 11. So, an older version of the CUDA toolkit (version 10) is needed to generate PTX instructions for Kepler GPUs. Instructions to retrieve the CUDA toolkit version 10 on Archlinux: ------------------------------------------------------------------ While the current version of the CUDA toolkit can be found in the `community` repository (included by default) under the name `cuda`, the older version 10 is available via the Arch Linux Archive: For this, the easiest way here is to use the `downgrade` tool (AUR): Run `downgrade gcc8 gcc8-libs cuda` (might require root privileges) and choose the latest gcc8 and gcc8-libs version for gcc8 (dependency of CUDA 10) and for CUDA 10 choose the latest CUDA 10 release. The downgrade utility will then download and install these packages. After this, you can use nvcc to generate PTX instructions for Kepler GPUs as well. 0707010000000A000081A40000000000000000000000016293A0700000185D000000000000000000000000000000000000003900000000gnome-remote-desktop-41.3/data/grd-cuda-avc-utils_30.ptx// // Generated by NVIDIA NVVM Compiler // // Compiler Build ID: CL-27506705 // Cuda compilation tools, release 10.2, V10.2.89 // Based on LLVM 3.4svn // .version 6.5 .target sm_30 .address_size 64 // .globl convert_2x2_bgrx_area_to_yuv420_nv12 .visible .entry convert_2x2_bgrx_area_to_yuv420_nv12( .param .u64 convert_2x2_bgrx_area_to_yuv420_nv12_param_0, .param .u64 convert_2x2_bgrx_area_to_yuv420_nv12_param_1, .param .u16 convert_2x2_bgrx_area_to_yuv420_nv12_param_2, .param .u16 convert_2x2_bgrx_area_to_yuv420_nv12_param_3, .param .u16 convert_2x2_bgrx_area_to_yuv420_nv12_param_4, .param .u16 convert_2x2_bgrx_area_to_yuv420_nv12_param_5, .param .u16 convert_2x2_bgrx_area_to_yuv420_nv12_param_6, .param .u16 convert_2x2_bgrx_area_to_yuv420_nv12_param_7 ) { .reg .pred %p<15>; .reg .b16 %rs<23>; .reg .b32 %r<127>; .reg .b64 %rd<36>; ld.param.u64 %rd6, [convert_2x2_bgrx_area_to_yuv420_nv12_param_0]; ld.param.u64 %rd7, [convert_2x2_bgrx_area_to_yuv420_nv12_param_1]; ld.param.u16 %rs7, [convert_2x2_bgrx_area_to_yuv420_nv12_param_2]; ld.param.u16 %rs8, [convert_2x2_bgrx_area_to_yuv420_nv12_param_3]; ld.param.u16 %rs9, [convert_2x2_bgrx_area_to_yuv420_nv12_param_4]; ld.param.u16 %rs10, [convert_2x2_bgrx_area_to_yuv420_nv12_param_6]; ld.param.u16 %rs11, [convert_2x2_bgrx_area_to_yuv420_nv12_param_7]; mov.u32 %r30, %ntid.x; mov.u32 %r31, %ctaid.x; mov.u32 %r32, %tid.x; mad.lo.s32 %r1, %r30, %r31, %r32; mov.u32 %r33, %ntid.y; mov.u32 %r34, %ctaid.y; mov.u32 %r35, %tid.y; mad.lo.s32 %r2, %r33, %r34, %r35; and.b32 %r36, %r1, 65535; ld.param.u16 %r37, [convert_2x2_bgrx_area_to_yuv420_nv12_param_5]; shr.u32 %r38, %r37, 1; and.b32 %r3, %r2, 65535; cvt.u32.u16 %r4, %rs10; shr.u32 %r5, %r4, 1; setp.ge.u32 %p1, %r3, %r5; setp.ge.u32 %p2, %r36, %r38; or.pred %p3, %p1, %p2; @%p3 bra BB0_10; cvta.to.global.u64 %rd8, %rd6; cvt.u32.u16 %r42, %rs9; and.b32 %r43, %r1, 32767; shl.b32 %r44, %r1, 1; and.b32 %r6, %r44, 65534; mov.u32 %r45, 1; shl.b32 %r46, %r2, 1; and.b32 %r47, %r46, 65534; mul.lo.s32 %r48, %r47, %r42; cvt.u64.u32 %rd9, %r48; shl.b32 %r49, %r43, 3; cvt.u64.u32 %rd10, %r49; add.s64 %rd1, %rd9, %rd10; cvta.to.global.u64 %rd11, %rd7; add.s64 %rd2, %rd11, %rd1; setp.lt.u32 %p4, %r47, %r5; shl.b32 %r50, %r2, 2; sub.s32 %r51, %r45, %r4; selp.b32 %r52, 0, %r51, %p4; mov.u32 %r124, 0; add.s32 %r53, %r52, %r50; cvt.u64.u32 %rd12, %r53; and.b64 %rd13, %rd12, 65535; cvt.u64.u16 %rd14, %rs11; mul.lo.s64 %rd15, %rd13, %rd14; cvt.u64.u32 %rd16, %r6; add.s64 %rd17, %rd15, %rd16; add.s64 %rd3, %rd8, %rd17; add.s32 %r7, %r47, 1; and.b32 %r54, %r7, 65535; setp.lt.u32 %p5, %r54, %r5; shl.b32 %r55, %r7, 1; selp.b32 %r56, 0, %r51, %p5; add.s32 %r57, %r56, %r55; cvt.u64.u32 %rd18, %r57; and.b64 %rd19, %rd18, 65535; mul.lo.s64 %rd20, %rd19, %rd14; add.s64 %rd21, %rd20, %rd16; add.s64 %rd4, %rd8, %rd21; shr.u32 %r58, %r4, 2; setp.lt.u32 %p6, %r3, %r58; sub.s32 %r59, %r45, %r5; selp.b32 %r60, 0, %r59, %p6; shl.b32 %r61, %r3, 1; add.s32 %r62, %r61, %r60; cvt.u64.u32 %rd22, %r62; and.b64 %rd23, %rd22, 65535; cvt.u64.u16 %rd24, %rs10; add.s64 %rd25, %rd23, %rd24; mul.lo.s64 %rd26, %rd25, %rd14; add.s64 %rd27, %rd26, %rd16; add.s64 %rd5, %rd8, %rd27; cvt.u32.u16 %r63, %rs7; setp.ge.u32 %p7, %r6, %r63; cvt.u32.u16 %r64, %rs8; setp.ge.u32 %p8, %r47, %r64; mov.u16 %rs21, 0; or.pred %p9, %p7, %p8; mov.u16 %rs20, %rs21; mov.u32 %r125, %r124; mov.u32 %r126, %r124; @%p9 bra BB0_3; ld.global.u8 %rs13, [%rd2]; cvt.u32.u16 %r126, %rs13; ld.global.u8 %r125, [%rd2+1]; ld.global.u8 %r124, [%rd2+2]; mul.wide.u16 %r65, %rs13, 18; mad.lo.s32 %r66, %r125, 183, %r65; mad.lo.s32 %r67, %r124, 54, %r66; shr.u32 %r68, %r67, 8; cvt.u16.u32 %rs20, %r68; BB0_3: and.b32 %r73, %r2, 32767; shl.b32 %r74, %r73, 1; setp.ge.u32 %p10, %r74, %r64; st.global.u8 [%rd3], %rs20; add.s32 %r14, %r6, 1; setp.ge.u32 %p11, %r14, %r63; or.pred %p12, %p11, %p10; @%p12 bra BB0_5; ld.global.u8 %rs15, [%rd2+4]; cvt.u32.u16 %r77, %rs15; add.s32 %r126, %r77, %r126; ld.global.u8 %r78, [%rd2+5]; add.s32 %r125, %r78, %r125; ld.global.u8 %r79, [%rd2+6]; add.s32 %r124, %r79, %r124; mul.wide.u16 %r80, %rs15, 18; mad.lo.s32 %r81, %r78, 183, %r80; mad.lo.s32 %r82, %r79, 54, %r81; shr.u32 %r83, %r82, 8; cvt.u16.u32 %rs21, %r83; BB0_5: st.global.u8 [%rd3+1], %rs21; setp.lt.u32 %p13, %r7, %r64; @%p13 bra BB0_7; bra.uni BB0_6; BB0_7: cvt.u64.u16 %rd28, %rs9; add.s64 %rd29, %rd1, %rd28; add.s64 %rd31, %rd11, %rd29; ld.global.u8 %rs18, [%rd31]; cvt.u32.u16 %r86, %rs18; add.s32 %r126, %r86, %r126; ld.global.u8 %r87, [%rd31+1]; add.s32 %r125, %r87, %r125; ld.global.u8 %r88, [%rd31+2]; add.s32 %r124, %r88, %r124; mul.wide.u16 %r89, %rs18, 18; mad.lo.s32 %r90, %r87, 183, %r89; mad.lo.s32 %r91, %r88, 54, %r90; shr.u32 %r92, %r91, 8; st.global.u8 [%rd4], %r92; mov.u16 %rs22, 0; @%p11 bra BB0_9; add.s32 %r94, %r42, 4; and.b32 %r95, %r94, 65535; cvt.u64.u32 %rd32, %r95; add.s64 %rd33, %rd1, %rd32; add.s64 %rd35, %rd11, %rd33; ld.global.u8 %rs19, [%rd35]; cvt.u32.u16 %r96, %rs19; add.s32 %r126, %r96, %r126; ld.global.u8 %r97, [%rd35+1]; add.s32 %r125, %r97, %r125; ld.global.u8 %r98, [%rd35+2]; add.s32 %r124, %r98, %r124; mul.wide.u16 %r99, %rs19, 18; mad.lo.s32 %r100, %r97, 183, %r99; mad.lo.s32 %r101, %r98, 54, %r100; shr.u32 %r102, %r101, 8; cvt.u16.u32 %rs22, %r102; bra.uni BB0_9; BB0_6: mov.u16 %rs22, 0; st.global.u8 [%rd4], %rs22; BB0_9: st.global.u8 [%rd4+1], %rs22; bfe.u32 %r103, %r124, 2, 8; mul.lo.s32 %r104, %r103, -29; bfe.u32 %r105, %r125, 2, 8; mad.lo.s32 %r106, %r105, -99, %r104; bfe.u32 %r107, %r126, 2, 8; shl.b32 %r108, %r107, 7; add.s32 %r109, %r106, %r108; shr.u32 %r110, %r109, 8; add.s32 %r111, %r110, 128; st.global.u8 [%rd5], %r111; shl.b32 %r112, %r124, 5; and.b32 %r113, %r112, 32640; mad.lo.s32 %r114, %r105, -116, %r113; mad.lo.s32 %r115, %r107, -12, %r114; shr.u32 %r116, %r115, 8; add.s32 %r117, %r116, 128; st.global.u8 [%rd5+1], %r117; BB0_10: ret; } 0707010000000B000081A40000000000000000000000016293A07000000064000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/data/meson.buildif have_nvenc install_data(['grd-cuda-avc-utils_30.ptx'], install_dir: grd_datadir, ) endif 0707010000000C000081A40000000000000000000000016293A0700000042A000000000000000000000000000000000000003400000000gnome-remote-desktop-41.3/gnome-remote-desktop.doap<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:gnome="http://api.gnome.org/doap-extensions#" xmlns="http://usefulinc.com/ns/doap#"> <name xml:lang="en">gnome-remote-desktop</name> <shortdesc xml:lang="en">GNOME Remote Desktop</shortdesc> <description>Remote desktop daemon for GNOME using pipewire.</description> <!-- <homepage rdf:resource="http://www.gnome.org/" /> --> <download-page rdf:resource="http://download.gnome.org/sources/gnome-remote-desktop/" /> <bug-database rdf:resource="https://gitlab.gnome.org/GNOME/gnome-remote-desktop/issues/" /> <category rdf:resource="http://api.gnome.org/doap-extensions#core" /> <programming-language>C</programming-language> <maintainer> <foaf:Person> <foaf:name>Jonas Ådahl</foaf:name> <foaf:mbox rdf:resource="mailto:jadahl@gmail.com" /> <gnome:userid>jadahl</gnome:userid> </foaf:Person> </maintainer> </Project> 0707010000000D000081A40000000000000000000000016293A07000000EA5000000000000000000000000000000000000002600000000gnome-remote-desktop-41.3/meson.buildproject('gnome-remote-desktop', 'c', version: '41.3', meson_version: '>= 0.47.0', default_options: ['warning_level=1', 'buildtype=debugoptimized']) freerdp_req = '>= 2.3.0' fuse_req = '>= 3.9.1' nvenc_req = '>= 11' xkbcommon_req = '>= 1.0.0' gnome = import('gnome') i18n = import('i18n') cc = meson.get_compiler('c') cairo_dep = dependency('cairo') glib_dep = dependency('glib-2.0', version: '>= 2.68') gio_dep = dependency('gio-2.0') gio_unix_dep = dependency('gio-unix-2.0') pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.3.0') systemd_dep = dependency('systemd', required: get_option('systemd')) libsecret_dep = dependency('libsecret-1') libnotify_dep = dependency('libnotify') have_rdp = get_option('rdp') have_vnc = get_option('vnc') have_nvenc = get_option('nvenc') if not have_rdp and not have_vnc error('Must enable at least one backend') endif if have_nvenc and not have_rdp error('Support for hardware acceleration using NVENC requires the RDP backend') endif if have_rdp add_global_arguments('-D_GNU_SOURCE', language : 'c') freerdp_dep = dependency('freerdp2', version: freerdp_req) freerdp_client_dep = dependency('freerdp-client2', version: freerdp_req) freerdp_server_dep = dependency('freerdp-server2', version: freerdp_req) fuse_dep = dependency('fuse3', version: fuse_req) winpr_dep = dependency('winpr2', version: freerdp_req) xkbcommon_dep = dependency('xkbcommon', version: xkbcommon_req) if have_nvenc dl_dep = cc.find_library('dl', required: true) nvenc_dep = dependency('ffnvcodec', version: nvenc_req) endif endif if have_vnc libvncserver_dep = dependency('libvncserver') libvncclient_dep = dependency('libvncclient') endif prefix = get_option('prefix') libexecdir = join_paths(prefix, get_option('libexecdir')) datadir = join_paths(prefix, get_option('datadir')) schemadir = join_paths(datadir, 'glib-2.0', 'schemas') grd_datadir = join_paths(datadir, 'gnome-remote-desktop') cdata = configuration_data() cdata.set_quoted('GETTEXT_PACKAGE', 'gnome-remote-desktop') cdata.set_quoted('VERSION', meson.project_version()) cdata.set('HAVE_RDP', have_rdp) cdata.set('HAVE_VNC', have_vnc) cdata.set('HAVE_NVENC', have_nvenc) cdata.set_quoted('GRD_DATA_DIR', grd_datadir) configure_file(input: 'config.h.meson', output: 'config.h', configuration: cdata) configinc = include_directories('.') servicedir = get_option('systemd_user_unit_dir') if systemd_dep.found() if servicedir == '' servicedir = systemd_dep.get_pkgconfig_variable('systemduserunitdir') endif if servicedir == '' error('Couldn\'t determine systemd user unit service directory') endif endif top_srcdir = meson.current_source_dir() builddir = meson.current_build_dir() subdir('data') subdir('src') subdir('tests') subdir('po') meson.add_install_script('meson_post_install.py') output = [ '', '', ' GNOME Remote Desktop ' + meson.project_version(), ' ============================', '', ' Prefix....................... ' + prefix, ' libexecdir................... ' + libexecdir, ' datadir...................... ' + datadir, ' systemd user unit dir........ ' + servicedir, ' GSettings schema dir......... ' + schemadir, '', ' Backends:', '', ' RDP...................... ' + have_rdp.to_string(), ' VNC...................... ' + have_vnc.to_string(), '', ' Options for the RDP backend:', '', ' Support for hardware acceleration using NVENC and CUDA........' + have_nvenc.to_string(), '', ' Now type \'ninja -C ' + meson.build_root() + '\' to build ' + meson.project_name(), '', '', ] message('\n'.join(output)) 0707010000000E000081A40000000000000000000000016293A07000000254000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/meson_options.txtoption('rdp', type: 'boolean', value: true, description: 'Enable the RDP backend') option('vnc', type: 'boolean', value: true, description: 'Enable the VNC backend') option('nvenc', type: 'boolean', value: true, description: 'Build with support for hardware acceleration using NVENC and CUDA') option('systemd', type: 'boolean', value: true, description: 'Enable systemd support') option('systemd_user_unit_dir', type: 'string', value: '', description: 'systemd user service directory') 0707010000000F000081A40000000000000000000000016293A0700000013A000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/meson_post_install.py#!/usr/bin/env python3 import os import subprocess install_prefix = os.environ['MESON_INSTALL_PREFIX'] schemadir = os.path.join(install_prefix, 'share', 'glib-2.0', 'schemas') if not os.environ.get('DESTDIR'): print('Compiling gsettings schemas...') subprocess.call(['glib-compile-schemas', schemadir]) 07070100000010000041ED0000000000000000000000016293A07000000000000000000000000000000000000000000000001D00000000gnome-remote-desktop-41.3/po07070100000011000081A40000000000000000000000016293A070000000A6000000000000000000000000000000000000002500000000gnome-remote-desktop-41.3/po/LINGUAS# please keep this list sorted alphabetically # bg ca cs de el en_GB es eu fa fi fr fur gl he hr hu id is kk ko lt nl oc pl pt pt_BR ro ru sk sl sr sv tr uk vi zh_CN 07070100000012000081A40000000000000000000000016293A07000000056000000000000000000000000000000000000002900000000gnome-remote-desktop-41.3/po/POTFILES.insrc/grd-daemon.c src/grd-prompt.c src/org.gnome.desktop.remote-desktop.gschema.xml.in 07070100000013000081A40000000000000000000000016293A07000001252000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/bg.po# Bulgarian translation of gnome-remote-desktop po-file. # Copyright (C) 2021 Alexander Shopov. # This file is distributed under the same license as the gnome-remote-desktop package. # Alexander Shopov <ash@kambanaria.org>, 2012, 2015, 2016, 2017. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-10-03 17:07+0000\n" "PO-Revision-Date: 2021-10-04 09:59+0200\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/grd-daemon.c:365 msgid "GNOME Remote Desktop" msgstr "Отдалечена работна среда за GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Искате ли да споделите работната среда?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Потребител на компютъра „%s“ се опитва да управлява отдалечено работната ви " "среда." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Отказване" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Приемане" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Път към файла със сертификат" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "За да ползвате RDP със защита TLS, трябва да предоставите на сървъра за RDP " "както частния ключ, така и сертификата." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Път към файла с частния ключ" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Позволяване само на отдалечените връзки да виждат съдържанието на екрана" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Когато е зададено, отдалечените връзки по RDP не управляват входните " "устройства (като мишки и клавиатури)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Когато е зададено, отдалечените връзки по VNC не управляват входните " "устройства (като мишки и клавиатури)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Метод за идентификация за връзките по VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Методът за идентификация на VNC указва как тя да се извърши при установяване " "на връзка. В момента има два начина: • питане — потребителят бива изрично " "питан при всяка нова връзка, което задължава човек с физически достъп до " "машината да одобри връзката; • парола — отдалеченият потребител трябва да се " "идентифицира чрез предварително уговорена парола" 07070100000014000081A40000000000000000000000016293A07000000EDD000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/ca.po# Catalan translation for gnome-remote-desktop. # Jordi Mas i Hernàndez <jmas@softcatala.org>, 2021 # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-06-21 17:34+0000\n" "Last-Translator: Jordi Mas i Hernàndez <jmas@softcatala.org>\n" "Language-Team: Catalan <gnome@llistes.softcatala.org>\n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Escriptori remot del GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Voleu compartir l'escriptori?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un usuari de l'ordinador «%s» està intentant visualitzar o controlar " "remotament l'escriptori." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Rebutja" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Accepta" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Camí al fitxer del certificat" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Per a poder utilitzar RDP amb seguretat TLS, tant el fitxer de la clau " "privada com el fitxer del certificat s'han de proporcionar al servidor RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Camí al fitxer de clau privada" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Permet només a les connexions remotes per a veure el contingut de la pantalla" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Quan «view-only» és cert, les connexions RDP remotes no poden manipular els " "dispositius d'entrada (p. ex. ratolí i teclat)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Quan «view-only» és cert, les connexions VNC remotes no poden manipular els " "dispositius d'entrada (p. ex. ratolí i teclat)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Mètode utilitzat per a autenticar les connexions VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "El mètode d'autenticació VNC descriu com s'autentica una connexió remota. " "Actualment, es pot fer de dues maneres diferents: * «prompt» - demanant a " "l'usuari per a cada nova connexió, requerint a una persona amb accés físic a " "l'estació de treball que aprovi explícitament la nova connexió. * «password» " "- demanant al client remot que proporcioni una contrasenya coneguda" 07070100000015000081A40000000000000000000000016293A07000000FE2000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/cs.po# Czech translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # # Marek Černocký <marek@manet.cz>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-08-19 09:29+0000\n" "PO-Revision-Date: 2021-08-22 11:18+0200\n" "Last-Translator: Marek Černocký <marek@manet.cz>\n" "Language-Team: Czech <gnome-cs-list@gnome.org>\n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\n" "X-Generator: Gtranslator 3.38.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Vzdálená plocha GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Přejete si sdílet svoji plochu?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Uživatel na počítači „%s“ zkouší vzdáleně zobrazit nebo ovládat vaše " "uživatelské prostředí." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Zamítnout" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Přijmout" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Cesta k souboru s cerifikátem" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Aby protokol RDP bylo možné použít zabezpečený pomocí TLS, musí být serveru " "RDP poskytnuty soubory se soukromým klíčem a certifikátem." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Cesta k souboru se soukromým klíčem" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Vzdálenému připojení umožnit pouze zobrazení obsahu obrazovky" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Když je zapnuto „pouze zobrazení“, vzdálené připojení RDP nebude mít " "kontrolu nad vstupními zařízeními (např. myší a klávesnicí)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Když je zapnuto „pouze zobrazení“, vzdálené připojení VNC nebude mít " "kontrolu nad vstupními zařízeními (např. myší a klávesnicí)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metoda použitá k ověření spojení VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Metoda ověření VNC určuje, jak je ověřováno vzdálené připojení. V " "současnosti může být provedeno dvěma různými způsoby: • prompt – výzvou " "uživateli při každém novém připojení, kdy je nutná uživatelova fyzická " "přítomnost u zařízení, aby připojení výslovně schválil. • password – " "požaduje po vzdáleném klientovi znalost hesla" 07070100000016000081A40000000000000000000000016293A07000000FAA000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/de.po# German translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # # Philipp Kiemle <philipp.kiemle@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-13 09:47+0000\n" "PO-Revision-Date: 2021-08-12 21:48+0200\n" "Last-Translator: Tim Sabsch <tim@sabsch.com>\n" "Language-Team: German <gnome-de@gnome.org>\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Remote Desktop" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Möchten Sie Ihren Desktop teilen?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Ein Benutzer am Rechner »%s« versucht, Ihren Desktop aus der Ferne zu " "betrachten oder zu steuern." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Abweisen" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Akzeptieren" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Pfad zur Zertifikatsdatei" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Um RDP mit TLS-Sicherheit nutzen zu können, müssen sowohl die private " "Schlüsseldatei als auch die Zertifikatsdatei dem RDP-Server bereitgestellt " "werden." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Pfad zur privaten Schlüsseldatei" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Entfernten Verbindungen lediglich das Ansehen des Bildschirminhalts erlauben" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Wenn »Nur ansehen« wahr ist, können entfernte RDP-Verbindungen keine " "Eingabegeräte manipulieren (z.B. Maus und Tastatur)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Wenn »Nur ansehen« wahr ist, können entfernte VNC-Verbindungen keine " "Eingabegeräte manipulieren (z.B. Maus und Tastatur)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Zur Authentifizierung von VNC-Verbindungen genutzte Methode" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Die VNC-Authentifizierungsmethode gibt an, wie eine entfernte Verbindung " "verifiziert wird. Dies kann aktuell auf zwei verschiedene Arten geschehen: * " "Aufforderung - indem der Benutzer jedes Mal aufgefordert wird, die " "Verbindung zu bestätigen. Das erfordert physischen Zugang zum entfernten " "Rechner. * Passwort - indem der entfernte Benutzer ein bekanntes Passwort " "benötigt" 07070100000017000081A40000000000000000000000016293A07000001474000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/el.po# Greek translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Efstathios Iosifidis <eiosifidis@gnome.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-28 15:25+0000\n" "PO-Revision-Date: 2021-08-01 19:43+0300\n" "Language-Team: Greek <gnome-el-list@gnome.org>\n" "Language: el\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Last-Translator: Efstathios Iosifidis <eiosifidis@gnome.org>\n" "X-Generator: Poedit 2.3\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Απομακρυσμένη επιφάνεια εργασίας GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Θέλετε να διαμοιραστείτε την επιφάνεια εργασίας σας;" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Ένας χρήστης στον υπολογιστή «%s» προσπαθεί να δει ή να ελέγξει " "απομακρυσμένα την επιφάνεια εργασίας σας." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Άρνηση" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Αποδοχή" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Διαδρομή για το αρχείο πιστοποίησης" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Για να μπορέσετε να χρησιμοποιήσετε το RDP με την ασφάλεια TLS, τόσο το " "αρχείο ιδιωτικού κλειδιού όσο και το αρχείο πιστοποιητικού πρέπει να " "παρέχονται στον διακομιστή RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Διαδρομή για το αρχείο προσωπικού κλειδιού" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Να επιτρέπεται μόνο σε απομακρυσμένες συνδέσεις η προβολή του περιεχομένου " "της οθόνης" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Όταν είναι αληθές το μόνο για προβολή, οι απομακρυσμένες συνδέσεις RDP δεν " "μπορούν να χειριστούν συσκευές εισόδου (π.χ. ποντίκι και πληκτρολόγιο)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Όταν είναι αληθές το μόνο για προβολή, οι απομακρυσμένες συνδέσεις VNC δεν " "μπορούν να χειριστούν συσκευές εισόδου (π.χ. ποντίκι και πληκτρολόγιο)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Μέθοδος που χρησιμοποιείται για την πιστοποίηση συνδέσεων VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Η μέθοδος πιστοποίησης VNC περιγράφει τον τρόπο που πιστοποιείται μια " "απομακρυσμένη σύνδεση. Προς το παρόν μπορεί να γίνει με δύο διαφορετικούς " "τρόπους: * προτροπή - προτρέποντας τον χρήστη για κάθε νέα σύνδεση, " "απαιτώντας από ένα άτομο με φυσική πρόσβαση στο σταθμό εργασίας να εγκρίνει " "τη νέα σύνδεση. * συνθηματικό - απαιτώντας από τον απομακρυσμένο πελάτη να " "παρέχει ένα γνωστό συνθηματικό" 07070100000018000081A40000000000000000000000016293A07000000F27000000000000000000000000000000000000002600000000gnome-remote-desktop-41.3/po/en_GB.po# British English translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Zander Brown <zbrown@gnome.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-28 15:25+0000\n" "PO-Revision-Date: 2021-07-31 20:00+0100\n" "Last-Translator: Zander Brown <zbrown@gnome.org>\n" "Language-Team: English - United Kingdom <en_GB@li.org>\n" "Language: en_GB\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 40.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Remote Desktop" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Do you want to share your desktop?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Refuse" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Accept" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Path to the certificate file" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "To be able to use RDP with TLS Security, both the private key file and the " "certificate file need to be provided to the RDP server." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Path to the private key file" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Only allow remote connections to view the screen content" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Method used to authenticate VNC connections" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" 07070100000019000081A40000000000000000000000016293A0700000106D000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/es.po# Spanish translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # FULL NAME <EMAIL@ADDRESS>, 2021. # Daniel Mustieles <daniel.mustieles@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-07-13 11:41+0200\n" "Last-Translator: Daniel Mustieles <daniel.mustieles@gmail.com>\n" "Language-Team: Spanish - Spain <gnome-es-list@gnome.org>\n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 40.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Escritorio remoto GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "¿Quiere compartir su escritorio?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un usuario del equipo «%s» está intentando ver o controlar en remoto su " "equipo." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Rechazar" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Aceptar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Ruta al archivo del certificado" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 #| msgid "" #| "In Order to be able to use RDP with TLS Security, both the private key " #| "file and the certificate file need to be provided to the RDP server." msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Para poder usar RDP con seguridad TLS, los archivos de la clave privada y " "del certificado los debe proporcionar en servidor RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Ruta al archivo de clave privada" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Permitir sólo las conexiones para ver el contenido de la pantalla" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Cuando el modo de sólo lectura es cierto, las conexiones RDP remotas no " "pueden manipular los dispositivos de entrada (ej. teclado y ratón)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Cuando el modo de sólo lectura es cierto, las conexiones VNC remotas no " "pueden manipular los dispositivos de entrada (ej. teclado y ratón)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Método usado para autenticar conexiones VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "El método de autenticación VNC describe cómo se autentica una conexión " "remota. Actualmente esto se puede hacer de dos maneras diferentes: * prompt " "- preguntar siempre al usuario por cada nueva conexión, solicitando que una " "persona con acceso físico al equipo apruebe dicha conexión. * password - " "solicitar al cliente remoto que introduzca una contraseña conocida" 0707010000001A000081A40000000000000000000000016293A07000000F21000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/eu.po# Basque translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Asier Sarasua Garmendia <asiersarasua@ni.eus>, 2021. # msgid "" msgstr "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/issues\n" "POT-Creation-Date: 2021-06-26 19:16+0000\n" "PO-Revision-Date: 2021-06-26 19:16+0000\n" "Last-Translator: Asier Sarasua Garmendia <asiersarasua@ni.eus>\n" "Language-Team: Basque <librezale@librezale.eus>\n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Urruneko Mahaigaina" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Zure mahaigaina partekatu nahi al duzu?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "'%s' ordenagailuko erabiltzaile bat zure mahaigaina urrunetik ikusteko edo kontrolatzeko saiakera egiten ari da." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Ukatu" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Onartu" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Ziurtagiri-fitxategiaren bide-izena" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "RDP eta TLS segurtasuna erabiltzeko, gako pribatuaren fitxategia zein ziurtagiri-fitxategia, biak eman behar ditu RDP zerbitzariak." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Gako pribatuaren fitxategiaren bide-izena" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Onartu urruneko konexioek pantailaren edukia soilik ikus dezaten soilik" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "Soilik ikusteko aukera egia denean, urruneko RDP konexioek ezin dituzte manipulatu sarrera-gailuak (adibidez, sagua edo teklatu)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "Soilik ikusteko aukera egia denean, urruneko VNC konexioek ezin dituzte manipulatu sarrera-gailuak (adibidez, sagua edo teklatu)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "VNC konexioak autentifikatzeko erabili den metodoa" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "VNC autentifikazio-metodoak urruneko konexio bat nola autentifikatzen den deskribatzen du. Bi modutara egin daiteke: * prompt - konexio berri bakoitzerako, erabiltzaileak baimena eskatzea eta ordenagailura sarbide fisikoa duen pertsona bateak esplizituki konexio berria onartzea. * password - urruneko bezeroak pasahitz ezagun bat ematea" 0707010000001B000081A40000000000000000000000016293A070000011B6000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/fa.po# Persian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Danial Behzadi <dani.behzi@ubuntu.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-09-01 15:16+0430\n" "Last-Translator: Danial Behzadi <dani.behzi@ubuntu.com>\n" "Language-Team: Persian <fa@li.org>\n" "Language: fa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.2\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "میزکار دوردست گنوم" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "میخواهید میزکارتان را همرسانی کنید؟" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your desktop." msgstr "" "کاربری روی رایانهٔ «%s» در تلاش برای دیدن یا واپایش میزکارتان از راه دور است." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "رد" #: src/grd-prompt.c:136 msgid "Accept" msgstr "پذیرش" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "مسیر پروندهٔ گواهینامه" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file and " "the certificate file need to be provided to the RDP server." msgstr "" "برای استفاده از RDP با امنیت TLS، باید هر دو پروندهٔ گواهینامه و کلید خصوصی، به " "کارساز RDP داده شوند." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "مسیر پروندهٔ کلید خصوصی" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "اجازه به اتّصالهای دوردست فقط برای دیدن محتوای صفحه" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input devices " "(e.g. mouse and keyboard)." msgstr "" "هنگامی که فقطدیدنی روشن است، اتّصالهای RDP دودردست نمیتوانند افزارههای ورودی (مثل " "موشی و صفحهکلید) را دستکاری کنند." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input devices " "(e.g. mouse and keyboard)." msgstr "" "هنگامی که فقطدیدنی روشن است، اتّصالهای VNC دودردست نمیتوانند افزارههای ورودی (مثل " "موشی و صفحهکلید) را دستکاری کنند." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "روش استفادهشده برای تأیید هویت اتّصالهای VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * password - " "by requiring the remote client to provide a known password" msgstr "" "روش تأیید هویت VNC چگونگی تأیید هویت یک اتّصال دوردست را توضیح میدهد. در حال حاضر " "این تأیید هویت میتواند به دو روش مختف انجام شود: * اعلان - با اعلان به کاربر " "برای هر اتّصال جدید. نیازمند کسی با دسترسی فیزیکی به رایانه برای تأیید صریح اتّصال " "جدید. * گذرواژه - با نیاز به فراهم کردن گذرواژهای شناختهشده به دست کارخواه دوردست" 0707010000001C000081A40000000000000000000000016293A07000000F93000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/fi.po# Finnish translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-27 14:13+0000\n" "PO-Revision-Date: 2021-08-30 16:37+0300\n" "Language-Team: Finnish <lokalisointi-lista@googlegroups.com>\n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Last-Translator: Jiri Grönroos <jiri.gronroos+l10n@iki.fi>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Gnomen Etätyöpöytä" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Haluatko jakaa työpöytäsi?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Käyttäjä laitteella '%s' yrittää muodostaa etäyhteyden nähdäkseen tai " "ohjatakseen työpöytääsi." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Estä" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Hyväksy" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Polku varmennetiedostoon" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Jotta RDP:tä voidaan käyttää TLS-protokollan kanssa, on toimitettava sekä " "yksityisen avaimen tiedosto että sertifikaattitiedosto RDP-palvelimelle." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Polku yksityisen avaimen tiedostoon" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Salli etäyhteyksien nähdä vain näytön sisältö" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Kun vain luku -tila on käytössä, RDP-etäyhteydet eivät voi manipuloida " "syötelaitteita (esim. hiirtä ja näppäimistöä)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Kun vain luku -tila on käytössä, VNC-etäyhteydet eivät voi manipuloida " "syötelaitteita (esim. hiirtä ja näppäimistöä)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "VNC-yhteyksien todentamiseen käytetty menetelmä" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC-todennusmenetelmä kuvaa, kuinka etäyhteys todennetaan. Se voidaan tällä " "hetkellä tehdä kahdella eri tavalla: * kehote - pyytämällä käyttäjää " "jokaisesta uudesta yhteydestä ja vaatimalla henkilö, jolla on fyysinen pääsy " "työasemalle, hyväksymään nimenomaisesti uuden yhteyden. * salasana - " "vaatimalla etäkäyttäjää antamaan olemassa oleva salasana" 0707010000001D000081A40000000000000000000000016293A0700000108F000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/fr.po# French translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Titouan Bénard Le Bouffos <itotutona@evta.fr>, 2021 # Claude Paroz <claude@2xlibre.net>, 2021 # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-09-05 09:11+0000\n" "PO-Revision-Date: 2021-09-12 18:46+0200\n" "Last-Translator: Claude Paroz <claude@2xlibre.net>\n" "Language-Team: GNOME French Team <gnomefr@traduc.org>\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "X-Generator: Gtranslator 40.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Bureau à distance de GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Voulez vous partagez votre bureau ?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un utilisateur sur l’ordinateur « %s » essaie d’afficher ou de contrôler à " "distance votre bureau." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Refuser" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Accepter" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Chemin vers le fichier du certificat" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Afin de pouvoir utiliser RDP avec la sécurité TLS, le fichier de clé privée " "et le fichier de certificat doivent être fournis au serveur RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Chemin vers le fichier de clé privée" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Autoriser les connexions à distance à uniquement afficher le contenu de " "l’écran." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Lorsque la lecture seule est active, les connexions RDP distantes ne peuvent " "pas manipuler les périphériques d’entrée (par exemple, la souris et le " "clavier)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Lorsque la lecture seule est active, les connexions VNC distantes ne peuvent " "pas manipuler les périphériques d’entrée (par exemple, la souris et le " "clavier)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Méthode utilisée pour authentifier les connexions VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "La méthode d’authentification VNC décrit comment une connexion à distance " "est authentifiée. Cela peut actuellement être fait de deux manières " "différentes : * invite - en invitant l’utilisateur à chaque nouvelle " "connexion, exigeant qu’une personne ayant un accès physique au poste de " "travail approuve explicitement la nouvelle connexion. * mot de passe - en " "demandant au client distant de fournir un mot de passe connu" 0707010000001E000081A40000000000000000000000016293A07000000F57000000000000000000000000000000000000002400000000gnome-remote-desktop-41.3/po/fur.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-25 14:14+0000\n" "PO-Revision-Date: 2021-07-25 23:26+0200\n" "Language-Team: Friulian <f.t.public@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.0\n" "Last-Translator: Fabio Tomat <f.t.public@gmail.com>\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Language: fur\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Scritori lontan di GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Desideristu condividi il to scritori?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un utent sul computer '%s' al sta cirint di viodi di lontan o controlâ il to " "scritori." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Refude" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Acete" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Percors al file dal certificât" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Par podê rivâ a doprâ RDP cu la sigurece TLS, il servidôr RDP al à di furnî " "sedi il file de clâf privade sedi il file dal certificât." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Percors al file de clâf privade" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Permet aes conessions esternis dome di viodi il contignût dal schermi" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Cuant che view-only al è vêr, lis conessions esternis RDP no puedin manipolâ " "i dispositîfs di input (p.e. mouse e tastiere)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Cuant che view-only al è vêr, lis conessions VNC esternis no puedin manipolâ " "i dispositîfs di input (p.e. mouse e tastiere)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metodi doprât par autenticâ lis conessions VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Il metodi di autenticazion VNC al descrîf cemût che e à di vignî autenticade " "une conession esterne. Pal moment si pues fâ in dôs manieris: * prompt - " "domandant al utent par ogni gnove conession, che al domande che une persone " "e vedi acès fisic ae postazion di lavôr par aprovâ in maniere esplicite lis " "gnovis conessions. * password - domandant al client lontan di furnî une " "password cognossude" 0707010000001F000081A40000000000000000000000016293A070000010F9000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/gl.po# Galician translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # Fran Dieguez <frandieguez@gnome.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-08-25 20:20+0200\n" "Last-Translator: Fran Dieguez <frandieguez@gnome.org>\n" "Language-Team: Galician <Proxecto Trasno <proxecto@trasno.gal>>\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "X-Generator: Gtranslator 40.0\n" "X-DL-Team: gl\n" "X-DL-Module: gnome-remote-desktop\n" "X-DL-Branch: master\n" "X-DL-Domain: po\n" "X-DL-State: Translating\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Escritorio Remoto de GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Desexa compartir o seu escritorio?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un usuario no computador «%s» tenta ver ou controlar o seu computador de " "forma remota." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Rexeitar" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Aceptar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Ruta ao ficheiro de certificado" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 #| msgid "" #| "In Order to be able to use RDP with TLS Security, both the private key " #| "file and the certificate file need to be provided to the RDP server." msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Para poder usar RDP coa seguranza de TLS, precisa fornecerlle ao servidor un " "ficheiro de chave privada e un ficheiro de certificado." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Ruta ao ficheiro de chave privada" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Só permitir as conexións remotas para ver o contido da súa pantalla" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Cando view-only é verdadeiro, as conexións RDP remotas non poderán manipular " "os dispositivos de entrada (p.ex. rato e teclado)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Cando view-only é verdadeiro, as conexións VNC remotas non poderán manipular " "os dispositivos de entrada (p.ex. rato e teclado)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Método usado para autenticar as conexións de VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "O método de autenticación de VNC describe como unha conexión remota está " "autenticada. Actualmente pode facerse de dúas maneiras: * prompt - " "preguntándolle ao usuario con cada nova conexión, require que unha persoa " "teña acceso físico ao computador para aprobar explicitamente a nova " "conexión. * contrasinal - requiríndolle ao cliente remoto que forneza un " "contrasinal coñecido" 07070100000020000081A40000000000000000000000016293A070000010CA000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/he.po# Hebrew translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Yaron Shahrabani <sh.yaron@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-09-28 23:34+0300\n" "Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n" "Language-Team: Hebrew <he@li.org>\n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : n>10 && n%10==0 ? " "2 : 3);\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "שולחנות עבודה מרוחקים מבית GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "לשתף את המסך שלך?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "משתמש במחשב %s מנסה לצפות או לשלוט בשולחן העבודה שלך מרחוק." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "לסרב" #: src/grd-prompt.c:136 msgid "Accept" msgstr "לקבל" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "נתיב לקובץ האישור" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "כדי לאפשר שימוש ב־RDP עם אבטחת TLS, יש לספק לשרת ה־RDP גם את קובץ המפתח " "הפרטי וגם את קובץ האישור." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "נתיב לקובץ המפתח הפרטי" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "לאפשר רק לחיבורים מרוחקים לצפות בתוכן המסך" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "כאשר הערך view-only (צפייה בלבד) הוא true (אמת), חיבורי RDP מרוחקים לא " "יכולים לעשות שימוש בהתקני קלט (למשל: עכבר ומקלדת)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "כאשר הערך view-only (צפייה בלבד) הוא true (אמת), חיבורי VNC מרוחקים לא " "יכולים לעשות שימוש בהתקני קלט (למשל: עכבר ומקלדת)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "השיטה שתשמש לאמת חיבורי VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "שיטת אימות ה־VNC מתארת איך חיבור מרוחק מאומת. אפשר לעשות זאת כיום בשתי " "דרכים: * בקשת קלט - לבקש מהמשתמש לאשר כל חיבור כל פעם מחדש, מה שמצריך גישה " "פיזית לתחנת העבודה כדי לאשר את החיבורים החדשים באופן פעיל. * ססמה - לדרוש " "מהלקוח המרוחק לספק ססמה ידועה מראש" 07070100000021000081A40000000000000000000000016293A07000000F7F000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/hr.po# Croatian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-09-05 09:11+0000\n" "PO-Revision-Date: 2021-09-09 10:40+0200\n" "Language-Team: Croatian <hr@li.org>\n" "Language: hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Last-Translator: gogo <trebelnik2@gmail.com>\n" "X-Generator: Poedit 2.3\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Radna površina" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Želite li dijeliti svoju radnu površinu?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Korisnik na računalu '%s' pokušava udaljeno gledati ili upravljati vašom " "radnom površinom." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Uskrati" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Dopusti" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Putanja do datoteke vjerodajnice" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Kako bi mogli koristiti RDP s TLS sigurnosti, oboje datoteka privatnog " "ključa i datoteka vjerodajnice trebaju biti dostupni RDP poslužitelju." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Putanja do datoteke privatnog ključa" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Dopusti samo udaljenim povezivanjima da gledaju sadržaj zaslona" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Kada je view-only istina, udaljena RDP povezivanja ne mogu manipulirati " "ulaznim uređajima (npr. mišem i tipkovnicom)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Kada je view-only istina, udaljena VNC povezivanja ne mogu manipulirati " "ulaznim uređajima (npr. mišem i tipkovnicom)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Način koji se koristi za ovjeru VNC povezivanja" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Način VNC ovjere opisuje ovjeru udaljenog povezivanja. Trenutno se može " "učiniti na dva različita načina: * prompt - upitom korisnika za svako novo " "povezivanje, potrebna je osoba s fizičkim pristupom računalu kako bi " "izričito odobrila novo povezivanje. * password - zahtjev udaljenog klijenta " "za poznatom lozinkom" 07070100000022000081A40000000000000000000000016293A07000001009000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/hu.po# Hungarian translation for gnome-remote-desktop. # Copyright (C) 2021 Free Software Foundation, Inc. # This file is distributed under the same license as the epiphany package. # # Balázs Meskó <mesko.balazs at fsf dot hu>, 2021. msgid "" msgstr "" "Project-Id-Version: epiphany master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/epiphany/issues\n" "POT-Creation-Date: 2021-09-09 08:41+0000\n" "PO-Revision-Date: 2021-09-12 01:22+0200\n" "Last-Translator: Balázs Meskó <mesko.balazs at fsf dot hu>\n" "Language-Team: Hungarian <gnome-hu-list at gnome dot org>\n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Távoli asztal" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Szeretné megosztani az asztalát?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Egy felhasználó a(z) „%s” számítógépről próbálja távolról megtekinteni vagy " "vezérelni az asztalát." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Elutasítás" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Elfogadás" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "A tanúsítványfájl útvonala" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Annak érdekében, hogy TLS biztonsággal használhassa az RDP-t, mind a" " személyes kulcsot, mind a tanúsítványfájlt ugyanannak az RDP-kiszolgálónak" " kell biztosítania." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "A személyes kulcs fájl útvonala" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Csak a képernyő megtekintésének engedélyezése a távoli kapcsolatok számára" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Ha a „csak megtekintés” igaz, akkor a távoli RDP-kapcsolatok nem kezelhetik " "a bemeneti eszközöket (például az egeret és a billentyűzetet)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Ha a „csak megtekintés” igaz, akkor a távoli VNC-kapcsolatok nem kezelhetik " "a bemeneti eszközöket (például az egeret és a billentyűzetet)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "A VNC-kapcsolatok hitelesítéséhez használt módszer" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "A VNC hitelesítési módja a távoli kapcsolatok hitelesítését írja le. " "Jelenleg két különböző mód használható: * kérdés – a felhasználó " "megkérdezése az összes új kapcsolatnál, tehát egy fizikai hozzáféréssel " "rendelkező személy szükséges, aki explicit módon elfogadja az új " "kapcsolatot. * jelszó – a távoli kliensnek egy ismert jelszót kell megadnia" 07070100000023000081A40000000000000000000000016293A07000000F8A000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/id.po# Indonesian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Andika Triwidada <atriwidada@gnome.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-16 05:29+0000\n" "PO-Revision-Date: 2021-07-16 12:28+0700\n" "Last-Translator: Andika Triwidada <andika@gmail.com>\n" "Language-Team: Indonesian <gnome-l10n-id@googlegroups.com>\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.3\n" "X-Poedit-SourceCharset: UTF-8\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Desktop Jarak Jauh" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Apakah Anda ingin berbagi desktop Anda?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Pengguna pada komputer '%s' coba melihat atau mengendalikan desktop Anda " "dari jarak jauh." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Tolak" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Terima" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Path ke berkas sertifikat" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Agar dapat menggunakan RDP dengan Keamanan TLS, baik berkas kunci privat " "maupun berkas sertifikat perlu diberikan ke server RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Path ke berkas kunci privat" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Hanya perbolehkan sambungan jarak jauh melihat isi layar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Jika view-only berisi true, koneksi RDP jarak jauh tidak dapat memanipulasi " "perangkat masukan (mis. tetikus dan papan ketik)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Jika view-only berisi true, koneksi VNC jarak jauh tidak dapat memanipulasi " "perangkat masukan (mis. tetikus dan papan ketik)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metode yang digunakan untuk mengautentikasi koneksi VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Metode autentikasi VNC menjelaskan bagaimana koneksi jarak jauh " "diautentikasi. Saat ini dapat dilakukan dengan dua cara yang berbeda: * " "prompt - dengan bertanya ke pengguna untuk setiap koneksi baru, mengharuskan " "seseorang dengan akses fisik ke stasiun kerja untuk secara eksplisit " "menyetujui koneksi baru. * kata sandi - dengan mengharuskan klien jarak jauh " "untuk memberikan kata sandi yang diketahui" 07070100000024000081A40000000000000000000000016293A07000000F49000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/is.po# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Sveinn í Felli <sv1@fellsnet.is>, 2022. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/issu" "es\n" "POT-Creation-Date: 2021-12-08 09:25+0000\n" "PO-Revision-Date: 2022-01-29 10:08+0000\n" "Last-Translator: Sveinn í Felli <sv1@fellsnet.is>\n" "Language-Team: Icelandic <translation-team-is@lists.sourceforge.org>\n" "Language: is\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Lokalize 19.12.3\n" #: src/grd-daemon.c:367 msgid "GNOME Remote Desktop" msgstr "Fjartengt GNOME skjáborð" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Viltu deila skjáborðinu þínu með öðrum notendum?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Notandi á fjartengdu tölvunni '%s' er að reyna að skoða eða stýra skjáborðinu" " þínu." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Hafna" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Samþykkja" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Slóð að skilríkjaskránni" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Til að geta notað RDP með TLS-öryggi, verður að gefa upp slóð á einkalykils-" " og skilríkjaskránna til RDP-þjónsins." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Slóð að einkalykilskránni" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Einungis leyfa fjartengingum að skoða efni á skjánum" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Þegar einungis-skoða er virkt, geta fjartengdir RDP-notendur ekki stjórnað" " inntakstækjum (t.d. mús og lyklaborði)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Þegar einungis-skoða er virkt, geta fjartengdir VNC-notendur ekki stjórnað" " inntakstækjum (t.d. mús og lyklaborði)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Aðferð notuð við auðkenningu VNC-tenginga" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Aðferð við auðkenningu VNC-tenginga lýsir því hvernig fjartenging er" " auðkennd. Eins og er má gera það á tvo mismunandi vegu: * prompt - biðja" " notandann að staðfesta allar nýjar tengingar, sem krefst þess að raunveruleg" " manneskja hafi aðgang að viðkomandi tölvu og samþykki sérstakleg nýju" " tenginguna. * " "password - þar sem fjartengdi notandinn þarf að gefa upp þekkt lykilorð" 07070100000025000081A40000000000000000000000016293A070000012D5000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/kk.po# Kazakh translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Baurzhan Muftakhidinov <baurthefirst@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-08-30 13:43+0000\n" "PO-Revision-Date: 2021-08-30 22:17+0500\n" "Language-Team: Kazakh <kk_KZ@googlegroups.com>\n" "Language: kk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Last-Translator: Baurzhan Muftakhidinov <baurthefirst@gmail.com>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME қашықтағы үстелі" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Жұмыс үстелін ортақ пайдаланғыңыз келе ме?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "\"%s\" компьютеріндегі пайдаланушы жұмыс үстеліңізді қашықтан көруге немесе " "басқаруға талап жасап отыр." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Бас тарту" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Қабылдау" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Сертификат файлына дейінгі жол" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "TLS Security көмегімен RDP пайдалану мүмкіндігі үшін жеке кілт файлын да, " "сертификат файлын да RDP серверіне ұсыну қажет." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Жеке кілт файлына дейінгі жол" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Қашықтағы байланыстарға экран мазмұнын тек көруге рұқсат ету" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Тек карау режимінде, қашықтағы RDP байланыстары енгізу құрылғыларын басқара " "алмайды (мысалы, тышқан мен пернетақта)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Тек карау режимінде, қашықтағы VNC байланыстары енгізу құрылғыларын басқара " "алмайды (мысалы, тышқан мен пернетақта)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "VNC байланыстарын аутентификациялау үшін қолданылатын әдіс" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC аутентификация әдісі қашықтағы байланысты аутентификациялау әдісін " "сипаттайды. Қазіргі уақытта оны екі түрлі жолмен жасауға болады: * сұрау - " "пайдаланушыны әрбір жаңа байланысқа шақыру арқылы, жұмыс станциясына " "физикалық рұқсаты бар адамнан жаңа байланысты тікелей рұқсат етуді талап ету " "арқылы. * пароль - қашықтағы клиенттен белгілі парольді ұсынуды талап ету " "арқылы" 07070100000026000081A40000000000000000000000016293A07000000F1F000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/ko.po# Korean translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Seong-ho Cho <shcho@gnome.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-08-29 10:56+0000\n" "PO-Revision-Date: 2021-08-30 18:01+0900\n" "Last-Translator: Seong-ho Cho <shcho@gnome.org>\n" "Language-Team: Korean <gnome-kr@googlegroups.com>\n" "Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 2.3.1\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "그놈 원격 데스크톱" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "데스크톱을 공유하시겠습니까?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "'%s' 컴퓨터의 사용자가 데스크톱을 원격으로 보거나 제어하려고 합니다." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "거절" #: src/grd-prompt.c:136 msgid "Accept" msgstr "수락" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "인증 파일 경로" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "TLS 보안 수단으로 RDP 연결을 사용할 수 있으려면 개인키 파일과 인증 파일을 " "RDP 서버에 제공해야합니다." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "개인키 경로" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "화면 내용 보기 연결만 허용" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "view-only 값이 참이면, 원격 RDP 연결로 입력 장치(예: 마우스, 키보드)를 제어" "할 수 없습니다." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "view-only 값이 참이면, 원격 VNC 연결로 입력 장치(예: 마우스, 키보드)를 제어" "할 수 없습니다." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "VNC 연결 인증 방식" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC 인증 방식이란 원격 연결의 인증 방식을 말합니다. 현재 두가지 방식으로 가능" "합니다: * 질문 - 연결을 새로 할 때마다 질문하여 새 연결을 분명하게 허용할 수 " "있게 워크스테이션을 물리적으로 다룰 사람이 필요합니다. * 암호 - 이미 알고 있" "는 암호를 원격 클라이언트에서 입력합니다." 07070100000027000081A40000000000000000000000016293A07000000F81000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/lt.po# Lithuanian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Aurimas Černius <aurisc4@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-09-02 13:25+0000\n" "PO-Revision-Date: 2021-09-02 18:08+0300\n" "Last-Translator: Aurimas Černius <aurisc4@gmail.com>\n" "Language-Team: Lietuvių <gnome-lt@lists.akl.lt>\n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" "%100<10 || n%100>=20) ? 1 : 2)\n" "X-Generator: Gtranslator 40.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME nuotolinis darbastalis" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Ar norite dalintis savo darbastaliu?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Naudotojas kompiuteryje „%s“ bando nuotoliniu būdu matyti ir valdyti jūsų " "darbastalį." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Atsisakyti" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Sutikti" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Kelias iki liudijimo failo" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Norint naudotis RDP su TLS sauga, privataus rakto failas ir liudijimo failas " "turi būti pateikti RDP serveriui." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Kelias iki privataus rakto failo" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Leisti nuotoliniams ryšiams tik matyti ekraną" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Kai tik matymas yra įjungtas, nuotoliniai RDP ryšiai negali valdyti įvesties " "įrenginių (pvz. pelės ar klaviatūros)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Kai tik matymas yra įjungtas, nuotoliniai VNC ryšiai negali valdyti įvesties " "įrenginių (pvz. pelės ar klaviatūros)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metodas, naudojamas patvirtinti tapatybę VNC ryšiams" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC tapatybės patvirtinimo metodas nusako, kaip patvirtinamas nuotolinis " "ryšys. Tai gali būti atlikta dviem būdais: * užklausiant - klausiant " "naudotojo su kiekvienu ryšiu, reikalaujant asmens su fizine prieiga prie " "kompiuterio patvirtinti naują ryšį. * slaptažodžiu - reikalaujant nuotolinio " "kliento pateikti žinomą slaptažodį" 07070100000028000081A40000000000000000000000016293A07000000033000000000000000000000000000000000000002900000000gnome-remote-desktop-41.3/po/meson.buildi18n.gettext(meson.project_name(), preset: 'glib') 07070100000029000081A40000000000000000000000016293A0700000103D000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/nl.po# Dutch translation for gnome-remote-desktop.master # Copyright (C) 2021 gnome-remote-desktop.master # This file is distributed under the same license as the gnome-remote-desktop.master package. # # Marcia van den Hout <mvdh1176@gmail.com>, 2021. # Hannie Dumoleyn <hannie@ubuntu-nl.org>, 2021. # Nathan Follens <nfollens@gnome.org>, 2021. msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop.master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-27 14:13+0000\n" "PO-Revision-Date: 2021-09-02 15:24+0200\n" "Last-Translator: Nathan Follens <nfollens@gnome.org>\n" "Language-Team: Dutch <gnome-nl-list@gnome.org>\n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Gnome Extern bureaublad" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Wilt u uw bureaublad delen?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Een gebruiker op computer ‘%s’ probeert uw bureaublad op afstand te bekijken " "of te bedienen." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Weigeren" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Accepteren" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Pad naar het certificaatbestand" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Om RDP met TLS-beveiliging te kunnen gebruiken, moeten zowel het " "privésleutelbestand als het certificaatbestand aan de RDP-server worden " "verstrekt." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Pad naar het privésleutelbestand" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Externe verbindingen alleen toestaan om de scherminhoud te bekijken" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Als alleen-lezen waar is, kunnen externe RDP-verbindingen geen " "invoerapparaten manipuleren (zoals muis en toetsenbord)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Als alleen-lezen waar is, kunnen externe VNC-verbindingen geen " "invoerapparaten manipuleren (zoals muis en toetsenbord)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Methode die wordt gebruikt om VNC-verbindingen te verifiëren" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "De VNC-authenticatiemethode beschrijft hoe een externe verbinding wordt " "geverifieerd. Dit kan momenteel op twee verschillende manieren worden " "gedaan: * prompt (vragen) - door de gebruiker om elke nieuwe verbinding te " "vragen, waarbij een persoon met fysieke toegang tot het werkstation wordt " "gevraagd om de nieuwe verbinding uitdrukkelijk goed te keuren. * password " "(wachtwoord) - door de externe cliënt te vragen een bekend wachtwoord op te " "geven" 0707010000002A000081A40000000000000000000000016293A07000000F9D000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/oc.po# Occitan translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Quentin PAGÈS <pages_quentin@hotmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-10-03 17:07+0000\n" "PO-Revision-Date: 2021-11-10 19:22+0100\n" "Last-Translator: Quentin PAGÈS\n" "Language-Team: Occitan <totenoc@gmail.com>\n" "Language: oc\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:365 msgid "GNOME Remote Desktop" msgstr "Burèu distant de GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Volètz partejar vòstre burèu ?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un utilizaire sus l’ordenador « %s » ensajar de veire o contrarotlar vòstre " "burèu a distància." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Refusar" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Acceptar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Camin cap al fichièr de certificat" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Per poder utilizar RDP amb la seguretat TLS, tan lo fichièr de la clau " "privada que lo fichièr de certificat devon èsser fornits al servidor RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Camin cap al fichièr de clau privada" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Sonque autorizar las connexions distantas per veire lo contengut de l’ecran" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Se view-only es definit a «true», las connexion distantas RDP pòdon pas " "manipular las entradas dels aparelhs (ex la mirga e lo clavièr)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Se view-only es definit a «true», las connexion distantas VNC pòdon pas " "manipular las entradas dels aparelhs (ex la mirga e lo clavièr)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metòde utilizat per autentificar las connexions VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Lo metòde d’autentificacion VNC descriu cossí una connexion distanta es " "autentificada. Se pòt generalament far de dos biases : *fenèstra de convit - " "en convidant l’utilizaire per cada connexion, en demandant qu’una persona " "amb un accès fisic a l’estacion de trabalh qu’accepte explicitament la " "connexion. *senhal - en demandant al client alonhat de provesir un senhal " "conegut" 0707010000002B000081A40000000000000000000000016293A07000000F87000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/pl.po# Polish translation for gnome-remote-desktop. # Copyright © 2021 the gnome-remote-desktop authors. # This file is distributed under the same license as the gnome-remote-desktop package. # Piotr Drąg <piotrdrag@gmail.com>, 2021. # Aviary.pl <community-poland@mozilla.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-09-13 11:52+0000\n" "PO-Revision-Date: 2021-09-13 18:22+0200\n" "Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n" "Language-Team: Polish <community-poland@mozilla.org>\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Zdalny pulpit GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Udostępnić pulpit?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Użytkownik na komputerze „%s” chce zdalnie wyświetlać lub sterować pulpitem." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Odmów" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Zaakceptuj" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Ścieżka do pliku certyfikatu" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Aby używać RDP za pomocą zabezpieczeń TLS, plik klucza prywatnego i plik " "certyfikatu muszą zostać dostarczone do serwera RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Ścieżka do pliku klucza prywatnego" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Zdalne połączenia mogą tylko wyświetlać zawartość ekranu" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Po włączeniu zdalne połączenia RDP nie mogą korzystać z urządzeń wejściowych " "(np. myszy i klawiatury)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Po włączeniu zdalne połączenia VNC nie mogą korzystać z urządzeń wejściowych " "(np. myszy i klawiatury)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metoda używana do uwierzytelniania połączeń VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Metoda uwierzytelniania VNC opisuje, jak zdalne połączenie jest " "uwierzytelniane. Obecnie można to zrobić na dwa sposoby: • prompt (pytanie) " "— pytając użytkownika o każde nowe połączenie, przez co osoba z fizycznym " "dostępem do komputera musi zaakceptować nowe połączenie. • password (hasło) " "— wymagając od zdalnego klienta podania znanego hasła" 0707010000002C000081A40000000000000000000000016293A07000000FD8000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/pt.po# Portuguese translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Hugo Carvalho <hugokarvalho@hotmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-25 07:32+0000\n" "PO-Revision-Date: 2021-06-25 18:53+0100\n" "Language-Team: Portuguese <pt@li.org>\n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Last-Translator: Hugo Carvalho <hugokarvalho@hotmail.com>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Ambiente de trabalho remoto do GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Deseja partilhar o ambiente de trabalho?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Um utilizador no computador '%s' está a tentar visualizar ou controlar " "remotamente o ambiente de trabalho." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Recusar" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Aceitar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Caminho para o ficheiro de certificado" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Para poder usar o RDP com o TLS Security, tanto o ficheiro de chave privada " "quanto o ficheiro de certificado precisam de ser fornecidos ao servidor RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Caminho para o ficheiro de chave privada" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Apenas permita que ligações remotas visualizem o conteúdo do ecrã" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Quando as ligações RDP remotas não podem manipular dispositivos de entrada " "(por exemplo, rato e teclado)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Quando o \"apenas-ver\" é verdadeiro, as ligações VNC remotas não podem " "manipular dispositivos de entrada (por exemplo, rato e teclado)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Método usado para autenticar ligações VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "O método de autenticação VNC descreve como uma ligação remota é autenticada. " "Atualmente, a mesma pode ser feita de duas maneiras diferentes: * prompt - " "solicitando ao utilizador a cada nova ligação, exigindo que uma pessoa com " "acesso físico à estação de trabalho aprove explicitamente a nova ligação. * " "palavra-passe - exigindo que o cliente remoto forneça uma palavra-passe " "conhecida" 0707010000002D000081A40000000000000000000000016293A070000010F7000000000000000000000000000000000000002600000000gnome-remote-desktop-41.3/po/pt_BR.po# Brazilian Portuguese translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Enrico Nicoletto <liverig@gmail.com>, 2021. # Rafael Fontenelle <rafaelff@gnome.org>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-07-03 14:50-0300\n" "Last-Translator: Rafael Fontenelle <rafaelff@gnome.org>\n" "Language-Team: Brazilian Portuguese <gnome-pt_br-list@gnome.org>\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "X-Generator: Gtranslator 40.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Área de Trabalho Remota do GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Você deseja compartilhar sua área de trabalho?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Um usuário no computador “%s” está tentando remotamente visualizar ou " "controlar sua área de trabalho." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Recusar" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Aceitar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Caminho para o arquivo do certificado" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 #| msgid "" #| "In Order to be able to use RDP with TLS Security, both the private key " #| "file and the certificate file need to be provided to the RDP server." msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "A fim de possibilitar o uso do RDP com Segurança TLS, tanto o arquivo de " "chave privada quanto o arquivo de certificado precisam ser fornecidos pelo " "servidor RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Caminho para o arquivo de chave privada" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Permitir à conexões remotas apenas visualizar o conteúdo da tela" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Quando somente-visualização for verdadeiro, as conexões remotas RDP não " "manipularão dispositivos de entrada (ex.: mouse e teclado)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Quando somente-visualização for verdadeiro, as conexões remotas VNC não " "manipularão dispositivos de entrada (ex.: mouse e teclado)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Método usado para autenticar conexões VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "O método de autenticação VNC descreve como uma conexão remota é autenticada. " "Atualmente isso pode ser efetuado de duas formas distintas: * prompt - ao " "alertar o usuário a cada nova conexão, solicitando que uma pessoa com acesso " "físico a estação de trabalho aprove de forma explícita a nova conexão. * " "password - ao solicitar que o cliente remoto forneça uma senha conhecida " "pelo sistema" 0707010000002E000081A40000000000000000000000016293A0700000106D000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/ro.po# Romanian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Florentina Mușat <florentina.musat.28@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-02 12:38+0000\n" "PO-Revision-Date: 2021-07-02 18:43+0300\n" "Language-Team: Romanian <gnomero-list@lists.sourceforge.net>\n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2);;\n" "Last-Translator: Florentina Mușat <florentina.musat.28@gmail.com>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Desktop la distanță GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Doriți să vă partajați desktop-ul?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Un utilizator de pe calculatorul „%s” încearcă să vizualizeze sau să " "controleze de la distanță desktop-ul." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Refuză" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Acceptă" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Calea către fișierul certificat" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Pentru a putea utiliza RDP cu Securitate TLS, atât fișierul cheii private " "cât și fișierul certificat trebuie să fie furnizate serverului RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Calea către fișierul cheii private" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Permite doar conexiunilor de la distanță să vizualizeze conținutul ecranului" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Când doar vizualizare este adevărat, conexiunile RDP de la distanță nu pot " "manipula dispozitivele de intrare (de ex: maus și tastatură)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Când doar vizualizare este adevărat, conexiunile VNC de la distanță nu pot " "manipula dispozitivele de intrare (de ex: maus și tastatură)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metodă utilizată pentru autentificarea conexiunilor VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Metoda de autentificare VNC descrie cum se autentifică o conexiune de la " "distanță. În mod curent se poate efectua în două moduri diferite: * prompt - " "prin solicitarea utilizatorului pentru fiecare conexiune nouă, necesitând o " "persoană cu acces fizic la stația de lucru pentru a aproba explicit " "conexiunea nouă. * password - solicitând clientului de la distanță să " "furnizeze o parolă cunoscută" 0707010000002F000081A40000000000000000000000016293A070000013C6000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/ru.po# Russian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Alexey Rubtsov <rushills@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-07-04 16:40+0000\n" "PO-Revision-Date: 2021-07-05 16:51+0300\n" "Language-Team: Russian <gnome-cyr@gnome.org>\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Last-Translator: Alexey Rubtsov <rushills@gmail.com>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Удаленный рабочий стол GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Хотите поделиться своим рабочим столом?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Пользователь компьютера «%s» пытается удаленно просматривать или " "контролировать ваш рабочий стол." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Отказаться" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Принять" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Путь к файлу сертификата" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Чтобы использовать RDP с TLS Security, необходимо предоставить серверу RDP " "файл персонального ключа и файл сертификата." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Путь к файлу персонального ключа" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "" "Разрешить только удаленные подключения для просмотра содержимого экрана" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Когда значение «только для просмотра» установлено в true, удаленные RDP-" "подключения не могут управлять устройствами ввода (например, мышью и " "клавиатурой)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Когда значение «только для просмотра» равно true, удаленные соединения VNC " "не могут управлять устройствами ввода (например, мышью и клавиатурой)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Метод, используемый для аутентификации соединений VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Метод аутентификации VNC описывает способ аутентификации удаленного " "соединения. В настоящее время это может быть сделано двумя различными " "способами: * запрос - запрашивая пользователя при каждом новом подключении, " "требуя от лица, имеющего физический доступ к рабочей станции, явного " "одобрения нового подключения. * пароль - требуя от удаленного клиента " "предоставить известный пароль" 07070100000030000081A40000000000000000000000016293A07000001088000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/sk.po# Slovak translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Dušan Kazik <prescott66@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-08-31 03:50+0000\n" "PO-Revision-Date: 2021-08-31 16:14+0200\n" "Language-Team: Slovak <gnome-sk-list@gnome.org>\n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" "Last-Translator: Dušan Kazik <prescott66@gmail.com>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Vzdialená plocha prostredia GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Chcete sprístupniť vašu pracovnú plochu?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Používateľ počítača „%s“ sa pokúša vzdialene zobraziť alebo ovládať vašu " "pracovnú plochu." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Odmietnuť" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Prijať" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Cesta k súboru s certifikátom" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Aby bolo možné používanie protokolu RDP so zabezpečením TLS, musí byť " "poskytnutý serveru RDP súbor so súkromným kľúčom aj súbor s certifikátom." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Cesta k súboru so súkromným kľúčom" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Umožniť vzdialeným pripojeniam iba zobrazovať obsah obrazovky" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Keď je režim „iba zobraziť“ nastavený na TRUE, vzdialené pripojenia RDP " "nebudú môcť pohybovať vstupnými zariadeniami (napr. myšou a klávesnicou)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Keď je režim „iba zobraziť“ nastavený na TRUE, vzdialené pripojenia VNC " "nebudú môcť pohybovať vstupnými zariadeniami (napr. myšou a klávesnicou)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metóda použitá na overenie totožnosti pripojení VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Metóda overenia totožnosti protokolu VNC popisuje, ako je overovaná " "totožnosť vzdialeného pripojenia. Momentálne môže byť vykonaná dvoma " "spôsobmi: * prompt („výzva“) - používateľ bude vyzvaný pri každom novom " "pripojení, pričom bude potrebná osoba s fyzickým prístupom k pracovnej " "stanici, aby bolo možné explicitne schváliť nové pripojenie. * password " "(„heslo“) - od vzdialeného klienta bude potrebné poskytnutie známeho hesla" 07070100000031000081A40000000000000000000000016293A07000000F74000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/sl.po# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # # Matej Urbančič <mateju@svn.gnome.org>, 2021–. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-30 16:36+0000\n" "PO-Revision-Date: 2021-07-02 14:31+0200\n" "Last-Translator: \n" "Language-Team: Slovenščina <gnome-si@googlegroups.com>\n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" "%100==4 ? 3 : 0);\n" "X-Poedit-SourceCharset: utf-8\n" "X-Generator: Poedit 2.4.2\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Oddaljeno namizje GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Ali želite omogočiti souporabo namizja?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Uporabnik za računalnikom »%s« poskuša oddaljeno pregledovati oziroma " "upravljati namizje." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Zavrni" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Sprejmi" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Pot do datoteke potrdila" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Za uporabo RDP z varnostnim protokolom TLS Security morata biti datoteki " "zasebnega ključa in potrdila na voljo strežniku RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Pot do datoteke zasebnega ključa" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Oddaljenim povezavam dovoli le ogled vsebine zaslona" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Če je omogočen le ogled vsebine zaslona, prek povezave RDP ni mogoče " "upravljati z vhodnimi napravami (npr. miško in tipkovnico)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Če je omogočen le ogled vsebine zaslona, prek povezave VNC ni mogoče " "upravljati z vhodnimi napravami (npr. miško in tipkovnico)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Način za overjanje povezav VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Način overjanja povezave VNC določa protokol preverjanja pristnosti " "povezave. Trenutno je overjanje mogoče na dva načina: * z opozorilom – " "uporabniku se pokaže poziv, da mora za novo povezavo odobriti dostop, to pa " "zahteva fizični dostop do delovne postaje in * geslo – povezavo lahko " "vzpostavi oddaljen uporabnik z vpisom dodeljenega gesla." 07070100000032000081A40000000000000000000000016293A0700000129B000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/sr.po# Serbian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Марко Костић <marko.m.kostic@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-09-28 20:35+0000\n" "PO-Revision-Date: 2021-10-02 09:29+0200\n" "Language-Team: Serbian <gnome-sr@googlegroups.com>\n" "Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : n" "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "Last-Translator: Марко М. Костић <marko.m.kostic@gmail.com>\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:365 msgid "GNOME Remote Desktop" msgstr "Гномова удаљена површ" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Да ли желите поделити садржај вашег екрана?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Корисник на рачунару „%s“ жели да удаљено прегледа и управља вашом радном " "површином." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Одбиј" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Прихвати" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Путања до датотеке сертификата" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Да бисте користили РДП са ТЛС безбедношћу, морате дати датотеку приватног " "кључа и датотеку сертификата РДП серверу." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Путања до датотеке приватног кључа" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Само дозволи удаљеним везама прегледање садржаја екрана" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Када је само гледање омогућено, удаљене РДП везе не могу користити улазне " "уређаје (нпр.: миша и тастатуру)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Када је само гледање омогућено, удаљене ВНЦ везе не могу користити улазне " "уређаје (нпр.: миша и тастатуру)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Начин потврде идентитета ВНЦ веза" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Начин потврде идентитета ВНЦ веза описује како се удаљена веза " "аутентификује. Тренутно се то може урадити на два начина: * prompt - пита " "корисника за сваку нову везу, захтева особу са физичким приступом радној " "станици да би се свака нова веза експлицитно одобрила. * password - захтева " "од удаљеног клијента да достави постојећу лозинку" 07070100000033000081A40000000000000000000000016293A07000000FA7000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/sv.po# Swedish translation for gnome-remote-desktop. # Copyright © 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Luna Jernberg <droidbittin@gmail.com>, 2021. # Anders Jonsson <anders.jonsson@norsjovallen.se>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-06-25 09:26+0200\n" "Last-Translator: Luna Jernberg <droidbittin@gmail.com>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.4.2\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Fjärrskrivbord" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Vill du dela ditt skrivbord?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "En användare på datorn ”%s” försöker att fjärrvisa eller styra ditt " "skrivbord." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Neka" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Acceptera" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Sökväg till certifikatfil" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "För att kunna använda RDP med TLS-säkerhet måste både den privata " "nyckelfilen och certifikatfilen tillhandahållas till RDP-servern." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Sökväg till privat nyckelfil" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Tillåt endast fjärranslutningar att visa skärminnehållet" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "När view-only är true (sant) kan fjärr-RDP-anslutningar inte manipulera " "inmatningsenheter (t.ex. mus och tangentbord)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "När view-only är true (sant) kan fjärr-VNC-anslutningar inte manipulera " "inmatningsenheter (t.ex. mus och tangentbord)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Metod som används för att autentisera VNC-anslutningar" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC-autentiseringsmetoden beskriver hur en fjärranslutning autentiseras. Det " "kan för närvarande göras på två olika sätt: * prompt - genom att fråga " "användaren om varje ny anslutning, vilket kräver att en person med fysisk " "åtkomst till arbetsstationen uttryckligen godkänner den nya anslutningen. * " "password - genom att kräva att fjärrklienten anger ett känt lösenord" 07070100000034000081A40000000000000000000000016293A07000000F91000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/tr.po# Turkish translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # # Emin Tufan Çetin <etcetin@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-09-19 10:29+0000\n" "PO-Revision-Date: 2021-09-15 20:56+0300\n" "Last-Translator: Emin Tufan Çetin <etcetin@gmail.com>\n" "Language-Team: Turkish <gnometurk@gnome.org>\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME Uzak Masaüstü" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Masaüstünüzü paylaşmak istiyor musunuz?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "'%s' bilgisayarındaki kullanıcı masaüstünüzü uzaktan görüntülemek veya " "denetlemek istiyor." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Reddet" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Kabul Et" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Sertifika dosyası yolu" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "TLS Güvenliği ile RDP kullanmak için RDP sunucusuna hem özel anahtar dosyası " "hem de sertifika dosyası sağlanmalıdır." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Özel anahtar dosyası yolu" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Uzak bağlantıların yalnızca ekran içeriğini görmesine izin ver" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Yalnızca-görüntüle seçildiğinde uzak RDP bağlantıları girdi aygıtlarını " "(örn. fare ve klavye) hareket ettiremez." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Yalnızca-görüntüle seçildiğinde uzak VNC bağlantıları girdi aygıtlarını " "(örn. fare ve klavye) hareket ettiremez." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "VNC bağlantılarını doğrulamada kullanılacak yöntem" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC doğrulama yöntemi, uzak bağlantının nasıl doğrulanacağını tanımlar.Şu " "anda bunu yapmanın iki yolu vardır: * prompt (istemde bulun) - her yeni " "bağlantı için kullanıcıdan istemde bulunur, yeni bağlantıyı açıkça onaylamak " "için iş istasyonuna bir kişinin fiziksel erişimini gerektirir. * password " "(parola) - uzak istemcinin bilinen parolayı sağlamasını gerektirir" 07070100000035000081A40000000000000000000000016293A0700000146B000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/uk.po# Ukrainian translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # # Yuri Chornoivan <yurchor@ukr.net>, 2021. msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/issues\n" "POT-Creation-Date: 2021-06-24 07:32+0000\n" "PO-Revision-Date: 2021-06-24 14:54+0300\n" "Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n" "Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Lokalize 20.12.0\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Віддалена стільниця GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Хочете надати вашу стільницю у спільне користування?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Користувач на комп'ютері «%s» намагається віддалено переглянути вашу " "стільницю та керувати нею." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Відкинути" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Прийняти" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Шлях до файла сертифіката" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 #| msgid "" #| "In Order to be able to use RDP with TLS Security, both the private key " #| "file and the certificate file need to be provided to the RDP server." msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Що мати змогу користуватися RDP з захистом TLS, серверу RDP слід надати " "одразу файл закритого ключа і файл сертифіката." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Шлях до файла закритого ключа" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Дозволити віддалених з'єднанням лише переглядати вміст екрана" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Якщо view-only має значення true, користувачі віддалених з'єднань RDP не " "зможуть керувати пристроями введення (наприклад мишею або клавіатурою)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Якщо view-only має значення true, користувачі віддалених з'єднань VNC не " "зможуть керувати пристроями введення (наприклад мишею або клавіатурою)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Спосіб, який буде використано для розпізнавання у з'єднаннях VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Спосіб розпізнавання VNC описує те, як виконуватиметься розпізнавання " "користувачів віддаленого з'єднання. У поточній версії передбачено два різних " "способи: * prompt — запит до користувача при кожному новому з'єднанні, " "потребує явного підтвердження нового з'єднання від особи із фізичним " "доступом до робочої станції. * password — вимога до віддаленого клієнта щодо " "надання відомого системі пароля" 07070100000036000081A40000000000000000000000016293A07000001057000000000000000000000000000000000000002300000000gnome-remote-desktop-41.3/po/vi.po# Vietnamese translation for gnome-remote-desktop. # This file is distributed under the same license as the gnome-remote-desktop package. # Trần Ngọc Quân <vnwildman@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-17 16:48+0000\n" "PO-Revision-Date: 2021-06-18 13:56+0700\n" "Language-Team: Vietnamese <gnome-vi-list@gnome.org>\n" "Language: vi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "Last-Translator: Trần Ngọc Quân <vnwildman@gmail.com>\n" "X-Generator: Gtranslator 2.91.7\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "Điều khiển màn hình từ xa GNOME" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "Bạn có thực sự muốn chia sẻ màn hình của mình?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "" "Người dùng trên máy “%s” đang cố xem hoặc điều khiển màn hình làm việc của " "bạn từ xa." #: src/grd-prompt.c:131 msgid "Refuse" msgstr "Từ chối" #: src/grd-prompt.c:136 msgid "Accept" msgstr "Chấp nhận" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "Đường dẫn đến tập tin chứng thực" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In Order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "Với mục đích có thể dùng RPD an toàn bằng TLS, cả tập tin khóa riêng và " "chứng nhận cần phải được cung cấp đến máy phục vụ RDP." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "Đường dẫn đến tập tin khóa riêng" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "Chỉ cho phép các kết nối từ xa xem nội dung màn hình" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Khi chọn chỉ-xem, các kết nối RDP từ xa không thể thao tác các thiết bị nhập " "(như là chuột và bàn phím)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "" "Khi chọn chỉ-xem, các kết nối VNC từ xa không thể thao tác các thiết bị nhập " "(như là chuột và bàn phím)." #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "Phương thức được dùng để xác thực các kết nối VNC" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "Phương thức xác thực VNC mô tả cách máy từ xa được các thực. Nó hiện có thể " "thực hiện theo hai cách khác nhau: * nhắc - bằng cách nhắc người dùng khi " "tạo kết nối mới, yêu cầu một người có khả năng truy cập vật lý đến máy chủ " "để chấp thuận kết nối một cách rõ ràng. * mật khẩu - bằng cách yêu cầu máy " "khách từ xa cung cấp một mật khẩu đã biết" 07070100000037000081A40000000000000000000000016293A07000000E46000000000000000000000000000000000000002600000000gnome-remote-desktop-41.3/po/zh_CN.po# Chinese (China) translation for gnome-remote-desktop. # Copyright (C) 2021 gnome-remote-desktop's COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-remote-desktop package. # Boyuan Yang <073plan@gmail.com>, 2021. # msgid "" msgstr "" "Project-Id-Version: gnome-remote-desktop master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-remote-desktop/" "issues\n" "POT-Creation-Date: 2021-06-25 17:54+0000\n" "PO-Revision-Date: 2021-06-26 15:15-0400\n" "Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Last-Translator: Boyuan Yang <073plan@gmail.com>\n" "X-Generator: Poedit 2.4.2\n" #: src/grd-daemon.c:351 msgid "GNOME Remote Desktop" msgstr "GNOME 远程桌面" #: src/grd-prompt.c:124 #, c-format msgid "Do you want to share your desktop?" msgstr "您是否想要分享您的桌面?" #: src/grd-prompt.c:125 #, c-format msgid "" "A user on the computer '%s' is trying to remotely view or control your " "desktop." msgstr "计算机“%s”上的一个用户正在尝试远程查看或控制您的桌面。" #: src/grd-prompt.c:131 msgid "Refuse" msgstr "拒绝" #: src/grd-prompt.c:136 msgid "Accept" msgstr "接受" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:7 msgid "Path to the certificate file" msgstr "证书文件的路径" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:8 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:16 msgid "" "In order to be able to use RDP with TLS Security, both the private key file " "and the certificate file need to be provided to the RDP server." msgstr "" "如需使用支持 TLS 安全的 RDP 远程桌面,需要向 RDP 服务器提供私钥文件和证书文" "件。" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:15 msgid "Path to the private key file" msgstr "私钥文件的路径" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:23 #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:33 msgid "Only allow remote connections to view the screen content" msgstr "只允许远程连接查看屏幕内容" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:24 msgid "" "When view-only is true, remote RDP connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "若 view-only 为真,远程 RDP 连接不能操纵输入设备(如,鼠标和键盘)。" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:34 msgid "" "When view-only is true, remote VNC connections cannot manipulate input " "devices (e.g. mouse and keyboard)." msgstr "若 view-only 为真,远程 VNC 连接不能操纵输入设备(如,鼠标和键盘)。" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:41 msgid "Method used to authenticate VNC connections" msgstr "用于认证 VNC 连接的方法" #: src/org.gnome.desktop.remote-desktop.gschema.xml.in:42 msgid "" "The VNC authentication method describes how a remote connection is " "authenticated. It can currently be done in two different ways: * prompt - by " "prompting the user for each new connection, requiring a person with physical " "access to the workstation to explicitly approve the new connection. * " "password - by requiring the remote client to provide a known password" msgstr "" "VNC 认证方法描述了如何对远程连接的身份进行认证。当前有两种方式可用:* prompt " "- 每次建立新连接时提示用户,需要可以物理接触工作站设备的人明确接受每个新连" "接。* password - 要求远程客户端提供一个已知的密码" 07070100000038000041ED0000000000000000000000016293A07000000000000000000000000000000000000000000000001E00000000gnome-remote-desktop-41.3/src07070100000039000081A40000000000000000000000016293A070000000A3000000000000000000000000000000000000003E00000000gnome-remote-desktop-41.3/src/gnome-remote-desktop.service.in[Unit] Description=GNOME Remote Desktop [Service] Type=dbus BusName=org.gnome.RemoteDesktop ExecStart=@libexecdir@/gnome-remote-desktop-daemon Restart=on-failure 0707010000003A000081A40000000000000000000000016293A07000016BAD000000000000000000000000000000000000003200000000gnome-remote-desktop-41.3/src/grd-clipboard-rdp.c/* * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-clipboard-rdp.h" #include <glib/gstdio.h> #include <winpr/clipboard.h> #include "grd-rdp-fuse-clipboard.h" #include "grd-session-rdp.h" #define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 4 + 4 + 520) #define MAX_WAIT_TIME 4000 typedef struct _ServerFormatListUpdateContext { GrdClipboardRdp *clipboard_rdp; GList *mime_type_tables; } ServerFormatListUpdateContext; typedef struct _ServerFormatDataRequestContext { GrdClipboardRdp *clipboard_rdp; GrdMimeType mime_type; uint32_t src_format_id; uint32_t dst_format_id; gboolean needs_null_terminator; gboolean needs_conversion; } ServerFormatDataRequestContext; typedef struct _ClientFormatDataRequestContext { GrdMimeTypeTable mime_type_table; gboolean has_clip_data_id; uint32_t clip_data_id; } ClientFormatDataRequestContext; typedef struct _ClipDataEntry { uint32_t id; wClipboard *system; wClipboardDelegate *delegate; uint64_t serial; gboolean is_independent; gboolean has_file_list; gboolean requests_allowed; } ClipDataEntry; typedef struct _FormatData { uint8_t *data; uint32_t size; } FormatData; struct _GrdClipboardRdp { GrdClipboard parent; CliprdrServerContext *cliprdr_context; HANDLE stop_event; wClipboard *system; wClipboardDelegate *delegate; gboolean has_file_list; uint64_t serial; GHashTable *allowed_server_formats; GList *pending_server_formats; GList *queued_server_formats; gboolean server_file_contents_requests_allowed; uint16_t format_list_response_msg_flags; GHashTable *serial_entry_table; GHashTable *clip_data_table; struct { ClipDataEntry *entry; ClipDataEntry *entry_to_replace; gboolean serial_already_in_use; } clipboard_retrieval_context; struct { ClipDataEntry *entry; } clipboard_destruction_context; char *fuse_mount_path; GrdRdpFuseClipboard *rdp_fuse_clipboard; GHashTable *format_data_cache; GrdMimeType which_unicode_format; ServerFormatDataRequestContext *format_data_request_context; GHashTable *pending_client_requests; GQueue *ordered_client_requests; GMutex client_request_mutex; ClientFormatDataRequestContext *current_client_request; CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response; HANDLE clip_data_entry_event; HANDLE completed_clip_data_entry_event; HANDLE completed_format_list_event; HANDLE completed_format_data_request_event; HANDLE format_list_received_event; HANDLE format_list_response_received_event; HANDLE format_data_request_received_event; unsigned int pending_server_formats_drop_id; unsigned int client_request_abort_id; unsigned int clipboard_retrieval_id; unsigned int clipboard_destruction_id; unsigned int server_format_list_update_id; unsigned int server_format_data_request_id; unsigned int client_format_list_response_id; unsigned int client_format_data_response_id; }; G_DEFINE_TYPE (GrdClipboardRdp, grd_clipboard_rdp, GRD_TYPE_CLIPBOARD); static gboolean send_mime_type_content_request (GrdClipboardRdp *clipboard_rdp, GrdMimeTypeTable *mime_type_table); static void create_new_winpr_clipboard (GrdClipboardRdp *clipboard_rdp); static void update_allowed_server_formats (GrdClipboardRdp *clipboard_rdp, gboolean received_response) { GList *l; if (received_response) g_debug ("[RDP.CLIPRDR] Handling format list response from client"); if (received_response && clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_OK) { for (l = clipboard_rdp->pending_server_formats; l; l = l->next) { if (GPOINTER_TO_UINT (l->data) == GRD_MIME_TYPE_TEXT_URILIST) { ClipDataEntry *entry; if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (clipboard_rdp->serial), NULL, (gpointer *) &entry)) entry->requests_allowed = TRUE; clipboard_rdp->server_file_contents_requests_allowed = TRUE; } g_hash_table_add (clipboard_rdp->allowed_server_formats, l->data); } g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free); return; } if (received_response && !(clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_FAIL)) { g_warning ("[RDP.CLIPRDR] Protocol violation: Client did not set response " "flag. Assuming CB_RESPONSE_FAIL"); } clipboard_rdp->server_file_contents_requests_allowed = FALSE; g_hash_table_remove_all (clipboard_rdp->allowed_server_formats); g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free); } static void remove_clipboard_format_data_for_mime_type (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type) { FormatData *format_data; format_data = g_hash_table_lookup (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type)); if (!format_data) return; g_free (format_data->data); g_free (format_data); g_hash_table_remove (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type)); } static void remove_duplicated_clipboard_mime_types (GrdClipboardRdp *clipboard_rdp, GList **mime_type_list) { GrdMimeType mime_type; /** * Remove the "x-special/gnome-copied-files" mimetype, since we use the * "text/uri-list" mimetype instead. */ mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; if (g_list_find (*mime_type_list, GUINT_TO_POINTER (mime_type))) { *mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type)); remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type); } /** * We can only advertise CF_UNICODETEXT as remote format once, so ignore the * other local format if it exists. */ if (g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_PLAIN_UTF8)) && g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_UTF8_STRING))) { mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; *mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type)); remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type); } } static void update_clipboard_serial (GrdClipboardRdp *clipboard_rdp) { ++clipboard_rdp->serial; while (g_hash_table_contains (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (clipboard_rdp->serial))) ++clipboard_rdp->serial; g_debug ("[RDP.CLIPRDR] Updated clipboard serial to %lu", clipboard_rdp->serial); } static void send_mime_type_list (GrdClipboardRdp *clipboard_rdp, GList *mime_type_list) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FORMAT_LIST format_list = {0}; CLIPRDR_FORMAT *cliprdr_formats; ClipDataEntry *entry; GrdMimeType mime_type; uint32_t n_formats; uint32_t i; GList *l; if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (clipboard_rdp->serial), NULL, (gpointer *) &entry)) { g_debug ("[RDP.CLIPRDR] ClipDataEntry with id %u and serial %lu is now " "independent", entry->id, entry->serial); entry->is_independent = TRUE; create_new_winpr_clipboard (clipboard_rdp); } update_clipboard_serial (clipboard_rdp); n_formats = g_list_length (mime_type_list); cliprdr_formats = g_malloc0 (n_formats * sizeof (CLIPRDR_FORMAT)); for (i = 0, l = mime_type_list; i < n_formats; ++i, l = l->next) { mime_type = GPOINTER_TO_UINT (l->data); switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN: cliprdr_formats[i].formatId = CF_TEXT; break; case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: cliprdr_formats[i].formatId = CF_UNICODETEXT; clipboard_rdp->which_unicode_format = mime_type; break; case GRD_MIME_TYPE_TEXT_HTML: cliprdr_formats[i].formatId = CB_FORMAT_HTML; cliprdr_formats[i].formatName = "HTML Format"; break; case GRD_MIME_TYPE_IMAGE_BMP: cliprdr_formats[i].formatId = CF_DIB; break; case GRD_MIME_TYPE_IMAGE_TIFF: cliprdr_formats[i].formatId = CF_TIFF; break; case GRD_MIME_TYPE_IMAGE_GIF: cliprdr_formats[i].formatId = CB_FORMAT_GIF; break; case GRD_MIME_TYPE_IMAGE_JPEG: cliprdr_formats[i].formatId = CB_FORMAT_JPEG; break; case GRD_MIME_TYPE_IMAGE_PNG: cliprdr_formats[i].formatId = CB_FORMAT_PNG; break; case GRD_MIME_TYPE_TEXT_URILIST: /** * FileGroupDescriptorW does not have a consistent format id. It is * identified by its name. * When the client requests the content, it MUST use the id that we * told the client before when the clipboard format with the name * "FileGroupDescriptorW" was advertised. * * See also 1.3.1.2 Clipboard Format */ cliprdr_formats[i].formatId = CB_FORMAT_TEXTURILIST; cliprdr_formats[i].formatName = "FileGroupDescriptorW"; clipboard_rdp->server_file_contents_requests_allowed = FALSE; grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard); grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (rdp_fuse_clipboard); break; default: g_assert_not_reached (); } remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type); g_hash_table_remove (clipboard_rdp->allowed_server_formats, l->data); clipboard_rdp->pending_server_formats = g_list_append (clipboard_rdp->pending_server_formats, l->data); } format_list.msgType = CB_FORMAT_LIST; format_list.formats = cliprdr_formats; format_list.numFormats = n_formats; g_debug ("[RDP.CLIPRDR] Sending FormatList"); cliprdr_context->ServerFormatList (cliprdr_context, &format_list); g_free (cliprdr_formats); g_list_free (mime_type_list); } static gboolean drop_pending_server_formats (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; GList *queued_server_formats; g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not send " "format list response (Timeout reached)"); update_allowed_server_formats (clipboard_rdp, FALSE); queued_server_formats = g_steal_pointer (&clipboard_rdp->queued_server_formats); if (queued_server_formats) send_mime_type_list (clipboard_rdp, queued_server_formats); clipboard_rdp->pending_server_formats_drop_id = 0; return G_SOURCE_REMOVE; } static void grd_clipboard_rdp_update_client_mime_type_list (GrdClipboard *clipboard, GList *mime_type_list) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard); remove_duplicated_clipboard_mime_types (clipboard_rdp, &mime_type_list); if (!mime_type_list) return; if (clipboard_rdp->pending_server_formats) { if (clipboard_rdp->queued_server_formats) g_debug ("[RDP.CLIPRDR] Replacing queued server FormatList"); g_clear_pointer (&clipboard_rdp->queued_server_formats, g_list_free); g_debug ("[RDP.CLIPRDR] Queueing new FormatList"); clipboard_rdp->queued_server_formats = mime_type_list; if (!clipboard_rdp->pending_server_formats_drop_id) { clipboard_rdp->pending_server_formats_drop_id = g_timeout_add (MAX_WAIT_TIME, drop_pending_server_formats, clipboard_rdp); } return; } send_mime_type_list (clipboard_rdp, mime_type_list); } void grd_clipboard_rdp_lock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = {0}; g_debug ("[RDP.CLIPRDR] Locking clients clipboard data with clipDataId %u", clip_data_id); lock_clipboard_data.msgType = CB_LOCK_CLIPDATA; lock_clipboard_data.clipDataId = clip_data_id; cliprdr_context->ServerLockClipboardData (cliprdr_context, &lock_clipboard_data); } void grd_clipboard_rdp_unlock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = {0}; g_debug ("[RDP.CLIPRDR] Unlocking clients clipboard data associated to " "clipDataId %u", clip_data_id); unlock_clipboard_data.msgType = CB_UNLOCK_CLIPDATA; unlock_clipboard_data.clipDataId = clip_data_id; cliprdr_context->ServerUnlockClipboardData (cliprdr_context, &unlock_clipboard_data); } void grd_clipboard_rdp_request_remote_file_size_async (GrdClipboardRdp *clipboard_rdp, uint32_t stream_id, uint32_t list_index, gboolean has_clip_data_id, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0}; file_contents_request.msgType = CB_FILECONTENTS_REQUEST; file_contents_request.streamId = stream_id; file_contents_request.listIndex = list_index; file_contents_request.dwFlags = FILECONTENTS_SIZE; file_contents_request.cbRequested = 0x8; file_contents_request.haveClipDataId = has_clip_data_id; file_contents_request.clipDataId = clip_data_id; cliprdr_context->ServerFileContentsRequest (cliprdr_context, &file_contents_request); } void grd_clipboard_rdp_request_remote_file_range_async (GrdClipboardRdp *clipboard_rdp, uint32_t stream_id, uint32_t list_index, uint64_t offset, uint32_t requested_size, gboolean has_clip_data_id, uint32_t clip_data_id) { CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0}; file_contents_request.msgType = CB_FILECONTENTS_REQUEST; file_contents_request.streamId = stream_id; file_contents_request.listIndex = list_index; file_contents_request.dwFlags = FILECONTENTS_RANGE; file_contents_request.nPositionLow = offset & 0xFFFFFFFF; file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF; file_contents_request.cbRequested = requested_size; file_contents_request.haveClipDataId = has_clip_data_id; file_contents_request.clipDataId = clip_data_id; cliprdr_context->ServerFileContentsRequest (cliprdr_context, &file_contents_request); } static void track_serial_for_mime_type (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type, unsigned int serial) { GList *serials; serials = g_hash_table_lookup (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type)); serials = g_list_append (serials, GUINT_TO_POINTER (serial)); g_hash_table_insert (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type), serials); } static void enqueue_mime_type_content_request (GrdClipboardRdp *clipboard_rdp, GrdMimeTypeTable *mime_type_table, unsigned int serial) { GrdMimeType mime_type = mime_type_table->mime_type; GrdMimeTypeTable *mime_type_table_copy; g_debug ("[RDP.CLIPRDR] Queueing mime type content request for mime type %s " "with serial %u", grd_mime_type_to_string (mime_type), serial); track_serial_for_mime_type (clipboard_rdp, mime_type, serial); mime_type_table_copy = g_malloc0 (sizeof (GrdMimeTypeTable)); *mime_type_table_copy = *mime_type_table; g_queue_push_tail (clipboard_rdp->ordered_client_requests, GUINT_TO_POINTER (mime_type_table_copy)); } static void abort_client_requests_for_serials (GrdClipboardRdp *clipboard_rdp, GList *serials) { GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); unsigned int serial; GList *l; for (l = serials; l; l = l->next) { serial = GPOINTER_TO_UINT (l->data); grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); } g_list_free (serials); } static void abort_client_requests_for_mime_type (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type) { GList *serials; if (!g_hash_table_steal_extended (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &serials)) return; abort_client_requests_for_serials (clipboard_rdp, serials); } static void abort_client_requests_for_context (GrdClipboardRdp *clipboard_rdp, ClientFormatDataRequestContext *request_context) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; GrdMimeTypeTable *mime_type_table = &request_context->mime_type_table; GrdMimeType mime_type = mime_type_table->mime_type; uint32_t clip_data_id = request_context->clip_data_id; g_debug ("[RDP.CLIPRDR] Aborting FormatDataRequest for mime type %s", grd_mime_type_to_string (mime_type)); if (request_context->has_clip_data_id) grd_rdp_fuse_clipboard_clip_data_id_free (rdp_fuse_clipboard, clip_data_id); abort_client_requests_for_mime_type (clipboard_rdp, mime_type); } static void maybe_send_next_mime_type_content_request (GrdClipboardRdp *clipboard_rdp) { GrdMimeTypeTable *mime_type_table; GrdMimeType mime_type; GList *serials = NULL; mime_type_table = g_queue_pop_head (clipboard_rdp->ordered_client_requests); if (!mime_type_table) return; mime_type = mime_type_table->mime_type; serials = g_hash_table_lookup (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type)); if (!serials) { g_free (mime_type_table); maybe_send_next_mime_type_content_request (clipboard_rdp); return; } if (!send_mime_type_content_request (clipboard_rdp, mime_type_table)) { abort_client_requests_for_mime_type (clipboard_rdp, mime_type); g_free (mime_type_table); maybe_send_next_mime_type_content_request (clipboard_rdp); } } static gboolean abort_current_client_request (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClientFormatDataRequestContext *request_context; g_mutex_lock (&clipboard_rdp->client_request_mutex); request_context = g_steal_pointer (&clipboard_rdp->current_client_request); g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove); g_mutex_unlock (&clipboard_rdp->client_request_mutex); g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not send " "format data response (Timeout reached)"); abort_client_requests_for_context (clipboard_rdp, request_context); g_free (request_context); clipboard_rdp->client_request_abort_id = 0; maybe_send_next_mime_type_content_request (clipboard_rdp); return G_SOURCE_REMOVE; } static gboolean send_mime_type_content_request (GrdClipboardRdp *clipboard_rdp, GrdMimeTypeTable *mime_type_table) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FORMAT_DATA_REQUEST format_data_request = {0}; GrdMimeType mime_type = mime_type_table->mime_type; ClientFormatDataRequestContext *request_context; uint32_t clip_data_id = 0; g_assert (mime_type != GRD_MIME_TYPE_NONE); g_assert (!clipboard_rdp->current_client_request); g_assert (!clipboard_rdp->client_format_data_response_id); g_assert (!clipboard_rdp->client_request_abort_id); request_context = g_malloc0 (sizeof (ClientFormatDataRequestContext)); request_context->mime_type_table = *mime_type_table; if (clipboard_rdp->cliprdr_context->canLockClipData && (mime_type == GRD_MIME_TYPE_TEXT_URILIST || mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)) { clip_data_id = grd_rdp_fuse_clipboard_clip_data_id_new (rdp_fuse_clipboard); request_context->clip_data_id = clip_data_id; request_context->has_clip_data_id = TRUE; } format_data_request.msgType = CB_FORMAT_DATA_REQUEST; format_data_request.dataLen = 4; format_data_request.requestedFormatId = mime_type_table->rdp.format_id; g_mutex_lock (&clipboard_rdp->client_request_mutex); if (cliprdr_context->ServerFormatDataRequest (cliprdr_context, &format_data_request)) { g_mutex_unlock (&clipboard_rdp->client_request_mutex); if (request_context->has_clip_data_id) grd_rdp_fuse_clipboard_clip_data_id_free (rdp_fuse_clipboard, clip_data_id); g_free (request_context); return FALSE; } g_debug ("[RDP.CLIPRDR] Sent FormatDataRequest for mime type %s", grd_mime_type_to_string (mime_type)); clipboard_rdp->current_client_request = request_context; g_mutex_unlock (&clipboard_rdp->client_request_mutex); clipboard_rdp->client_request_abort_id = g_timeout_add (MAX_WAIT_TIME, abort_current_client_request, clipboard_rdp); return TRUE; } static void grd_clipboard_rdp_request_client_content_for_mime_type (GrdClipboard *clipboard, GrdMimeTypeTable *mime_type_table, unsigned int serial) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard); GrdMimeType mime_type = mime_type_table->mime_type; HANDLE completed_format_list_event = clipboard_rdp->completed_format_list_event; FormatData *format_data; if (mime_type == GRD_MIME_TYPE_NONE) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } if (g_hash_table_lookup_extended (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &format_data)) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, format_data->data, format_data->size); return; } if (WaitForSingleObject (completed_format_list_event, 0) == WAIT_TIMEOUT) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } if (clipboard_rdp->current_client_request) { enqueue_mime_type_content_request (clipboard_rdp, mime_type_table, serial); return; } g_assert (g_queue_is_empty (clipboard_rdp->ordered_client_requests)); if (!send_mime_type_content_request (clipboard_rdp, mime_type_table)) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } track_serial_for_mime_type (clipboard_rdp, mime_type, serial); } static void serialize_file_list (FILEDESCRIPTORW *files, uint32_t n_files, uint8_t **dst_data, uint32_t *dst_size) { FILEDESCRIPTORW *file; wStream* s = NULL; uint64_t last_write_time; uint32_t i, j; if (!files || !dst_data || !dst_size) return; if (!(s = Stream_New (NULL, 4 + n_files * CLIPRDR_FILEDESCRIPTOR_SIZE))) return; Stream_Write_UINT32 (s, n_files); /* cItems */ for (i = 0; i < n_files; ++i) { file = &files[i]; Stream_Write_UINT32 (s, file->dwFlags); /* flags */ Stream_Zero (s, 32); /* reserved1 */ Stream_Write_UINT32 (s, file->dwFileAttributes); /* fileAttributes */ Stream_Zero (s, 16); /* reserved2 */ last_write_time = file->ftLastWriteTime.dwHighDateTime; last_write_time <<= 32; last_write_time += file->ftLastWriteTime.dwLowDateTime; Stream_Write_UINT64 (s, last_write_time); /* lastWriteTime */ Stream_Write_UINT32 (s, file->nFileSizeHigh); /* fileSizeHigh */ Stream_Write_UINT32 (s, file->nFileSizeLow); /* fileSizeLow */ for (j = 0; j < 260; j++) /* cFileName */ Stream_Write_UINT16 (s, file->cFileName[j]); } Stream_SealLength (s); Stream_GetLength (s, *dst_size); Stream_GetBuffer (s, *dst_data); Stream_Free (s, FALSE); } static void grd_clipboard_rdp_submit_requested_server_content (GrdClipboard *clipboard, uint8_t *src_data, uint32_t src_size) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard); CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; ServerFormatDataRequestContext *request_context; CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0}; GrdMimeType mime_type; uint32_t src_format_id; uint32_t dst_format_id; uint8_t *dst_data = NULL; uint32_t dst_size = 0; BOOL success; request_context = g_steal_pointer (&clipboard_rdp->format_data_request_context); mime_type = request_context->mime_type; src_format_id = request_context->src_format_id; dst_format_id = request_context->dst_format_id; if (src_data) { if (request_context->needs_conversion) { if (request_context->needs_null_terminator) { char *pnull_terminator; src_data = g_realloc (src_data, src_size + 1); pnull_terminator = (char *) src_data + src_size; *pnull_terminator = '\0'; ++src_size; } success = ClipboardSetData (clipboard_rdp->system, src_format_id, src_data, src_size); if (success) { dst_data = ClipboardGetData (clipboard_rdp->system, dst_format_id, &dst_size); if (dst_data && mime_type == GRD_MIME_TYPE_TEXT_URILIST) { uint64_t serial = clipboard_rdp->serial; ClipDataEntry *entry; FILEDESCRIPTORW *files; uint32_t n_files; files = (FILEDESCRIPTORW *) dst_data; n_files = dst_size / sizeof (FILEDESCRIPTORW); dst_data = NULL; dst_size = 0; serialize_file_list (files, n_files, &dst_data, &dst_size); g_free (files); clipboard_rdp->has_file_list = TRUE; if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (serial), NULL, (gpointer *) &entry)) entry->has_file_list = TRUE; } } if (!success || !dst_data) { g_warning ("[RDP.CLIPRDR] Converting clipboard content for " "client failed"); } } else { dst_data = g_steal_pointer (&src_data); dst_size = src_size; } } format_data_response.msgType = CB_FORMAT_DATA_RESPONSE; format_data_response.msgFlags = dst_data ? CB_RESPONSE_OK : CB_RESPONSE_FAIL; format_data_response.dataLen = dst_size; format_data_response.requestedFormatData = dst_data; cliprdr_context->ServerFormatDataResponse (cliprdr_context, &format_data_response); g_free (src_data); g_free (dst_data); g_free (request_context); SetEvent (clipboard_rdp->completed_format_data_request_event); } /** * FreeRDP already updated our capabilites after the client told us * about its capabilities, there is nothing to do here */ static uint32_t cliprdr_client_capabilities (CliprdrServerContext *cliprdr_context, const CLIPRDR_CAPABILITIES *capabilities) { g_autoptr (GStrvBuilder) client_capabilities = NULL; char **client_caps_strings; g_autofree char *caps_string = NULL; client_capabilities = g_strv_builder_new (); if (cliprdr_context->useLongFormatNames) g_strv_builder_add (client_capabilities, "long format names"); if (cliprdr_context->streamFileClipEnabled) g_strv_builder_add (client_capabilities, "stream file clip"); if (cliprdr_context->fileClipNoFilePaths) g_strv_builder_add (client_capabilities, "file clip no file paths"); if (cliprdr_context->canLockClipData) g_strv_builder_add (client_capabilities, "can lock clip data"); if (cliprdr_context->hasHugeFileSupport) g_strv_builder_add (client_capabilities, "huge file support"); client_caps_strings = g_strv_builder_end (client_capabilities); caps_string = g_strjoinv (", ", client_caps_strings); g_message ("[RDP.CLIPRDR] Client capabilities: %s", caps_string); g_strfreev (client_caps_strings); return CHANNEL_RC_OK; } /** * Client sent us a Temporary Directory PDU. * We don't handle the CF_HDROP format however. It's a relict of the past. */ static uint32_t cliprdr_temp_directory (CliprdrServerContext *cliprdr_context, const CLIPRDR_TEMP_DIRECTORY *temp_directory) { g_debug ("[RDP.CLIPRDR] Client sent a Temporary Directory PDU with path \"%s\"", temp_directory->szTempDir); return CHANNEL_RC_OK; } static void abort_all_client_requests (GrdClipboardRdp *clipboard_rdp) { ClientFormatDataRequestContext *request_context; GHashTableIter iter; GList *serials; g_debug ("[RDP.CLIPRDR] Aborting all pending client requests"); g_mutex_lock (&clipboard_rdp->client_request_mutex); request_context = g_steal_pointer (&clipboard_rdp->current_client_request); g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove); g_mutex_unlock (&clipboard_rdp->client_request_mutex); if (request_context) abort_client_requests_for_context (clipboard_rdp, request_context); g_free (request_context); g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove); g_hash_table_iter_init (&iter, clipboard_rdp->pending_client_requests); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &serials)) { abort_client_requests_for_serials (clipboard_rdp, serials); g_hash_table_iter_remove (&iter); } g_queue_clear_full (clipboard_rdp->ordered_client_requests, g_free); } static gboolean update_server_format_list (gpointer user_data) { ServerFormatListUpdateContext *update_context = user_data; GrdClipboardRdp *clipboard_rdp = update_context->clipboard_rdp; GrdClipboard *clipboard = GRD_CLIPBOARD (update_context->clipboard_rdp); GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {0}; GList *mime_type_tables; GrdMimeTypeTable *mime_type_table; GList *l; mime_type_tables = g_steal_pointer (&update_context->mime_type_tables); for (l = mime_type_tables; l; l = l->next) { mime_type_table = l->data; /* Also indirectly handles the GRD_MIME_TYPE_XS_GNOME_COPIED_FILES case */ if (mime_type_table->mime_type == GRD_MIME_TYPE_TEXT_URILIST) { clipboard_rdp->server_file_contents_requests_allowed = FALSE; grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard); grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (rdp_fuse_clipboard); } remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type_table->mime_type); g_hash_table_remove (clipboard_rdp->allowed_server_formats, GUINT_TO_POINTER (mime_type_table->mime_type)); } abort_all_client_requests (clipboard_rdp); grd_clipboard_update_server_mime_type_list (clipboard, mime_type_tables); format_list_response.msgType = CB_FORMAT_LIST_RESPONSE; format_list_response.msgFlags = CB_RESPONSE_OK; cliprdr_context->ServerFormatListResponse (cliprdr_context, &format_list_response); /** * Any FileContentsRequest that is still waiting for a FileContentsResponse * won't get a response any more, when the client does not support clipboard * data locking. So, drop all requests without clipDataId here. */ if (!cliprdr_context->canLockClipData) grd_rdp_fuse_clipboard_dismiss_all_no_cdi_requests (rdp_fuse_clipboard); WaitForSingleObject (clipboard_rdp->format_list_received_event, INFINITE); clipboard_rdp->server_format_list_update_id = 0; ResetEvent (clipboard_rdp->format_list_received_event); SetEvent (clipboard_rdp->completed_format_list_event); return G_SOURCE_REMOVE; } static void update_context_free (gpointer data) { ServerFormatListUpdateContext *update_context = data; g_clear_list (&update_context->mime_type_tables, g_free); g_free (update_context); } /** * Client notifies us that its clipboard is updated with new clipboard data */ static uint32_t cliprdr_client_format_list (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_LIST *format_list) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; ServerFormatListUpdateContext *update_context; GHashTable *found_mime_types; GrdMimeTypeTable *mime_type_table = NULL; GList *mime_type_tables = NULL; GrdMimeType mime_type; gboolean already_has_text_format = FALSE; HANDLE events[2]; uint32_t i; found_mime_types = g_hash_table_new (NULL, NULL); /** * The text format CF_TEXT can depend on the CF_LOCALE content. In such * situations, we might not get the correct content from WinPR. * CF_UNICODETEXT is not affected by the CF_LOCALE content, so try to find * the CF_UNICODETEXT first to ensure that we use CF_UNICODETEXT, when both * formats are available and CF_TEXT is listed first. */ for (i = 0; i < format_list->numFormats; ++i) { if (format_list->formats[i].formatId == CF_UNICODETEXT) { mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING; mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); already_has_text_format = TRUE; g_debug ("[RDP.CLIPRDR] Force using CF_UNICODETEXT over CF_TEXT as " "external format for text/plain;charset=utf-8 and UTF8_STRING"); break; } } for (i = 0; i < format_list->numFormats; ++i) { mime_type = GRD_MIME_TYPE_NONE; /** * FileGroupDescriptorW does not have a consistent id. The name however, * is always the same. * * If the client uses short format names, the formatName is truncated to * either 32 ASCII 8 characters or 16 UTF-16 characters. */ if (format_list->formats[i].formatName && (strcmp (format_list->formats[i].formatName, "FileGroupDescriptorW") == 0 || strcmp (format_list->formats[i].formatName, "FileGroupDescri") == 0)) { if (!g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_URILIST))) { /** * Advertise the "x-special/gnome-copied-files" format in addition to * the "text/uri-list" format */ mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); } mime_type = GRD_MIME_TYPE_TEXT_URILIST; } else if (format_list->formats[i].formatName && strcmp (format_list->formats[i].formatName, "HTML Format") == 0) { mime_type = GRD_MIME_TYPE_TEXT_HTML; } else { switch (format_list->formats[i].formatId) { case CF_TEXT: case CF_UNICODETEXT: case CF_OEMTEXT: if (!already_has_text_format) { mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; g_assert (!g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (mime_type))); mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING; already_has_text_format = TRUE; g_debug ("[RDP.CLIPRDR] Client advertised data for " "text/plain;charset=utf-8 and UTF8_STRING " "(external format: id: %u, name: %s)", format_list->formats[i].formatId, format_list->formats[i].formatName); } break; case CF_DIB: mime_type = GRD_MIME_TYPE_IMAGE_BMP; break; case CF_TIFF: mime_type = GRD_MIME_TYPE_IMAGE_TIFF; break; case CB_FORMAT_GIF: mime_type = GRD_MIME_TYPE_IMAGE_GIF; break; case CB_FORMAT_JPEG: mime_type = GRD_MIME_TYPE_IMAGE_JPEG; break; case CB_FORMAT_PNG: mime_type = GRD_MIME_TYPE_IMAGE_PNG; break; default: g_debug ("[RDP.CLIPRDR] Client advertised unknown format: id: %u, " "name: %s", format_list->formats[i].formatId, format_list->formats[i].formatName); } } if (mime_type != GRD_MIME_TYPE_NONE && !g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (mime_type))) { mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = mime_type; mime_type_table->rdp.format_id = format_list->formats[i].formatId; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type)); } else if (mime_type != GRD_MIME_TYPE_NONE) { g_debug ("[RDP.CLIPRDR] Ignoring duplicated format: id: %u, name: %s", format_list->formats[i].formatId, format_list->formats[i].formatName); } } g_hash_table_destroy (found_mime_types); if (clipboard_rdp->server_format_list_update_id) { g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format list " "without being able to response to last update first"); } events[0] = clipboard_rdp->stop_event; events[1] = clipboard_rdp->completed_format_list_event; WaitForMultipleObjects (2, events, FALSE, INFINITE); if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0) { g_list_free_full (mime_type_tables, g_free); return CHANNEL_RC_OK; } ResetEvent (clipboard_rdp->completed_format_list_event); update_context = g_malloc0 (sizeof (ServerFormatListUpdateContext)); update_context->clipboard_rdp = clipboard_rdp; update_context->mime_type_tables = mime_type_tables; clipboard_rdp->server_format_list_update_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, update_server_format_list, update_context, update_context_free); SetEvent (clipboard_rdp->format_list_received_event); return CHANNEL_RC_OK; } static gboolean handle_format_list_response (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; GList *queued_server_formats; if (clipboard_rdp->pending_server_formats) update_allowed_server_formats (clipboard_rdp, TRUE); g_clear_handle_id (&clipboard_rdp->pending_server_formats_drop_id, g_source_remove); WaitForSingleObject (clipboard_rdp->format_list_response_received_event, INFINITE); clipboard_rdp->client_format_list_response_id = 0; ResetEvent (clipboard_rdp->format_list_response_received_event); queued_server_formats = g_steal_pointer (&clipboard_rdp->queued_server_formats); if (queued_server_formats) send_mime_type_list (clipboard_rdp, queued_server_formats); return G_SOURCE_REMOVE; } /** * Client sent us a response to our format list pdu */ static uint32_t cliprdr_client_format_list_response (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_LIST_RESPONSE *format_list_response) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; HANDLE format_list_response_received_event; format_list_response_received_event = clipboard_rdp->format_list_response_received_event; if (WaitForSingleObject (format_list_response_received_event, 0) == WAIT_OBJECT_0) { g_warning ("[RDP.CLIPRDR] Wrong message sequence: Received an unexpected " "format list response. Ignoring..."); return CHANNEL_RC_OK; } clipboard_rdp->format_list_response_msg_flags = format_list_response->msgFlags; clipboard_rdp->client_format_list_response_id = g_idle_add (handle_format_list_response, clipboard_rdp); SetEvent (format_list_response_received_event); return CHANNEL_RC_OK; } static gboolean retrieve_current_clipboard (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClipDataEntry *entry = clipboard_rdp->clipboard_retrieval_context.entry; ClipDataEntry *entry_to_replace; uint64_t serial = clipboard_rdp->serial; gboolean serial_already_in_use; g_debug ("[RDP.CLIPRDR] Tracking serial %lu for ClipDataEntry", clipboard_rdp->serial); entry_to_replace = clipboard_rdp->clipboard_retrieval_context.entry_to_replace; if (entry_to_replace) { g_hash_table_remove (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (entry_to_replace->serial)); } serial_already_in_use = g_hash_table_contains (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (serial)); if (!serial_already_in_use) { g_hash_table_insert (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (serial), entry); } clipboard_rdp->clipboard_retrieval_context.serial_already_in_use = serial_already_in_use; entry->serial = serial; entry->has_file_list = clipboard_rdp->has_file_list; entry->requests_allowed = clipboard_rdp->server_file_contents_requests_allowed; entry->system = clipboard_rdp->system; entry->delegate = clipboard_rdp->delegate; WaitForSingleObject (clipboard_rdp->clip_data_entry_event, INFINITE); clipboard_rdp->clipboard_retrieval_id = 0; ResetEvent (clipboard_rdp->clip_data_entry_event); SetEvent (clipboard_rdp->completed_clip_data_entry_event); return G_SOURCE_REMOVE; } static gboolean is_clip_data_entry_user_of_serial (gpointer key, gpointer value, gpointer user_data) { ClipDataEntry *entry = value; uint64_t serial = GPOINTER_TO_UINT (user_data); return entry->serial == serial; } /** * Client requests us to retain all file stream data on the clipboard */ static uint32_t cliprdr_client_lock_clipboard_data (CliprdrServerContext *cliprdr_context, const CLIPRDR_LOCK_CLIPBOARD_DATA *lock_clipboard_data) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; uint32_t clip_data_id = lock_clipboard_data->clipDataId; ClipDataEntry *entry; HANDLE events[2]; if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0) return CHANNEL_RC_OK; g_debug ("[RDP.CLIPRDR] Client requested a lock with clipDataId: %u", clip_data_id); clipboard_rdp->clipboard_retrieval_context.entry_to_replace = NULL; if (g_hash_table_lookup_extended (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_warning ("[RDP.CLIPRDR] Protocol violation: Client requested a lock with" " an existing clipDataId %u. Replacing existing ClipDataEntry", clip_data_id); clipboard_rdp->clipboard_retrieval_context.entry_to_replace = entry; } ResetEvent (clipboard_rdp->completed_clip_data_entry_event); entry = g_malloc0 (sizeof (ClipDataEntry)); clipboard_rdp->clipboard_retrieval_context.entry = entry; clipboard_rdp->clipboard_retrieval_id = g_idle_add (retrieve_current_clipboard, clipboard_rdp); SetEvent (clipboard_rdp->clip_data_entry_event); events[0] = clipboard_rdp->stop_event; events[1] = clipboard_rdp->completed_clip_data_entry_event; WaitForMultipleObjects (2, events, FALSE, INFINITE); if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0) return CHANNEL_RC_OK; if (clipboard_rdp->clipboard_retrieval_context.serial_already_in_use) { uint64_t serial = entry->serial; g_warning ("[RDP.CLIPRDR] Protocol violation: Double lock detected (" "Clipboard serial already in use). Replacing the clipDataId."); g_free (entry); entry = g_hash_table_find (clipboard_rdp->clip_data_table, is_clip_data_entry_user_of_serial, GUINT_TO_POINTER (serial)); g_hash_table_steal (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (entry->id)); } entry->id = clip_data_id; g_debug ("[RDP.CLIPRDR] Tracking lock with clipDataId %u", clip_data_id); g_hash_table_insert (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), entry); return CHANNEL_RC_OK; } static gboolean handle_clip_data_entry_destruction (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClipDataEntry *entry; entry = g_steal_pointer (&clipboard_rdp->clipboard_destruction_context.entry); g_debug ("[RDP.CLIPRDR] Deleting ClipDataEntry with serial %lu", entry->serial); g_hash_table_remove (clipboard_rdp->serial_entry_table, GUINT_TO_POINTER (entry->serial)); WaitForSingleObject (clipboard_rdp->clip_data_entry_event, INFINITE); clipboard_rdp->clipboard_destruction_id = 0; ResetEvent (clipboard_rdp->clip_data_entry_event); SetEvent (clipboard_rdp->completed_clip_data_entry_event); return G_SOURCE_REMOVE; } /** * Client notifies us that the file stream data for a specific clip data id MUST * now be released */ static uint32_t cliprdr_client_unlock_clipboard_data (CliprdrServerContext *cliprdr_context, const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlock_clipboard_data) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; uint32_t clip_data_id = unlock_clipboard_data->clipDataId; ClipDataEntry *entry; HANDLE events[2]; if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0) return CHANNEL_RC_OK; g_debug ("[RDP.CLIPRDR] Client requested an unlock with clipDataId: %u", clip_data_id); if (!g_hash_table_lookup_extended (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_warning ("[RDP.CLIPRDR] Protocol violation: ClipDataEntry with id %u " "does not exist", clip_data_id); return CHANNEL_RC_OK; } g_debug ("[RDP.CLIPRDR] Removing lock with clipDataId: %u", clip_data_id); ResetEvent (clipboard_rdp->completed_clip_data_entry_event); clipboard_rdp->clipboard_destruction_context.entry = entry; clipboard_rdp->clipboard_destruction_id = g_idle_add (handle_clip_data_entry_destruction, clipboard_rdp); SetEvent (clipboard_rdp->clip_data_entry_event); events[0] = clipboard_rdp->stop_event; events[1] = clipboard_rdp->completed_clip_data_entry_event; WaitForMultipleObjects (2, events, FALSE, INFINITE); g_debug ("[RDP.CLIPRDR] Untracking lock with clipDataId %u", clip_data_id); g_hash_table_remove (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id)); return CHANNEL_RC_OK; } static gboolean request_server_format_data (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; ServerFormatDataRequestContext *request_context; GrdMimeType mime_type; request_context = clipboard_rdp->format_data_request_context; mime_type = request_context->mime_type; if (!g_hash_table_contains (clipboard_rdp->allowed_server_formats, GUINT_TO_POINTER (mime_type))) { CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0}; format_data_response.msgType = CB_FORMAT_DATA_RESPONSE; format_data_response.msgFlags = CB_RESPONSE_FAIL; cliprdr_context->ServerFormatDataResponse (cliprdr_context, &format_data_response); g_clear_pointer (&clipboard_rdp->format_data_request_context, g_free); WaitForSingleObject (clipboard_rdp->format_data_request_received_event, INFINITE); clipboard_rdp->server_format_data_request_id = 0; ResetEvent (clipboard_rdp->format_data_request_received_event); SetEvent (clipboard_rdp->completed_format_data_request_event); return G_SOURCE_REMOVE; } grd_clipboard_request_server_content_for_mime_type_async (clipboard, mime_type); WaitForSingleObject (clipboard_rdp->format_data_request_received_event, INFINITE); clipboard_rdp->server_format_data_request_id = 0; ResetEvent (clipboard_rdp->format_data_request_received_event); return G_SOURCE_REMOVE; } /** * Client knows our format list, it requests now the data from our clipboard */ static uint32_t cliprdr_client_format_data_request (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_DATA_REQUEST *format_data_request) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; ServerFormatDataRequestContext *request_context; CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0}; GrdMimeType mime_type = GRD_MIME_TYPE_NONE; uint32_t src_format_id = 0; uint32_t dst_format_id; gboolean needs_null_terminator = FALSE; gboolean needs_conversion = FALSE; HANDLE events[2]; dst_format_id = format_data_request->requestedFormatId; switch (dst_format_id) { case CF_TEXT: mime_type = GRD_MIME_TYPE_TEXT_PLAIN; needs_null_terminator = TRUE; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/plain"); break; case CF_UNICODETEXT: mime_type = clipboard_rdp->which_unicode_format; needs_null_terminator = TRUE; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "UTF8_STRING"); break; case CB_FORMAT_HTML: mime_type = GRD_MIME_TYPE_TEXT_HTML; needs_null_terminator = TRUE; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/html"); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "HTML Format"); break; case CF_DIB: mime_type = GRD_MIME_TYPE_IMAGE_BMP; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "image/bmp"); break; case CF_TIFF: mime_type = GRD_MIME_TYPE_IMAGE_TIFF; break; case CB_FORMAT_GIF: mime_type = GRD_MIME_TYPE_IMAGE_GIF; break; case CB_FORMAT_JPEG: mime_type = GRD_MIME_TYPE_IMAGE_JPEG; break; case CB_FORMAT_PNG: mime_type = GRD_MIME_TYPE_IMAGE_PNG; break; case CB_FORMAT_TEXTURILIST: mime_type = GRD_MIME_TYPE_TEXT_URILIST; needs_conversion = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/uri-list"); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "FileGroupDescriptorW"); break; } if (clipboard_rdp->server_format_data_request_id) { g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format data " "request without being able to push response for last request " "yet."); } events[0] = clipboard_rdp->stop_event; events[1] = clipboard_rdp->completed_format_data_request_event; WaitForMultipleObjects (2, events, FALSE, INFINITE); if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0) return CHANNEL_RC_OK; if (mime_type != GRD_MIME_TYPE_NONE) { ResetEvent (clipboard_rdp->completed_format_data_request_event); request_context = g_malloc0 (sizeof (ServerFormatDataRequestContext)); request_context->clipboard_rdp = clipboard_rdp; request_context->mime_type = mime_type; request_context->src_format_id = src_format_id; request_context->dst_format_id = dst_format_id; request_context->needs_null_terminator = needs_null_terminator; request_context->needs_conversion = needs_conversion; g_assert (!clipboard_rdp->format_data_request_context); clipboard_rdp->format_data_request_context = request_context; clipboard_rdp->server_format_data_request_id = g_idle_add (request_server_format_data, clipboard_rdp); SetEvent (clipboard_rdp->format_data_request_received_event); return CHANNEL_RC_OK; } format_data_response.msgType = CB_FORMAT_DATA_RESPONSE; format_data_response.msgFlags = CB_RESPONSE_FAIL; format_data_response.dataLen = 0; format_data_response.requestedFormatData = NULL; return cliprdr_context->ServerFormatDataResponse (cliprdr_context, &format_data_response); } static gboolean extract_format_data_response (GrdClipboardRdp *clipboard_rdp, CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response, uint8_t **data, uint32_t *size) { gboolean response_ok; *data = (uint8_t *) format_data_response->requestedFormatData; *size = format_data_response->dataLen; response_ok = format_data_response->msgFlags & CB_RESPONSE_OK; *size = response_ok && *data ? *size : 0; if (!response_ok) g_clear_pointer (data, g_free); return response_ok && *data; } static uint8_t * get_uri_list_from_packet_file_list (GrdClipboardRdp *clipboard_rdp, uint8_t *src_data, uint32_t src_size, uint32_t *dst_size, gboolean has_clip_data_id, uint32_t clip_data_id) { FILEDESCRIPTORW *files = NULL; FILEDESCRIPTORW *file; uint32_t n_files = 0; g_autofree char *clip_data_dir_name = NULL; char *filename = NULL; char *escaped_name; char *file_uri; GArray *dst_data; uint32_t i; clip_data_dir_name = has_clip_data_id ? g_strdup_printf ("%u", clip_data_id) : g_strdup_printf ("%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID); *dst_size = 0; dst_data = g_array_new (TRUE, TRUE, sizeof (char)); cliprdr_parse_file_list (src_data, src_size, &files, &n_files); for (i = 0; i < n_files; ++i) { file = &files[i]; if (ConvertFromUnicode (CP_UTF8, 0, file->cFileName, -1, &filename, 0, NULL, NULL) <= 0) { g_array_free (dst_data, TRUE); g_free (files); return NULL; } if (strchr (filename, '\\')) { g_free (filename); continue; } escaped_name = g_uri_escape_string (filename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); file_uri = g_strdup_printf ("file://%s/%s/%s\r\n", clipboard_rdp->fuse_mount_path, clip_data_dir_name, escaped_name); g_array_append_vals (dst_data, file_uri, strlen (file_uri)); g_free (file_uri); g_free (escaped_name); g_free (filename); } *dst_size = dst_data->len; g_free (files); return (uint8_t *) g_array_free (dst_data, FALSE); } static uint8_t * convert_client_content_for_server (GrdClipboardRdp *clipboard_rdp, uint8_t *src_data, uint32_t src_size, GrdMimeType mime_type, uint32_t src_format_id, uint32_t *dst_size, gboolean has_clip_data_id, uint32_t clip_data_id) { GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; uint32_t dst_format_id; uint8_t *dst_data; gboolean is_null_terminated = FALSE; BOOL success; *dst_size = 0; switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: is_null_terminated = TRUE; dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "UTF8_STRING"); break; case GRD_MIME_TYPE_TEXT_HTML: is_null_terminated = TRUE; src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "HTML Format"); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/html"); break; case GRD_MIME_TYPE_IMAGE_BMP: dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "image/bmp"); break; case GRD_MIME_TYPE_TEXT_URILIST: case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES: src_format_id = ClipboardGetFormatId (clipboard_rdp->system, "FileGroupDescriptorW"); dst_format_id = ClipboardGetFormatId (clipboard_rdp->system, "text/uri-list"); break; default: g_assert_not_reached (); } success = ClipboardSetData (clipboard_rdp->system, src_format_id, src_data, src_size); if (!success) { g_warning ("[RDP.CLIPRDR] Converting clipboard content failed"); return NULL; } if (mime_type == GRD_MIME_TYPE_TEXT_URILIST || mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) { dst_data = get_uri_list_from_packet_file_list (clipboard_rdp, src_data, src_size, dst_size, has_clip_data_id, clip_data_id); } else { dst_data = ClipboardGetData (clipboard_rdp->system, dst_format_id, dst_size); } if (!dst_data) { g_warning ("[RDP.CLIPRDR] Converting clipboard content failed"); return NULL; } if (is_null_terminated) { uint8_t *null_terminator_pos = memchr (dst_data, '\0', *dst_size); if (null_terminator_pos) *dst_size = null_terminator_pos - dst_data; } if (mime_type == GRD_MIME_TYPE_TEXT_URILIST || mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) { FILEDESCRIPTORW *files = NULL; FILEDESCRIPTORW *file; uint32_t n_files = 0; gboolean result; g_autofree char *clip_data_dir_name = NULL; char *filename = NULL; char *escaped_name; char *full_filepath; GrdMimeType second_mime_type; FormatData *format_data; GArray *data_nautilus; const char *nautilus_header; uint8_t *dst_data_nautilus; uint32_t dst_size_nautilus; uint32_t i; cliprdr_parse_file_list (src_data, src_size, &files, &n_files); if (has_clip_data_id) { result = grd_rdp_fuse_clipboard_set_cdi_selection (rdp_fuse_clipboard, files, n_files, clip_data_id); } else { result = grd_rdp_fuse_clipboard_set_no_cdi_selection (rdp_fuse_clipboard, files, n_files); } if (!result) { g_free (files); g_free (dst_data); return NULL; } clip_data_dir_name = has_clip_data_id ? g_strdup_printf ("%u", clip_data_id) : g_strdup_printf ("%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID); data_nautilus = g_array_new (TRUE, TRUE, sizeof (char)); nautilus_header = "copy"; g_array_append_vals (data_nautilus, nautilus_header, strlen (nautilus_header)); for (i = 0; i < n_files; ++i) { file = &files[i]; if (ConvertFromUnicode (CP_UTF8, 0, file->cFileName, -1, &filename, 0, NULL, NULL) <= 0) { g_array_free (data_nautilus, TRUE); grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard); g_free (files); g_free (dst_data); return NULL; } if (strchr (filename, '\\')) { g_free (filename); continue; } escaped_name = g_uri_escape_string (filename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); full_filepath = g_strdup_printf ("\nfile://%s/%s/%s", clipboard_rdp->fuse_mount_path, clip_data_dir_name, escaped_name); g_array_append_vals (data_nautilus, full_filepath, strlen (full_filepath)); g_free (full_filepath); g_free (escaped_name); g_free (filename); } dst_size_nautilus = data_nautilus->len; dst_data_nautilus = (uint8_t *) g_array_free (data_nautilus, FALSE); format_data = g_malloc0 (sizeof (FormatData)); if (mime_type == GRD_MIME_TYPE_TEXT_URILIST) { /** * Also create the format data for the * "x-special/gnome-copied-files" mimetype */ second_mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; format_data->size = dst_size_nautilus; format_data->data = dst_data_nautilus; } else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) { /** * Also create the format data for the "text/uri-list" mimetype */ second_mime_type = GRD_MIME_TYPE_TEXT_URILIST; format_data->size = *dst_size; format_data->data = dst_data; *dst_size = dst_size_nautilus; dst_data = dst_data_nautilus; } else { g_assert_not_reached (); } g_hash_table_insert (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (second_mime_type), format_data); g_free (files); } if (!dst_data) g_warning ("[RDP.CLIPRDR] Converting clipboard content failed"); return dst_data; } static gboolean prepare_client_content_for_server (GrdClipboardRdp *clipboard_rdp, uint8_t **src_data, uint32_t src_size, GrdMimeTypeTable *mime_type_table, gboolean has_clip_data_id, uint32_t clip_data_id) { GrdMimeType mime_type = mime_type_table->mime_type; uint32_t src_format_id = mime_type_table->rdp.format_id; FormatData *format_data; uint8_t *dst_data = NULL; uint32_t dst_size = 0; switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: case GRD_MIME_TYPE_TEXT_HTML: case GRD_MIME_TYPE_IMAGE_BMP: case GRD_MIME_TYPE_TEXT_URILIST: case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES: dst_data = convert_client_content_for_server (clipboard_rdp, *src_data, src_size, mime_type, src_format_id, &dst_size, has_clip_data_id, clip_data_id); break; default: dst_data = g_steal_pointer (src_data); dst_size = src_size; } if (!dst_data) return FALSE; format_data = g_malloc0 (sizeof (FormatData)); format_data->size = dst_size; format_data->data = dst_data; g_hash_table_insert (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type), format_data); return TRUE; } static void serve_mime_type_content (GrdClipboardRdp *clipboard_rdp, GrdMimeType mime_type) { GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); FormatData *format_data; GList *serials; unsigned int serial; GList *l; if (!g_hash_table_steal_extended (clipboard_rdp->pending_client_requests, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &serials)) return; if (!g_hash_table_lookup_extended (clipboard_rdp->format_data_cache, GUINT_TO_POINTER (mime_type), NULL, (gpointer *) &format_data)) g_assert_not_reached (); for (l = serials; l; l = l->next) { serial = GPOINTER_TO_UINT (l->data); grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, format_data->data, format_data->size); } g_list_free (serials); } static gboolean handle_format_data_response (gpointer user_data) { GrdClipboardRdp *clipboard_rdp = user_data; ClientFormatDataRequestContext *request_context; CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response; GrdMimeTypeTable *mime_type_table; GrdMimeType mime_type; uint8_t *src_data; uint32_t src_size; g_mutex_lock (&clipboard_rdp->client_request_mutex); request_context = g_steal_pointer (&clipboard_rdp->current_client_request); format_data_response = g_steal_pointer (&clipboard_rdp->format_data_response); clipboard_rdp->client_format_data_response_id = 0; g_mutex_unlock (&clipboard_rdp->client_request_mutex); mime_type_table = &request_context->mime_type_table; mime_type = mime_type_table->mime_type; if (extract_format_data_response (clipboard_rdp, format_data_response, &src_data, &src_size) && prepare_client_content_for_server (clipboard_rdp, &src_data, src_size, mime_type_table, request_context->has_clip_data_id, request_context->clip_data_id)) { serve_mime_type_content (clipboard_rdp, mime_type); if (mime_type == GRD_MIME_TYPE_TEXT_URILIST) serve_mime_type_content (clipboard_rdp, GRD_MIME_TYPE_XS_GNOME_COPIED_FILES); else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES) serve_mime_type_content (clipboard_rdp, GRD_MIME_TYPE_TEXT_URILIST); } else { abort_client_requests_for_context (clipboard_rdp, request_context); } g_free (src_data); g_free (format_data_response); g_free (request_context); g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove); maybe_send_next_mime_type_content_request (clipboard_rdp); return G_SOURCE_REMOVE; } /** * Clients response to our data request that we sent */ static uint32_t cliprdr_client_format_data_response (CliprdrServerContext *cliprdr_context, const CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; g_mutex_lock (&clipboard_rdp->client_request_mutex); if (!clipboard_rdp->current_client_request) { g_mutex_unlock (&clipboard_rdp->client_request_mutex); return CHANNEL_RC_OK; } if (clipboard_rdp->format_data_response) { g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData); g_clear_pointer (&clipboard_rdp->format_data_response, g_free); } clipboard_rdp->format_data_response = g_memdup2 (format_data_response, sizeof (CLIPRDR_FORMAT_DATA_RESPONSE)); clipboard_rdp->format_data_response->requestedFormatData = g_memdup2 (format_data_response->requestedFormatData, format_data_response->dataLen); if (!clipboard_rdp->client_format_data_response_id) { clipboard_rdp->client_format_data_response_id = g_idle_add (handle_format_data_response, clipboard_rdp); } g_mutex_unlock (&clipboard_rdp->client_request_mutex); return CHANNEL_RC_OK; } static uint32_t delegate_request_file_contents_size (wClipboardDelegate *delegate, const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request) { wClipboardFileSizeRequest file_size_request = {0}; file_size_request.streamId = file_contents_request->streamId; file_size_request.listIndex = file_contents_request->listIndex; return delegate->ClientRequestFileSize (delegate, &file_size_request); } static uint32_t delegate_request_file_contents_range (wClipboardDelegate *delegate, const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request) { wClipboardFileRangeRequest file_range_request = {0}; file_range_request.streamId = file_contents_request->streamId; file_range_request.listIndex = file_contents_request->listIndex; file_range_request.nPositionLow = file_contents_request->nPositionLow; file_range_request.nPositionHigh = file_contents_request->nPositionHigh; file_range_request.cbRequested = file_contents_request->cbRequested; return delegate->ClientRequestFileRange (delegate, &file_range_request); } static uint32_t send_file_contents_response_failure (CliprdrServerContext *cliprdr_context, uint32_t stream_id) { CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0}; file_contents_response.msgType = CB_FILECONTENTS_RESPONSE; file_contents_response.msgFlags = CB_RESPONSE_FAIL; file_contents_response.streamId = stream_id; return cliprdr_context->ServerFileContentsResponse (cliprdr_context, &file_contents_response); } /** * Client requests us to send either the size of the remote file or a portion * of the data in the file */ static uint32_t cliprdr_client_file_contents_request (CliprdrServerContext *cliprdr_context, const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; wClipboardDelegate *delegate = clipboard_rdp->delegate; gboolean has_file_list = clipboard_rdp->has_file_list; gboolean requests_allowed = clipboard_rdp->server_file_contents_requests_allowed; uint32_t clip_data_id = file_contents_request->clipDataId; uint32_t stream_id = file_contents_request->streamId; ClipDataEntry *entry = NULL; uint32_t error = NO_ERROR; if (file_contents_request->haveClipDataId) g_debug ("[RDP.CLIPRDR] FileContentsRequest has clipDataId %u", clip_data_id); else g_debug ("[RDP.CLIPRDR] FileContentsRequest does not have a clipDataId"); if (file_contents_request->haveClipDataId) { if (!g_hash_table_lookup_extended (clipboard_rdp->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) return send_file_contents_response_failure (cliprdr_context, stream_id); if (!entry->requests_allowed) { g_debug ("[RDP.CLIPRDR] ClipDataEntry with id %u is not eligible of " "requesting file contents.", clip_data_id); } delegate = entry->delegate; has_file_list = entry->has_file_list; requests_allowed = entry->requests_allowed; } if (!requests_allowed || !has_file_list) return send_file_contents_response_failure (cliprdr_context, stream_id); if (file_contents_request->dwFlags & FILECONTENTS_SIZE && file_contents_request->dwFlags & FILECONTENTS_RANGE) { /** * FILECONTENTS_SIZE and FILECONTENTS_RANGE are not allowed * to be set at the same time */ return send_file_contents_response_failure (cliprdr_context, stream_id); } if (file_contents_request->dwFlags & FILECONTENTS_SIZE) { error = delegate_request_file_contents_size (delegate, file_contents_request); } else if (file_contents_request->dwFlags & FILECONTENTS_RANGE) { error = delegate_request_file_contents_range (delegate, file_contents_request); } else { error = ERROR_INVALID_DATA; } if (error) return send_file_contents_response_failure (cliprdr_context, stream_id); return CHANNEL_RC_OK; } /** * Clients response to our file contents request that we sent */ static uint32_t cliprdr_client_file_contents_response (CliprdrServerContext *cliprdr_context, const CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response) { GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom; GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard; grd_rdp_fuse_clipboard_submit_file_contents_response ( rdp_fuse_clipboard, file_contents_response->streamId, file_contents_response->msgFlags & CB_RESPONSE_OK, file_contents_response->requestedData, file_contents_response->cbRequested); return CHANNEL_RC_OK; } static uint32_t cliprdr_file_size_success (wClipboardDelegate *delegate, const wClipboardFileSizeRequest *file_size_request, uint64_t file_size) { GrdClipboardRdp *clipboard_rdp = delegate->custom; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0}; file_contents_response.msgType = CB_FILECONTENTS_RESPONSE; file_contents_response.msgFlags = CB_RESPONSE_OK; file_contents_response.streamId = file_size_request->streamId; file_contents_response.cbRequested = sizeof (uint64_t); file_contents_response.requestedData = (uint8_t *) &file_size; return cliprdr_context->ServerFileContentsResponse (cliprdr_context, &file_contents_response); } static uint32_t cliprdr_file_size_failure (wClipboardDelegate *delegate, const wClipboardFileSizeRequest *file_size_request, uint32_t error) { GrdClipboardRdp *clipboard_rdp = delegate->custom; return send_file_contents_response_failure (clipboard_rdp->cliprdr_context, file_size_request->streamId); } static uint32_t cliprdr_file_range_success (wClipboardDelegate *delegate, const wClipboardFileRangeRequest *file_range_request, const uint8_t *data, uint32_t size) { GrdClipboardRdp *clipboard_rdp = delegate->custom; CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context; CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0}; file_contents_response.msgType = CB_FILECONTENTS_RESPONSE; file_contents_response.msgFlags = CB_RESPONSE_OK; file_contents_response.streamId = file_range_request->streamId; file_contents_response.cbRequested = size; file_contents_response.requestedData = data; return cliprdr_context->ServerFileContentsResponse (cliprdr_context, &file_contents_response); } static uint32_t cliprdr_file_range_failure (wClipboardDelegate *delegate, const wClipboardFileRangeRequest *file_range_request, uint32_t error) { GrdClipboardRdp *clipboard_rdp = delegate->custom; return send_file_contents_response_failure (clipboard_rdp->cliprdr_context, file_range_request->streamId); } static void create_new_winpr_clipboard (GrdClipboardRdp *clipboard_rdp) { g_debug ("[RDP.CLIPRDR] Creating new WinPR clipboard"); clipboard_rdp->system = ClipboardCreate (); clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system); clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success; clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure; clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success; clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure; clipboard_rdp->delegate->basePath = NULL; clipboard_rdp->delegate->custom = clipboard_rdp; clipboard_rdp->has_file_list = FALSE; } GrdClipboardRdp * grd_clipboard_rdp_new (GrdSessionRdp *session_rdp, HANDLE vcm, HANDLE stop_event) { GrdClipboardRdp *clipboard_rdp; GrdClipboard *clipboard; CliprdrServerContext *cliprdr_context; clipboard_rdp = g_object_new (GRD_TYPE_CLIPBOARD_RDP, NULL); cliprdr_context = cliprdr_server_context_new (vcm); if (!clipboard_rdp || !cliprdr_context) { g_warning ("[RDP.CLIPRDR] An error occurred while creating the RDP clipboard"); g_clear_pointer (&cliprdr_context, cliprdr_server_context_free); g_clear_object (&clipboard_rdp); return NULL; } clipboard_rdp->cliprdr_context = cliprdr_context; clipboard_rdp->stop_event = stop_event; clipboard = GRD_CLIPBOARD (clipboard_rdp); grd_clipboard_initialize (clipboard, GRD_SESSION (session_rdp)); cliprdr_context->useLongFormatNames = TRUE; cliprdr_context->streamFileClipEnabled = TRUE; cliprdr_context->fileClipNoFilePaths = TRUE; cliprdr_context->canLockClipData = TRUE; cliprdr_context->hasHugeFileSupport = TRUE; cliprdr_context->ClientCapabilities = cliprdr_client_capabilities; cliprdr_context->TempDirectory = cliprdr_temp_directory; cliprdr_context->ClientFormatList = cliprdr_client_format_list; cliprdr_context->ClientFormatListResponse = cliprdr_client_format_list_response; cliprdr_context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data; cliprdr_context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data; cliprdr_context->ClientFormatDataRequest = cliprdr_client_format_data_request; cliprdr_context->ClientFormatDataResponse = cliprdr_client_format_data_response; cliprdr_context->ClientFileContentsRequest = cliprdr_client_file_contents_request; cliprdr_context->ClientFileContentsResponse = cliprdr_client_file_contents_response; cliprdr_context->custom = clipboard_rdp; if (cliprdr_context->Start (cliprdr_context)) { g_message ("[RDP.CLIPRDR] An error occurred while starting the RDP " "clipboard. The RDP client might not support the CLIPRDR channel"); g_clear_pointer (&clipboard_rdp->cliprdr_context, cliprdr_server_context_free); g_clear_object (&clipboard_rdp); return NULL; } return clipboard_rdp; } static gboolean clear_format_data (gpointer key, gpointer value, gpointer user_data) { FormatData *format_data = value; g_free (format_data->data); g_free (format_data); return TRUE; } static gboolean clear_client_requests (gpointer key, gpointer value, gpointer user_data) { GList *serials = value; g_list_free (serials); return TRUE; } static void grd_clipboard_rdp_dispose (GObject *object) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object); GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp); if (clipboard_rdp->cliprdr_context) { clipboard_rdp->cliprdr_context->Stop (clipboard_rdp->cliprdr_context); grd_clipboard_disable_clipboard (clipboard); } g_clear_pointer (&clipboard_rdp->current_client_request, g_free); if (clipboard_rdp->format_data_response) { g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData); g_clear_pointer (&clipboard_rdp->format_data_response, g_free); } if (clipboard_rdp->clipboard_retrieval_id) g_clear_pointer (&clipboard_rdp->clipboard_retrieval_context.entry, g_free); g_clear_pointer (&clipboard_rdp->format_data_request_context, g_free); g_clear_pointer (&clipboard_rdp->queued_server_formats, g_list_free); g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free); if (clipboard_rdp->ordered_client_requests) { g_queue_free_full (clipboard_rdp->ordered_client_requests, g_free); clipboard_rdp->ordered_client_requests = NULL; } g_hash_table_foreach_remove (clipboard_rdp->pending_client_requests, clear_client_requests, NULL); g_hash_table_foreach_remove (clipboard_rdp->format_data_cache, clear_format_data, NULL); g_clear_object (&clipboard_rdp->rdp_fuse_clipboard); rmdir (clipboard_rdp->fuse_mount_path); g_clear_pointer (&clipboard_rdp->fuse_mount_path, g_free); g_assert (g_hash_table_size (clipboard_rdp->pending_client_requests) == 0); g_assert (g_hash_table_size (clipboard_rdp->format_data_cache) == 0); g_clear_pointer (&clipboard_rdp->pending_client_requests, g_hash_table_unref); g_clear_pointer (&clipboard_rdp->format_data_cache, g_hash_table_unref); g_clear_pointer (&clipboard_rdp->clip_data_table, g_hash_table_destroy); g_clear_pointer (&clipboard_rdp->serial_entry_table, g_hash_table_destroy); g_clear_pointer (&clipboard_rdp->allowed_server_formats, g_hash_table_destroy); g_clear_pointer (&clipboard_rdp->format_data_request_received_event, CloseHandle); g_clear_pointer (&clipboard_rdp->format_list_response_received_event, CloseHandle); g_clear_pointer (&clipboard_rdp->format_list_received_event, CloseHandle); g_clear_pointer (&clipboard_rdp->completed_format_data_request_event, CloseHandle); g_clear_pointer (&clipboard_rdp->completed_format_list_event, CloseHandle); g_clear_pointer (&clipboard_rdp->completed_clip_data_entry_event, CloseHandle); g_clear_pointer (&clipboard_rdp->clip_data_entry_event, CloseHandle); g_clear_pointer (&clipboard_rdp->system, ClipboardDestroy); g_clear_pointer (&clipboard_rdp->cliprdr_context, cliprdr_server_context_free); g_clear_handle_id (&clipboard_rdp->pending_server_formats_drop_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->clipboard_retrieval_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->clipboard_destruction_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->server_format_list_update_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->server_format_data_request_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->client_format_list_response_id, g_source_remove); g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove); G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->dispose (object); } static void grd_clipboard_rdp_finalize (GObject *object) { GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object); g_mutex_clear (&clipboard_rdp->client_request_mutex); G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->finalize (object); } static void clip_data_entry_free (gpointer data) { ClipDataEntry *entry = data; g_debug ("[RDP.CLIPRDR] Freeing ClipDataEntry with id %u and serial %lu. " "ClipDataEntry is independent: %s", entry->id, entry->serial, entry->is_independent ? "true" : "false"); if (entry->is_independent) g_clear_pointer (&entry->system, ClipboardDestroy); g_free (entry); } static void grd_clipboard_rdp_init (GrdClipboardRdp *clipboard_rdp) { const char *grd_path = "/gnome-remote-desktop"; const char *cliprdr_template = "/cliprdr-XXXXXX"; g_autofree char *base_path = NULL; g_autofree char *template_path = NULL; base_path = g_strdup_printf ("%s%s", g_get_user_runtime_dir (), grd_path); template_path = g_strdup_printf ("%s%s", base_path, cliprdr_template); if (g_access (base_path, F_OK)) { if (mkdir (base_path, 0700)) { g_error ("Failed to create base runtime directory for " "gnome-remote-desktop: %s", g_strerror (errno)); } } if (!mkdtemp (template_path)) { g_error ("Failed to create clipboard file directory %s: %s", template_path, g_strerror (errno)); } clipboard_rdp->system = ClipboardCreate (); clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system); clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success; clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure; clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success; clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure; clipboard_rdp->delegate->basePath = NULL; clipboard_rdp->delegate->custom = clipboard_rdp; clipboard_rdp->clip_data_entry_event = CreateEvent (NULL, TRUE, FALSE, NULL); clipboard_rdp->completed_clip_data_entry_event = CreateEvent (NULL, TRUE, TRUE, NULL); clipboard_rdp->completed_format_list_event = CreateEvent (NULL, TRUE, TRUE, NULL); clipboard_rdp->completed_format_data_request_event = CreateEvent (NULL, TRUE, TRUE, NULL); clipboard_rdp->format_list_received_event = CreateEvent (NULL, TRUE, FALSE, NULL); clipboard_rdp->format_list_response_received_event = CreateEvent (NULL, TRUE, FALSE, NULL); clipboard_rdp->format_data_request_received_event = CreateEvent (NULL, TRUE, FALSE, NULL); clipboard_rdp->allowed_server_formats = g_hash_table_new (NULL, NULL); clipboard_rdp->serial_entry_table = g_hash_table_new_full (NULL, NULL, NULL, clip_data_entry_free); clipboard_rdp->clip_data_table = g_hash_table_new (NULL, NULL); clipboard_rdp->format_data_cache = g_hash_table_new (NULL, NULL); clipboard_rdp->pending_client_requests = g_hash_table_new (NULL, NULL); clipboard_rdp->ordered_client_requests = g_queue_new (); g_mutex_init (&clipboard_rdp->client_request_mutex); clipboard_rdp->fuse_mount_path = g_steal_pointer (&template_path); clipboard_rdp->rdp_fuse_clipboard = grd_rdp_fuse_clipboard_new (clipboard_rdp, clipboard_rdp->fuse_mount_path); } static void grd_clipboard_rdp_class_init (GrdClipboardRdpClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GrdClipboardClass *clipboard_class = GRD_CLIPBOARD_CLASS (klass); object_class->dispose = grd_clipboard_rdp_dispose; object_class->finalize = grd_clipboard_rdp_finalize; clipboard_class->update_client_mime_type_list = grd_clipboard_rdp_update_client_mime_type_list; clipboard_class->request_client_content_for_mime_type = grd_clipboard_rdp_request_client_content_for_mime_type; clipboard_class->submit_requested_server_content = grd_clipboard_rdp_submit_requested_server_content; } 0707010000003B000081A40000000000000000000000016293A07000000A9C000000000000000000000000000000000000003200000000gnome-remote-desktop-41.3/src/grd-clipboard-rdp.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_CLIPBOARD_RDP_H #define GRD_CLIPBOARD_RDP_H #include <freerdp/server/cliprdr.h> #include "grd-clipboard.h" #define GRD_TYPE_CLIPBOARD_RDP (grd_clipboard_rdp_get_type ()) G_DECLARE_FINAL_TYPE (GrdClipboardRdp, grd_clipboard_rdp, GRD, CLIPBOARD_RDP, GrdClipboard); GrdClipboardRdp *grd_clipboard_rdp_new (GrdSessionRdp *session_rdp, HANDLE vcm, HANDLE stop_event); void grd_clipboard_rdp_lock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp, uint32_t clip_data_id); void grd_clipboard_rdp_unlock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp, uint32_t clip_data_id); void grd_clipboard_rdp_request_remote_file_size_async (GrdClipboardRdp *clipboard_rdp, uint32_t stream_id, uint32_t list_index, gboolean has_clip_data_id, uint32_t clip_data_id); void grd_clipboard_rdp_request_remote_file_range_async (GrdClipboardRdp *clipboard_rdp, uint32_t stream_id, uint32_t list_index, uint64_t offset, uint32_t requested_size, gboolean has_clip_data_id, uint32_t clip_data_id); #endif /* GRD_CLIPBOARD_RDP_H */ 0707010000003C000081A40000000000000000000000016293A07000001A05000000000000000000000000000000000000003200000000gnome-remote-desktop-41.3/src/grd-clipboard-vnc.c/* * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-clipboard-vnc.h" #include "grd-session-vnc.h" struct _GrdClipboardVnc { GrdClipboard parent; GrdSessionVnc *session_vnc; char *clipboard_utf8_string; }; G_DEFINE_TYPE (GrdClipboardVnc, grd_clipboard_vnc, GRD_TYPE_CLIPBOARD); static void grd_clipboard_vnc_update_client_mime_type_list (GrdClipboard *clipboard, GList *mime_type_list) { GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (clipboard); gboolean found_utf8_string = FALSE; GrdMimeType mime_type; GList *l; for (l = mime_type_list; l && !found_utf8_string; l = l->next) { mime_type = GPOINTER_TO_UINT (l->data); switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN: break; case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: case GRD_MIME_TYPE_TEXT_UTF8_STRING: found_utf8_string = TRUE; break; case GRD_MIME_TYPE_TEXT_HTML: case GRD_MIME_TYPE_IMAGE_BMP: case GRD_MIME_TYPE_IMAGE_TIFF: case GRD_MIME_TYPE_IMAGE_GIF: case GRD_MIME_TYPE_IMAGE_JPEG: case GRD_MIME_TYPE_IMAGE_PNG: case GRD_MIME_TYPE_TEXT_URILIST: case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES: break; default: g_assert_not_reached (); } } if (found_utf8_string) { g_clear_pointer (&clipboard_vnc->clipboard_utf8_string, g_free); grd_clipboard_request_server_content_for_mime_type_async (clipboard, mime_type); } g_list_free (mime_type_list); } static void grd_clipboard_vnc_request_client_content_for_mime_type (GrdClipboard *clipboard, GrdMimeTypeTable *mime_type_table, unsigned int serial) { GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (clipboard); uint32_t size; size = strlen (clipboard_vnc->clipboard_utf8_string); grd_clipboard_submit_client_content_for_mime_type ( clipboard, serial, (uint8_t *) clipboard_vnc->clipboard_utf8_string, size); } static void grd_clipboard_vnc_submit_requested_server_content (GrdClipboard *clipboard, uint8_t *src_data, uint32_t src_size) { GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (clipboard); g_autoptr (GError) error = NULL; char *dst_data; if (!src_data) return; dst_data = g_convert ((char *) src_data, src_size, "iso8859-1", "utf-8", NULL, NULL, &error); if (!dst_data) { g_warning ("[VNC.Clipboard] Failed to convert clipboard content: %s", error->message); g_free (src_data); return; } grd_session_vnc_set_client_clipboard_text (clipboard_vnc->session_vnc, dst_data, strlen (dst_data)); g_free (src_data); g_free (dst_data); } void grd_clipboard_vnc_maybe_enable_clipboard (GrdClipboardVnc *clipboard_vnc) { grd_clipboard_maybe_enable_clipboard (GRD_CLIPBOARD (clipboard_vnc)); } void grd_clipboard_vnc_set_clipboard_text (GrdClipboardVnc *clipboard_vnc, char *text, int text_length) { GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_vnc); g_autoptr (GError) error = NULL; GrdMimeTypeTable *mime_type_table; GList *mime_type_tables = NULL; g_clear_pointer (&clipboard_vnc->clipboard_utf8_string, g_free); clipboard_vnc->clipboard_utf8_string = g_convert (text, text_length, "utf-8", "iso8859-1", NULL, NULL, &error); if (!clipboard_vnc->clipboard_utf8_string) { g_warning ("[VNC.Clipboard] Failed to convert clipboard content: %s", error->message); return; } mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable)); mime_type_table->mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING; mime_type_tables = g_list_append (mime_type_tables, mime_type_table); grd_clipboard_update_server_mime_type_list (clipboard, mime_type_tables); } GrdClipboardVnc * grd_clipboard_vnc_new (GrdSessionVnc *session_vnc) { GrdClipboardVnc *clipboard_vnc; clipboard_vnc = g_object_new (GRD_TYPE_CLIPBOARD_VNC, NULL); clipboard_vnc->session_vnc = session_vnc; grd_clipboard_initialize (GRD_CLIPBOARD (clipboard_vnc), GRD_SESSION (session_vnc)); return clipboard_vnc; } static void grd_clipboard_vnc_dispose (GObject *object) { GrdClipboardVnc *clipboard_vnc = GRD_CLIPBOARD_VNC (object); grd_clipboard_disable_clipboard (GRD_CLIPBOARD (clipboard_vnc)); g_clear_pointer (&clipboard_vnc->clipboard_utf8_string, g_free); G_OBJECT_CLASS (grd_clipboard_vnc_parent_class)->dispose (object); } static void grd_clipboard_vnc_init (GrdClipboardVnc *clipboard_vnc) { } static void grd_clipboard_vnc_class_init (GrdClipboardVncClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GrdClipboardClass *clipboard_class = GRD_CLIPBOARD_CLASS (klass); object_class->dispose = grd_clipboard_vnc_dispose; clipboard_class->update_client_mime_type_list = grd_clipboard_vnc_update_client_mime_type_list; clipboard_class->request_client_content_for_mime_type = grd_clipboard_vnc_request_client_content_for_mime_type; clipboard_class->submit_requested_server_content = grd_clipboard_vnc_submit_requested_server_content; } 0707010000003D000081A40000000000000000000000016293A070000005C0000000000000000000000000000000000000003200000000gnome-remote-desktop-41.3/src/grd-clipboard-vnc.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_CLIPBOARD_VNC_H #define GRD_CLIPBOARD_VNC_H #include "grd-clipboard.h" #define GRD_TYPE_CLIPBOARD_VNC (grd_clipboard_vnc_get_type ()) G_DECLARE_FINAL_TYPE (GrdClipboardVnc, grd_clipboard_vnc, GRD, CLIPBOARD_VNC, GrdClipboard); GrdClipboardVnc *grd_clipboard_vnc_new (GrdSessionVnc *session_vnc); void grd_clipboard_vnc_maybe_enable_clipboard (GrdClipboardVnc *clipboard_vnc); void grd_clipboard_vnc_set_clipboard_text (GrdClipboardVnc *clipboard_vnc, char *text, int text_length); #endif /* GRD_CLIPBOARD_VNC_H */ 0707010000003E000081A40000000000000000000000016293A07000003B1D000000000000000000000000000000000000002E00000000gnome-remote-desktop-41.3/src/grd-clipboard.c/* * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-clipboard.h" #include <gio/gio.h> #include <gio/gunixinputstream.h> #include "grd-session.h" #define MAX_READ_TIME 4000 typedef struct _ReadMimeTypeContentContext { GrdClipboard *clipboard; int fd; GCancellable *cancellable; } ReadMimeTypeContentContext; typedef struct _ReadMimeTypeContentResult { uint8_t *data; uint32_t size; } ReadMimeTypeContentResult; typedef struct _GrdClipboardPrivate { GrdSession *session; gboolean enabled; GHashTable *client_mime_type_tables; ReadMimeTypeContentResult *read_result; GCancellable *read_cancellable; unsigned int abort_read_source_id; gboolean has_pending_read_operation; GCond pending_read_cond; GMutex pending_read_mutex; } GrdClipboardPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GrdClipboard, grd_clipboard, G_TYPE_OBJECT); static void handle_read_result (GrdClipboard *clipboard, ReadMimeTypeContentResult *read_result) { GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard); GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); priv->has_pending_read_operation = FALSE; /* Discard the read_result, if the clipboard is already disabled. */ if (!priv->enabled) return; if (read_result->data) g_debug ("Clipboard[SelectionRead]: Request successful"); else g_debug ("Clipboard[SelectionRead]: Request failed"); klass->submit_requested_server_content (clipboard, read_result->data, read_result->size); } static void flush_pending_read_result (GrdClipboard *clipboard) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); ReadMimeTypeContentResult *read_result; g_mutex_lock (&priv->pending_read_mutex); while (!priv->read_result) g_cond_wait (&priv->pending_read_cond, &priv->pending_read_mutex); g_mutex_unlock (&priv->pending_read_mutex); read_result = g_steal_pointer (&priv->read_result); handle_read_result (clipboard, read_result); g_free (read_result); } static void abort_current_read_operation (GrdClipboard *clipboard) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); if (!priv->has_pending_read_operation) return; g_debug ("Clipboard[SelectionRead]: Aborting current read operation"); g_cancellable_cancel (priv->read_cancellable); g_clear_object (&priv->read_cancellable); g_clear_handle_id (&priv->abort_read_source_id, g_source_remove); flush_pending_read_result (clipboard); } void grd_clipboard_update_server_mime_type_list (GrdClipboard *clipboard, GList *mime_type_tables) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); GList *l; g_debug ("Clipboard[SetSelection]: Updating servers clipboard"); for (l = mime_type_tables; l; l = l->next) { GrdMimeTypeTable *mime_type_table = l->data; GrdMimeType mime_type; mime_type = mime_type_table->mime_type; g_debug ("Clipboard[SetSelection]: Update contains mime type %s", grd_mime_type_to_string (mime_type)); g_hash_table_insert (priv->client_mime_type_tables, GUINT_TO_POINTER (mime_type), mime_type_table); } if (!priv->enabled) { g_debug ("Clipboard[EnableClipboard]: Enabling clipboard"); priv->enabled = grd_session_enable_clipboard (priv->session, clipboard, mime_type_tables); if (priv->enabled) g_debug ("Clipboard[EnableClipboard]: Clipboard enabled"); else g_debug ("Clipboard[EnableClipboard]: Clipboard could not be enabled"); } else { abort_current_read_operation (clipboard); if (mime_type_tables) grd_session_set_selection (priv->session, mime_type_tables); } g_debug ("Clipboard[SetSelection]: Update complete"); g_list_free (mime_type_tables); } static void async_read_operation_complete (GObject *source_object, GAsyncResult *result, gpointer user_data) { GrdClipboard *clipboard = user_data; GrdClipboardPrivate *priv; ReadMimeTypeContentContext *read_context = g_task_get_task_data (G_TASK (result)); ReadMimeTypeContentResult *read_result; if (g_cancellable_is_cancelled (read_context->cancellable)) return; priv = grd_clipboard_get_instance_private (clipboard); g_assert (priv->has_pending_read_operation); g_clear_object (&priv->read_cancellable); g_clear_handle_id (&priv->abort_read_source_id, g_source_remove); read_result = g_steal_pointer (&priv->read_result); handle_read_result (clipboard, read_result); g_free (read_result); } static void clear_read_context (gpointer data) { ReadMimeTypeContentContext *read_context = data; g_object_unref (read_context->cancellable); g_free (data); } static void read_mime_type_content_in_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { ReadMimeTypeContentContext *read_context = task_data; GrdClipboard *clipboard = read_context->clipboard; GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); ReadMimeTypeContentResult *read_result; GInputStream *input_stream; GArray *data; gboolean success = FALSE; g_autoptr (GError) error = NULL; input_stream = g_unix_input_stream_new (read_context->fd, TRUE); data = g_array_new (FALSE, TRUE, sizeof (uint8_t)); while (TRUE) { int len; uint8_t buffer[1024]; len = g_input_stream_read (input_stream, buffer, G_N_ELEMENTS (buffer), read_context->cancellable, &error); if (len < 0) { g_warning ("Clipboard[SelectionRead]: Failed to read mime type " "content: %s", error->message); break; } else if (len == 0) { success = TRUE; break; } else { g_array_append_vals (data, buffer, len); } } read_result = g_malloc0 (sizeof (ReadMimeTypeContentResult)); if (success && data->len > 0) { read_result->size = data->len; read_result->data = (uint8_t *) g_array_free (data, FALSE); } else { g_array_free (data, TRUE); } g_object_unref (input_stream); g_mutex_lock (&priv->pending_read_mutex); priv->read_result = read_result; g_cond_signal (&priv->pending_read_cond); g_mutex_unlock (&priv->pending_read_mutex); } static gboolean abort_mime_type_content_read (gpointer user_data) { GrdClipboard *clipboard = user_data; GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); g_debug ("Clipboard[SelectionRead]: Aborting current read operation " "(Timeout reached)"); g_assert (priv->has_pending_read_operation); g_assert (priv->abort_read_source_id); priv->abort_read_source_id = 0; abort_current_read_operation (clipboard); return G_SOURCE_REMOVE; } static void read_mime_type_content_async (GrdClipboard *clipboard, int fd) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); ReadMimeTypeContentContext *read_context; GTask *task; abort_current_read_operation (clipboard); priv->read_cancellable = g_cancellable_new (); priv->has_pending_read_operation = TRUE; g_assert (!priv->read_result); read_context = g_malloc0 (sizeof (ReadMimeTypeContentContext)); read_context->clipboard = clipboard; read_context->fd = fd; read_context->cancellable = g_object_ref (priv->read_cancellable); task = g_task_new (NULL, NULL, async_read_operation_complete, clipboard); g_task_set_task_data (task, read_context, clear_read_context); g_task_run_in_thread (task, read_mime_type_content_in_thread); g_object_unref (task); priv->abort_read_source_id = g_timeout_add (MAX_READ_TIME, abort_mime_type_content_read, clipboard); } void grd_clipboard_request_server_content_for_mime_type_async (GrdClipboard *clipboard, GrdMimeType mime_type) { GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard); GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); int fd; g_return_if_fail (klass->submit_requested_server_content); if (!priv->enabled) return; g_debug ("Clipboard[SelectionRead]: Requesting data from servers clipboard" " (mime type: %s)", grd_mime_type_to_string (mime_type)); fd = grd_session_selection_read (priv->session, mime_type); if (fd == -1) { g_debug ("Clipboard[SelectionRead]: Request failed"); klass->submit_requested_server_content (clipboard, NULL, 0); return; } read_mime_type_content_async (clipboard, fd); } void grd_clipboard_initialize (GrdClipboard *clipboard, GrdSession *session) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); priv->session = session; } void grd_clipboard_maybe_enable_clipboard (GrdClipboard *clipboard) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); g_debug ("Clipboard[EnableClipboard]: Enabling clipboard"); if (priv->enabled) { g_debug ("Clipboard[EnableClipboard]: Clipboard already enabled"); return; } priv->enabled = grd_session_enable_clipboard (priv->session, clipboard, NULL); if (priv->enabled) g_debug ("Clipboard[EnableClipboard]: Clipboard enabled"); else g_debug ("Clipboard[EnableClipboard]: Clipboard could not be enabled"); } void grd_clipboard_disable_clipboard (GrdClipboard *clipboard) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); if (!priv->enabled) return; g_debug ("Clipboard[DisableClipboard]: Disabling clipboard"); grd_session_disable_clipboard (priv->session); priv->enabled = FALSE; } void grd_clipboard_update_client_mime_type_list (GrdClipboard *clipboard, GList *mime_type_list) { GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard); GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); GList *l; /** * Ensure that the response with the read mime type content is sent to the * client first, before sending the new mime type list */ abort_current_read_operation (clipboard); g_assert (priv->enabled); if (!klass->update_client_mime_type_list) return; for (l = mime_type_list; l; l = l->next) g_hash_table_remove (priv->client_mime_type_tables, l->data); g_debug ("Clipboard[SelectionOwnerChanged]: Updating clients clipboard"); klass->update_client_mime_type_list (clipboard, mime_type_list); g_debug ("Clipboard[SelectionOwnerChanged]: Update complete"); } void grd_clipboard_submit_client_content_for_mime_type (GrdClipboard *clipboard, unsigned int serial, const uint8_t *data, uint32_t size) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); g_assert (priv->enabled); if (data && size) g_debug ("Clipboard[SelectionTransfer]: Request for serial %u was successful", serial); else g_debug ("Clipboard[SelectionTransfer]: Request for serial %u failed", serial); grd_session_selection_write (priv->session, serial, data, size); } void grd_clipboard_request_client_content_for_mime_type (GrdClipboard *clipboard, GrdMimeType mime_type, unsigned int serial) { GrdClipboardClass *klass = GRD_CLIPBOARD_GET_CLASS (clipboard); GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); GrdMimeTypeTable *mime_type_table = NULL; g_assert (priv->enabled); g_return_if_fail (klass->request_client_content_for_mime_type); g_debug ("Clipboard[SelectionTransfer]: Requesting data from clients clipboard" " (mime type: %s, serial: %u)", grd_mime_type_to_string (mime_type), serial); mime_type_table = g_hash_table_lookup (priv->client_mime_type_tables, GUINT_TO_POINTER (mime_type)); if (!mime_type_table) { grd_clipboard_submit_client_content_for_mime_type (clipboard, serial, NULL, 0); return; } klass->request_client_content_for_mime_type (clipboard, mime_type_table, serial); } static void free_mime_type_table (gpointer data) { GrdMimeTypeTable *mime_type_table = data; g_free (mime_type_table); } static void grd_clipboard_dispose (GObject *object) { GrdClipboard *clipboard = GRD_CLIPBOARD (object); GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); abort_current_read_operation (clipboard); g_clear_pointer (&priv->client_mime_type_tables, g_hash_table_destroy); G_OBJECT_CLASS (grd_clipboard_parent_class)->dispose (object); } static void grd_clipboard_finalize (GObject *object) { GrdClipboard *clipboard = GRD_CLIPBOARD (object); GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); g_mutex_clear (&priv->pending_read_mutex); g_cond_clear (&priv->pending_read_cond); G_OBJECT_CLASS (grd_clipboard_parent_class)->finalize (object); } static void grd_clipboard_init (GrdClipboard *clipboard) { GrdClipboardPrivate *priv = grd_clipboard_get_instance_private (clipboard); priv->client_mime_type_tables = g_hash_table_new_full (NULL, NULL, NULL, free_mime_type_table); g_cond_init (&priv->pending_read_cond); g_mutex_init (&priv->pending_read_mutex); } static void grd_clipboard_class_init (GrdClipboardClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_clipboard_dispose; object_class->finalize = grd_clipboard_finalize; } 0707010000003F000081A40000000000000000000000016293A07000000BC0000000000000000000000000000000000000002E00000000gnome-remote-desktop-41.3/src/grd-clipboard.h/* * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_CLIPBOARD_H #define GRD_CLIPBOARD_H #include <glib-object.h> #include <stdint.h> #include "grd-mime-type.h" #include "grd-types.h" #define GRD_TYPE_CLIPBOARD (grd_clipboard_get_type ()) G_DECLARE_DERIVABLE_TYPE (GrdClipboard, grd_clipboard, GRD, CLIPBOARD, GObject); struct _GrdClipboardClass { GObjectClass parent_class; void (*update_client_mime_type_list) (GrdClipboard *clipboard, GList *mime_type_list); void (*request_client_content_for_mime_type) (GrdClipboard *clipboard, GrdMimeTypeTable *mime_type_table, unsigned int serial); void (*submit_requested_server_content) (GrdClipboard *clipboard, uint8_t *data, uint32_t size); }; void grd_clipboard_update_server_mime_type_list (GrdClipboard *clipboard, GList *mime_type_tables); void grd_clipboard_request_server_content_for_mime_type_async (GrdClipboard *clipboard, GrdMimeType mime_type); void grd_clipboard_initialize (GrdClipboard *clipboard, GrdSession *session); void grd_clipboard_maybe_enable_clipboard (GrdClipboard *clipboard); void grd_clipboard_disable_clipboard (GrdClipboard *clipboard); void grd_clipboard_update_client_mime_type_list (GrdClipboard *clipboard, GList *mime_type_list); void grd_clipboard_submit_client_content_for_mime_type (GrdClipboard *clipboard, unsigned int serial, const uint8_t *data, uint32_t size); void grd_clipboard_request_client_content_for_mime_type (GrdClipboard *clipboard, GrdMimeType mime_type, unsigned int serial); #endif /* GRD_CLIPBOARD_H */ 07070100000040000081A40000000000000000000000016293A07000000F0F000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-context.c/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #include "config.h" #include "grd-context.h" #include "grd-dbus-remote-desktop.h" #include "grd-dbus-screen-cast.h" static const GDebugKey grd_debug_keys[] = { { "vnc", GRD_DEBUG_VNC }, }; struct _GrdContext { GObject parent; GMainContext *main_context; GrdDBusRemoteDesktop *remote_desktop_proxy; GrdDBusScreenCast *screen_cast_proxy; GrdSettings *settings; GList *sessions; GrdDebugFlags debug_flags; }; G_DEFINE_TYPE (GrdContext, grd_context, G_TYPE_OBJECT); GrdDBusRemoteDesktop * grd_context_get_remote_desktop_proxy (GrdContext *context) { return context->remote_desktop_proxy; } GrdDBusScreenCast * grd_context_get_screen_cast_proxy (GrdContext *context) { return context->screen_cast_proxy; } void grd_context_set_remote_desktop_proxy (GrdContext *context, GrdDBusRemoteDesktop *proxy) { g_clear_object (&context->remote_desktop_proxy); context->remote_desktop_proxy = proxy; } void grd_context_set_screen_cast_proxy (GrdContext *context, GrdDBusScreenCast *proxy) { g_clear_object (&context->screen_cast_proxy); context->screen_cast_proxy = proxy; } GMainContext * grd_context_get_main_context (GrdContext *context) { return context->main_context; } static void on_session_stopped (GrdSession *session, GrdContext *context) { context->sessions = g_list_remove (context->sessions, session); } void grd_context_add_session (GrdContext *context, GrdSession *session) { context->sessions = g_list_append (context->sessions, session); g_signal_connect (session, "stopped", G_CALLBACK (on_session_stopped), context); } GList * grd_context_get_sessions (GrdContext *context) { return context->sessions; } GrdSettings * grd_context_get_settings (GrdContext *context) { return context->settings; } GrdDebugFlags grd_context_get_debug_flags (GrdContext *context) { return context->debug_flags; } static void init_debug_flags (GrdContext *context) { const char *debug_env; debug_env = g_getenv ("GNOME_REMOTE_DESKTOP_DEBUG"); if (debug_env) { context->debug_flags = g_parse_debug_string (debug_env, grd_debug_keys, G_N_ELEMENTS (grd_debug_keys)); } } static void grd_context_finalize (GObject *object) { GrdContext *context = GRD_CONTEXT (object); g_clear_object (&context->remote_desktop_proxy); g_clear_object (&context->screen_cast_proxy); g_clear_object (&context->settings); G_OBJECT_CLASS (grd_context_parent_class)->finalize (object); } static void grd_context_init (GrdContext *context) { context->main_context = g_main_context_default (); init_debug_flags (context); context->settings = g_object_new (GRD_TYPE_SETTINGS, NULL); } static void grd_context_class_init (GrdContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = grd_context_finalize; } 07070100000041000081A40000000000000000000000016293A07000000867000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-context.h/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_CONTEXT_H #define GRD_CONTEXT_H #include <glib-object.h> #include "grd-dbus-remote-desktop.h" #include "grd-dbus-screen-cast.h" #include "grd-settings.h" #include "grd-types.h" typedef enum _GrdDebugFlags { GRD_DEBUG_NONE = 0, GRD_DEBUG_VNC = 1 << 0, } GrdDebugFlags; #define GRD_TYPE_CONTEXT (grd_context_get_type ()) G_DECLARE_FINAL_TYPE (GrdContext, grd_context, GRD, CONTEXT, GObject); GrdDBusRemoteDesktop * grd_context_get_remote_desktop_proxy (GrdContext *context); GrdDBusScreenCast * grd_context_get_screen_cast_proxy (GrdContext *context); void grd_context_set_remote_desktop_proxy (GrdContext *context, GrdDBusRemoteDesktop *proxy); void grd_context_set_screen_cast_proxy (GrdContext *context, GrdDBusScreenCast *proxy); GrdPipeWireStreamMonitor *grd_context_get_pipewire_stream_monitor (GrdContext *context); GMainContext *grd_context_get_main_context (GrdContext *context); void grd_context_add_session (GrdContext *context, GrdSession *session); GList * grd_context_get_sessions (GrdContext *context); GrdSettings * grd_context_get_settings (GrdContext *context); GrdDebugFlags grd_context_get_debug_flags (GrdContext *context); #endif /* GRD_CONTEXT_H */ 07070100000042000081A40000000000000000000000016293A070000008DE000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-control.c/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #include "config.h" #include <gio/gio.h> #include <stdio.h> #include <string.h> #include "grd-private.h" int main (int argc, char **argv) { g_autoptr(GApplication) app = NULL; gboolean terminate = FALSE; GOptionEntry entries[] = { { "terminate", 0, 0, G_OPTION_ARG_NONE, &terminate, "Terminate the daemon", NULL }, { NULL } }; GError *error = NULL; GOptionContext *context; context = g_option_context_new ("- control gnome-remote-desktop"); g_option_context_add_main_entries (context, entries, NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("Invalid option: %s\n", error->message); g_error_free (error); return 1; } if (!terminate) { g_printerr ("%s", g_option_context_get_help (context, TRUE, NULL)); return 1; } app = g_application_new (GRD_DAEMON_APPLICATION_ID, 0); if (!g_application_register (app, NULL, NULL)) { g_warning ("Failed to register with application\n"); return 1; } if (!g_application_get_is_registered (app)) { g_warning ("Not registered\n"); return 1; } if (!g_application_get_is_remote (app)) { g_warning ("Failed to connect to application\n"); return 1; } if (terminate) g_action_group_activate_action (G_ACTION_GROUP (app), "terminate", NULL); else g_assert_not_reached (); return 0; } 07070100000043000081A40000000000000000000000016293A07000001764000000000000000000000000000000000000003400000000gnome-remote-desktop-41.3/src/grd-cuda-avc-utils.cu/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /* * Generate the PTX instructions with: * nvcc -arch=compute_30 -ptx grd-cuda-avc-utils.cu -o grd-cuda-avc-utils_30.ptx * * Note: This requires CUDA < 11, since the generation of Kepler capable * PTX code was removed from CUDA 11. */ #include <stdint.h> extern "C" { __device__ uint16_t nv12_get_interlaced_y_1x1 (uint16_t y_1x1, uint16_t aligned_height) { if (y_1x1 < aligned_height >> 1) return y_1x1 << 1; return (y_1x1 << 1) - aligned_height + 1; } __device__ uint16_t nv12_get_interlaced_y_2x2 (uint16_t y_2x2, uint16_t aligned_height) { if (y_2x2 < aligned_height >> 2) return y_2x2 << 1; return (y_2x2 << 1) - (aligned_height >> 1) + 1; } __device__ uint8_t rgb_to_y (uint8_t r, uint8_t g, uint8_t b) { return (54 * r + 183 * g + 18 * b) >> 8; } __device__ uint8_t rgb_to_u (uint8_t r, uint8_t g, uint8_t b) { return ((-29 * r - 99 * g + 128 * b) >> 8) + 128; } __device__ uint8_t rgb_to_v (uint8_t r, uint8_t g, uint8_t b) { return ((128 * r - 116 * g - 12 * b) >> 8) + 128; } __global__ void convert_2x2_bgrx_area_to_yuv420_nv12 (uint8_t *dst_data, uint8_t *src_data, uint16_t src_width, uint16_t src_height, uint16_t src_stride, uint16_t aligned_width, uint16_t aligned_height, uint16_t aligned_stride) { uint8_t *src, *dst_y0, *dst_y1, *dst_y2, *dst_y3, *dst_u, *dst_v; uint16_t s0, s1, s2, s3; int32_t r_a, g_a, b_a; uint8_t r, g, b; uint16_t x_1x1, y_1x1; uint16_t x_2x2, y_2x2; x_2x2 = blockIdx.x * blockDim.x + threadIdx.x; y_2x2 = blockIdx.y * blockDim.y + threadIdx.y; if (x_2x2 >= aligned_width >> 1 || y_2x2 >= aligned_height >> 1) return; /* * ------------- * | d_0 | d_1 | * ------------- * | d_2 | d_3 | * ------------- */ s0 = 0; s1 = 4; s2 = src_stride; s3 = src_stride + 4; /* * Technically, the correct positions for the Y data in the resulting NV12 * image would be the following: * * d0 = 0; * d1 = 1; * d2 = aligned_stride; * d3 = aligned_stride + 1; * * However, since MBAFF is used as frame field mode, NVENC requires the input * frame to be interlaced. * If the frame is not interlaced, then even lines end up in the position * y / 2, instead of y and odd lines end up in the position y / 2 + * aligned_height / 2, instead of y. * So, calculate the interlaced y position via a dedicated function, which * ensures that the lines in the input frame end up in the resulting frame to * be at the correct position. * Doing this now in the kernel here, instead of after the BGRX -> YUV420 * conversion, saves a huge amount of time, since each thread only has a * super tiny overhead to perform this action, while a normal * device-to-device copy operation can take at least several milliseconds. */ x_1x1 = x_2x2 << 1; y_1x1 = y_2x2 << 1; src = src_data + y_1x1 * src_stride + (x_1x1 << 2); dst_y0 = dst_data + nv12_get_interlaced_y_1x1 (y_1x1, aligned_height) * aligned_stride + x_1x1; dst_y1 = dst_y0 + 1; dst_y2 = dst_data + nv12_get_interlaced_y_1x1 (y_1x1 + 1, aligned_height) * aligned_stride + x_1x1; dst_y3 = dst_y2 + 1; dst_u = dst_data + aligned_height * aligned_stride + nv12_get_interlaced_y_2x2 (y_2x2, aligned_height) * aligned_stride + x_1x1; dst_v = dst_u + 1; /* d_0 */ if (x_1x1 < src_width && y_1x1 < src_height) { b_a = b = src[s0 + 0]; g_a = g = src[s0 + 1]; r_a = r = src[s0 + 2]; *dst_y0 = rgb_to_y (r, g, b); } else { b_a = b = 0; g_a = g = 0; r_a = r = 0; *dst_y0 = 0; } if (x_1x1 + 1 < src_width && y_1x1 < src_height) { /* d_1 */ b_a += b = src[s1 + 0]; g_a += g = src[s1 + 1]; r_a += r = src[s1 + 2]; *dst_y1 = rgb_to_y (r, g, b); } else { *dst_y1 = 0; } if (y_1x1 + 1 < src_height) { /* d_2 */ b_a += b = src[s2 + 0]; g_a += g = src[s2 + 1]; r_a += r = src[s2 + 2]; *dst_y2 = rgb_to_y (r, g, b); if (x_1x1 + 1 < src_width) { /* d_3 */ b_a += b = src[s3 + 0]; g_a += g = src[s3 + 1]; r_a += r = src[s3 + 2]; *dst_y3 = rgb_to_y (r, g, b); } else { *dst_y3 = 0; } } else { *dst_y2 = 0; *dst_y3 = 0; } b_a >>= 2; g_a >>= 2; r_a >>= 2; *dst_u = rgb_to_u (r_a, g_a, b_a); *dst_v = rgb_to_v (r_a, g_a, b_a); } } 07070100000044000081A40000000000000000000000016293A07000002B23000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/src/grd-daemon.c/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #include "config.h" #include "grd-daemon.h" #include <gio/gio.h> #include <glib/gi18n.h> #include <glib/gstdio.h> #include <stdio.h> #include <stdlib.h> #include "grd-context.h" #include "grd-dbus-remote-desktop.h" #include "grd-private.h" #include "grd-rdp-server.h" #include "grd-session.h" #include "grd-vnc-server.h" struct _GrdDaemon { GApplication parent; GCancellable *cancellable; guint remote_desktop_watch_name_id; guint screen_cast_watch_name_id; GrdContext *context; #ifdef HAVE_RDP GrdRdpServer *rdp_server; #endif #ifdef HAVE_VNC GrdVncServer *vnc_server; #endif }; G_DEFINE_TYPE (GrdDaemon, grd_daemon, G_TYPE_APPLICATION) static gboolean is_daemon_ready (GrdDaemon *daemon) { if (!grd_context_get_remote_desktop_proxy (daemon->context) || !grd_context_get_screen_cast_proxy (daemon->context)) return FALSE; return TRUE; } #ifdef HAVE_RDP static gboolean init_rdp_server (GrdDaemon *daemon) { GrdSettings *settings = grd_context_get_settings (daemon->context); g_autoptr (GError) error = NULL; gboolean result = FALSE; daemon->rdp_server = NULL; if (!g_access (grd_settings_get_rdp_server_cert (settings), F_OK) && !g_access (grd_settings_get_rdp_server_key (settings), F_OK)) { daemon->rdp_server = grd_rdp_server_new (daemon->context); if (!(result = grd_rdp_server_start (daemon->rdp_server, &error))) g_warning ("Failed to initialize RDP server: %s\n", error->message); else g_message ("Initialized RDP server"); } else { g_message ("Didn't initialize RDP server: not configured"); } return result; } #endif /* HAVE_RDP */ #ifdef HAVE_VNC static gboolean init_vnc_server (GrdDaemon *daemon) { g_autoptr (GError) error = NULL; gboolean result; daemon->vnc_server = grd_vnc_server_new (daemon->context); if (!(result = grd_vnc_server_start (daemon->vnc_server, &error))) g_warning ("Failed to initialize VNC server: %s\n", error->message); else g_message ("Initialized VNC server"); return result; } #endif /* HAVE_VNC */ static void maybe_enable_services (GrdDaemon *daemon) { gboolean has_one_backend = FALSE; if (!is_daemon_ready (daemon)) return; #ifdef HAVE_RDP has_one_backend = init_rdp_server (daemon) || has_one_backend; #endif #ifdef HAVE_VNC has_one_backend = init_vnc_server (daemon) || has_one_backend; #endif if (!has_one_backend) { g_warning ("No backend initialized successfully. Exiting"); g_application_release (G_APPLICATION (daemon)); } } static void close_all_sessions (GrdDaemon *daemon) { GList *l; while ((l = grd_context_get_sessions (daemon->context))) { GrdSession *session = l->data; grd_session_stop (session); } } static void disable_services (GrdDaemon *daemon) { close_all_sessions (daemon); #ifdef HAVE_RDP g_clear_object (&daemon->rdp_server); #endif #ifdef HAVE_VNC g_clear_object (&daemon->vnc_server); #endif } static void on_remote_desktop_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDaemon *daemon = user_data; GrdDBusRemoteDesktop *proxy; GError *error = NULL; proxy = grd_dbus_remote_desktop_proxy_new_for_bus_finish (result, &error); if (!proxy) { g_warning ("Failed to create remote desktop proxy: %s", error->message); g_error_free (error); return; } grd_context_set_remote_desktop_proxy (daemon->context, proxy); maybe_enable_services (daemon); } static void on_screen_cast_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDaemon *daemon = user_data; GrdDBusScreenCast *proxy; GError *error = NULL; proxy = grd_dbus_screen_cast_proxy_new_for_bus_finish (result, &error); if (!proxy) { g_warning ("Failed to create screen cast proxy: %s", error->message); return; } grd_context_set_screen_cast_proxy (daemon->context, proxy); maybe_enable_services (daemon); } static void on_remote_desktop_name_appeared (GDBusConnection *connection, const char *name, const char *name_owner, gpointer user_data) { GrdDaemon *daemon = user_data; grd_dbus_remote_desktop_proxy_new_for_bus (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, MUTTER_REMOTE_DESKTOP_BUS_NAME, MUTTER_REMOTE_DESKTOP_OBJECT_PATH, daemon->cancellable, on_remote_desktop_proxy_acquired, daemon); } static void on_remote_desktop_name_vanished (GDBusConnection *connection, const char *name, gpointer user_data) { GrdDaemon *daemon = user_data; disable_services (daemon); grd_context_set_remote_desktop_proxy (daemon->context, NULL); } static void on_screen_cast_name_appeared (GDBusConnection *connection, const char *name, const char *name_owner, gpointer user_data) { GrdDaemon *daemon = user_data; grd_dbus_screen_cast_proxy_new_for_bus (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, MUTTER_SCREEN_CAST_BUS_NAME, MUTTER_SCREEN_CAST_OBJECT_PATH, daemon->cancellable, on_screen_cast_proxy_acquired, daemon); } static void on_screen_cast_name_vanished (GDBusConnection *connection, const char *name, gpointer user_data) { GrdDaemon *daemon = user_data; disable_services (daemon); grd_context_set_screen_cast_proxy (daemon->context, NULL); } static void grd_daemon_init (GrdDaemon *daemon) { daemon->context = g_object_new (GRD_TYPE_CONTEXT, NULL); } static void grd_daemon_startup (GApplication *app) { GrdDaemon *daemon = GRD_DAEMON (app); daemon->remote_desktop_watch_name_id = g_bus_watch_name (G_BUS_TYPE_SESSION, MUTTER_REMOTE_DESKTOP_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, on_remote_desktop_name_appeared, on_remote_desktop_name_vanished, daemon, NULL); daemon->screen_cast_watch_name_id = g_bus_watch_name (G_BUS_TYPE_SESSION, MUTTER_SCREEN_CAST_BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, on_screen_cast_name_appeared, on_screen_cast_name_vanished, daemon, NULL); daemon->cancellable = g_cancellable_new (); /* Run indefinitely, until told to exit. */ g_application_hold (app); G_APPLICATION_CLASS (grd_daemon_parent_class)->startup (app); } static void grd_daemon_shutdown (GApplication *app) { GrdDaemon *daemon = GRD_DAEMON (app); g_cancellable_cancel (daemon->cancellable); g_clear_object (&daemon->cancellable); disable_services (daemon); grd_context_set_remote_desktop_proxy (daemon->context, NULL); g_bus_unwatch_name (daemon->remote_desktop_watch_name_id); daemon->remote_desktop_watch_name_id = 0; grd_context_set_screen_cast_proxy (daemon->context, NULL); g_bus_unwatch_name (daemon->screen_cast_watch_name_id); daemon->screen_cast_watch_name_id = 0; g_clear_object (&daemon->context); G_APPLICATION_CLASS (grd_daemon_parent_class)->shutdown (app); } static void grd_daemon_class_init (GrdDaemonClass *klass) { GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass); g_application_class->startup = grd_daemon_startup; g_application_class->shutdown = grd_daemon_shutdown; } static void activate_terminate (GAction *action, GVariant *parameter, GrdDaemon *daemon) { g_application_release (G_APPLICATION (daemon)); } static void add_actions (GApplication *app) { g_autoptr(GSimpleAction) action = NULL; action = g_simple_action_new ("terminate", NULL); g_signal_connect (action, "activate", G_CALLBACK (activate_terminate), app); g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (action)); } int main (int argc, char **argv) { GrdSettings *settings; gboolean print_version = FALSE; int rdp_port = -1; int vnc_port = -1; GOptionEntry entries[] = { { "version", 0, 0, G_OPTION_ARG_NONE, &print_version, "Print version", NULL }, { "rdp-port", 0, 0, G_OPTION_ARG_INT, &rdp_port, "RDP port", NULL }, { "vnc-port", 0, 0, G_OPTION_ARG_INT, &vnc_port, "VNC port", NULL }, { NULL } }; g_autoptr(GOptionContext) context = NULL; g_autoptr(GApplication) app = NULL; GError *error = NULL; g_set_application_name (_("GNOME Remote Desktop")); context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("Invalid option: %s\n", error->message); g_error_free (error); return EXIT_FAILURE; } if (print_version) { g_print ("GNOME Remote Desktop %s\n", VERSION); return EXIT_SUCCESS; } app = g_object_new (GRD_TYPE_DAEMON, "application-id", GRD_DAEMON_APPLICATION_ID, "flags", G_APPLICATION_IS_SERVICE, NULL); add_actions (app); settings = grd_context_get_settings (GRD_DAEMON (app)->context); if (rdp_port != -1) grd_settings_override_rdp_port (settings, rdp_port); if (vnc_port != -1) grd_settings_override_vnc_port (settings, vnc_port); return g_application_run (app, argc, argv); } 07070100000045000081A40000000000000000000000016293A0700000049C000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/src/grd-daemon.h/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_DAEMON_H #define GRD_DAEMON_H #include <gio/gio.h> #include "grd-dbus-remote-desktop.h" typedef struct _GrdDaemon GrdDaemon; #define GRD_TYPE_DAEMON (grd_daemon_get_type ()) G_DECLARE_FINAL_TYPE (GrdDaemon, grd_daemon, GRD, DAEMON, GApplication); GrdDBusRemoteDesktop *grd_daemon_get_dbus_proxy (GrdDaemon *daemon); #endif /* GRD_DAEMON_H */ 07070100000046000081A40000000000000000000000016293A07000000BDA000000000000000000000000000000000000003100000000gnome-remote-desktop-41.3/src/grd-damage-utils.c/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-damage-utils.h" #include <string.h> bool grd_is_tile_dirty (cairo_rectangle_int_t *tile, uint8_t *current_data, uint8_t *prev_data, uint32_t stride, uint32_t bytes_per_pixel) { uint32_t y; for (y = tile->y; y < tile->y + tile->height; ++y) { if (memcmp (prev_data + y * stride + tile->x * bytes_per_pixel, current_data + y * stride + tile->x * bytes_per_pixel, tile->width * bytes_per_pixel)) return true; } return false; } cairo_region_t * grd_get_damage_region (uint8_t *current_data, uint8_t *prev_data, uint32_t surface_width, uint32_t surface_height, uint32_t tile_width, uint32_t tile_height, uint32_t stride, uint32_t bytes_per_pixel) { cairo_region_t *damage_region; cairo_rectangle_int_t tile; uint32_t cols, rows; uint32_t x, y; damage_region = cairo_region_create (); if (current_data == NULL || prev_data == NULL) { tile.x = tile.y = 0; tile.width = surface_width; tile.height = surface_height; cairo_region_union_rectangle (damage_region, &tile); return damage_region; } cols = surface_width / tile_width + (surface_width % tile_width ? 1 : 0); rows = surface_height / tile_height + (surface_height % tile_height ? 1 : 0); for (y = 0; y < rows; ++y) { for (x = 0; x < cols; ++x) { tile.x = x * tile_width; tile.y = y * tile_height; tile.width = surface_width - tile.x < tile_width ? surface_width - tile.x : tile_width; tile.height = surface_height - tile.y < tile_height ? surface_height - tile.y : tile_height; if (grd_is_tile_dirty (&tile, current_data, prev_data, stride, bytes_per_pixel)) cairo_region_union_rectangle (damage_region, &tile); } } return damage_region; } 07070100000047000081A40000000000000000000000016293A070000006AF000000000000000000000000000000000000003100000000gnome-remote-desktop-41.3/src/grd-damage-utils.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_DAMAGE_UTILS_H #define GRD_DAMAGE_UTILS_H #include <cairo/cairo.h> #include <stdbool.h> #include <stdint.h> cairo_region_t *grd_get_damage_region (uint8_t *current_data, uint8_t *prev_data, uint32_t surface_width, uint32_t surface_height, uint32_t tile_width, uint32_t tile_height, uint32_t stride, uint32_t bytes_per_pixel); bool grd_is_tile_dirty (cairo_rectangle_int_t *tile, uint8_t *current_data, uint8_t *prev_data, uint32_t stride, uint32_t bytes_per_pixel); #endif /* GRD_DAMAGE_UTILS_H */ 07070100000048000081A40000000000000000000000016293A070000003A1000000000000000000000000000000000000002A00000000gnome-remote-desktop-41.3/src/grd-enums.h/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef GRD_ENUMS_H #define GRD_ENUMS_H typedef enum { GRD_VNC_AUTH_METHOD_PROMPT, GRD_VNC_AUTH_METHOD_PASSWORD } GrdVncAuthMethod; #endif /* GRD_ENUMS_H */ 07070100000049000081A40000000000000000000000016293A07000000B60000000000000000000000000000000000000002E00000000gnome-remote-desktop-41.3/src/grd-mime-type.c/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-mime-type.h" #include <gio/gio.h> const char * grd_mime_type_to_string (GrdMimeType mime_type) { switch (mime_type) { case GRD_MIME_TYPE_TEXT_PLAIN: return "text/plain"; case GRD_MIME_TYPE_TEXT_PLAIN_UTF8: return "text/plain;charset=utf-8"; case GRD_MIME_TYPE_TEXT_UTF8_STRING: return "UTF8_STRING"; case GRD_MIME_TYPE_TEXT_HTML: return "text/html"; case GRD_MIME_TYPE_IMAGE_BMP: return "image/bmp"; case GRD_MIME_TYPE_IMAGE_TIFF: return "image/tiff"; case GRD_MIME_TYPE_IMAGE_GIF: return "image/gif"; case GRD_MIME_TYPE_IMAGE_JPEG: return "image/jpeg"; case GRD_MIME_TYPE_IMAGE_PNG: return "image/png"; case GRD_MIME_TYPE_TEXT_URILIST: return "text/uri-list"; case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES: return "x-special/gnome-copied-files"; default: return NULL; } g_assert_not_reached (); } GrdMimeType grd_mime_type_from_string (const char *mime_type_string) { if (strcmp (mime_type_string, "text/plain") == 0) return GRD_MIME_TYPE_TEXT_PLAIN; else if (strcmp (mime_type_string, "text/plain;charset=utf-8") == 0) return GRD_MIME_TYPE_TEXT_PLAIN_UTF8; else if (strcmp (mime_type_string, "UTF8_STRING") == 0) return GRD_MIME_TYPE_TEXT_UTF8_STRING; else if (strcmp (mime_type_string, "text/html") == 0) return GRD_MIME_TYPE_TEXT_HTML; else if (strcmp (mime_type_string, "image/bmp") == 0) return GRD_MIME_TYPE_IMAGE_BMP; else if (strcmp (mime_type_string, "image/tiff") == 0) return GRD_MIME_TYPE_IMAGE_TIFF; else if (strcmp (mime_type_string, "image/gif") == 0) return GRD_MIME_TYPE_IMAGE_GIF; else if (strcmp (mime_type_string, "image/jpeg") == 0) return GRD_MIME_TYPE_IMAGE_JPEG; else if (strcmp (mime_type_string, "image/png") == 0) return GRD_MIME_TYPE_IMAGE_PNG; else if (strcmp (mime_type_string, "text/uri-list") == 0) return GRD_MIME_TYPE_TEXT_URILIST; else if (strcmp (mime_type_string, "x-special/gnome-copied-files") == 0) return GRD_MIME_TYPE_XS_GNOME_COPIED_FILES; return GRD_MIME_TYPE_NONE; } 0707010000004A000081A40000000000000000000000016293A0700000072E000000000000000000000000000000000000002E00000000gnome-remote-desktop-41.3/src/grd-mime-type.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_MIME_TYPE_H #define GRD_MIME_TYPE_H #include <stdint.h> typedef enum _GrdMimeType { GRD_MIME_TYPE_NONE, GRD_MIME_TYPE_TEXT_PLAIN, /* text/plain */ GRD_MIME_TYPE_TEXT_PLAIN_UTF8, /* text/plain;charset=utf-8 */ GRD_MIME_TYPE_TEXT_UTF8_STRING, /* UTF8_STRING */ GRD_MIME_TYPE_TEXT_HTML, /* text/html */ GRD_MIME_TYPE_IMAGE_BMP, /* image/bmp */ GRD_MIME_TYPE_IMAGE_TIFF, /* image/tiff */ GRD_MIME_TYPE_IMAGE_GIF, /* image/gif */ GRD_MIME_TYPE_IMAGE_JPEG, /* image/jpeg */ GRD_MIME_TYPE_IMAGE_PNG, /* image/png */ GRD_MIME_TYPE_TEXT_URILIST, /* text/uri-list */ GRD_MIME_TYPE_XS_GNOME_COPIED_FILES, /* x-special/gnome-copied-files */ } GrdMimeType; typedef struct _GrdMimeTypeTable { GrdMimeType mime_type; struct { uint32_t format_id; } rdp; } GrdMimeTypeTable; const char *grd_mime_type_to_string (GrdMimeType mime_type); GrdMimeType grd_mime_type_from_string (const char *mime_type_string); #endif /* GRD_MIME_TYPE_H */ 0707010000004B000081A40000000000000000000000016293A070000007F0000000000000000000000000000000000000003300000000gnome-remote-desktop-41.3/src/grd-pipewire-utils.c/* * Copyright (C) 2015 Red Hat Inc. * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-pipewire-utils.h" #include <linux/dma-buf.h> #include <pipewire/pipewire.h> #include <spa/param/video/raw.h> #include <sys/ioctl.h> #include <errno.h> static gboolean is_pipewire_initialized = FALSE; void grd_maybe_initialize_pipewire (void) { if (!is_pipewire_initialized) { pw_init (NULL, NULL); is_pipewire_initialized = TRUE; } } gboolean grd_spa_pixel_format_to_grd_pixel_format (uint32_t spa_format, GrdPixelFormat *out_format) { if (spa_format == SPA_VIDEO_FORMAT_RGBA) *out_format = GRD_PIXEL_FORMAT_RGBA8888; else return FALSE; return TRUE; } void grd_sync_dma_buf (int fd, uint64_t start_or_end) { struct dma_buf_sync sync = { 0 }; sync.flags = start_or_end | DMA_BUF_SYNC_READ; while (TRUE) { int ret; ret = ioctl (fd, DMA_BUF_IOCTL_SYNC, &sync); if (ret == -1 && errno == EINTR) { continue; } else if (ret == -1) { g_warning ("Failed to synchronize DMA buffer: %s", g_strerror (errno)); break; } else { break; } } } 0707010000004C000081A40000000000000000000000016293A070000005AE000000000000000000000000000000000000003300000000gnome-remote-desktop-41.3/src/grd-pipewire-utils.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_PIPEWIRE_UTILS_H #define GRD_PIPEWIRE_UTILS_H #include <gio/gio.h> #include <stdint.h> #include "grd-types.h" #define CURSOR_META_SIZE(width, height) \ (sizeof(struct spa_meta_cursor) + \ sizeof(struct spa_meta_bitmap) + width * height * 4) typedef struct _GrdPipeWireSource { GSource base; struct pw_loop *pipewire_loop; } GrdPipeWireSource; void grd_maybe_initialize_pipewire (void); gboolean grd_spa_pixel_format_to_grd_pixel_format (uint32_t spa_format, GrdPixelFormat *out_format); void grd_sync_dma_buf (int fd, uint64_t start_or_end); #endif /* GRD_PIPEWIRE_UTILS_H */ 0707010000004D000081A40000000000000000000000016293A070000004D5000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-private.h/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_PRIVATE_H #define GRD_PRIVATE_H #define GRD_DAEMON_APPLICATION_ID "org.gnome.RemoteDesktop" #define MUTTER_REMOTE_DESKTOP_BUS_NAME "org.gnome.Mutter.RemoteDesktop" #define MUTTER_REMOTE_DESKTOP_OBJECT_PATH "/org/gnome/Mutter/RemoteDesktop" #define MUTTER_SCREEN_CAST_BUS_NAME "org.gnome.Mutter.ScreenCast" #define MUTTER_SCREEN_CAST_OBJECT_PATH "/org/gnome/Mutter/ScreenCast" #endif /* GRD_PRIVATE_H */ 0707010000004E000081A40000000000000000000000016293A0700000147E000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/src/grd-prompt.c/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include <glib/gi18n.h> #include <libnotify/notify.h> #include "grd-prompt.h" typedef struct _GrdPromptResult { GrdPromptResponse response; } GrdPromptResult; struct _GrdPrompt { GObject parent; }; G_DEFINE_TYPE (GrdPrompt, grd_prompt, G_TYPE_OBJECT) static void handle_notification_response (NotifyNotification *notification, char *response, gpointer user_data) { GTask *task = G_TASK (user_data); if (g_strcmp0 (response, "accept") == 0) { g_task_return_int (task, GRD_PROMPT_RESPONSE_ACCEPT); } else if (g_strcmp0 (response, "refuse") == 0 || g_strcmp0 (response, "closed") == 0) { g_task_return_int (task, GRD_PROMPT_RESPONSE_REFUSE); } else { g_warning ("Unknown prompt response '%s'", response); g_task_return_int (task, GRD_PROMPT_RESPONSE_REFUSE); } } static void on_notification_closed (NotifyNotification *notification, gpointer user_data) { handle_notification_response (notification, "closed", user_data); } static gboolean cancelled_idle_callback (gpointer user_data) { GTask *task = G_TASK (user_data); g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Prompt was cancelled"); return G_SOURCE_REMOVE; } static void on_cancellable_cancelled (GCancellable *cancellable, GTask *task) { NotifyNotification *notification = NOTIFY_NOTIFICATION (g_task_get_task_data (task)); notify_notification_close (notification, NULL); g_idle_add (cancelled_idle_callback, task); } static gboolean show_notification_idle_callback (gpointer user_data) { GTask *task = G_TASK (user_data); GCancellable *cancellable = g_task_get_cancellable (task); NotifyNotification *notification; GError *error = NULL; if (g_cancellable_is_cancelled (cancellable)) return G_SOURCE_REMOVE; notification = g_task_get_task_data (task); if (!notify_notification_show (notification, &error)) g_task_return_error (task, error); return G_SOURCE_REMOVE; } void grd_prompt_query_async (GrdPrompt *prompt, const char *remote_host, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NotifyNotification *notification; GTask *task; g_autofree char *summary = NULL; g_autofree char *body = NULL; task = g_task_new (G_OBJECT (prompt), cancellable, callback, user_data); summary = g_strdup_printf (_("Do you want to share your desktop?")); body = g_strdup_printf (_("A user on the computer '%s' is trying to remotely view or control your desktop."), remote_host); notification = notify_notification_new (summary, body, "preferences-desktop-remote-desktop"); notify_notification_add_action (notification, "refuse", _("Refuse"), handle_notification_response, task, NULL); notify_notification_add_action (notification, "accept", _("Accept"), handle_notification_response, task, NULL); g_task_set_task_data (task, notification, g_object_unref); g_signal_connect (notification, "closed", G_CALLBACK (on_notification_closed), task); g_cancellable_connect (cancellable, G_CALLBACK (on_cancellable_cancelled), task, NULL); g_idle_add (show_notification_idle_callback, task); } gboolean grd_prompt_query_finish (GrdPrompt *prompt, GAsyncResult *result, GrdPromptResponse *out_response, GError **error) { g_autoptr(GTask) task = G_TASK (result); GrdPromptResponse response; response = g_task_propagate_int (task, error); if (response == -1) return FALSE; *out_response = response; return TRUE; } static void grd_prompt_init (GrdPrompt *prompt) { } static void grd_prompt_class_init (GrdPromptClass *klass) { notify_init (g_get_application_name ()); } 0707010000004F000081A40000000000000000000000016293A07000000659000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/src/grd-prompt.h/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef GRD_PROMPT_H #define GRD_PROMPT_H typedef enum _GrdPromptResponse { GRD_PROMPT_RESPONSE_ACCEPT, GRD_PROMPT_RESPONSE_REFUSE } GrdPromptResponse; #define GRD_TYPE_PROMPT (grd_prompt_get_type ()) G_DECLARE_FINAL_TYPE (GrdPrompt, grd_prompt, GRD, PROMPT, GObject) void grd_prompt_query_async (GrdPrompt *prompt, const char *remote_host, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean grd_prompt_query_finish (GrdPrompt *prompt, GAsyncResult *result, GrdPromptResponse *response, GError **error); #endif /* GRD_PROMPT_H */ 07070100000050000081A40000000000000000000000016293A070000031B1000000000000000000000000000000000000003400000000gnome-remote-desktop-41.3/src/grd-rdp-event-queue.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-event-queue.h" #include <xkbcommon/xkbcommon-keysyms.h> typedef enum _RdpEventType { RDP_EVENT_TYPE_NONE, RDP_EVENT_TYPE_INPUT_KBD_KEYCODE, RDP_EVENT_TYPE_INPUT_KBD_KEYSYM, RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS, RDP_EVENT_TYPE_INPUT_PTR_BUTTON, RDP_EVENT_TYPE_INPUT_PTR_AXIS, } RdpEventType; typedef struct _RdpEvent { RdpEventType type; /* RDP_EVENT_TYPE_INPUT_KBD_KEYCODE */ struct { uint32_t keycode; GrdKeyState state; } input_kbd_keycode; /* RDP_EVENT_TYPE_INPUT_KBD_KEYSYM */ struct { uint32_t keysym; GrdKeyState state; } input_kbd_keysym; /* RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS */ struct { double x; double y; } input_ptr_motion_abs; /* RDP_EVENT_TYPE_INPUT_PTR_BUTTON */ struct { int32_t button; GrdButtonState state; } input_ptr_button; /* RDP_EVENT_TYPE_INPUT_PTR_AXIS */ struct { double dx; double dy; GrdPointerAxisFlags flags; } input_ptr_axis; } RdpEvent; typedef struct _RdpSynchronizationEvent { gboolean caps_lock_state; gboolean num_lock_state; } RdpSynchronizationEvent; struct _GrdRdpEventQueue { GObject parent; GrdSessionRdp *session_rdp; GSource *flush_source; GMutex event_mutex; GQueue *queue; RdpSynchronizationEvent *rdp_sync_event; gboolean pending_sync_caps_lock; gboolean pending_sync_num_lock; gboolean caps_lock_state; gboolean num_lock_state; }; G_DEFINE_TYPE (GrdRdpEventQueue, grd_rdp_event_queue, G_TYPE_OBJECT); static void queue_rdp_event (GrdRdpEventQueue *rdp_event_queue, RdpEvent *rdp_event) { g_mutex_lock (&rdp_event_queue->event_mutex); g_queue_push_tail (rdp_event_queue->queue, rdp_event); g_mutex_unlock (&rdp_event_queue->event_mutex); g_source_set_ready_time (rdp_event_queue->flush_source, 0); } void grd_rdp_event_queue_add_input_event_keyboard_keycode (GrdRdpEventQueue *rdp_event_queue, uint32_t keycode, GrdKeyState state) { RdpEvent *rdp_event; rdp_event = g_malloc0 (sizeof (RdpEvent)); rdp_event->type = RDP_EVENT_TYPE_INPUT_KBD_KEYCODE; rdp_event->input_kbd_keycode.keycode = keycode; rdp_event->input_kbd_keycode.state = state; queue_rdp_event (rdp_event_queue, rdp_event); } void grd_rdp_event_queue_add_input_event_keyboard_keysym (GrdRdpEventQueue *rdp_event_queue, uint32_t keysym, GrdKeyState state) { RdpEvent *rdp_event; rdp_event = g_malloc0 (sizeof (RdpEvent)); rdp_event->type = RDP_EVENT_TYPE_INPUT_KBD_KEYSYM; rdp_event->input_kbd_keysym.keysym = keysym; rdp_event->input_kbd_keysym.state = state; queue_rdp_event (rdp_event_queue, rdp_event); } void grd_rdp_event_queue_add_input_event_pointer_motion_abs (GrdRdpEventQueue *rdp_event_queue, double x, double y) { RdpEvent *rdp_event; rdp_event = g_malloc0 (sizeof (RdpEvent)); rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS; rdp_event->input_ptr_motion_abs.x = x; rdp_event->input_ptr_motion_abs.y = y; queue_rdp_event (rdp_event_queue, rdp_event); } void grd_rdp_event_queue_add_input_event_pointer_button (GrdRdpEventQueue *rdp_event_queue, int32_t button, GrdButtonState state) { RdpEvent *rdp_event; rdp_event = g_malloc0 (sizeof (RdpEvent)); rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_BUTTON; rdp_event->input_ptr_button.button = button; rdp_event->input_ptr_button.state = state; queue_rdp_event (rdp_event_queue, rdp_event); } void grd_rdp_event_queue_add_input_event_pointer_axis (GrdRdpEventQueue *rdp_event_queue, double dx, double dy, GrdPointerAxisFlags flags) { RdpEvent *rdp_event; rdp_event = g_malloc0 (sizeof (RdpEvent)); rdp_event->type = RDP_EVENT_TYPE_INPUT_PTR_AXIS; rdp_event->input_ptr_axis.dx = dx; rdp_event->input_ptr_axis.dy = dy; rdp_event->input_ptr_axis.flags = flags; queue_rdp_event (rdp_event_queue, rdp_event); } void grd_rdp_event_queue_update_caps_lock_state (GrdRdpEventQueue *rdp_event_queue, gboolean caps_lock_state) { rdp_event_queue->caps_lock_state = caps_lock_state; rdp_event_queue->pending_sync_caps_lock = FALSE; if (rdp_event_queue->pending_sync_num_lock) return; g_source_set_ready_time (rdp_event_queue->flush_source, 0); } void grd_rdp_event_queue_update_num_lock_state (GrdRdpEventQueue *rdp_event_queue, gboolean num_lock_state) { rdp_event_queue->num_lock_state = num_lock_state; rdp_event_queue->pending_sync_num_lock = FALSE; if (rdp_event_queue->pending_sync_caps_lock) return; g_source_set_ready_time (rdp_event_queue->flush_source, 0); } void grd_rdp_event_queue_add_synchronization_event (GrdRdpEventQueue *rdp_event_queue, gboolean caps_lock_state, gboolean num_lock_state) { RdpSynchronizationEvent *rdp_sync_event; rdp_sync_event = g_malloc0 (sizeof (RdpSynchronizationEvent)); rdp_sync_event->caps_lock_state = caps_lock_state; rdp_sync_event->num_lock_state = num_lock_state; g_mutex_lock (&rdp_event_queue->event_mutex); g_clear_pointer (&rdp_event_queue->rdp_sync_event, g_free); rdp_event_queue->rdp_sync_event = rdp_sync_event; g_mutex_unlock (&rdp_event_queue->event_mutex); g_source_set_ready_time (rdp_event_queue->flush_source, 0); } static void handle_synchronization_event (GrdRdpEventQueue *rdp_event_queue) { GrdSession *session = GRD_SESSION (rdp_event_queue->session_rdp); RdpSynchronizationEvent *rdp_sync_event; rdp_sync_event = g_steal_pointer (&rdp_event_queue->rdp_sync_event); if (rdp_sync_event->caps_lock_state != rdp_event_queue->caps_lock_state) { g_debug ("Synchronizing caps lock state to be %s", rdp_sync_event->caps_lock_state ? "locked": "unlocked"); grd_session_notify_keyboard_keysym (session, XKB_KEY_Caps_Lock, GRD_KEY_STATE_PRESSED); grd_session_notify_keyboard_keysym (session, XKB_KEY_Caps_Lock, GRD_KEY_STATE_RELEASED); rdp_event_queue->pending_sync_caps_lock = TRUE; } if (rdp_sync_event->num_lock_state != rdp_event_queue->num_lock_state) { g_debug ("Synchronizing num lock state to be %s", rdp_sync_event->num_lock_state ? "locked": "unlocked"); grd_session_notify_keyboard_keysym (session, XKB_KEY_Num_Lock, GRD_KEY_STATE_PRESSED); grd_session_notify_keyboard_keysym (session, XKB_KEY_Num_Lock, GRD_KEY_STATE_RELEASED); rdp_event_queue->pending_sync_num_lock = TRUE; } g_free (rdp_sync_event); } static void process_rdp_events (GrdRdpEventQueue *rdp_event_queue) { GrdSession *session = GRD_SESSION (rdp_event_queue->session_rdp); RdpEvent *rdp_event; if (rdp_event_queue->rdp_sync_event && !rdp_event_queue->pending_sync_caps_lock && !rdp_event_queue->pending_sync_num_lock) handle_synchronization_event (rdp_event_queue); while ((rdp_event = g_queue_pop_head (rdp_event_queue->queue))) { switch (rdp_event->type) { case RDP_EVENT_TYPE_NONE: break; case RDP_EVENT_TYPE_INPUT_KBD_KEYCODE: grd_session_notify_keyboard_keycode ( session, rdp_event->input_kbd_keycode.keycode, rdp_event->input_kbd_keycode.state); break; case RDP_EVENT_TYPE_INPUT_KBD_KEYSYM: grd_session_notify_keyboard_keysym (session, rdp_event->input_kbd_keysym.keysym, rdp_event->input_kbd_keysym.state); break; case RDP_EVENT_TYPE_INPUT_PTR_MOTION_ABS: grd_session_notify_pointer_motion_absolute ( session, rdp_event->input_ptr_motion_abs.x, rdp_event->input_ptr_motion_abs.y); break; case RDP_EVENT_TYPE_INPUT_PTR_BUTTON: grd_session_notify_pointer_button (session, rdp_event->input_ptr_button.button, rdp_event->input_ptr_button.state); break; case RDP_EVENT_TYPE_INPUT_PTR_AXIS: grd_session_notify_pointer_axis (session, rdp_event->input_ptr_axis.dx, rdp_event->input_ptr_axis.dy, rdp_event->input_ptr_axis.flags); break; } g_free (rdp_event); } } static gboolean flush_rdp_events (gpointer user_data) { GrdRdpEventQueue *rdp_event_queue = user_data; g_mutex_lock (&rdp_event_queue->event_mutex); process_rdp_events (rdp_event_queue); g_mutex_unlock (&rdp_event_queue->event_mutex); return G_SOURCE_CONTINUE; } static gboolean flush_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs flush_source_funcs = { .dispatch = flush_source_dispatch, }; GrdRdpEventQueue * grd_rdp_event_queue_new (GrdSessionRdp *session_rdp) { GrdRdpEventQueue *rdp_event_queue; GSource *flush_source; rdp_event_queue = g_object_new (GRD_TYPE_RDP_EVENT_QUEUE, NULL); rdp_event_queue->session_rdp = session_rdp; flush_source = g_source_new (&flush_source_funcs, sizeof (GSource)); g_source_set_callback (flush_source, flush_rdp_events, rdp_event_queue, NULL); g_source_set_ready_time (flush_source, -1); g_source_attach (flush_source, NULL); rdp_event_queue->flush_source = flush_source; return rdp_event_queue; } void free_rdp_event (gpointer data) { RdpEvent *rdp_event = data; g_free (rdp_event); } static void grd_rdp_event_queue_dispose (GObject *object) { GrdRdpEventQueue *rdp_event_queue = GRD_RDP_EVENT_QUEUE (object); /** * Process all events to ensure that remaining keysym-released events are sent */ process_rdp_events (rdp_event_queue); g_clear_pointer (&rdp_event_queue->rdp_sync_event, g_free); g_queue_free_full (rdp_event_queue->queue, free_rdp_event); g_source_destroy (rdp_event_queue->flush_source); g_clear_pointer (&rdp_event_queue->flush_source, g_source_unref); G_OBJECT_CLASS (grd_rdp_event_queue_parent_class)->dispose (object); } static void grd_rdp_event_queue_finalize (GObject *object) { GrdRdpEventQueue *rdp_event_queue = GRD_RDP_EVENT_QUEUE (object); g_mutex_clear (&rdp_event_queue->event_mutex); G_OBJECT_CLASS (grd_rdp_event_queue_parent_class)->finalize (object); } static void grd_rdp_event_queue_init (GrdRdpEventQueue *rdp_event_queue) { rdp_event_queue->queue = g_queue_new (); g_mutex_init (&rdp_event_queue->event_mutex); rdp_event_queue->pending_sync_caps_lock = TRUE; rdp_event_queue->pending_sync_num_lock = TRUE; } static void grd_rdp_event_queue_class_init (GrdRdpEventQueueClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_event_queue_dispose; object_class->finalize = grd_rdp_event_queue_finalize; } 07070100000051000081A40000000000000000000000016293A07000000C7D000000000000000000000000000000000000003400000000gnome-remote-desktop-41.3/src/grd-rdp-event-queue.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_EVENT_QUEUE_H #define GRD_RDP_EVENT_QUEUE_H #include <glib-object.h> #include <stdint.h> #include "grd-session.h" #include "grd-types.h" #define GRD_TYPE_RDP_EVENT_QUEUE (grd_rdp_event_queue_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpEventQueue, grd_rdp_event_queue, GRD, RDP_EVENT_QUEUE, GObject); GrdRdpEventQueue *grd_rdp_event_queue_new (GrdSessionRdp *session_rdp); void grd_rdp_event_queue_add_input_event_keyboard_keycode (GrdRdpEventQueue *rdp_event_queue, uint32_t keycode, GrdKeyState state); void grd_rdp_event_queue_add_input_event_keyboard_keysym (GrdRdpEventQueue *rdp_event_queue, uint32_t keysym, GrdKeyState state); void grd_rdp_event_queue_add_input_event_pointer_motion_abs (GrdRdpEventQueue *rdp_event_queue, double x, double y); void grd_rdp_event_queue_add_input_event_pointer_button (GrdRdpEventQueue *rdp_event_queue, int32_t button, GrdButtonState state); void grd_rdp_event_queue_add_input_event_pointer_axis (GrdRdpEventQueue *rdp_event_queue, double dx, double dy, GrdPointerAxisFlags flags); void grd_rdp_event_queue_update_caps_lock_state (GrdRdpEventQueue *rdp_event_queue, gboolean caps_lock_state); void grd_rdp_event_queue_update_num_lock_state (GrdRdpEventQueue *rdp_event_queue, gboolean num_lock_state); void grd_rdp_event_queue_add_synchronization_event (GrdRdpEventQueue *rdp_event_queue, gboolean caps_lock_state, gboolean num_lock_state); #endif /* GRD_RDP_EVENT_QUEUE_H */ 07070100000052000081A40000000000000000000000016293A070000003D3000000000000000000000000000000000000003300000000gnome-remote-desktop-41.3/src/grd-rdp-frame-info.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_FRAME_INFO_H #define GRD_RDP_FRAME_INFO_H typedef struct _GrdRdpFrameInfo { uint32_t frame_id; int64_t enc_time_us; int64_t ack_time_us; } GrdRdpFrameInfo; #endif /* GRD_RDP_FRAME_INFO_H */ 07070100000053000081A40000000000000000000000016293A0700000CCB6000000000000000000000000000000000000003700000000gnome-remote-desktop-41.3/src/grd-rdp-fuse-clipboard.c/* * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-fuse-clipboard.h" #define FUSE_USE_VERSION 35 #include <fuse3/fuse_lowlevel.h> #include <unistd.h> #include "grd-clipboard-rdp.h" #define CLIP_DATA_ENTRY_DROP_TIMEOUT_MS (60 * 1000) #define CLIP_DATA_ENTRY_DROP_TIMEOUT_DELTA_US (10 * G_USEC_PER_SEC) #define WIN32_FILETIME_TO_UNIX_EPOCH UINT64_C (11644473600) typedef enum _FuseLowlevelOperationType { FUSE_LL_OPERATION_NONE, FUSE_LL_OPERATION_LOOKUP, FUSE_LL_OPERATION_GETATTR, FUSE_LL_OPERATION_READ, } FuseLowlevelOperationType; typedef struct _FuseFileStealContext { gboolean all_files; gboolean has_clip_data_id; uint32_t clip_data_id; GList *fuse_files; } FuseFileStealContext; typedef struct _ClearRdpFuseRequestContext { GrdRdpFuseClipboard *rdp_fuse_clipboard; gboolean all_files; gboolean has_clip_data_id; uint32_t clip_data_id; } ClearRdpFuseRequestContext; typedef struct _FuseFile FuseFile; typedef struct _ClipDataEntry ClipDataEntry; struct _FuseFile { FuseFile *parent; GList *children; char *filename; char *filename_with_root; uint32_t list_idx; fuse_ino_t ino; gboolean is_directory; gboolean is_readonly; gboolean has_size; uint64_t size; gboolean has_last_write_time; uint64_t last_write_time_unix; gboolean has_clip_data_id; uint32_t clip_data_id; ClipDataEntry *entry; }; struct _ClipDataEntry { FuseFile *clip_data_dir; gboolean has_clip_data_id; uint32_t clip_data_id; GrdClipboardRdp *clipboard_rdp; gboolean had_file_contents_request; struct { GrdRdpFuseClipboard *rdp_fuse_clipboard; unsigned int drop_id; int64_t drop_id_timeout_set_us; } drop_context; }; typedef struct _RdpFuseFileContentsRequest { FuseFile *fuse_file; fuse_req_t fuse_req; FuseLowlevelOperationType operation_type; uint32_t stream_id; } RdpFuseFileContentsRequest; struct _GrdRdpFuseClipboard { GObject parent; GrdClipboardRdp *clipboard_rdp; char *mount_path; GThread *fuse_thread; struct fuse_session *fuse_handle; HANDLE start_event; HANDLE stop_event; GSource *timeout_reset_source; GHashTable *timeouts_to_reset; GMutex filesystem_mutex; GMutex selection_mutex; GHashTable *inode_table; GHashTable *clip_data_table; GHashTable *request_table; FuseFile *root_dir; ClipDataEntry *no_cdi_entry; fuse_ino_t next_ino; uint32_t next_clip_data_id; uint32_t next_stream_id; }; G_DEFINE_TYPE (GrdRdpFuseClipboard, grd_rdp_fuse_clipboard, G_TYPE_OBJECT); static gboolean should_remove_fuse_file (FuseFile *fuse_file, gboolean all_files, gboolean has_clip_data_id, uint32_t clip_data_id) { if (all_files) return TRUE; if (fuse_file->ino == FUSE_ROOT_ID) return FALSE; if (!fuse_file->has_clip_data_id && !has_clip_data_id) return TRUE; if (fuse_file->has_clip_data_id && has_clip_data_id && fuse_file->clip_data_id == clip_data_id) return TRUE; return FALSE; } static gboolean collect_fuse_file_to_steal (gpointer key, gpointer value, gpointer user_data) { FuseFile *fuse_file = value; FuseFileStealContext *steal_context = user_data; if (!should_remove_fuse_file (fuse_file, steal_context->all_files, steal_context->has_clip_data_id, steal_context->clip_data_id)) return FALSE; steal_context->fuse_files = g_list_prepend (steal_context->fuse_files, fuse_file); return TRUE; } static gboolean maybe_clear_rdp_fuse_request (gpointer key, gpointer value, gpointer user_data) { RdpFuseFileContentsRequest *rdp_fuse_request = value; ClearRdpFuseRequestContext *clear_context = user_data; GrdRdpFuseClipboard *rdp_fuse_clipboard = clear_context->rdp_fuse_clipboard; if (!should_remove_fuse_file (rdp_fuse_request->fuse_file, clear_context->all_files, clear_context->has_clip_data_id, clear_context->clip_data_id)) return FALSE; if (rdp_fuse_clipboard->fuse_handle) fuse_reply_err (rdp_fuse_request->fuse_req, EIO); g_free (rdp_fuse_request); return TRUE; } void grd_rdp_fuse_clipboard_dismiss_all_no_cdi_requests (GrdRdpFuseClipboard *rdp_fuse_clipboard) { ClearRdpFuseRequestContext clear_context = {0}; clear_context.rdp_fuse_clipboard = rdp_fuse_clipboard; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); g_hash_table_foreach_remove (rdp_fuse_clipboard->request_table, maybe_clear_rdp_fuse_request, &clear_context); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); } static void invalidate_inode (gpointer data, gpointer user_data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = user_data; FuseFile *fuse_file = data; FuseFile *child; GList *l; for (l = fuse_file->children; l; l = l->next) { child = l->data; fuse_lowlevel_notify_delete (rdp_fuse_clipboard->fuse_handle, fuse_file->ino, child->ino, child->filename, strlen (child->filename)); } g_debug ("[FUSE Clipboard] Invalidating inode %lu for file \"%s\"", fuse_file->ino, fuse_file->filename); fuse_lowlevel_notify_inval_inode (rdp_fuse_clipboard->fuse_handle, fuse_file->ino, 0, 0); g_debug ("[FUSE Clipboard] Inode %lu invalidated", fuse_file->ino); } static void fuse_file_free (gpointer data) { FuseFile *fuse_file = data; g_list_free (fuse_file->children); g_free (fuse_file->filename_with_root); g_free (fuse_file); } static void clear_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, gboolean all_selections, ClipDataEntry *entry) { FuseFileStealContext steal_context = {0}; ClearRdpFuseRequestContext clear_context = {0}; FuseFile *clip_data_dir = NULL; g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->selection_mutex)); g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->filesystem_mutex)); if (entry) { FuseFile *root_dir = rdp_fuse_clipboard->root_dir; clip_data_dir = g_steal_pointer (&entry->clip_data_dir); root_dir->children = g_list_remove (root_dir->children, clip_data_dir); steal_context.has_clip_data_id = clear_context.has_clip_data_id = entry->has_clip_data_id; steal_context.clip_data_id = clear_context.clip_data_id = entry->clip_data_id; } steal_context.all_files = clear_context.all_files = all_selections; clear_context.rdp_fuse_clipboard = rdp_fuse_clipboard; if (entry && entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Clearing selection for clipDataId %u", entry->clip_data_id); else g_debug ("[FUSE Clipboard] Clearing selection%s", all_selections ? "s" : ""); g_hash_table_foreach_remove (rdp_fuse_clipboard->request_table, maybe_clear_rdp_fuse_request, &clear_context); g_hash_table_foreach_steal (rdp_fuse_clipboard->inode_table, collect_fuse_file_to_steal, &steal_context); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); /** * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive * a FUSE request (e.g. read()), then FUSE would block in read() since * filesystem_mutex would still be locked, if we wouldn't unlock it here. * fuse_lowlevel_notify_inval_inode() will block, since it waits on the FUSE * operation to finish. * So, to avoid a deadlock here, unlock the mutex and reply all incoming * operations with -ENOENT until the invalidation process is complete. */ g_list_foreach (steal_context.fuse_files, invalidate_inode, rdp_fuse_clipboard); if (clip_data_dir) { fuse_lowlevel_notify_delete (rdp_fuse_clipboard->fuse_handle, rdp_fuse_clipboard->root_dir->ino, clip_data_dir->ino, clip_data_dir->filename, strlen (clip_data_dir->filename)); } g_list_free_full (steal_context.fuse_files, fuse_file_free); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (entry && entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Selection cleared for clipDataId %u", entry->clip_data_id); else g_debug ("[FUSE Clipboard] Selection%s cleared", all_selections ? "s" : ""); } static void clear_all_selections (GrdRdpFuseClipboard *rdp_fuse_clipboard) { g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); clear_selection (rdp_fuse_clipboard, TRUE, NULL); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } static void clear_entry_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, ClipDataEntry *entry) { clear_selection (rdp_fuse_clipboard, FALSE, entry); } uint32_t grd_rdp_fuse_clipboard_clip_data_id_new (GrdRdpFuseClipboard *rdp_fuse_clipboard) { GrdClipboardRdp *clipboard_rdp = rdp_fuse_clipboard->clipboard_rdp; ClipDataEntry *entry = NULL; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (G_UNLIKELY (g_hash_table_size (rdp_fuse_clipboard->clip_data_table) >= UINT32_MAX)) { GHashTableIter iter; ClipDataEntry *iter_value; g_debug ("[FUSE Clipboard] All clipDataIds used. Removing the oldest one"); g_hash_table_iter_init (&iter, rdp_fuse_clipboard->clip_data_table); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &iter_value)) { g_assert (iter_value->drop_context.drop_id); if (!entry || entry->drop_context.drop_id_timeout_set_us > iter_value->drop_context.drop_id_timeout_set_us) entry = iter_value; } g_debug ("[FUSE Clipboard] Force clearing selection with clipDataId %u", entry->clip_data_id); clear_entry_selection (rdp_fuse_clipboard, entry); g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id)); } entry = g_malloc0 (sizeof (ClipDataEntry)); entry->clipboard_rdp = clipboard_rdp; entry->has_clip_data_id = TRUE; entry->clip_data_id = rdp_fuse_clipboard->next_clip_data_id; while (g_hash_table_contains (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id))) ++entry->clip_data_id; rdp_fuse_clipboard->next_clip_data_id = entry->clip_data_id + 1; g_hash_table_insert (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id), entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); grd_clipboard_rdp_lock_remote_clipboard_data (clipboard_rdp, entry->clip_data_id); return entry->clip_data_id; } void grd_rdp_fuse_clipboard_clip_data_id_free (GrdRdpFuseClipboard *rdp_fuse_clipboard, uint32_t clip_data_id) { ClipDataEntry *entry; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!g_hash_table_lookup_extended (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return; } clear_entry_selection (rdp_fuse_clipboard, entry); g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id)); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } void grd_rdp_fuse_clipboard_clear_no_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard) { g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (rdp_fuse_clipboard->no_cdi_entry) clear_entry_selection (rdp_fuse_clipboard, rdp_fuse_clipboard->no_cdi_entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } static gboolean drop_clip_data_entry (gpointer user_data) { ClipDataEntry *entry = user_data; GrdRdpFuseClipboard *rdp_fuse_clipboard = entry->drop_context.rdp_fuse_clipboard; g_debug ("[FUSE Clipboard] Dropping ClipDataEntry with clipDataId %u", entry->clip_data_id); g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); clear_entry_selection (rdp_fuse_clipboard, entry); entry->drop_context.drop_id = 0; g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id)); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return G_SOURCE_REMOVE; } static void collect_instant_droppable_clip_data_entry (gpointer key, gpointer value, gpointer user_data) { ClipDataEntry *entry = value; GList **droppable_clip_data_entries = user_data; if (!entry->had_file_contents_request) { *droppable_clip_data_entries = g_list_prepend (*droppable_clip_data_entries, entry); } } static void clear_instant_droppable_clip_data_entries (GrdRdpFuseClipboard *rdp_fuse_clipboard) { GList *droppable_clip_data_entries = NULL; GList *l; g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->selection_mutex)); g_assert (!g_mutex_trylock (&rdp_fuse_clipboard->filesystem_mutex)); g_hash_table_foreach (rdp_fuse_clipboard->clip_data_table, collect_instant_droppable_clip_data_entry, &droppable_clip_data_entries); for (l = droppable_clip_data_entries; l; l = l->next) { ClipDataEntry *entry = l->data; g_debug ("[FUSE Clipboard] Instantly clearing selection for clipDataId %u", entry->clip_data_id); clear_entry_selection (rdp_fuse_clipboard, entry); g_hash_table_remove (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (entry->clip_data_id)); } g_list_free (droppable_clip_data_entries); } static void maybe_set_clip_data_entry_timeout (gpointer key, gpointer value, gpointer user_data) { ClipDataEntry *entry = value; if (entry->drop_context.drop_id) return; g_debug ("[FUSE Clipboard] Setting timeout for selection with clipDataId %u", entry->clip_data_id); entry->drop_context.rdp_fuse_clipboard = user_data; entry->drop_context.drop_id = g_timeout_add (CLIP_DATA_ENTRY_DROP_TIMEOUT_MS, drop_clip_data_entry, entry); entry->drop_context.drop_id_timeout_set_us = g_get_monotonic_time (); } void grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (GrdRdpFuseClipboard *rdp_fuse_clipboard) { g_debug ("[FUSE Clipboard] Lazily clearing all selections with clipDataId"); g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); clear_instant_droppable_clip_data_entries (rdp_fuse_clipboard); g_hash_table_foreach (rdp_fuse_clipboard->clip_data_table, maybe_set_clip_data_entry_timeout, rdp_fuse_clipboard); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); } static fuse_ino_t get_next_free_inode (GrdRdpFuseClipboard *rdp_fuse_clipboard) { fuse_ino_t ino = rdp_fuse_clipboard->next_ino; while (ino == 0 || ino == FUSE_ROOT_ID || g_hash_table_contains (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (ino))) ++ino; rdp_fuse_clipboard->next_ino = ino + 1; return ino; } static FuseFile * clip_data_dir_new (GrdRdpFuseClipboard *rdp_fuse_clipboard, gboolean has_clip_data_id, uint32_t clip_data_id) { FuseFile *root_dir = rdp_fuse_clipboard->root_dir; FuseFile *clip_data_dir; clip_data_dir = g_malloc0 (sizeof (FuseFile)); clip_data_dir->parent = rdp_fuse_clipboard->root_dir; clip_data_dir->filename_with_root = has_clip_data_id ? g_strdup_printf ("/%u", clip_data_id) : g_strdup_printf ("/%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID); clip_data_dir->filename = strrchr (clip_data_dir->filename_with_root, '/') + 1; clip_data_dir->ino = get_next_free_inode (rdp_fuse_clipboard); clip_data_dir->is_directory = TRUE; clip_data_dir->is_readonly = TRUE; clip_data_dir->has_clip_data_id = has_clip_data_id; clip_data_dir->clip_data_id = clip_data_id; root_dir->children = g_list_append (root_dir->children, clip_data_dir); clip_data_dir->parent = root_dir; g_hash_table_insert (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (clip_data_dir->ino), clip_data_dir); return clip_data_dir; } static gboolean is_fuse_file_parent (gpointer key, gpointer value, gpointer user_data) { FuseFile *fuse_file = value; const char *parent_path = user_data; if (strcmp (parent_path, fuse_file->filename_with_root) == 0) return TRUE; return FALSE; } static FuseFile * get_parent_directory (GrdRdpFuseClipboard *rdp_fuse_clipboard, const char *path) { FuseFile *parent; char *parent_path; parent_path = g_path_get_dirname (path); parent = g_hash_table_find (rdp_fuse_clipboard->inode_table, is_fuse_file_parent, parent_path); g_free (parent_path); return parent; } static gboolean set_selection_for_clip_data_entry (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files, ClipDataEntry *entry) { FuseFile *clip_data_dir = entry->clip_data_dir; uint32_t clip_data_id = clip_data_dir->clip_data_id; uint32_t i; if (entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Setting selection for clipDataId %u", clip_data_id); else g_debug ("[FUSE Clipboard] Setting selection"); for (i = 0; i < n_files; ++i) { FILEDESCRIPTORW *file; FuseFile *fuse_file, *parent; char *filename = NULL; uint32_t j; file = &files[i]; fuse_file = g_malloc0 (sizeof (FuseFile)); if (!(file->dwFlags & FD_ATTRIBUTES)) g_warning ("[RDP.CLIPRDR] Client did not set the FD_ATTRIBUTES flag"); if (ConvertFromUnicode (CP_UTF8, 0, file->cFileName, -1, &filename, 0, NULL, NULL) <= 0) { g_warning ("[RDP.CLIPRDR] Failed to convert filename. Aborting " "SelectionTransfer"); clear_entry_selection (rdp_fuse_clipboard, entry); g_free (fuse_file); return FALSE; } for (j = 0; filename[j]; ++j) { if (filename[j] == '\\') filename[j] = '/'; } fuse_file->filename_with_root = g_strdup_printf ("%s/%s", clip_data_dir->filename_with_root, filename); fuse_file->filename = strrchr (fuse_file->filename_with_root, '/') + 1; g_free (filename); parent = get_parent_directory (rdp_fuse_clipboard, fuse_file->filename_with_root); parent->children = g_list_append (parent->children, fuse_file); fuse_file->parent = parent; fuse_file->list_idx = i; fuse_file->ino = get_next_free_inode (rdp_fuse_clipboard); fuse_file->has_clip_data_id = entry->has_clip_data_id; fuse_file->clip_data_id = clip_data_id; fuse_file->entry = entry; if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) fuse_file->is_directory = TRUE; if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY) fuse_file->is_readonly = TRUE; if (file->dwFlags & FD_FILESIZE) { fuse_file->size = ((uint64_t) file->nFileSizeHigh << 32) + file->nFileSizeLow; fuse_file->has_size = TRUE; } if (file->dwFlags & FD_WRITETIME) { uint64_t filetime; filetime = file->ftLastWriteTime.dwHighDateTime; filetime <<= 32; filetime += file->ftLastWriteTime.dwLowDateTime; fuse_file->last_write_time_unix = filetime / (10 * G_USEC_PER_SEC) - WIN32_FILETIME_TO_UNIX_EPOCH; fuse_file->has_last_write_time = TRUE; } g_hash_table_insert (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (fuse_file->ino), fuse_file); } if (entry->has_clip_data_id) g_debug ("[FUSE Clipboard] Selection set for clipDataId %u", clip_data_id); else g_debug ("[FUSE Clipboard] Selection set"); return TRUE; } gboolean grd_rdp_fuse_clipboard_set_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files, uint32_t clip_data_id) { ClipDataEntry *entry; gboolean result; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!g_hash_table_lookup_extended (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return FALSE; } entry->clip_data_dir = clip_data_dir_new (rdp_fuse_clipboard, TRUE, clip_data_id); result = set_selection_for_clip_data_entry (rdp_fuse_clipboard, files, n_files, entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return result; } gboolean grd_rdp_fuse_clipboard_set_no_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files) { gboolean result; g_mutex_lock (&rdp_fuse_clipboard->selection_mutex); g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!rdp_fuse_clipboard->no_cdi_entry) rdp_fuse_clipboard->no_cdi_entry = g_malloc0 (sizeof (ClipDataEntry)); if (rdp_fuse_clipboard->no_cdi_entry->clip_data_dir) clear_entry_selection (rdp_fuse_clipboard, rdp_fuse_clipboard->no_cdi_entry); rdp_fuse_clipboard->no_cdi_entry->clip_data_dir = clip_data_dir_new (rdp_fuse_clipboard, FALSE, 0); result = set_selection_for_clip_data_entry (rdp_fuse_clipboard, files, n_files, rdp_fuse_clipboard->no_cdi_entry); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_unlock (&rdp_fuse_clipboard->selection_mutex); return result; } static void write_file_attributes (FuseFile *fuse_file, struct stat *attr) { memset (attr, 0, sizeof (struct stat)); if (!fuse_file) return; attr->st_ino = fuse_file->ino; if (fuse_file->is_directory) { attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755); attr->st_nlink = 2; } else { attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644); attr->st_nlink = 1; attr->st_size = fuse_file->size; } attr->st_uid = getuid (); attr->st_gid = getgid (); attr->st_atime = attr->st_mtime = attr->st_ctime = (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time (NULL)); } static void maybe_queue_clip_data_entry_timeout_reset (GrdRdpFuseClipboard *rdp_fuse_clipboard, ClipDataEntry *entry) { int64_t drop_id_timeout_set_us = entry->drop_context.drop_id_timeout_set_us; int64_t now_us; if (!entry->drop_context.drop_id) return; now_us = g_get_monotonic_time (); if (now_us - drop_id_timeout_set_us < CLIP_DATA_ENTRY_DROP_TIMEOUT_DELTA_US) return; g_debug ("[FUSE Clipboard] Queueing a timeout reset for selection with " "clipDataId %u", entry->clip_data_id); g_hash_table_add (rdp_fuse_clipboard->timeouts_to_reset, GUINT_TO_POINTER (entry->clip_data_id)); g_source_set_ready_time (rdp_fuse_clipboard->timeout_reset_source, 0); } void grd_rdp_fuse_clipboard_submit_file_contents_response (GrdRdpFuseClipboard *rdp_fuse_clipboard, uint32_t stream_id, gboolean response_ok, const uint8_t *data, uint32_t size) { RdpFuseFileContentsRequest *rdp_fuse_request; struct fuse_entry_param entry = {0}; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!g_hash_table_steal_extended (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (stream_id), NULL, (gpointer *) &rdp_fuse_request)) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return; } if (!response_ok) { g_warning ("[RDP.CLIPRDR] Failed to retrieve file data for file \"%s\" " "from the client", rdp_fuse_request->fuse_file->filename); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (rdp_fuse_request->fuse_req, EIO); g_free (rdp_fuse_request); return; } maybe_queue_clip_data_entry_timeout_reset (rdp_fuse_clipboard, rdp_fuse_request->fuse_file->entry); if (rdp_fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP || rdp_fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) { g_debug ("[FUSE Clipboard] Received file size for file \"%s\" with stream " "id %u", rdp_fuse_request->fuse_file->filename, stream_id); rdp_fuse_request->fuse_file->size = *((uint64_t *) data); rdp_fuse_request->fuse_file->has_size = TRUE; entry.ino = rdp_fuse_request->fuse_file->ino; write_file_attributes (rdp_fuse_request->fuse_file, &entry.attr); entry.attr_timeout = 1.0; entry.entry_timeout = 1.0; } else if (rdp_fuse_request->operation_type == FUSE_LL_OPERATION_READ) { g_debug ("[FUSE Clipboard] Received file range for file \"%s\" with stream " "id %u", rdp_fuse_request->fuse_file->filename, stream_id); } else { g_assert_not_reached (); } g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); switch (rdp_fuse_request->operation_type) { case FUSE_LL_OPERATION_NONE: break; case FUSE_LL_OPERATION_LOOKUP: fuse_reply_entry (rdp_fuse_request->fuse_req, &entry); break; case FUSE_LL_OPERATION_GETATTR: fuse_reply_attr (rdp_fuse_request->fuse_req, &entry.attr, entry.attr_timeout); break; case FUSE_LL_OPERATION_READ: fuse_reply_buf (rdp_fuse_request->fuse_req, (const char *) data, size); break; } g_free (rdp_fuse_request); } static RdpFuseFileContentsRequest * rdp_fuse_file_contents_request_new (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *fuse_file, fuse_req_t fuse_req) { RdpFuseFileContentsRequest *rdp_fuse_request; uint32_t stream_id = rdp_fuse_clipboard->next_stream_id; fuse_file->entry->had_file_contents_request = TRUE; maybe_queue_clip_data_entry_timeout_reset (rdp_fuse_clipboard, fuse_file->entry); rdp_fuse_request = g_malloc0 (sizeof (RdpFuseFileContentsRequest)); rdp_fuse_request->fuse_file = fuse_file; rdp_fuse_request->fuse_req = fuse_req; while (g_hash_table_contains (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (stream_id))) ++stream_id; rdp_fuse_request->stream_id = stream_id; rdp_fuse_clipboard->next_stream_id = stream_id + 1; return rdp_fuse_request; } static void request_file_size_async (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *fuse_file, fuse_req_t fuse_req, FuseLowlevelOperationType operation_type) { GrdClipboardRdp *clipboard_rdp = rdp_fuse_clipboard->clipboard_rdp; RdpFuseFileContentsRequest *rdp_fuse_request; rdp_fuse_request = rdp_fuse_file_contents_request_new (rdp_fuse_clipboard, fuse_file, fuse_req); rdp_fuse_request->operation_type = operation_type; g_hash_table_insert (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (rdp_fuse_request->stream_id), rdp_fuse_request); g_debug ("[FUSE Clipboard] Requesting file size for file \"%s\" with stream id" " %u", fuse_file->filename, rdp_fuse_request->stream_id); grd_clipboard_rdp_request_remote_file_size_async (clipboard_rdp, rdp_fuse_request->stream_id, fuse_file->list_idx, fuse_file->has_clip_data_id, fuse_file->clip_data_id); } static void request_file_range_async (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *fuse_file, fuse_req_t fuse_req, uint64_t offset, uint32_t requested_size) { GrdClipboardRdp *clipboard_rdp = rdp_fuse_clipboard->clipboard_rdp; RdpFuseFileContentsRequest *rdp_fuse_request; rdp_fuse_request = rdp_fuse_file_contents_request_new (rdp_fuse_clipboard, fuse_file, fuse_req); rdp_fuse_request->operation_type = FUSE_LL_OPERATION_READ; g_hash_table_insert (rdp_fuse_clipboard->request_table, GUINT_TO_POINTER (rdp_fuse_request->stream_id), rdp_fuse_request); g_debug ("[FUSE Clipboard] Requesting file range (%u Bytes at offset %lu) for " "file \"%s\" with stream id %u", requested_size, offset, fuse_file->filename, rdp_fuse_request->stream_id); grd_clipboard_rdp_request_remote_file_range_async (clipboard_rdp, rdp_fuse_request->stream_id, fuse_file->list_idx, offset, requested_size, fuse_file->has_clip_data_id, fuse_file->clip_data_id); } static FuseFile * get_fuse_file_by_ino (GrdRdpFuseClipboard *rdp_fuse_clipboard, fuse_ino_t fuse_ino) { FuseFile *fuse_file; fuse_file = g_hash_table_lookup (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (fuse_ino)); return fuse_file; } static FuseFile * get_fuse_file_by_name_from_parent (GrdRdpFuseClipboard *rdp_fuse_clipboard, FuseFile *parent, const char *name) { FuseFile *child; GList *l; for (l = parent->children; l; l = l->next) { child = l->data; if (strcmp (name, child->filename) == 0) return child; } /** * This is not an error since several applications try to find specific files, * e.g. nautilus tries to find "/.Trash", etc. */ g_debug ("[FUSE Clipboard] Requested file \"%s\" in directory \"%s\" does not " "exist", name, parent->filename); return NULL; } static void fuse_ll_lookup (fuse_req_t fuse_req, fuse_ino_t parent_ino, const char *name) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *parent, *fuse_file; struct fuse_entry_param entry = {0}; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(parent = get_fuse_file_by_ino (rdp_fuse_clipboard, parent_ino)) || !parent->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (!(fuse_file = get_fuse_file_by_name_from_parent ( rdp_fuse_clipboard, parent, name))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } g_debug ("[FUSE Clipboard] lookup() has been called for \"%s\"", name); g_debug ("[FUSE Clipboard] Parent is \"%s\", child is \"%s\"", parent->filename_with_root, fuse_file->filename_with_root); if (!fuse_file->is_directory && !fuse_file->has_size) { request_file_size_async (rdp_fuse_clipboard, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return; } entry.ino = fuse_file->ino; write_file_attributes (fuse_file, &entry.attr); entry.attr_timeout = 1.0; entry.entry_timeout = 1.0; g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_entry (fuse_req, &entry); } static void fuse_ll_getattr (fuse_req_t fuse_req, fuse_ino_t fuse_ino, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; struct stat attr = {0}; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } g_debug ("[FUSE Clipboard] getattr() has been called for file \"%s\"", fuse_file->filename_with_root); if (!fuse_file->is_directory && !fuse_file->has_size) { request_file_size_async (rdp_fuse_clipboard, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return; } write_file_attributes (fuse_file, &attr); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_attr (fuse_req, &attr, 1.0); } static void fuse_ll_open (fuse_req_t fuse_req, fuse_ino_t fuse_ino, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; g_autofree char *filename_with_root = NULL; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, EISDIR); return; } filename_with_root = g_strdup (fuse_file->filename_with_root); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); if ((file_info->flags & O_ACCMODE) != O_RDONLY) { fuse_reply_err (fuse_req, EACCES); return; } /* Using direct_io also increases FUSE_MAX_PAGES_PER_REQ */ file_info->direct_io = 1; g_debug ("[FUSE Clipboard] Opening file \"%s\"", filename_with_root); fuse_reply_open (fuse_req, file_info); } static void fuse_ll_read (fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size, off_t offset, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, EISDIR); return; } if (!fuse_file->has_size || offset > fuse_file->size) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, EINVAL); return; } size = MIN (size, 8 * 1024 * 1024); g_assert (size > 0); request_file_range_async (rdp_fuse_clipboard, fuse_file, fuse_req, offset, size); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); } static void fuse_ll_opendir (fuse_req_t fuse_req, fuse_ino_t fuse_ino, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file; g_autofree char *filename_with_root = NULL; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (!fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOTDIR); return; } filename_with_root = g_strdup (fuse_file->filename_with_root); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); if ((file_info->flags & O_ACCMODE) != O_RDONLY) { fuse_reply_err (fuse_req, EACCES); return; } g_debug ("[FUSE Clipboard] Opening directory \"%s\"", filename_with_root); fuse_reply_open (fuse_req, file_info); } static void fuse_ll_readdir (fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size, off_t offset, struct fuse_file_info *file_info) { GrdRdpFuseClipboard *rdp_fuse_clipboard = fuse_req_userdata (fuse_req); FuseFile *fuse_file, *child; struct stat attr = {0}; size_t written_size, entry_size; char *filename; char *buf; off_t i; GList *l; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); if (!(fuse_file = get_fuse_file_by_ino (rdp_fuse_clipboard, fuse_ino))) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOENT); return; } if (!fuse_file->is_directory) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_err (fuse_req, ENOTDIR); return; } g_debug ("[FUSE Clipboard] Reading directory \"%s\" at offset %lu", fuse_file->filename_with_root, offset); if (offset >= g_list_length (fuse_file->children) + 1) { g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_buf (fuse_req, NULL, 0); return; } buf = g_malloc0 (max_size); written_size = 0; for (i = offset; i < 2; ++i) { if (i == 0) { write_file_attributes (fuse_file, &attr); filename = "."; } else if (i == 1) { write_file_attributes (fuse_file->parent, &attr); attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID; attr.st_mode = fuse_file->parent ? attr.st_mode : 0555; filename = ".."; } else { g_assert_not_reached (); } /** * buf needs to be large enough to hold the entry. If it's not, then the * entry is not filled in but the size of the entry is still returned. */ entry_size = fuse_add_direntry (fuse_req, buf + written_size, max_size - written_size, filename, &attr, i); if (entry_size > max_size - written_size) break; written_size += entry_size; } for (l = fuse_file->children, i = 2; l; l = l->next, ++i) { if (i <= offset) continue; child = l->data; write_file_attributes (child, &attr); entry_size = fuse_add_direntry (fuse_req, buf + written_size, max_size - written_size, child->filename, &attr, i); if (entry_size > max_size - written_size) break; written_size += entry_size; } g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); fuse_reply_buf (fuse_req, buf, written_size); g_free (buf); } static const struct fuse_lowlevel_ops fuse_ll_ops = { .lookup = fuse_ll_lookup, .getattr = fuse_ll_getattr, .open = fuse_ll_open, .read = fuse_ll_read, .opendir = fuse_ll_opendir, .readdir = fuse_ll_readdir, }; static gpointer fuse_thread_func (gpointer data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = data; struct fuse_args args = {0}; char *argv[1]; int result; g_debug ("[FUSE Clipboard] FUSE thread started"); argv[0] = program_invocation_name; args.argc = 1; args.argv = argv; rdp_fuse_clipboard->fuse_handle = fuse_session_new (&args, &fuse_ll_ops, sizeof (fuse_ll_ops), rdp_fuse_clipboard); if (!rdp_fuse_clipboard->fuse_handle) g_error ("[FUSE Clipboard] Failed to create FUSE filesystem"); if (fuse_session_mount (rdp_fuse_clipboard->fuse_handle, rdp_fuse_clipboard->mount_path)) g_error ("[FUSE Clipboard] Failed to mount FUSE filesystem"); fuse_daemonize (1); SetEvent (rdp_fuse_clipboard->start_event); g_debug ("[FUSE Clipboard] Starting FUSE session"); result = fuse_session_loop (rdp_fuse_clipboard->fuse_handle); if (result < 0) g_error ("fuse_loop() failed: %s", g_strerror (-result)); g_debug ("[FUSE Clipboard] Unmounting FUSE filesystem"); fuse_session_unmount (rdp_fuse_clipboard->fuse_handle); WaitForSingleObject (rdp_fuse_clipboard->stop_event, INFINITE); g_clear_pointer (&rdp_fuse_clipboard->fuse_handle, fuse_session_destroy); return NULL; } GrdRdpFuseClipboard * grd_rdp_fuse_clipboard_new (GrdClipboardRdp *clipboard_rdp, const char *mount_path) { GrdRdpFuseClipboard *rdp_fuse_clipboard; rdp_fuse_clipboard = g_object_new (GRD_TYPE_RDP_FUSE_CLIPBOARD, NULL); rdp_fuse_clipboard->clipboard_rdp = clipboard_rdp; rdp_fuse_clipboard->mount_path = g_strdup (mount_path); rdp_fuse_clipboard->start_event = CreateEvent (NULL, TRUE, FALSE, NULL); rdp_fuse_clipboard->stop_event = CreateEvent (NULL, TRUE, FALSE, NULL); rdp_fuse_clipboard->fuse_thread = g_thread_new ("RDP FUSE clipboard thread", fuse_thread_func, rdp_fuse_clipboard); if (!rdp_fuse_clipboard->fuse_thread) g_error ("[FUSE Clipboard] Failed to create FUSE thread"); WaitForSingleObject (rdp_fuse_clipboard->start_event, INFINITE); g_clear_pointer (&rdp_fuse_clipboard->start_event, CloseHandle); return rdp_fuse_clipboard; } static void dismiss_all_requests (GrdRdpFuseClipboard *rdp_fuse_clipboard) { ClearRdpFuseRequestContext clear_context = {0}; clear_context.rdp_fuse_clipboard = rdp_fuse_clipboard; clear_context.all_files = TRUE; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); g_hash_table_foreach_remove (rdp_fuse_clipboard->request_table, maybe_clear_rdp_fuse_request, &clear_context); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); } static void grd_rdp_fuse_clipboard_dispose (GObject *object) { GrdRdpFuseClipboard *rdp_fuse_clipboard = GRD_RDP_FUSE_CLIPBOARD (object); if (rdp_fuse_clipboard->timeout_reset_source) { g_source_destroy (rdp_fuse_clipboard->timeout_reset_source); g_clear_pointer (&rdp_fuse_clipboard->timeout_reset_source, g_source_unref); } if (rdp_fuse_clipboard->fuse_thread) { struct stat attr; g_assert (rdp_fuse_clipboard->fuse_handle); g_assert (!rdp_fuse_clipboard->start_event); clear_all_selections (rdp_fuse_clipboard); g_debug ("[FUSE Clipboard] Stopping FUSE thread"); fuse_session_exit (rdp_fuse_clipboard->fuse_handle); dismiss_all_requests (rdp_fuse_clipboard); SetEvent (rdp_fuse_clipboard->stop_event); /** * FUSE does not immediately stop the session after fuse_session_exit() has * been called. * Instead, it waits on a new operation. Upon retrieving this new * operation, it checks the exit flag and then stops the session loop. * So, trigger a FUSE operation by poking at the FUSE root dir to * effectively stop the session. */ stat (rdp_fuse_clipboard->mount_path, &attr); g_debug ("[FUSE Clipboard] Waiting on FUSE thread"); g_clear_pointer (&rdp_fuse_clipboard->fuse_thread, g_thread_join); g_debug ("[FUSE Clipboard] FUSE thread stopped"); g_clear_pointer (&rdp_fuse_clipboard->stop_event, CloseHandle); } g_clear_pointer (&rdp_fuse_clipboard->mount_path, g_free); if (rdp_fuse_clipboard->request_table) { dismiss_all_requests (rdp_fuse_clipboard); g_clear_pointer (&rdp_fuse_clipboard->request_table, g_hash_table_unref); } g_clear_pointer (&rdp_fuse_clipboard->no_cdi_entry, g_free); g_clear_pointer (&rdp_fuse_clipboard->clip_data_table, g_hash_table_destroy); g_clear_pointer (&rdp_fuse_clipboard->inode_table, g_hash_table_destroy); g_clear_pointer (&rdp_fuse_clipboard->timeouts_to_reset, g_hash_table_destroy); G_OBJECT_CLASS (grd_rdp_fuse_clipboard_parent_class)->dispose (object); } static void grd_rdp_fuse_clipboard_finalize (GObject *object) { GrdRdpFuseClipboard *rdp_fuse_clipboard = GRD_RDP_FUSE_CLIPBOARD (object); g_mutex_clear (&rdp_fuse_clipboard->selection_mutex); g_mutex_clear (&rdp_fuse_clipboard->filesystem_mutex); G_OBJECT_CLASS (grd_rdp_fuse_clipboard_parent_class)->finalize (object); } static void clip_data_entry_free (gpointer data) { ClipDataEntry *entry = data; grd_clipboard_rdp_unlock_remote_clipboard_data (entry->clipboard_rdp, entry->clip_data_id); g_clear_handle_id (&entry->drop_context.drop_id, g_source_remove); g_free (entry); } static FuseFile * fuse_file_new_root (void) { FuseFile *root_dir; root_dir = g_malloc0 (sizeof (FuseFile)); root_dir->filename_with_root = strdup ("/"); root_dir->filename = root_dir->filename_with_root; root_dir->ino = FUSE_ROOT_ID; root_dir->is_directory = TRUE; root_dir->is_readonly = TRUE; return root_dir; } static gboolean maybe_reset_clip_data_entry_timeout (gpointer key, gpointer value, gpointer user_data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = user_data; uint32_t clip_data_id = GPOINTER_TO_UINT (key); ClipDataEntry *entry; if (!g_hash_table_lookup_extended (rdp_fuse_clipboard->clip_data_table, GUINT_TO_POINTER (clip_data_id), NULL, (gpointer *) &entry)) return TRUE; if (!entry->drop_context.drop_id) return TRUE; g_debug ("[FUSE Clipboard] Resetting timeout for selection with clipDataId %u", clip_data_id); g_clear_handle_id (&entry->drop_context.drop_id, g_source_remove); entry->drop_context.drop_id = g_timeout_add (CLIP_DATA_ENTRY_DROP_TIMEOUT_MS, drop_clip_data_entry, entry); entry->drop_context.drop_id_timeout_set_us = g_get_monotonic_time (); return TRUE; } static gboolean reset_clip_data_entry_timeouts (gpointer user_data) { GrdRdpFuseClipboard *rdp_fuse_clipboard = user_data; g_mutex_lock (&rdp_fuse_clipboard->filesystem_mutex); g_hash_table_foreach_remove (rdp_fuse_clipboard->timeouts_to_reset, maybe_reset_clip_data_entry_timeout, rdp_fuse_clipboard); g_mutex_unlock (&rdp_fuse_clipboard->filesystem_mutex); return G_SOURCE_CONTINUE; } static gboolean timeout_reset_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs timeout_reset_source_funcs = { .dispatch = timeout_reset_source_dispatch, }; static void grd_rdp_fuse_clipboard_init (GrdRdpFuseClipboard *rdp_fuse_clipboard) { GSource *timeout_reset_source; rdp_fuse_clipboard->timeouts_to_reset = g_hash_table_new (NULL, NULL); rdp_fuse_clipboard->inode_table = g_hash_table_new (NULL, NULL); rdp_fuse_clipboard->clip_data_table = g_hash_table_new_full (NULL, NULL, NULL, clip_data_entry_free); rdp_fuse_clipboard->request_table = g_hash_table_new (NULL, NULL); rdp_fuse_clipboard->root_dir = fuse_file_new_root (); g_hash_table_insert (rdp_fuse_clipboard->inode_table, GUINT_TO_POINTER (rdp_fuse_clipboard->root_dir->ino), rdp_fuse_clipboard->root_dir); g_mutex_init (&rdp_fuse_clipboard->filesystem_mutex); g_mutex_init (&rdp_fuse_clipboard->selection_mutex); timeout_reset_source = g_source_new (&timeout_reset_source_funcs, sizeof (GSource)); g_source_set_callback (timeout_reset_source, reset_clip_data_entry_timeouts, rdp_fuse_clipboard, NULL); g_source_set_ready_time (timeout_reset_source, -1); g_source_attach (timeout_reset_source, NULL); rdp_fuse_clipboard->timeout_reset_source = timeout_reset_source; } static void grd_rdp_fuse_clipboard_class_init (GrdRdpFuseClipboardClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_fuse_clipboard_dispose; object_class->finalize = grd_rdp_fuse_clipboard_finalize; } 07070100000054000081A40000000000000000000000016293A07000000BBC000000000000000000000000000000000000003700000000gnome-remote-desktop-41.3/src/grd-rdp-fuse-clipboard.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_FUSE_CLIPBOARD_H #define GRD_RDP_FUSE_CLIPBOARD_H #include <glib-object.h> #include <winpr2/winpr/shell.h> #include "grd-types.h" #define GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID (UINT64_C (1) << 32) #define GRD_TYPE_RDP_FUSE_CLIPBOARD (grd_rdp_fuse_clipboard_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpFuseClipboard, grd_rdp_fuse_clipboard, GRD, RDP_FUSE_CLIPBOARD, GObject); GrdRdpFuseClipboard *grd_rdp_fuse_clipboard_new (GrdClipboardRdp *clipboard_rdp, const char *mount_path); uint32_t grd_rdp_fuse_clipboard_clip_data_id_new (GrdRdpFuseClipboard *rdp_fuse_clipboard); void grd_rdp_fuse_clipboard_clip_data_id_free (GrdRdpFuseClipboard *rdp_fuse_clipboard, uint32_t clip_data_id); void grd_rdp_fuse_clipboard_dismiss_all_no_cdi_requests (GrdRdpFuseClipboard *rdp_fuse_clipboard); void grd_rdp_fuse_clipboard_clear_no_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard); void grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (GrdRdpFuseClipboard *rdp_fuse_clipboard); gboolean grd_rdp_fuse_clipboard_set_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files, uint32_t clip_data_id); gboolean grd_rdp_fuse_clipboard_set_no_cdi_selection (GrdRdpFuseClipboard *rdp_fuse_clipboard, FILEDESCRIPTORW *files, uint32_t n_files); void grd_rdp_fuse_clipboard_submit_file_contents_response (GrdRdpFuseClipboard *rdp_fuse_clipboard, uint32_t stream_id, gboolean response_ok, const uint8_t *data, uint32_t size); #endif /* GRD_RDP_FUSE_CLIPBOARD_H */ 07070100000055000081A40000000000000000000000016293A070000017A3000000000000000000000000000000000000003600000000gnome-remote-desktop-41.3/src/grd-rdp-gfx-frame-log.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-gfx-frame-log.h" #include "grd-rdp-frame-info.h" struct _GrdRdpGfxFrameLog { GObject parent; GQueue *encoded_frames; GQueue *acked_frames; GHashTable *tracked_frames; }; G_DEFINE_TYPE (GrdRdpGfxFrameLog, grd_rdp_gfx_frame_log, G_TYPE_OBJECT); static void track_enc_frame_info (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t enc_time_us) { GrdRdpFrameInfo *frame_info; frame_info = g_malloc0 (sizeof (GrdRdpFrameInfo)); frame_info->frame_id = frame_id; frame_info->enc_time_us = enc_time_us; g_queue_push_tail (frame_log->encoded_frames, frame_info); } static void track_ack_frame_info (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t ack_time_us) { GrdRdpFrameInfo *frame_info; frame_info = g_malloc0 (sizeof (GrdRdpFrameInfo)); frame_info->frame_id = frame_id; frame_info->ack_time_us = ack_time_us; g_queue_push_tail (frame_log->acked_frames, frame_info); } void grd_rdp_gfx_frame_log_track_frame (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t enc_time_us) { track_enc_frame_info (frame_log, frame_id, enc_time_us); g_hash_table_add (frame_log->tracked_frames, GUINT_TO_POINTER (frame_id)); } void grd_rdp_gfx_frame_log_ack_tracked_frame (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t ack_time_us) { if (!g_hash_table_remove (frame_log->tracked_frames, GUINT_TO_POINTER (frame_id))) return; track_ack_frame_info (frame_log, frame_id, ack_time_us); } void grd_rdp_gfx_frame_log_unack_last_acked_frame (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t enc_ack_time_us) { GrdRdpFrameInfo *frame_info; if ((frame_info = g_queue_pop_tail (frame_log->acked_frames))) { g_assert (frame_info->frame_id == frame_id); g_assert (frame_info->ack_time_us == enc_ack_time_us); } g_free (frame_info); g_hash_table_add (frame_log->tracked_frames, GUINT_TO_POINTER (frame_id)); } static void clear_old_enc_frame_infos (GrdRdpGfxFrameLog *frame_log, int64_t current_time_us) { GrdRdpFrameInfo *frame_info; while ((frame_info = g_queue_peek_head (frame_log->encoded_frames)) && current_time_us - frame_info->enc_time_us >= 1 * G_USEC_PER_SEC) g_free (g_queue_pop_head (frame_log->encoded_frames)); } static void clear_old_ack_frame_infos (GrdRdpGfxFrameLog *frame_log, int64_t current_time_us) { GrdRdpFrameInfo *frame_info; while ((frame_info = g_queue_peek_head (frame_log->acked_frames)) && current_time_us - frame_info->ack_time_us >= 1 * G_USEC_PER_SEC) g_free (g_queue_pop_head (frame_log->acked_frames)); } void grd_rdp_gfx_frame_log_update_rates (GrdRdpGfxFrameLog *frame_log, uint32_t *enc_rate, uint32_t *ack_rate) { int64_t current_time_us; current_time_us = g_get_monotonic_time (); clear_old_enc_frame_infos (frame_log, current_time_us); clear_old_ack_frame_infos (frame_log, current_time_us); /* * Every remaining frame time, tracked in encoded_frames or acked_frames, is * now younger than 1 second. * The list lengths therefore represent the encoded_frames/s and * acked_frames/s values. */ *enc_rate = g_queue_get_length (frame_log->encoded_frames); *ack_rate = g_queue_get_length (frame_log->acked_frames); } uint32_t grd_rdp_gfx_frame_log_get_unacked_frames_count (GrdRdpGfxFrameLog *frame_log) { return g_hash_table_size (frame_log->tracked_frames); } void grd_rdp_gfx_frame_log_clear (GrdRdpGfxFrameLog *frame_log) { g_hash_table_remove_all (frame_log->tracked_frames); } GrdRdpGfxFrameLog * grd_rdp_gfx_frame_log_new (void) { GrdRdpGfxFrameLog *frame_log; frame_log = g_object_new (GRD_TYPE_RDP_GFX_FRAME_LOG, NULL); return frame_log; } static void grd_rdp_gfx_frame_log_dispose (GObject *object) { GrdRdpGfxFrameLog *frame_log = GRD_RDP_GFX_FRAME_LOG (object); if (frame_log->acked_frames) { g_queue_free_full (frame_log->acked_frames, g_free); frame_log->acked_frames = NULL; } if (frame_log->encoded_frames) { g_queue_free_full (frame_log->encoded_frames, g_free); frame_log->encoded_frames = NULL; } g_clear_pointer (&frame_log->tracked_frames, g_hash_table_destroy); G_OBJECT_CLASS (grd_rdp_gfx_frame_log_parent_class)->dispose (object); } static void grd_rdp_gfx_frame_log_init (GrdRdpGfxFrameLog *frame_log) { frame_log->tracked_frames = g_hash_table_new (NULL, NULL); frame_log->encoded_frames = g_queue_new (); frame_log->acked_frames = g_queue_new (); } static void grd_rdp_gfx_frame_log_class_init (GrdRdpGfxFrameLogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_gfx_frame_log_dispose; } 07070100000056000081A40000000000000000000000016293A070000008C4000000000000000000000000000000000000003600000000gnome-remote-desktop-41.3/src/grd-rdp-gfx-frame-log.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_GFX_FRAME_LOG_H #define GRD_RDP_GFX_FRAME_LOG_H #include <glib-object.h> #include <stdint.h> #include "grd-types.h" #define GRD_TYPE_RDP_GFX_FRAME_LOG (grd_rdp_gfx_frame_log_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpGfxFrameLog, grd_rdp_gfx_frame_log, GRD, RDP_GFX_FRAME_LOG, GObject); GrdRdpGfxFrameLog *grd_rdp_gfx_frame_log_new (void); void grd_rdp_gfx_frame_log_track_frame (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t enc_time_us); void grd_rdp_gfx_frame_log_ack_tracked_frame (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t ack_time_us); void grd_rdp_gfx_frame_log_unack_last_acked_frame (GrdRdpGfxFrameLog *frame_log, uint32_t frame_id, int64_t enc_ack_time_us); void grd_rdp_gfx_frame_log_update_rates (GrdRdpGfxFrameLog *frame_log, uint32_t *enc_rate, uint32_t *ack_rate); uint32_t grd_rdp_gfx_frame_log_get_unacked_frames_count (GrdRdpGfxFrameLog *frame_log); void grd_rdp_gfx_frame_log_clear (GrdRdpGfxFrameLog *frame_log); #endif /* GRD_RDP_GFX_FRAME_LOG_H */ 07070100000057000081A40000000000000000000000016293A070000033D7000000000000000000000000000000000000003400000000gnome-remote-desktop-41.3/src/grd-rdp-gfx-surface.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-gfx-surface.h" #include "grd-rdp-gfx-frame-log.h" #include "grd-rdp-graphics-pipeline.h" #include "grd-rdp-surface.h" #include "grd-session-rdp.h" #define ACTIVATE_THROTTLING_TH_DEFAULT 2 #define DEACTIVATE_THROTTLING_TH_DEFAULT 1 typedef enum _ThrottlingState { THROTTLING_STATE_INACTIVE, THROTTLING_STATE_ACTIVE, THROTTLING_STATE_ACTIVE_LOWERING_LATENCY, } ThrottlingState; struct _GrdRdpGfxSurface { GObject parent; GrdRdpGraphicsPipeline *graphics_pipeline; GrdSessionRdp *session_rdp; GrdRdpSurface *rdp_surface; gboolean created; uint16_t surface_id; uint32_t codec_context_id; uint32_t serial; GSource *pending_encode_source; GrdRdpGfxFrameLog *frame_log; int64_t nw_auto_last_rtt_us; ThrottlingState throttling_state; /* Throttling triggers on >= activate_throttling_th */ uint32_t activate_throttling_th; /* Throttling triggers on <= deactivate_throttling_th */ uint32_t deactivate_throttling_th; }; G_DEFINE_TYPE (GrdRdpGfxSurface, grd_rdp_gfx_surface, G_TYPE_OBJECT); uint16_t grd_rdp_gfx_surface_get_surface_id (GrdRdpGfxSurface *gfx_surface) { return gfx_surface->surface_id; } uint32_t grd_rdp_gfx_surface_get_codec_context_id (GrdRdpGfxSurface *gfx_surface) { return gfx_surface->codec_context_id; } uint32_t grd_rdp_gfx_surface_get_serial (GrdRdpGfxSurface *gfx_surface) { return gfx_surface->serial; } GrdRdpSurface * grd_rdp_gfx_surface_get_rdp_surface (GrdRdpGfxSurface *gfx_surface) { return gfx_surface->rdp_surface; } static uint32_t get_activate_throttling_th_from_rtt (GrdRdpGfxSurface *gfx_surface, int64_t rtt_us) { GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface; int64_t refresh_rate = rdp_surface->refresh_rate; uint32_t activate_throttling_th; uint32_t delayed_frames; delayed_frames = rtt_us * refresh_rate / G_USEC_PER_SEC; activate_throttling_th = MAX (2, MIN (delayed_frames + 2, refresh_rate)); g_assert (activate_throttling_th > gfx_surface->deactivate_throttling_th); return activate_throttling_th; } void grd_rdp_gfx_surface_unack_frame (GrdRdpGfxSurface *gfx_surface, uint32_t frame_id, int64_t enc_time_us) { GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface; GrdRdpGfxFrameLog *frame_log = gfx_surface->frame_log; uint32_t current_activate_throttling_th; uint32_t n_unacked_frames; uint32_t enc_rate = 0; uint32_t ack_rate = 0; grd_rdp_gfx_frame_log_track_frame (frame_log, frame_id, enc_time_us); n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log); grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate); switch (gfx_surface->throttling_state) { case THROTTLING_STATE_INACTIVE: gfx_surface->activate_throttling_th = get_activate_throttling_th_from_rtt ( gfx_surface, gfx_surface->nw_auto_last_rtt_us); if (n_unacked_frames >= gfx_surface->activate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE; rdp_surface->encoding_suspended = TRUE; } break; case THROTTLING_STATE_ACTIVE: current_activate_throttling_th = get_activate_throttling_th_from_rtt ( gfx_surface, gfx_surface->nw_auto_last_rtt_us); if (current_activate_throttling_th < gfx_surface->activate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE_LOWERING_LATENCY; rdp_surface->encoding_suspended = TRUE; } else { gfx_surface->activate_throttling_th = current_activate_throttling_th; rdp_surface->encoding_suspended = enc_rate > ack_rate + 1; } break; case THROTTLING_STATE_ACTIVE_LOWERING_LATENCY: g_assert (rdp_surface->encoding_suspended); break; } } void grd_rdp_gfx_surface_ack_frame (GrdRdpGfxSurface *gfx_surface, uint32_t frame_id, int64_t ack_time_us) { GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface; GrdRdpGfxFrameLog *frame_log = gfx_surface->frame_log; gboolean encoding_was_suspended; uint32_t current_activate_throttling_th; uint32_t n_unacked_frames; uint32_t enc_rate = 0; uint32_t ack_rate = 0; grd_rdp_gfx_frame_log_ack_tracked_frame (frame_log, frame_id, ack_time_us); encoding_was_suspended = rdp_surface->encoding_suspended; n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log); grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate); switch (gfx_surface->throttling_state) { case THROTTLING_STATE_INACTIVE: break; case THROTTLING_STATE_ACTIVE: if (n_unacked_frames <= gfx_surface->deactivate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE; rdp_surface->encoding_suspended = FALSE; break; } current_activate_throttling_th = get_activate_throttling_th_from_rtt ( gfx_surface, gfx_surface->nw_auto_last_rtt_us); if (current_activate_throttling_th < gfx_surface->activate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE_LOWERING_LATENCY; rdp_surface->encoding_suspended = TRUE; } else { gfx_surface->activate_throttling_th = current_activate_throttling_th; rdp_surface->encoding_suspended = enc_rate > ack_rate; } break; case THROTTLING_STATE_ACTIVE_LOWERING_LATENCY: current_activate_throttling_th = get_activate_throttling_th_from_rtt ( gfx_surface, gfx_surface->nw_auto_last_rtt_us); if (n_unacked_frames < current_activate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE; rdp_surface->encoding_suspended = FALSE; } else if (n_unacked_frames == current_activate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE; rdp_surface->encoding_suspended = enc_rate > ack_rate; } else if (n_unacked_frames > current_activate_throttling_th) { g_assert (rdp_surface->encoding_suspended); } else { g_assert_not_reached (); } break; } if (encoding_was_suspended && !rdp_surface->encoding_suspended) g_source_set_ready_time (gfx_surface->pending_encode_source, 0); } static void reevaluate_encoding_suspension_state (GrdRdpGfxSurface *gfx_surface) { GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface; GrdRdpGfxFrameLog *frame_log = gfx_surface->frame_log; uint32_t n_unacked_frames; uint32_t enc_rate = 0; uint32_t ack_rate = 0; n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log); grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate); switch (gfx_surface->throttling_state) { case THROTTLING_STATE_INACTIVE: gfx_surface->activate_throttling_th = get_activate_throttling_th_from_rtt ( gfx_surface, gfx_surface->nw_auto_last_rtt_us); if (n_unacked_frames >= gfx_surface->activate_throttling_th) { gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE; rdp_surface->encoding_suspended = TRUE; } break; case THROTTLING_STATE_ACTIVE: g_assert (gfx_surface->activate_throttling_th > gfx_surface->deactivate_throttling_th); g_assert (n_unacked_frames > gfx_surface->deactivate_throttling_th); g_assert (rdp_surface->encoding_suspended); break; case THROTTLING_STATE_ACTIVE_LOWERING_LATENCY: /* * While the graphics pipeline rewrites the frame history, the RTT * detection mechanism cannot submit a new round trip time. */ g_assert_not_reached (); break; } } void grd_rdp_gfx_surface_unack_last_acked_frame (GrdRdpGfxSurface *gfx_surface, uint32_t frame_id, int64_t enc_ack_time_us) { grd_rdp_gfx_frame_log_unack_last_acked_frame (gfx_surface->frame_log, frame_id, enc_ack_time_us); reevaluate_encoding_suspension_state (gfx_surface); } void grd_rdp_gfx_surface_clear_all_unacked_frames (GrdRdpGfxSurface *gfx_surface) { GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface; gboolean encoding_was_suspended; grd_rdp_gfx_frame_log_clear (gfx_surface->frame_log); encoding_was_suspended = rdp_surface->encoding_suspended; gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE; rdp_surface->encoding_suspended = FALSE; if (encoding_was_suspended) g_source_set_ready_time (gfx_surface->pending_encode_source, 0); } void grd_rdp_gfx_surface_notify_new_round_trip_time (GrdRdpGfxSurface *gfx_surface, int64_t round_trip_time_us) { gfx_surface->nw_auto_last_rtt_us = round_trip_time_us; } static gboolean maybe_encode_pending_frame (gpointer user_data) { GrdRdpGfxSurface *gfx_surface = user_data; grd_session_rdp_maybe_encode_pending_frame (gfx_surface->session_rdp, gfx_surface->rdp_surface); return G_SOURCE_CONTINUE; } static gboolean pending_encode_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs pending_encode_source_funcs = { .dispatch = pending_encode_source_dispatch, }; GrdRdpGfxSurface * grd_rdp_gfx_surface_new (GrdRdpGraphicsPipeline *graphics_pipeline, GrdSessionRdp *session_rdp, GMainContext *pipeline_context, GrdRdpSurface *rdp_surface, uint16_t surface_id, uint32_t serial) { GrdRdpGfxSurface *gfx_surface; gfx_surface = g_object_new (GRD_TYPE_RDP_GFX_SURFACE, NULL); gfx_surface->graphics_pipeline = graphics_pipeline; gfx_surface->session_rdp = session_rdp; gfx_surface->rdp_surface = rdp_surface; gfx_surface->surface_id = surface_id; /* * Use the same id for the codec context as for the surface * (only relevant for RDPGFX_WIRE_TO_SURFACE_PDU_2 PDUs) */ gfx_surface->codec_context_id = surface_id; grd_rdp_graphics_pipeline_create_surface (graphics_pipeline, gfx_surface); gfx_surface->created = TRUE; gfx_surface->pending_encode_source = g_source_new (&pending_encode_source_funcs, sizeof (GSource)); g_source_set_callback (gfx_surface->pending_encode_source, maybe_encode_pending_frame, gfx_surface, NULL); g_source_set_ready_time (gfx_surface->pending_encode_source, -1); g_source_attach (gfx_surface->pending_encode_source, pipeline_context); return gfx_surface; } static void grd_rdp_gfx_surface_dispose (GObject *object) { GrdRdpGfxSurface *gfx_surface = GRD_RDP_GFX_SURFACE (object); if (gfx_surface->pending_encode_source) { g_source_destroy (gfx_surface->pending_encode_source); g_clear_pointer (&gfx_surface->pending_encode_source, g_source_unref); } if (gfx_surface->created) { grd_rdp_graphics_pipeline_delete_surface (gfx_surface->graphics_pipeline, gfx_surface); gfx_surface->created = FALSE; } g_clear_object (&gfx_surface->frame_log); G_OBJECT_CLASS (grd_rdp_gfx_surface_parent_class)->dispose (object); } static void grd_rdp_gfx_surface_init (GrdRdpGfxSurface *gfx_surface) { gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE; gfx_surface->activate_throttling_th = ACTIVATE_THROTTLING_TH_DEFAULT; gfx_surface->deactivate_throttling_th = DEACTIVATE_THROTTLING_TH_DEFAULT; g_assert (gfx_surface->activate_throttling_th > gfx_surface->deactivate_throttling_th); gfx_surface->frame_log = grd_rdp_gfx_frame_log_new (); } static void grd_rdp_gfx_surface_class_init (GrdRdpGfxSurfaceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_gfx_surface_dispose; } 07070100000058000081A40000000000000000000000016293A07000000B0F000000000000000000000000000000000000003400000000gnome-remote-desktop-41.3/src/grd-rdp-gfx-surface.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_GFX_SURFACE_H #define GRD_RDP_GFX_SURFACE_H #include <glib-object.h> #include <stdint.h> #include "grd-types.h" #define GRD_TYPE_RDP_GFX_SURFACE (grd_rdp_gfx_surface_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpGfxSurface, grd_rdp_gfx_surface, GRD, RDP_GFX_SURFACE, GObject); GrdRdpGfxSurface *grd_rdp_gfx_surface_new (GrdRdpGraphicsPipeline *graphics_pipeline, GrdSessionRdp *session_rdp, GMainContext *pipeline_context, GrdRdpSurface *rdp_surface, uint16_t surface_id, uint32_t serial); uint16_t grd_rdp_gfx_surface_get_surface_id (GrdRdpGfxSurface *gfx_surface); uint32_t grd_rdp_gfx_surface_get_codec_context_id (GrdRdpGfxSurface *gfx_surface); uint32_t grd_rdp_gfx_surface_get_serial (GrdRdpGfxSurface *gfx_surface); GrdRdpSurface *grd_rdp_gfx_surface_get_rdp_surface (GrdRdpGfxSurface *gfx_surface); void grd_rdp_gfx_surface_unack_frame (GrdRdpGfxSurface *gfx_surface, uint32_t frame_id, int64_t enc_time_us); void grd_rdp_gfx_surface_ack_frame (GrdRdpGfxSurface *gfx_surface, uint32_t frame_id, int64_t ack_time_us); void grd_rdp_gfx_surface_unack_last_acked_frame (GrdRdpGfxSurface *gfx_surface, uint32_t frame_id, int64_t enc_ack_time_us); void grd_rdp_gfx_surface_clear_all_unacked_frames (GrdRdpGfxSurface *gfx_surface); void grd_rdp_gfx_surface_notify_new_round_trip_time (GrdRdpGfxSurface *gfx_surface, int64_t round_trip_time_us); #endif /* GRD_RDP_GFX_SURFACE_H */ 07070100000059000081A40000000000000000000000016293A0700000D6D3000000000000000000000000000000000000003A00000000gnome-remote-desktop-41.3/src/grd-rdp-graphics-pipeline.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-graphics-pipeline.h" #include <winpr/sysinfo.h> #include "grd-rdp-frame-info.h" #include "grd-rdp-gfx-surface.h" #include "grd-rdp-network-autodetection.h" #include "grd-rdp-surface.h" #include "grd-session-rdp.h" #ifdef HAVE_NVENC #include "grd-rdp-nvenc.h" #endif /* HAVE_NVENC */ #define ENC_TIMES_CHECK_INTERVAL_MS 1000 #define MAX_TRACKED_ENC_FRAMES 1000 typedef enum _HwAccelAPI { HW_ACCEL_API_NONE = 0, HW_ACCEL_API_NVENC = 1 << 0, } HwAccelAPI; typedef struct _HWAccelContext { HwAccelAPI api; uint32_t encode_session_id; gboolean has_first_frame; } HWAccelContext; typedef struct _GfxSurfaceContext { GrdRdpGfxSurface *gfx_surface; uint64_t ref_count; } GfxSurfaceContext; typedef struct _GfxFrameInfo { GrdRdpFrameInfo frame_info; uint32_t surface_serial; } GfxFrameInfo; struct _GrdRdpGraphicsPipeline { GObject parent; RdpgfxServerContext *rdpgfx_context; HANDLE stop_event; gboolean channel_opened; gboolean initialized; uint32_t initial_version; GrdSessionRdp *session_rdp; GMainContext *pipeline_context; GrdRdpNetworkAutodetection *network_autodetection; wStream *encode_stream; RFX_CONTEXT *rfx_context; GSource *protocol_reset_source; GMutex caps_mutex; RDPGFX_CAPSET *cap_sets; uint16_t n_cap_sets; GMutex gfx_mutex; GHashTable *surface_table; GHashTable *codec_context_table; /* Unacknowledged Frames ADM element */ GHashTable *frame_serial_table; GHashTable *serial_surface_table; gboolean frame_acks_suspended; GQueue *encoded_frames; uint32_t total_frames_encoded; GSource *rtt_pause_source; GQueue *enc_times; GHashTable *surface_hwaccel_table; #ifdef HAVE_NVENC GrdRdpNvenc *rdp_nvenc; #endif /* HAVE_NVENC */ uint32_t next_frame_id; uint16_t next_surface_id; uint32_t next_serial; }; G_DEFINE_TYPE (GrdRdpGraphicsPipeline, grd_rdp_graphics_pipeline, G_TYPE_OBJECT); #ifdef HAVE_NVENC void grd_rdp_graphics_pipeline_set_nvenc (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpNvenc *rdp_nvenc) { graphics_pipeline->rdp_nvenc = rdp_nvenc; } #endif /* HAVE_NVENC */ void grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpGfxSurface *gfx_surface) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; RDPGFX_CREATE_SURFACE_PDU create_surface = {0}; GrdRdpSurface *rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface); uint16_t surface_id = grd_rdp_gfx_surface_get_surface_id (gfx_surface); uint32_t surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface); GfxSurfaceContext *surface_context; #ifdef HAVE_NVENC HWAccelContext *hwaccel_context; uint32_t encode_session_id; #endif /* HAVE_NVENC */ surface_context = g_malloc0 (sizeof (GfxSurfaceContext)); g_mutex_lock (&graphics_pipeline->gfx_mutex); g_hash_table_insert (graphics_pipeline->surface_table, GUINT_TO_POINTER (surface_id), gfx_surface); surface_context->gfx_surface = gfx_surface; g_hash_table_insert (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial), surface_context); #ifdef HAVE_NVENC if ((rdpgfx_context->rdpcontext->settings->GfxAVC444v2 || rdpgfx_context->rdpcontext->settings->GfxAVC444 || rdpgfx_context->rdpcontext->settings->GfxH264) && graphics_pipeline->rdp_nvenc && grd_rdp_nvenc_create_encode_session (graphics_pipeline->rdp_nvenc, &encode_session_id, rdp_surface->width, rdp_surface->height, rdp_surface->refresh_rate)) { g_debug ("[RDP.RDPGFX] Creating NVENC session for surface %u", surface_id); hwaccel_context = g_malloc0 (sizeof (HWAccelContext)); hwaccel_context->api = HW_ACCEL_API_NVENC; hwaccel_context->encode_session_id = encode_session_id; g_hash_table_insert (graphics_pipeline->surface_hwaccel_table, GUINT_TO_POINTER (surface_id), hwaccel_context); } #endif /* HAVE_NVENC */ g_mutex_unlock (&graphics_pipeline->gfx_mutex); create_surface.surfaceId = surface_id; create_surface.width = rdp_surface->width; create_surface.height = rdp_surface->height; create_surface.pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888; rdpgfx_context->CreateSurface (rdpgfx_context, &create_surface); } void grd_rdp_graphics_pipeline_delete_surface (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpGfxSurface *gfx_surface) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; RDPGFX_DELETE_ENCODING_CONTEXT_PDU delete_encoding_context = {0}; RDPGFX_DELETE_SURFACE_PDU delete_surface = {0}; gboolean needs_encoding_context_deletion = FALSE; GfxSurfaceContext *surface_context; #ifdef HAVE_NVENC HWAccelContext *hwaccel_context; #endif /* HAVE_NVENC */ uint16_t surface_id; uint32_t codec_context_id; uint32_t surface_serial; if (!graphics_pipeline->channel_opened) return; surface_id = grd_rdp_gfx_surface_get_surface_id (gfx_surface); codec_context_id = grd_rdp_gfx_surface_get_codec_context_id (gfx_surface); surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface); g_mutex_lock (&graphics_pipeline->gfx_mutex); if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial), NULL, (gpointer *) &surface_context)) g_assert_not_reached (); surface_context->gfx_surface = NULL; if (surface_context->ref_count == 0) { g_hash_table_remove (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial)); } #ifdef HAVE_NVENC if (g_hash_table_steal_extended (graphics_pipeline->surface_hwaccel_table, GUINT_TO_POINTER (surface_id), NULL, (gpointer *) &hwaccel_context)) { g_debug ("[RDP.RDPGFX] Destroying NVENC session for surface %u", surface_id); g_assert (hwaccel_context->api == HW_ACCEL_API_NVENC); grd_rdp_nvenc_free_encode_session (graphics_pipeline->rdp_nvenc, hwaccel_context->encode_session_id); g_free (hwaccel_context); } #endif /* HAVE_NVENC */ if (g_hash_table_steal_extended (graphics_pipeline->codec_context_table, GUINT_TO_POINTER (codec_context_id), NULL, NULL)) needs_encoding_context_deletion = TRUE; g_hash_table_remove (graphics_pipeline->surface_table, GUINT_TO_POINTER (surface_id)); g_mutex_unlock (&graphics_pipeline->gfx_mutex); if (needs_encoding_context_deletion) { delete_encoding_context.surfaceId = surface_id; delete_encoding_context.codecContextId = codec_context_id; rdpgfx_context->DeleteEncodingContext (rdpgfx_context, &delete_encoding_context); } delete_surface.surfaceId = surface_id; rdpgfx_context->DeleteSurface (rdpgfx_context, &delete_surface); } void grd_rdp_graphics_pipeline_reset_graphics (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t width, uint32_t height, MONITOR_DEF *monitors, uint32_t n_monitors) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; RDPGFX_RESET_GRAPHICS_PDU reset_graphics = {0}; GList *surfaces; GList *l; g_debug ("[RDP.RDPGFX] Resetting graphics"); g_mutex_lock (&graphics_pipeline->gfx_mutex); surfaces = g_hash_table_get_values (graphics_pipeline->surface_table); g_hash_table_steal_all (graphics_pipeline->surface_table); g_mutex_unlock (&graphics_pipeline->gfx_mutex); for (l = surfaces; l; l = l->next) { GrdRdpGfxSurface *gfx_surface = l->data; GrdRdpSurface *rdp_surface; rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface); g_clear_object (&rdp_surface->gfx_surface); } g_list_free (surfaces); /* * width and height refer here to the size of the Graphics Output Buffer * ADM (Abstract Data Model) element */ reset_graphics.width = width; reset_graphics.height = height; reset_graphics.monitorCount = n_monitors; reset_graphics.monitorDefArray = monitors; rdpgfx_context->ResetGraphics (rdpgfx_context, &reset_graphics); } void grd_rdp_graphics_pipeline_notify_new_round_trip_time (GrdRdpGraphicsPipeline *graphics_pipeline, uint64_t round_trip_time_us) { GrdRdpGfxSurface *gfx_surface; GHashTableIter iter; g_mutex_lock (&graphics_pipeline->gfx_mutex); g_hash_table_iter_init (&iter, graphics_pipeline->surface_table); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &gfx_surface)) grd_rdp_gfx_surface_notify_new_round_trip_time (gfx_surface, round_trip_time_us); g_mutex_unlock (&graphics_pipeline->gfx_mutex); } static uint32_t get_next_free_frame_id (GrdRdpGraphicsPipeline *graphics_pipeline) { uint32_t frame_id = graphics_pipeline->next_frame_id; g_mutex_lock (&graphics_pipeline->gfx_mutex); while (g_hash_table_contains (graphics_pipeline->frame_serial_table, GUINT_TO_POINTER (frame_id))) ++frame_id; g_mutex_unlock (&graphics_pipeline->gfx_mutex); graphics_pipeline->next_frame_id = frame_id + 1; return frame_id; } static void surface_serial_ref (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t surface_serial) { GfxSurfaceContext *surface_context; if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial), NULL, (gpointer *) &surface_context)) g_assert_not_reached (); ++surface_context->ref_count; } static void surface_serial_unref (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t surface_serial) { GfxSurfaceContext *surface_context; if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial), NULL, (gpointer *) &surface_context)) g_assert_not_reached (); g_assert (surface_context->ref_count > 0); --surface_context->ref_count; if (!surface_context->gfx_surface && surface_context->ref_count == 0) { g_hash_table_remove (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial)); } } static void gfx_frame_info_free (GrdRdpGraphicsPipeline *graphics_pipeline, GfxFrameInfo *gfx_frame_info) { uint32_t surface_serial = gfx_frame_info->surface_serial; g_hash_table_remove (graphics_pipeline->frame_serial_table, GUINT_TO_POINTER (gfx_frame_info->frame_info.frame_id)); surface_serial_unref (graphics_pipeline, surface_serial); g_free (gfx_frame_info); } static void reduce_tracked_frame_infos (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t max_tracked_frames) { while (g_queue_peek_head (graphics_pipeline->encoded_frames) && g_queue_get_length (graphics_pipeline->encoded_frames) > max_tracked_frames) { gfx_frame_info_free (graphics_pipeline, g_queue_pop_head (graphics_pipeline->encoded_frames)); } } static void enqueue_tracked_frame_info (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t surface_serial, uint32_t frame_id, int64_t enc_time_us) { GfxFrameInfo *gfx_frame_info; g_assert (MAX_TRACKED_ENC_FRAMES > 1); reduce_tracked_frame_infos (graphics_pipeline, MAX_TRACKED_ENC_FRAMES - 1); gfx_frame_info = g_malloc0 (sizeof (GfxFrameInfo)); gfx_frame_info->frame_info.frame_id = frame_id; gfx_frame_info->frame_info.enc_time_us = enc_time_us; gfx_frame_info->surface_serial = surface_serial; g_queue_push_tail (graphics_pipeline->encoded_frames, gfx_frame_info); } #ifdef HAVE_NVENC static gboolean refresh_gfx_surface_avc420 (GrdRdpGraphicsPipeline *graphics_pipeline, HWAccelContext *hwaccel_context, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *src_data, int64_t *enc_time_us) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; GrdRdpGfxSurface *gfx_surface = rdp_surface->gfx_surface; RDPGFX_SURFACE_COMMAND cmd = {0}; RDPGFX_START_FRAME_PDU cmd_start = {0}; RDPGFX_END_FRAME_PDU cmd_end = {0}; RDPGFX_AVC420_BITMAP_STREAM avc420 = {0}; SYSTEMTIME system_time; cairo_rectangle_int_t cairo_rect, region_extents; int n_rects; uint16_t surface_width = rdp_surface->width; uint16_t surface_height = rdp_surface->height; uint16_t aligned_width; uint16_t aligned_height; uint32_t surface_serial; int64_t enc_ack_time_us; int i; if (!rdp_surface->valid) rdp_surface->valid = TRUE; aligned_width = surface_width + (surface_width % 16 ? 16 - surface_width % 16 : 0); aligned_height = surface_height + (surface_height % 64 ? 64 - surface_height % 64 : 0); if (!grd_rdp_nvenc_avc420_encode_bgrx_frame (graphics_pipeline->rdp_nvenc, hwaccel_context->encode_session_id, src_data, surface_width, surface_height, aligned_width, aligned_height, &avc420.data, &avc420.length)) { g_warning ("[RDP.RDPGFX] Failed to encode YUV420 frame"); return FALSE; } GetSystemTime (&system_time); cmd_start.timestamp = system_time.wHour << 22 | system_time.wMinute << 16 | system_time.wSecond << 10 | system_time.wMilliseconds; cmd_start.frameId = get_next_free_frame_id (graphics_pipeline); cmd_end.frameId = cmd_start.frameId; cairo_region_get_extents (region, ®ion_extents); cmd.surfaceId = grd_rdp_gfx_surface_get_surface_id (gfx_surface); cmd.codecId = RDPGFX_CODECID_AVC420; cmd.format = PIXEL_FORMAT_BGRX32; cmd.left = 0; cmd.top = 0; cmd.right = region_extents.x + region_extents.width; cmd.bottom = region_extents.y + region_extents.height; cmd.length = 0; cmd.data = NULL; cmd.extra = &avc420; avc420.meta.numRegionRects = n_rects = cairo_region_num_rectangles (region); avc420.meta.regionRects = g_malloc0 (n_rects * sizeof (RECTANGLE_16)); avc420.meta.quantQualityVals = g_malloc0 (n_rects * sizeof (RDPGFX_H264_QUANT_QUALITY)); for (i = 0; i < n_rects; ++i) { cairo_region_get_rectangle (region, i, &cairo_rect); avc420.meta.regionRects[i].left = cairo_rect.x; avc420.meta.regionRects[i].top = cairo_rect.y; avc420.meta.regionRects[i].right = cairo_rect.x + cairo_rect.width; avc420.meta.regionRects[i].bottom = cairo_rect.y + cairo_rect.height; avc420.meta.quantQualityVals[i].qp = 22; avc420.meta.quantQualityVals[i].p = hwaccel_context->has_first_frame ? 1 : 0; avc420.meta.quantQualityVals[i].qualityVal = 100; } hwaccel_context->has_first_frame = TRUE; g_mutex_lock (&graphics_pipeline->gfx_mutex); enc_ack_time_us = g_get_monotonic_time (); grd_rdp_gfx_surface_unack_frame (gfx_surface, cmd_start.frameId, enc_ack_time_us); surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface); g_hash_table_insert (graphics_pipeline->frame_serial_table, GUINT_TO_POINTER (cmd_start.frameId), GUINT_TO_POINTER (surface_serial)); surface_serial_ref (graphics_pipeline, surface_serial); ++graphics_pipeline->total_frames_encoded; if (graphics_pipeline->frame_acks_suspended) { grd_rdp_gfx_surface_ack_frame (gfx_surface, cmd_start.frameId, enc_ack_time_us); enqueue_tracked_frame_info (graphics_pipeline, surface_serial, cmd_start.frameId, enc_ack_time_us); } g_mutex_unlock (&graphics_pipeline->gfx_mutex); rdpgfx_context->SurfaceFrameCommand (rdpgfx_context, &cmd, &cmd_start, &cmd_end); *enc_time_us = enc_ack_time_us; g_free (avc420.data); g_free (avc420.meta.quantQualityVals); g_free (avc420.meta.regionRects); return TRUE; } #endif /* HAVE_NVENC */ static gboolean rfx_progressive_write_message (RFX_MESSAGE *rfx_message, wStream *s, gboolean needs_progressive_header) { uint32_t block_len; uint32_t *qv; RFX_TILE *rfx_tile; uint32_t tiles_data_size; uint16_t i; if (needs_progressive_header) { /* RFX_PROGRESSIVE_SYNC */ block_len = 12; if (!Stream_EnsureRemainingCapacity (s, block_len)) return FALSE; Stream_Write_UINT16 (s, 0xCCC0); /* blockType */ Stream_Write_UINT32 (s, block_len); /* blockLen */ Stream_Write_UINT32 (s, 0xCACCACCA); /* magic */ Stream_Write_UINT16 (s, 0x0100); /* version */ /* RFX_PROGRESSIVE_CONTEXT */ block_len = 10; if (!Stream_EnsureRemainingCapacity (s, block_len)) return FALSE; Stream_Write_UINT16 (s, 0xCCC3); /* blockType */ Stream_Write_UINT32 (s, block_len); /* blockLen */ Stream_Write_UINT8 (s, 0); /* ctxId */ Stream_Write_UINT16 (s, 0x0040); /* tileSize */ Stream_Write_UINT8 (s, 0); /* flags */ } /* RFX_PROGRESSIVE_FRAME_BEGIN */ block_len = 12; if (!Stream_EnsureRemainingCapacity (s, block_len)) return FALSE; Stream_Write_UINT16 (s, 0xCCC1); /* blockType */ Stream_Write_UINT32 (s, block_len); /* blockLen */ Stream_Write_UINT32 (s, rfx_message->frameIdx); /* frameIndex */ Stream_Write_UINT16 (s, 1); /* regionCount */ /* RFX_PROGRESSIVE_REGION */ block_len = 18; block_len += rfx_message->numRects * 8; block_len += rfx_message->numQuant * 5; tiles_data_size = rfx_message->numTiles * 22; for (i = 0; i < rfx_message->numTiles; i++) { rfx_tile = rfx_message->tiles[i]; tiles_data_size += rfx_tile->YLen + rfx_tile->CbLen + rfx_tile->CrLen; } block_len += tiles_data_size; if (!Stream_EnsureRemainingCapacity (s, block_len)) return FALSE; Stream_Write_UINT16 (s, 0xCCC4); /* blockType */ Stream_Write_UINT32 (s, block_len); /* blockLen */ Stream_Write_UINT8 (s, 0x40); /* tileSize */ Stream_Write_UINT16 (s, rfx_message->numRects); /* numRects */ Stream_Write_UINT8 (s, rfx_message->numQuant); /* numQuant */ Stream_Write_UINT8 (s, 0); /* numProgQuant */ Stream_Write_UINT8 (s, 0); /* flags */ Stream_Write_UINT16 (s, rfx_message->numTiles); /* numTiles */ Stream_Write_UINT32 (s, tiles_data_size); /* tilesDataSize */ for (i = 0; i < rfx_message->numRects; i++) { /* TS_RFX_RECT */ Stream_Write_UINT16 (s, rfx_message->rects[i].x); /* x */ Stream_Write_UINT16 (s, rfx_message->rects[i].y); /* y */ Stream_Write_UINT16 (s, rfx_message->rects[i].width); /* width */ Stream_Write_UINT16 (s, rfx_message->rects[i].height); /* height */ } /* * The RFX_COMPONENT_CODEC_QUANT structure differs from the * TS_RFX_CODEC_QUANT ([MS-RDPRFX] section 2.2.2.1.5) structure with respect * to the order of the bands. * 0 1 2 3 4 5 6 7 8 9 * RDPRFX: LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1 * RDPEGFX: LL3, HL3, LH3, HH3, HL2, LH2, HH2, HL1, LH1, HH1 */ for (i = 0, qv = rfx_message->quantVals; i < rfx_message->numQuant; ++i, qv += 10) { /* RFX_COMPONENT_CODEC_QUANT */ Stream_Write_UINT8 (s, qv[0] + (qv[2] << 4)); /* LL3, HL3 */ Stream_Write_UINT8 (s, qv[1] + (qv[3] << 4)); /* LH3, HH3 */ Stream_Write_UINT8 (s, qv[5] + (qv[4] << 4)); /* HL2, LH2 */ Stream_Write_UINT8 (s, qv[6] + (qv[8] << 4)); /* HH2, HL1 */ Stream_Write_UINT8 (s, qv[7] + (qv[9] << 4)); /* LH1, HH1 */ } for (i = 0; i < rfx_message->numTiles; ++i) { /* RFX_PROGRESSIVE_TILE_SIMPLE */ rfx_tile = rfx_message->tiles[i]; block_len = 22 + rfx_tile->YLen + rfx_tile->CbLen + rfx_tile->CrLen; Stream_Write_UINT16 (s, 0xCCC5); /* blockType */ Stream_Write_UINT32 (s, block_len); /* blockLen */ Stream_Write_UINT8 (s, rfx_tile->quantIdxY); /* quantIdxY */ Stream_Write_UINT8 (s, rfx_tile->quantIdxCb); /* quantIdxCb */ Stream_Write_UINT8 (s, rfx_tile->quantIdxCr); /* quantIdxCr */ Stream_Write_UINT16 (s, rfx_tile->xIdx); /* xIdx */ Stream_Write_UINT16 (s, rfx_tile->yIdx); /* yIdx */ Stream_Write_UINT8 (s, 0); /* flags */ Stream_Write_UINT16 (s, rfx_tile->YLen); /* YLen */ Stream_Write_UINT16 (s, rfx_tile->CbLen); /* CbLen */ Stream_Write_UINT16 (s, rfx_tile->CrLen); /* CrLen */ Stream_Write_UINT16 (s, 0); /* tailLen */ Stream_Write (s, rfx_tile->YData, rfx_tile->YLen); /* YData */ Stream_Write (s, rfx_tile->CbData, rfx_tile->CbLen); /* CbData */ Stream_Write (s, rfx_tile->CrData, rfx_tile->CrLen); /* CrData */ } /* RFX_PROGRESSIVE_FRAME_END */ block_len = 6; if (!Stream_EnsureRemainingCapacity (s, block_len)) return FALSE; Stream_Write_UINT16 (s, 0xCCC2); /* blockType */ Stream_Write_UINT32 (s, block_len); /* blockLen */ return TRUE; } static gboolean refresh_gfx_surface_rfx_progressive (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *src_data, int64_t *enc_time_us) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp; GrdRdpGfxSurface *gfx_surface = rdp_surface->gfx_surface; uint32_t src_stride = grd_session_rdp_get_stride_for_width (session_rdp, rdp_surface->width); RDPGFX_SURFACE_COMMAND cmd = {0}; RDPGFX_START_FRAME_PDU cmd_start = {0}; RDPGFX_END_FRAME_PDU cmd_end = {0}; gboolean needs_progressive_header = FALSE; cairo_rectangle_int_t cairo_rect; RFX_RECT *rfx_rects, *rfx_rect; int n_rects; RFX_MESSAGE *rfx_message; SYSTEMTIME system_time; uint32_t codec_context_id; uint32_t surface_serial; int64_t enc_ack_time_us; int i; graphics_pipeline->rfx_context->mode = RLGR1; if (!rdp_surface->valid) { rfx_context_reset (graphics_pipeline->rfx_context, rdp_surface->width, rdp_surface->height); rdp_surface->valid = TRUE; } codec_context_id = grd_rdp_gfx_surface_get_codec_context_id (gfx_surface); g_mutex_lock (&graphics_pipeline->gfx_mutex); if (!g_hash_table_contains (graphics_pipeline->codec_context_table, GUINT_TO_POINTER (codec_context_id))) needs_progressive_header = TRUE; g_mutex_unlock (&graphics_pipeline->gfx_mutex); n_rects = cairo_region_num_rectangles (region); rfx_rects = g_malloc0 (n_rects * sizeof (RFX_RECT)); for (i = 0; i < n_rects; ++i) { cairo_region_get_rectangle (region, i, &cairo_rect); rfx_rect = &rfx_rects[i]; rfx_rect->x = cairo_rect.x; rfx_rect->y = cairo_rect.y; rfx_rect->width = cairo_rect.width; rfx_rect->height = cairo_rect.height; } rfx_message = rfx_encode_message (graphics_pipeline->rfx_context, rfx_rects, n_rects, src_data, rdp_surface->width, rdp_surface->height, src_stride); g_free (rfx_rects); GetSystemTime (&system_time); cmd_start.timestamp = system_time.wHour << 22 | system_time.wMinute << 16 | system_time.wSecond << 10 | system_time.wMilliseconds; cmd_start.frameId = get_next_free_frame_id (graphics_pipeline); cmd_end.frameId = cmd_start.frameId; cmd.surfaceId = grd_rdp_gfx_surface_get_surface_id (gfx_surface); cmd.codecId = RDPGFX_CODECID_CAPROGRESSIVE; cmd.contextId = codec_context_id; cmd.format = PIXEL_FORMAT_BGRX32; Stream_SetPosition (graphics_pipeline->encode_stream, 0); if (!rfx_progressive_write_message (rfx_message, graphics_pipeline->encode_stream, needs_progressive_header)) { g_warning ("[RDP.RDPGFX] rfx_progressive_write_message() failed"); rfx_message_free (graphics_pipeline->rfx_context, rfx_message); return FALSE; } rfx_message_free (graphics_pipeline->rfx_context, rfx_message); cmd.length = Stream_GetPosition (graphics_pipeline->encode_stream); cmd.data = Stream_Buffer (graphics_pipeline->encode_stream); g_mutex_lock (&graphics_pipeline->gfx_mutex); if (needs_progressive_header) { g_hash_table_insert (graphics_pipeline->codec_context_table, GUINT_TO_POINTER (codec_context_id), gfx_surface); } enc_ack_time_us = g_get_monotonic_time (); grd_rdp_gfx_surface_unack_frame (gfx_surface, cmd_start.frameId, enc_ack_time_us); surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface); g_hash_table_insert (graphics_pipeline->frame_serial_table, GUINT_TO_POINTER (cmd_start.frameId), GUINT_TO_POINTER (surface_serial)); surface_serial_ref (graphics_pipeline, surface_serial); ++graphics_pipeline->total_frames_encoded; if (graphics_pipeline->frame_acks_suspended) { grd_rdp_gfx_surface_ack_frame (gfx_surface, cmd_start.frameId, enc_ack_time_us); enqueue_tracked_frame_info (graphics_pipeline, surface_serial, cmd_start.frameId, enc_ack_time_us); } g_mutex_unlock (&graphics_pipeline->gfx_mutex); rdpgfx_context->SurfaceFrameCommand (rdpgfx_context, &cmd, &cmd_start, &cmd_end); *enc_time_us = enc_ack_time_us; return TRUE; } static uint16_t get_next_free_surface_id (GrdRdpGraphicsPipeline *graphics_pipeline) { uint16_t surface_id = graphics_pipeline->next_surface_id; g_mutex_lock (&graphics_pipeline->gfx_mutex); while (g_hash_table_contains (graphics_pipeline->surface_table, GUINT_TO_POINTER (surface_id))) ++surface_id; g_mutex_unlock (&graphics_pipeline->gfx_mutex); graphics_pipeline->next_surface_id = surface_id + 1; return surface_id; } static uint32_t get_next_free_serial (GrdRdpGraphicsPipeline *graphics_pipeline) { uint32_t serial = graphics_pipeline->next_serial; g_mutex_lock (&graphics_pipeline->gfx_mutex); while (g_hash_table_contains (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (serial))) ++serial; g_mutex_unlock (&graphics_pipeline->gfx_mutex); graphics_pipeline->next_serial = serial + 1; return serial; } static void map_surface_to_output (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpGfxSurface *gfx_surface) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU map_surface_to_output = {0}; GrdRdpSurface *rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface); uint16_t surface_id = grd_rdp_gfx_surface_get_surface_id (gfx_surface); map_surface_to_output.surfaceId = surface_id; map_surface_to_output.outputOriginX = rdp_surface->output_origin_x; map_surface_to_output.outputOriginY = rdp_surface->output_origin_y; rdpgfx_context->MapSurfaceToOutput (rdpgfx_context, &map_surface_to_output); } static void clear_old_enc_times (GrdRdpGraphicsPipeline *graphics_pipeline, int64_t current_time_us) { int64_t *tracked_enc_time_us; while ((tracked_enc_time_us = g_queue_peek_head (graphics_pipeline->enc_times)) && current_time_us - *tracked_enc_time_us >= 1 * G_USEC_PER_SEC) g_free (g_queue_pop_head (graphics_pipeline->enc_times)); } static void track_enc_time (GrdRdpGraphicsPipeline *graphics_pipeline, int64_t enc_time_us) { int64_t *tracked_enc_time_us; tracked_enc_time_us = g_malloc0 (sizeof (int64_t)); *tracked_enc_time_us = enc_time_us; g_queue_push_tail (graphics_pipeline->enc_times, tracked_enc_time_us); } static gboolean maybe_slow_down_rtts (gpointer user_data) { GrdRdpGraphicsPipeline *graphics_pipeline = user_data; g_mutex_lock (&graphics_pipeline->gfx_mutex); clear_old_enc_times (graphics_pipeline, g_get_monotonic_time ()); if (g_queue_get_length (graphics_pipeline->enc_times) == 0) { grd_rdp_network_autodetection_set_rtt_consumer_necessity ( graphics_pipeline->network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX, GRD_RDP_NW_AUTODETECT_RTT_NEC_LOW); g_clear_pointer (&graphics_pipeline->rtt_pause_source, g_source_unref); g_mutex_unlock (&graphics_pipeline->gfx_mutex); return G_SOURCE_REMOVE; } g_mutex_unlock (&graphics_pipeline->gfx_mutex); return G_SOURCE_CONTINUE; } static void ensure_rtt_receivement (GrdRdpGraphicsPipeline *graphics_pipeline) { g_assert (!graphics_pipeline->rtt_pause_source); grd_rdp_network_autodetection_set_rtt_consumer_necessity ( graphics_pipeline->network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX, GRD_RDP_NW_AUTODETECT_RTT_NEC_HIGH); graphics_pipeline->rtt_pause_source = g_timeout_source_new (ENC_TIMES_CHECK_INTERVAL_MS); g_source_set_callback (graphics_pipeline->rtt_pause_source, maybe_slow_down_rtts, graphics_pipeline, NULL); g_source_attach (graphics_pipeline->rtt_pause_source, graphics_pipeline->pipeline_context); } void grd_rdp_graphics_pipeline_refresh_gfx (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *src_data) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; rdpSettings *rdp_settings = rdpgfx_context->rdpcontext->settings; GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp; #ifdef HAVE_NVENC HWAccelContext *hwaccel_context; uint16_t surface_id; #endif /* HAVE_NVENC */ int64_t enc_time_us; gboolean success; g_mutex_lock (&graphics_pipeline->gfx_mutex); if (rdp_settings->NetworkAutoDetect && !graphics_pipeline->rtt_pause_source) ensure_rtt_receivement (graphics_pipeline); g_mutex_unlock (&graphics_pipeline->gfx_mutex); if (!rdp_surface->gfx_surface) rdp_surface->valid = FALSE; if (!rdp_surface->valid) g_clear_object (&rdp_surface->gfx_surface); if (!rdp_surface->gfx_surface) { rdp_surface->gfx_surface = grd_rdp_gfx_surface_new ( graphics_pipeline, session_rdp, graphics_pipeline->pipeline_context, rdp_surface, get_next_free_surface_id (graphics_pipeline), get_next_free_serial (graphics_pipeline)); map_surface_to_output (graphics_pipeline, rdp_surface->gfx_surface); } #ifdef HAVE_NVENC surface_id = grd_rdp_gfx_surface_get_surface_id (rdp_surface->gfx_surface); if (rdp_settings->GfxH264 && g_hash_table_lookup_extended (graphics_pipeline->surface_hwaccel_table, GUINT_TO_POINTER (surface_id), NULL, (gpointer *) &hwaccel_context)) { g_assert (hwaccel_context->api == HW_ACCEL_API_NVENC); success = refresh_gfx_surface_avc420 (graphics_pipeline, hwaccel_context, rdp_surface, region, src_data, &enc_time_us); } else #endif /* HAVE_NVENC */ { success = refresh_gfx_surface_rfx_progressive (graphics_pipeline, rdp_surface, region, src_data, &enc_time_us); } if (success) { g_mutex_lock (&graphics_pipeline->gfx_mutex); clear_old_enc_times (graphics_pipeline, g_get_monotonic_time ()); track_enc_time (graphics_pipeline, enc_time_us); if (rdp_settings->NetworkAutoDetect && !graphics_pipeline->rtt_pause_source) ensure_rtt_receivement (graphics_pipeline); g_mutex_unlock (&graphics_pipeline->gfx_mutex); } } static uint32_t cap_list[] = { RDPGFX_CAPVERSION_106, RDPGFX_CAPVERSION_105, RDPGFX_CAPVERSION_104, RDPGFX_CAPVERSION_103, RDPGFX_CAPVERSION_102, RDPGFX_CAPVERSION_101, RDPGFX_CAPVERSION_10, RDPGFX_CAPVERSION_81, RDPGFX_CAPVERSION_8, }; static gboolean cap_sets_contains_supported_version (RDPGFX_CAPSET *cap_sets, uint16_t n_cap_sets) { size_t i; uint16_t j; for (i = 0; i < G_N_ELEMENTS (cap_list); ++i) { for (j = 0; j < n_cap_sets; ++j) { if (cap_sets[j].version == cap_list[i]) return TRUE; } } g_warning ("[RDP.RDPGFX] Client did not advertise any supported " "capability set"); return FALSE; } static uint32_t rdpgfx_caps_advertise (RdpgfxServerContext *rdpgfx_context, const RDPGFX_CAPS_ADVERTISE_PDU *caps_advertise) { GrdRdpGraphicsPipeline *graphics_pipeline = rdpgfx_context->custom; GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp; g_debug ("[RDP.RDPGFX] Received a CapsAdvertise PDU"); if (graphics_pipeline->initialized && graphics_pipeline->initial_version < RDPGFX_CAPVERSION_103) { g_warning ("[RDP.RDPGFX] Protocol violation: Received an illegal " "CapsAdvertise PDU (RDPGFX: initialized, initial " "version < 103)"); grd_session_rdp_notify_error (graphics_pipeline->session_rdp, ERRINFO_GRAPHICS_SUBSYSTEM_FAILED); return CHANNEL_RC_ALREADY_INITIALIZED; } if (!cap_sets_contains_supported_version (caps_advertise->capsSets, caps_advertise->capsSetCount)) { g_warning ("[RDP.RDPGFX] CapsAdvertise PDU does NOT contain any supported " "capability sets"); grd_session_rdp_notify_error (graphics_pipeline->session_rdp, ERRINFO_GRAPHICS_SUBSYSTEM_FAILED); return CHANNEL_RC_UNSUPPORTED_VERSION; } g_mutex_lock (&graphics_pipeline->caps_mutex); g_clear_pointer (&graphics_pipeline->cap_sets, g_free); graphics_pipeline->n_cap_sets = caps_advertise->capsSetCount; graphics_pipeline->cap_sets = g_memdup2 (caps_advertise->capsSets, graphics_pipeline->n_cap_sets * sizeof (RDPGFX_CAPSET)); g_mutex_unlock (&graphics_pipeline->caps_mutex); grd_session_rdp_notify_graphics_pipeline_reset (session_rdp); g_source_set_ready_time (graphics_pipeline->protocol_reset_source, 0); return CHANNEL_RC_OK; } static uint32_t rdpgfx_cache_import_offer (RdpgfxServerContext *rdpgfx_context, const RDPGFX_CACHE_IMPORT_OFFER_PDU *cache_import_offer) { RDPGFX_CACHE_IMPORT_REPLY_PDU cache_import_reply = {0}; return rdpgfx_context->CacheImportReply (rdpgfx_context, &cache_import_reply); } static void maybe_rewrite_frame_history (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t pending_frame_acks) { GfxFrameInfo *gfx_frame_info; if (g_queue_get_length (graphics_pipeline->encoded_frames) == 0) return; reduce_tracked_frame_infos (graphics_pipeline, pending_frame_acks + 1); while ((gfx_frame_info = g_queue_pop_tail (graphics_pipeline->encoded_frames))) { GrdRdpFrameInfo *frame_info = &gfx_frame_info->frame_info; uint32_t surface_serial = gfx_frame_info->surface_serial; GfxSurfaceContext *surface_context; if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial), NULL, (gpointer *) &surface_context)) g_assert_not_reached (); if (surface_context->gfx_surface) { grd_rdp_gfx_surface_unack_last_acked_frame (surface_context->gfx_surface, frame_info->frame_id, frame_info->enc_time_us); } g_free (gfx_frame_info); } } static void clear_all_unacked_frames_in_gfx_surface (gpointer key, gpointer value, gpointer user_data) { GrdRdpGfxSurface *gfx_surface = value; grd_rdp_gfx_surface_clear_all_unacked_frames (gfx_surface); } static gboolean frame_serial_free (gpointer key, gpointer value, gpointer user_data) { GrdRdpGraphicsPipeline *graphics_pipeline = user_data; uint32_t surface_serial = GPOINTER_TO_UINT (value); surface_serial_unref (graphics_pipeline, surface_serial); return TRUE; } static void suspend_frame_acknowledgement (GrdRdpGraphicsPipeline *graphics_pipeline) { graphics_pipeline->frame_acks_suspended = TRUE; g_hash_table_foreach (graphics_pipeline->surface_table, clear_all_unacked_frames_in_gfx_surface, NULL); reduce_tracked_frame_infos (graphics_pipeline, 0); g_hash_table_foreach_remove (graphics_pipeline->frame_serial_table, frame_serial_free, graphics_pipeline); } static void handle_frame_ack_event (GrdRdpGraphicsPipeline *graphics_pipeline, const RDPGFX_FRAME_ACKNOWLEDGE_PDU *frame_acknowledge) { uint32_t pending_frame_acks; gpointer value = NULL; pending_frame_acks = graphics_pipeline->total_frames_encoded - frame_acknowledge->totalFramesDecoded; if (pending_frame_acks <= MAX_TRACKED_ENC_FRAMES && !g_hash_table_contains (graphics_pipeline->frame_serial_table, GUINT_TO_POINTER (frame_acknowledge->frameId))) return; maybe_rewrite_frame_history (graphics_pipeline, pending_frame_acks); if (frame_acknowledge->queueDepth != SUSPEND_FRAME_ACKNOWLEDGEMENT) graphics_pipeline->frame_acks_suspended = FALSE; if (g_hash_table_steal_extended (graphics_pipeline->frame_serial_table, GUINT_TO_POINTER (frame_acknowledge->frameId), NULL, &value)) { GfxSurfaceContext *surface_context; uint32_t surface_serial; surface_serial = GPOINTER_TO_UINT (value); if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table, GUINT_TO_POINTER (surface_serial), NULL, (gpointer *) &surface_context)) g_assert_not_reached (); if (surface_context->gfx_surface) { grd_rdp_gfx_surface_ack_frame (surface_context->gfx_surface, frame_acknowledge->frameId, g_get_monotonic_time ()); } surface_serial_unref (graphics_pipeline, surface_serial); } if (frame_acknowledge->queueDepth == SUSPEND_FRAME_ACKNOWLEDGEMENT) suspend_frame_acknowledgement (graphics_pipeline); } static uint32_t rdpgfx_frame_acknowledge (RdpgfxServerContext *rdpgfx_context, const RDPGFX_FRAME_ACKNOWLEDGE_PDU *frame_acknowledge) { GrdRdpGraphicsPipeline *graphics_pipeline = rdpgfx_context->custom; g_mutex_lock (&graphics_pipeline->gfx_mutex); handle_frame_ack_event (graphics_pipeline, frame_acknowledge); g_mutex_unlock (&graphics_pipeline->gfx_mutex); return CHANNEL_RC_OK; } static uint32_t rdpgfx_qoe_frame_acknowledge (RdpgfxServerContext *rdpgfx_context, const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU *qoe_frame_acknowledge) { return CHANNEL_RC_OK; } void grd_rdp_graphics_pipeline_maybe_init (GrdRdpGraphicsPipeline *graphics_pipeline) { RdpgfxServerContext *rdpgfx_context; if (!graphics_pipeline) return; if (graphics_pipeline->channel_opened) return; if (WaitForSingleObject (graphics_pipeline->stop_event, 0) == WAIT_OBJECT_0) return; rdpgfx_context = graphics_pipeline->rdpgfx_context; if (!rdpgfx_context->Open (rdpgfx_context)) { g_warning ("[RDP.RDPGFX] Failed to open Graphics Pipeline. The client " "probably falsely advertised GFX support"); grd_session_rdp_notify_error (graphics_pipeline->session_rdp, ERRINFO_GRAPHICS_SUBSYSTEM_FAILED); return; } graphics_pipeline->channel_opened = TRUE; return; } GrdRdpGraphicsPipeline * grd_rdp_graphics_pipeline_new (GrdSessionRdp *session_rdp, GMainContext *pipeline_context, HANDLE vcm, HANDLE stop_event, rdpContext *rdp_context, GrdRdpNetworkAutodetection *network_autodetection, wStream *encode_stream, RFX_CONTEXT *rfx_context) { GrdRdpGraphicsPipeline *graphics_pipeline; RdpgfxServerContext *rdpgfx_context; graphics_pipeline = g_object_new (GRD_TYPE_RDP_GRAPHICS_PIPELINE, NULL); rdpgfx_context = rdpgfx_server_context_new (vcm); if (!rdpgfx_context) g_error ("[RDP.RDPGFX] Failed to create server context"); graphics_pipeline->rdpgfx_context = rdpgfx_context; graphics_pipeline->stop_event = stop_event; graphics_pipeline->session_rdp = session_rdp; graphics_pipeline->pipeline_context = pipeline_context; graphics_pipeline->network_autodetection = network_autodetection; graphics_pipeline->encode_stream = encode_stream; graphics_pipeline->rfx_context = rfx_context; rdpgfx_context->CapsAdvertise = rdpgfx_caps_advertise; rdpgfx_context->CacheImportOffer = rdpgfx_cache_import_offer; rdpgfx_context->FrameAcknowledge = rdpgfx_frame_acknowledge; rdpgfx_context->QoeFrameAcknowledge = rdpgfx_qoe_frame_acknowledge; rdpgfx_context->rdpcontext = rdp_context; rdpgfx_context->custom = graphics_pipeline; if (rdp_context->settings->NetworkAutoDetect && !graphics_pipeline->rtt_pause_source) ensure_rtt_receivement (graphics_pipeline); return graphics_pipeline; } static void reset_graphics_pipeline (GrdRdpGraphicsPipeline *graphics_pipeline) { GList *surfaces; GList *l; g_mutex_lock (&graphics_pipeline->gfx_mutex); surfaces = g_hash_table_get_values (graphics_pipeline->surface_table); g_hash_table_steal_all (graphics_pipeline->surface_table); reduce_tracked_frame_infos (graphics_pipeline, 0); g_hash_table_foreach_remove (graphics_pipeline->frame_serial_table, frame_serial_free, graphics_pipeline); g_mutex_unlock (&graphics_pipeline->gfx_mutex); for (l = surfaces; l; l = l->next) { GrdRdpGfxSurface *gfx_surface = l->data; GrdRdpSurface *rdp_surface; rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface); g_clear_object (&rdp_surface->gfx_surface); } g_list_free (surfaces); g_mutex_lock (&graphics_pipeline->gfx_mutex); graphics_pipeline->frame_acks_suspended = FALSE; graphics_pipeline->total_frames_encoded = 0; g_assert (g_hash_table_size (graphics_pipeline->surface_table) == 0); g_assert (g_hash_table_size (graphics_pipeline->codec_context_table) == 0); g_assert (g_hash_table_size (graphics_pipeline->frame_serial_table) == 0); g_assert (g_hash_table_size (graphics_pipeline->serial_surface_table) == 0); g_assert (g_queue_get_length (graphics_pipeline->encoded_frames) == 0); g_mutex_unlock (&graphics_pipeline->gfx_mutex); } static void grd_rdp_graphics_pipeline_dispose (GObject *object) { GrdRdpGraphicsPipeline *graphics_pipeline = GRD_RDP_GRAPHICS_PIPELINE (object); if (graphics_pipeline->channel_opened) { reset_graphics_pipeline (graphics_pipeline); graphics_pipeline->rdpgfx_context->Close (graphics_pipeline->rdpgfx_context); graphics_pipeline->channel_opened = FALSE; } if (graphics_pipeline->rtt_pause_source) { g_source_destroy (graphics_pipeline->rtt_pause_source); g_clear_pointer (&graphics_pipeline->rtt_pause_source, g_source_unref); } if (graphics_pipeline->protocol_reset_source) { g_source_destroy (graphics_pipeline->protocol_reset_source); g_clear_pointer (&graphics_pipeline->protocol_reset_source, g_source_unref); } if (graphics_pipeline->enc_times) { g_queue_free_full (graphics_pipeline->enc_times, g_free); graphics_pipeline->enc_times = NULL; } if (graphics_pipeline->encoded_frames) { g_assert (g_queue_get_length (graphics_pipeline->encoded_frames) == 0); g_clear_pointer (&graphics_pipeline->encoded_frames, g_queue_free); } g_assert (g_hash_table_size (graphics_pipeline->surface_hwaccel_table) == 0); g_clear_pointer (&graphics_pipeline->surface_hwaccel_table, g_hash_table_destroy); g_clear_pointer (&graphics_pipeline->cap_sets, g_free); g_assert (g_hash_table_size (graphics_pipeline->serial_surface_table) == 0); g_clear_pointer (&graphics_pipeline->serial_surface_table, g_hash_table_destroy); g_clear_pointer (&graphics_pipeline->frame_serial_table, g_hash_table_destroy); g_clear_pointer (&graphics_pipeline->codec_context_table, g_hash_table_destroy); g_clear_pointer (&graphics_pipeline->surface_table, g_hash_table_destroy); g_clear_pointer (&graphics_pipeline->rdpgfx_context, rdpgfx_server_context_free); G_OBJECT_CLASS (grd_rdp_graphics_pipeline_parent_class)->dispose (object); } static void grd_rdp_graphics_pipeline_finalize (GObject *object) { GrdRdpGraphicsPipeline *graphics_pipeline = GRD_RDP_GRAPHICS_PIPELINE (object); g_mutex_clear (&graphics_pipeline->gfx_mutex); G_OBJECT_CLASS (grd_rdp_graphics_pipeline_parent_class)->finalize (object); } static const char * rdpgfx_caps_version_to_string (uint32_t caps_version) { switch (caps_version) { case RDPGFX_CAPVERSION_106: return "RDPGFX_CAPVERSION_106"; case RDPGFX_CAPVERSION_105: return "RDPGFX_CAPVERSION_105"; case RDPGFX_CAPVERSION_104: return "RDPGFX_CAPVERSION_104"; case RDPGFX_CAPVERSION_103: return "RDPGFX_CAPVERSION_103"; case RDPGFX_CAPVERSION_102: return "RDPGFX_CAPVERSION_102"; case RDPGFX_CAPVERSION_101: return "RDPGFX_CAPVERSION_101"; case RDPGFX_CAPVERSION_10: return "RDPGFX_CAPVERSION_10"; case RDPGFX_CAPVERSION_81: return "RDPGFX_CAPVERSION_81"; case RDPGFX_CAPVERSION_8: return "RDPGFX_CAPVERSION_8"; default: g_assert_not_reached (); } return NULL; } static gboolean test_caps_version (GrdRdpGraphicsPipeline *graphics_pipeline, RDPGFX_CAPSET *cap_sets, uint16_t n_cap_sets, uint32_t caps_version) { RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context; rdpSettings *rdp_settings = rdpgfx_context->rdpcontext->settings; RDPGFX_CAPS_CONFIRM_PDU caps_confirm = {0}; uint16_t i; for (i = 0; i < n_cap_sets; ++i) { if (cap_sets[i].version == caps_version) { uint32_t flags = cap_sets[i].flags; switch (caps_version) { case RDPGFX_CAPVERSION_106: case RDPGFX_CAPVERSION_105: case RDPGFX_CAPVERSION_104: case RDPGFX_CAPVERSION_103: case RDPGFX_CAPVERSION_102: case RDPGFX_CAPVERSION_101: case RDPGFX_CAPVERSION_10: rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 = rdp_settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED); break; case RDPGFX_CAPVERSION_81: rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 = FALSE; rdp_settings->GfxH264 = !!(flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED); break; case RDPGFX_CAPVERSION_8: rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 = FALSE; rdp_settings->GfxH264 = FALSE; break; default: g_assert_not_reached (); } g_message ("[RDP.RDPGFX] CapsAdvertise: Accepting capability set with version " "%s, Client cap flags: H264 (AVC444): %s, H264 (AVC420): %s", rdpgfx_caps_version_to_string (caps_version), rdp_settings->GfxAVC444v2 ? "true" : "false", rdp_settings->GfxH264 ? "true" : "false"); if (!graphics_pipeline->initialized) graphics_pipeline->initial_version = caps_version; graphics_pipeline->initialized = TRUE; reset_graphics_pipeline (graphics_pipeline); caps_confirm.capsSet = &cap_sets[i]; rdpgfx_context->CapsConfirm (rdpgfx_context, &caps_confirm); return TRUE; } } return FALSE; } static gboolean reset_protocol (gpointer user_data) { GrdRdpGraphicsPipeline *graphics_pipeline = user_data; GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp; RDPGFX_CAPSET *cap_sets; uint16_t n_cap_sets; size_t i; g_mutex_lock (&graphics_pipeline->caps_mutex); cap_sets = g_steal_pointer (&graphics_pipeline->cap_sets); n_cap_sets = graphics_pipeline->n_cap_sets; g_mutex_unlock (&graphics_pipeline->caps_mutex); if (!cap_sets || !n_cap_sets) { g_assert (graphics_pipeline->initialized); g_free (cap_sets); return G_SOURCE_CONTINUE; } for (i = 0; i < G_N_ELEMENTS (cap_list); ++i) { if (test_caps_version (graphics_pipeline, cap_sets, n_cap_sets, cap_list[i])) { grd_session_rdp_notify_graphics_pipeline_ready (session_rdp); g_free (cap_sets); return G_SOURCE_CONTINUE; } } g_free (cap_sets); /* * CapsAdvertise already checked the capability sets to have at least one * supported version. * It is therefore impossible to hit this path. */ g_assert_not_reached (); return G_SOURCE_CONTINUE; } static gboolean protocol_reset_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs protocol_reset_source_funcs = { .dispatch = protocol_reset_source_dispatch, }; static void grd_rdp_graphics_pipeline_init (GrdRdpGraphicsPipeline *graphics_pipeline) { GSource *protocol_reset_source; graphics_pipeline->surface_table = g_hash_table_new (NULL, NULL); graphics_pipeline->codec_context_table = g_hash_table_new (NULL, NULL); graphics_pipeline->frame_serial_table = g_hash_table_new (NULL, NULL); graphics_pipeline->serial_surface_table = g_hash_table_new_full (NULL, NULL, NULL, g_free); graphics_pipeline->surface_hwaccel_table = g_hash_table_new (NULL, NULL); graphics_pipeline->encoded_frames = g_queue_new (); graphics_pipeline->enc_times = g_queue_new (); g_mutex_init (&graphics_pipeline->gfx_mutex); protocol_reset_source = g_source_new (&protocol_reset_source_funcs, sizeof (GSource)); g_source_set_callback (protocol_reset_source, reset_protocol, graphics_pipeline, NULL); g_source_set_ready_time (protocol_reset_source, -1); g_source_attach (protocol_reset_source, graphics_pipeline->pipeline_context); graphics_pipeline->protocol_reset_source = protocol_reset_source; } static void grd_rdp_graphics_pipeline_class_init (GrdRdpGraphicsPipelineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_graphics_pipeline_dispose; object_class->finalize = grd_rdp_graphics_pipeline_finalize; } 0707010000005A000081A40000000000000000000000016293A07000000DF1000000000000000000000000000000000000003A00000000gnome-remote-desktop-41.3/src/grd-rdp-graphics-pipeline.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_GRAPHICS_PIPELINE_H #define GRD_RDP_GRAPHICS_PIPELINE_H #include <cairo/cairo.h> #include <freerdp/server/rdpgfx.h> #include <glib-object.h> #include "grd-types.h" #define GRD_TYPE_RDP_GRAPHICS_PIPELINE (grd_rdp_graphics_pipeline_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpGraphicsPipeline, grd_rdp_graphics_pipeline, GRD, RDP_GRAPHICS_PIPELINE, GObject); GrdRdpGraphicsPipeline *grd_rdp_graphics_pipeline_new (GrdSessionRdp *session_rdp, GMainContext *pipeline_context, HANDLE vcm, HANDLE stop_event, rdpContext *rdp_context, GrdRdpNetworkAutodetection *network_autodetection, wStream *encode_stream, RFX_CONTEXT *rfx_context); void grd_rdp_graphics_pipeline_maybe_init (GrdRdpGraphicsPipeline *graphics_pipeline); #ifdef HAVE_NVENC void grd_rdp_graphics_pipeline_set_nvenc (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpNvenc *rdp_nvenc); #endif /* HAVE_NVENC */ void grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpGfxSurface *gfx_surface); void grd_rdp_graphics_pipeline_delete_surface (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpGfxSurface *gfx_surface); void grd_rdp_graphics_pipeline_reset_graphics (GrdRdpGraphicsPipeline *graphics_pipeline, uint32_t width, uint32_t height, MONITOR_DEF *monitors, uint32_t n_monitors); void grd_rdp_graphics_pipeline_notify_new_round_trip_time (GrdRdpGraphicsPipeline *graphics_pipeline, uint64_t round_trip_time_us); void grd_rdp_graphics_pipeline_refresh_gfx (GrdRdpGraphicsPipeline *graphics_pipeline, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *src_data); #endif /* GRD_RDP_GRAPHICS_PIPELINE_H */ 0707010000005B000081A40000000000000000000000016293A07000003A01000000000000000000000000000000000000003E00000000gnome-remote-desktop-41.3/src/grd-rdp-network-autodetection.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-network-autodetection.h" #include "grd-rdp-graphics-pipeline.h" #include "grd-rdp-private.h" #define BW_MEASURE_SEQUENCE_NUMBER 0 #define PING_INTERVAL_HIGH_MS 70 #define PING_INTERVAL_LOW_MS 700 #define RTT_AVG_PERIOD_US (500 * 1000) typedef enum _PingInterval { PING_INTERVAL_HIGH, PING_INTERVAL_LOW, } PingInterval; typedef struct _PingInfo { uint16_t sequence_number; int64_t ping_time_us; } PingInfo; typedef struct _RTTInfo { int64_t round_trip_time_us; int64_t response_time_us; } RTTInfo; struct _GrdRdpNetworkAutodetection { GObject parent; rdpContext *rdp_context; rdpAutoDetect *rdp_autodetect; GMutex shutdown_mutex; gboolean in_shutdown; GMutex consumer_mutex; GrdRdpNwAutodetectRTTConsumer rtt_consumers; GrdRdpNwAutodetectRTTConsumer rtt_high_nec_consumers; GMutex sequence_mutex; GHashTable *sequences; GSource *ping_source; GQueue *pings; GQueue *round_trip_times; PingInterval ping_interval; uint16_t next_sequence_number; }; G_DEFINE_TYPE (GrdRdpNetworkAutodetection, grd_rdp_network_autodetection, G_TYPE_OBJECT); void grd_rdp_network_autodetection_invoke_shutdown (GrdRdpNetworkAutodetection *network_autodetection) { g_mutex_lock (&network_autodetection->shutdown_mutex); network_autodetection->in_shutdown = TRUE; g_mutex_unlock (&network_autodetection->shutdown_mutex); } static gboolean has_rtt_consumer (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer) { g_assert (!g_mutex_trylock (&network_autodetection->consumer_mutex)); return !!(network_autodetection->rtt_consumers & rtt_consumer); } static gboolean is_active_high_nec_rtt_consumer (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer) { if (!has_rtt_consumer (network_autodetection, rtt_consumer)) return FALSE; if (network_autodetection->rtt_high_nec_consumers & rtt_consumer) return TRUE; return FALSE; } static uint16_t get_next_free_sequence_number (GrdRdpNetworkAutodetection *network_autodetection) { uint16_t sequence_number = network_autodetection->next_sequence_number; while (sequence_number == BW_MEASURE_SEQUENCE_NUMBER || g_hash_table_contains (network_autodetection->sequences, GUINT_TO_POINTER (sequence_number))) ++sequence_number; network_autodetection->next_sequence_number = sequence_number + 1; return sequence_number; } static gboolean emit_ping (gpointer user_data) { GrdRdpNetworkAutodetection *network_autodetection = user_data; rdpAutoDetect *rdp_autodetect = network_autodetection->rdp_autodetect; PingInfo *ping_info; ping_info = g_malloc0 (sizeof (PingInfo)); g_mutex_lock (&network_autodetection->sequence_mutex); ping_info->sequence_number = get_next_free_sequence_number (network_autodetection); ping_info->ping_time_us = g_get_monotonic_time (); g_hash_table_add (network_autodetection->sequences, GUINT_TO_POINTER (ping_info->sequence_number)); g_queue_push_tail (network_autodetection->pings, ping_info); g_mutex_unlock (&network_autodetection->sequence_mutex); rdp_autodetect->RTTMeasureRequest (network_autodetection->rdp_context, ping_info->sequence_number); return G_SOURCE_CONTINUE; } static void update_ping_source (GrdRdpNetworkAutodetection *network_autodetection) { GrdRdpNwAutodetectRTTConsumer active_high_nec_rtt_consumers; PingInterval new_ping_interval_type; uint32_t ping_interval_ms; active_high_nec_rtt_consumers = GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE; if (is_active_high_nec_rtt_consumer ( network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX)) active_high_nec_rtt_consumers |= GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX; if (active_high_nec_rtt_consumers) new_ping_interval_type = PING_INTERVAL_HIGH; else new_ping_interval_type = PING_INTERVAL_LOW; if ((network_autodetection->rtt_consumers == GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE || network_autodetection->ping_interval != new_ping_interval_type) && network_autodetection->ping_source) { g_source_destroy (network_autodetection->ping_source); g_clear_pointer (&network_autodetection->ping_source, g_source_unref); } if (network_autodetection->rtt_consumers == GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE || network_autodetection->ping_interval == new_ping_interval_type) return; g_assert (!network_autodetection->ping_source); emit_ping (network_autodetection); switch (new_ping_interval_type) { case PING_INTERVAL_HIGH: ping_interval_ms = PING_INTERVAL_HIGH_MS; break; case PING_INTERVAL_LOW: ping_interval_ms = PING_INTERVAL_LOW_MS; break; } network_autodetection->ping_source = g_timeout_source_new (ping_interval_ms); g_source_set_callback (network_autodetection->ping_source, emit_ping, network_autodetection, NULL); g_source_attach (network_autodetection->ping_source, NULL); network_autodetection->ping_interval = new_ping_interval_type; } void grd_rdp_network_autodetection_ensure_rtt_consumer (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer) { g_assert (rtt_consumer != GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE); g_mutex_lock (&network_autodetection->consumer_mutex); if (!has_rtt_consumer (network_autodetection, rtt_consumer)) network_autodetection->rtt_consumers |= rtt_consumer; update_ping_source (network_autodetection); g_mutex_unlock (&network_autodetection->consumer_mutex); } void grd_rdp_network_autodetection_remove_rtt_consumer (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer) { g_mutex_lock (&network_autodetection->consumer_mutex); network_autodetection->rtt_consumers &= ~rtt_consumer; update_ping_source (network_autodetection); g_mutex_unlock (&network_autodetection->consumer_mutex); } void grd_rdp_network_autodetection_set_rtt_consumer_necessity (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer, GrdRdpNwAutodetectRTTNecessity rtt_necessity) { GrdRdpNwAutodetectRTTNecessity current_rtt_necessity; g_assert (rtt_consumer != GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE); g_assert (rtt_necessity == GRD_RDP_NW_AUTODETECT_RTT_NEC_HIGH || rtt_necessity == GRD_RDP_NW_AUTODETECT_RTT_NEC_LOW); g_mutex_lock (&network_autodetection->consumer_mutex); if (network_autodetection->rtt_high_nec_consumers & rtt_consumer) current_rtt_necessity = GRD_RDP_NW_AUTODETECT_RTT_NEC_HIGH; else current_rtt_necessity = GRD_RDP_NW_AUTODETECT_RTT_NEC_LOW; if (current_rtt_necessity == rtt_necessity) { g_mutex_unlock (&network_autodetection->consumer_mutex); return; } switch (rtt_necessity) { case GRD_RDP_NW_AUTODETECT_RTT_NEC_HIGH: network_autodetection->rtt_high_nec_consumers |= rtt_consumer; break; case GRD_RDP_NW_AUTODETECT_RTT_NEC_LOW: network_autodetection->rtt_high_nec_consumers &= ~rtt_consumer; break; } if (has_rtt_consumer (network_autodetection, rtt_consumer)) update_ping_source (network_autodetection); g_mutex_unlock (&network_autodetection->consumer_mutex); } static void track_round_trip_time (GrdRdpNetworkAutodetection *network_autodetection, int64_t ping_time_us, int64_t pong_time_us) { RTTInfo *rtt_info; rtt_info = g_malloc0 (sizeof (RTTInfo)); rtt_info->round_trip_time_us = MIN (pong_time_us - ping_time_us, G_USEC_PER_SEC); rtt_info->response_time_us = pong_time_us; g_queue_push_tail (network_autodetection->round_trip_times, rtt_info); } static void remove_old_round_trip_times (GrdRdpNetworkAutodetection *network_autodetection) { int64_t current_time_us; RTTInfo *rtt_info; current_time_us = g_get_monotonic_time (); while ((rtt_info = g_queue_peek_head (network_autodetection->round_trip_times)) && current_time_us - rtt_info->response_time_us >= RTT_AVG_PERIOD_US) g_free (g_queue_pop_head (network_autodetection->round_trip_times)); } static int64_t get_current_avg_round_trip_time_us (GrdRdpNetworkAutodetection *network_autodetection) { int64_t sum_round_trip_times_us = 0; uint32_t total_round_trip_times; RTTInfo *rtt_info; GQueue *tmp; remove_old_round_trip_times (network_autodetection); if (!g_queue_get_length (network_autodetection->round_trip_times)) return 0; tmp = g_queue_copy (network_autodetection->round_trip_times); total_round_trip_times = g_queue_get_length (tmp); while ((rtt_info = g_queue_pop_head (tmp))) sum_round_trip_times_us += rtt_info->round_trip_time_us; g_queue_free (tmp); return sum_round_trip_times_us / total_round_trip_times; } static BOOL autodetect_rtt_measure_response (rdpContext *rdp_context, uint16_t sequence_number) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context; GrdRdpNetworkAutodetection *network_autodetection; PingInfo *ping_info; int64_t pong_time_us; int64_t avg_round_trip_time_us; gboolean has_rtt_consumer_rdpgfx = FALSE; network_autodetection = rdp_peer_context->network_autodetection; pong_time_us = g_get_monotonic_time (); g_mutex_lock (&network_autodetection->sequence_mutex); if (!g_hash_table_contains (network_autodetection->sequences, GUINT_TO_POINTER (sequence_number))) { g_mutex_unlock (&network_autodetection->sequence_mutex); return TRUE; } while ((ping_info = g_queue_pop_head (network_autodetection->pings)) && ping_info->sequence_number != sequence_number) { g_hash_table_remove (network_autodetection->sequences, GUINT_TO_POINTER (ping_info->sequence_number)); g_clear_pointer (&ping_info, g_free); } if (ping_info) { int64_t ping_time_us = ping_info->ping_time_us; g_assert (ping_info->sequence_number == sequence_number); track_round_trip_time (network_autodetection, ping_time_us, pong_time_us); avg_round_trip_time_us = get_current_avg_round_trip_time_us (network_autodetection); g_hash_table_remove (network_autodetection->sequences, GUINT_TO_POINTER (ping_info->sequence_number)); } g_mutex_unlock (&network_autodetection->sequence_mutex); g_mutex_lock (&network_autodetection->consumer_mutex); has_rtt_consumer_rdpgfx = has_rtt_consumer ( network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX); g_mutex_unlock (&network_autodetection->consumer_mutex); g_mutex_lock (&network_autodetection->shutdown_mutex); if (ping_info && !network_autodetection->in_shutdown && has_rtt_consumer_rdpgfx) { grd_rdp_graphics_pipeline_notify_new_round_trip_time ( rdp_peer_context->graphics_pipeline, avg_round_trip_time_us); } g_mutex_unlock (&network_autodetection->shutdown_mutex); g_free (ping_info); return TRUE; } GrdRdpNetworkAutodetection * grd_rdp_network_autodetection_new (rdpContext *rdp_context) { GrdRdpNetworkAutodetection *network_autodetection; rdpAutoDetect *rdp_autodetect = rdp_context->autodetect; network_autodetection = g_object_new (GRD_TYPE_RDP_NETWORK_AUTODETECTION, NULL); network_autodetection->rdp_context = rdp_context; network_autodetection->rdp_autodetect = rdp_autodetect; rdp_autodetect->RTTMeasureResponse = autodetect_rtt_measure_response; return network_autodetection; } static void grd_rdp_network_autodetection_dispose (GObject *object) { GrdRdpNetworkAutodetection *network_autodetection = GRD_RDP_NETWORK_AUTODETECTION (object); if (network_autodetection->ping_source) { g_source_destroy (network_autodetection->ping_source); g_clear_pointer (&network_autodetection->ping_source, g_source_unref); } if (network_autodetection->round_trip_times) { g_queue_free_full (network_autodetection->round_trip_times, g_free); network_autodetection->round_trip_times = NULL; } if (network_autodetection->pings) { g_queue_free_full (network_autodetection->pings, g_free); network_autodetection->pings = NULL; } g_clear_pointer (&network_autodetection->sequences, g_hash_table_destroy); G_OBJECT_CLASS (grd_rdp_network_autodetection_parent_class)->dispose (object); } static void grd_rdp_network_autodetection_finalize (GObject *object) { GrdRdpNetworkAutodetection *network_autodetection = GRD_RDP_NETWORK_AUTODETECTION (object); g_mutex_clear (&network_autodetection->sequence_mutex); g_mutex_clear (&network_autodetection->consumer_mutex); g_mutex_clear (&network_autodetection->shutdown_mutex); G_OBJECT_CLASS (grd_rdp_network_autodetection_parent_class)->finalize (object); } static void grd_rdp_network_autodetection_init (GrdRdpNetworkAutodetection *network_autodetection) { network_autodetection->sequences = g_hash_table_new (NULL, NULL); network_autodetection->pings = g_queue_new (); network_autodetection->round_trip_times = g_queue_new (); g_mutex_init (&network_autodetection->shutdown_mutex); g_mutex_init (&network_autodetection->consumer_mutex); g_mutex_init (&network_autodetection->sequence_mutex); } static void grd_rdp_network_autodetection_class_init (GrdRdpNetworkAutodetectionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_network_autodetection_dispose; object_class->finalize = grd_rdp_network_autodetection_finalize; } 0707010000005C000081A40000000000000000000000016293A070000009AE000000000000000000000000000000000000003E00000000gnome-remote-desktop-41.3/src/grd-rdp-network-autodetection.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_NETWORK_AUTODETECTION_H #define GRD_RDP_NETWORK_AUTODETECTION_H #include <freerdp/freerdp.h> #include <glib-object.h> #define GRD_TYPE_RDP_NETWORK_AUTODETECTION (grd_rdp_network_autodetection_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpNetworkAutodetection, grd_rdp_network_autodetection, GRD, RDP_NETWORK_AUTODETECTION, GObject); typedef enum _GrdRdpNwAutodetectRTTConsumer { GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_NONE = 0, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX = 1 << 0, } GrdRdpNwAutodetectRTTConsumer; typedef enum _GrdRdpNwAutodetectRTTNecessity { GRD_RDP_NW_AUTODETECT_RTT_NEC_HIGH, GRD_RDP_NW_AUTODETECT_RTT_NEC_LOW, } GrdRdpNwAutodetectRTTNecessity; GrdRdpNetworkAutodetection *grd_rdp_network_autodetection_new (rdpContext *rdp_context); void grd_rdp_network_autodetection_invoke_shutdown (GrdRdpNetworkAutodetection *network_autodetection); void grd_rdp_network_autodetection_ensure_rtt_consumer (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer); void grd_rdp_network_autodetection_remove_rtt_consumer (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer); void grd_rdp_network_autodetection_set_rtt_consumer_necessity (GrdRdpNetworkAutodetection *network_autodetection, GrdRdpNwAutodetectRTTConsumer rtt_consumer, GrdRdpNwAutodetectRTTNecessity rtt_necessity); #endif /* GRD_RDP_NETWORK_AUTODETECTION_H */ 0707010000005D000081A40000000000000000000000016293A07000004D4E000000000000000000000000000000000000002E00000000gnome-remote-desktop-41.3/src/grd-rdp-nvenc.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-nvenc.h" #include <ffnvcodec/dynlink_loader.h> typedef struct _NvEncEncodeSession { void *encoder; uint16_t enc_width; uint16_t enc_height; NV_ENC_OUTPUT_PTR buffer_out; } NvEncEncodeSession; struct _GrdRdpNvenc { GObject parent; CudaFunctions *cuda_funcs; NvencFunctions *nvenc_funcs; NV_ENCODE_API_FUNCTION_LIST nvenc_api; CUdevice cu_device; CUcontext cu_context; gboolean initialized; CUmodule cu_module_avc_utils; CUfunction cu_bgrx_to_yuv420; GHashTable *encode_sessions; uint32_t next_encode_session_id; }; G_DEFINE_TYPE (GrdRdpNvenc, grd_rdp_nvenc, G_TYPE_OBJECT); void grd_rdp_nvenc_push_cuda_context (GrdRdpNvenc *rdp_nvenc) { if (rdp_nvenc->cuda_funcs->cuCtxPushCurrent (rdp_nvenc->cu_context) != CUDA_SUCCESS) g_error ("[HWAccel.CUDA] Failed to push CUDA context"); } void grd_rdp_nvenc_pop_cuda_context (GrdRdpNvenc *rdp_nvenc) { CUcontext cu_context; rdp_nvenc->cuda_funcs->cuCtxPopCurrent (&cu_context); } static uint32_t get_next_free_encode_session_id (GrdRdpNvenc *rdp_nvenc) { uint32_t encode_session_id = rdp_nvenc->next_encode_session_id; while (g_hash_table_contains (rdp_nvenc->encode_sessions, GUINT_TO_POINTER (encode_session_id))) ++encode_session_id; rdp_nvenc->next_encode_session_id = encode_session_id + 1; return encode_session_id; } gboolean grd_rdp_nvenc_create_encode_session (GrdRdpNvenc *rdp_nvenc, uint32_t *encode_session_id, uint16_t surface_width, uint16_t surface_height, uint16_t refresh_rate) { NvEncEncodeSession *encode_session; NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS open_params = {0}; NV_ENC_INITIALIZE_PARAMS init_params = {0}; NV_ENC_CONFIG encode_config = {0}; NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = {0}; uint16_t aligned_width; uint16_t aligned_height; aligned_width = surface_width + (surface_width % 16 ? 16 - surface_width % 16 : 0); aligned_height = surface_height + (surface_height % 64 ? 64 - surface_height % 64 : 0); *encode_session_id = get_next_free_encode_session_id (rdp_nvenc); encode_session = g_malloc0 (sizeof (NvEncEncodeSession)); encode_session->enc_width = aligned_width; encode_session->enc_height = aligned_height; open_params.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER; open_params.deviceType = NV_ENC_DEVICE_TYPE_CUDA; open_params.device = rdp_nvenc->cu_context; open_params.apiVersion = NVENCAPI_VERSION; if (rdp_nvenc->nvenc_api.nvEncOpenEncodeSessionEx ( &open_params, &encode_session->encoder) != NV_ENC_SUCCESS) { g_debug ("[HWAccel.NVENC] Failed to open encode session"); g_free (encode_session); return FALSE; } encode_config.version = NV_ENC_CONFIG_VER; encode_config.profileGUID = NV_ENC_H264_PROFILE_PROGRESSIVE_HIGH_GUID; encode_config.gopLength = NVENC_INFINITE_GOPLENGTH; encode_config.frameIntervalP = 1; encode_config.frameFieldMode = NV_ENC_PARAMS_FRAME_FIELD_MODE_MBAFF; encode_config.mvPrecision = NV_ENC_MV_PRECISION_QUARTER_PEL; encode_config.rcParams.version = NV_ENC_RC_PARAMS_VER; encode_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_VBR; encode_config.rcParams.averageBitRate = 0; encode_config.rcParams.maxBitRate = 0; encode_config.rcParams.targetQuality = 22; encode_config.encodeCodecConfig.h264Config.idrPeriod = NVENC_INFINITE_GOPLENGTH; encode_config.encodeCodecConfig.h264Config.chromaFormatIDC = 1; init_params.version = NV_ENC_INITIALIZE_PARAMS_VER; init_params.encodeGUID = NV_ENC_CODEC_H264_GUID; init_params.encodeWidth = aligned_width; init_params.encodeHeight = aligned_height; init_params.darWidth = surface_width; init_params.darHeight = surface_height; init_params.frameRateNum = refresh_rate; init_params.frameRateDen = 1; init_params.enablePTD = 1; init_params.encodeConfig = &encode_config; if (rdp_nvenc->nvenc_api.nvEncInitializeEncoder ( encode_session->encoder, &init_params) != NV_ENC_SUCCESS) { NV_ENC_PIC_PARAMS pic_params = {0}; g_warning ("[HWAccel.NVENC] Failed to initialize encoder"); pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS; rdp_nvenc->nvenc_api.nvEncEncodePicture (encode_session->encoder, &pic_params); rdp_nvenc->nvenc_api.nvEncDestroyEncoder (encode_session->encoder); g_free (encode_session); return FALSE; } create_bitstream_buffer.version = NV_ENC_CREATE_BITSTREAM_BUFFER_VER; if (rdp_nvenc->nvenc_api.nvEncCreateBitstreamBuffer ( encode_session->encoder, &create_bitstream_buffer) != NV_ENC_SUCCESS) { NV_ENC_PIC_PARAMS pic_params = {0}; g_warning ("[HWAccel.NVENC] Failed to create bitstream buffer"); pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS; rdp_nvenc->nvenc_api.nvEncEncodePicture (encode_session->encoder, &pic_params); rdp_nvenc->nvenc_api.nvEncDestroyEncoder (encode_session->encoder); g_free (encode_session); return FALSE; } encode_session->buffer_out = create_bitstream_buffer.bitstreamBuffer; g_hash_table_insert (rdp_nvenc->encode_sessions, GUINT_TO_POINTER (*encode_session_id), encode_session); return TRUE; } void grd_rdp_nvenc_free_encode_session (GrdRdpNvenc *rdp_nvenc, uint32_t encode_session_id) { NvEncEncodeSession *encode_session; NV_ENC_PIC_PARAMS pic_params = {0}; if (!g_hash_table_steal_extended (rdp_nvenc->encode_sessions, GUINT_TO_POINTER (encode_session_id), NULL, (gpointer *) &encode_session)) return; rdp_nvenc->nvenc_api.nvEncDestroyBitstreamBuffer (encode_session->encoder, encode_session->buffer_out); pic_params.encodePicFlags = NV_ENC_PIC_FLAG_EOS; rdp_nvenc->nvenc_api.nvEncEncodePicture (encode_session->encoder, &pic_params); rdp_nvenc->nvenc_api.nvEncDestroyEncoder (encode_session->encoder); g_free (encode_session); } gboolean grd_rdp_nvenc_avc420_encode_bgrx_frame (GrdRdpNvenc *rdp_nvenc, uint32_t encode_session_id, uint8_t *src_data, uint16_t src_width, uint16_t src_height, uint16_t aligned_width, uint16_t aligned_height, uint8_t **bitstream, uint32_t *bitstream_size) { NvEncEncodeSession *encode_session; CUDA_MEMCPY2D cu_memcpy_2d = {0}; NV_ENC_REGISTER_RESOURCE register_res = {0}; NV_ENC_MAP_INPUT_RESOURCE map_input_res = {0}; NV_ENC_PIC_PARAMS pic_params = {0}; NV_ENC_LOCK_BITSTREAM lock_bitstream = {0}; CUstream cu_stream = NULL; CUdeviceptr bgrx_buffer = 0, nv12_buffer = 0; size_t bgrx_pitch = 0, nv12_pitch = 0; unsigned int grid_dim_x, grid_dim_y, grid_dim_z; unsigned int block_dim_x, block_dim_y, block_dim_z; void *args[8]; if (!g_hash_table_lookup_extended (rdp_nvenc->encode_sessions, GUINT_TO_POINTER (encode_session_id), NULL, (gpointer *) &encode_session)) return FALSE; g_assert (encode_session->enc_width == aligned_width); g_assert (encode_session->enc_height == aligned_height); if (rdp_nvenc->cuda_funcs->cuStreamCreate (&cu_stream, 0) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to create stream"); return FALSE; } if (rdp_nvenc->cuda_funcs->cuMemAllocPitch ( &bgrx_buffer, &bgrx_pitch, src_width * 4, src_height, 4) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to allocate BGRX buffer"); rdp_nvenc->cuda_funcs->cuStreamDestroy (cu_stream); return FALSE; } cu_memcpy_2d.srcMemoryType = CU_MEMORYTYPE_HOST; cu_memcpy_2d.srcHost = src_data; cu_memcpy_2d.srcPitch = src_width * 4; cu_memcpy_2d.dstMemoryType = CU_MEMORYTYPE_DEVICE; cu_memcpy_2d.dstDevice = bgrx_buffer; cu_memcpy_2d.dstPitch = bgrx_pitch; cu_memcpy_2d.WidthInBytes = src_width * 4; cu_memcpy_2d.Height = src_height; if (rdp_nvenc->cuda_funcs->cuMemcpy2DAsync ( &cu_memcpy_2d, cu_stream) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to initiate H2D copy"); rdp_nvenc->cuda_funcs->cuMemFree (bgrx_buffer); rdp_nvenc->cuda_funcs->cuStreamDestroy (cu_stream); return FALSE; } if (rdp_nvenc->cuda_funcs->cuMemAllocPitch ( &nv12_buffer, &nv12_pitch, aligned_width, aligned_height + aligned_height / 2, 4) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to allocate NV12 buffer"); rdp_nvenc->cuda_funcs->cuStreamSynchronize (cu_stream); rdp_nvenc->cuda_funcs->cuMemFree (bgrx_buffer); rdp_nvenc->cuda_funcs->cuStreamDestroy (cu_stream); return FALSE; } /* Threads per blocks */ block_dim_x = 32; block_dim_y = 8; block_dim_z = 1; /* Amount of blocks per grid */ grid_dim_x = aligned_width / 2 / block_dim_x + (aligned_width / 2 % block_dim_x ? 1 : 0); grid_dim_y = aligned_height / 2 / block_dim_y + (aligned_height / 2 % block_dim_y ? 1 : 0); grid_dim_z = 1; args[0] = &nv12_buffer; args[1] = &bgrx_buffer; args[2] = &src_width; args[3] = &src_height; args[4] = &bgrx_pitch; args[5] = &aligned_width; args[6] = &aligned_height; args[7] = &aligned_width; if (rdp_nvenc->cuda_funcs->cuLaunchKernel ( rdp_nvenc->cu_bgrx_to_yuv420, grid_dim_x, grid_dim_y, grid_dim_z, block_dim_x, block_dim_y, block_dim_z, 0, cu_stream, args, NULL) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to launch BGRX_TO_YUV420 kernel"); rdp_nvenc->cuda_funcs->cuStreamSynchronize (cu_stream); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); rdp_nvenc->cuda_funcs->cuMemFree (bgrx_buffer); rdp_nvenc->cuda_funcs->cuStreamDestroy (cu_stream); return FALSE; } if (rdp_nvenc->cuda_funcs->cuStreamSynchronize (cu_stream) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to synchronize stream"); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); rdp_nvenc->cuda_funcs->cuMemFree (bgrx_buffer); rdp_nvenc->cuda_funcs->cuStreamDestroy (cu_stream); return FALSE; } rdp_nvenc->cuda_funcs->cuStreamDestroy (cu_stream); rdp_nvenc->cuda_funcs->cuMemFree (bgrx_buffer); register_res.version = NV_ENC_REGISTER_RESOURCE_VER; register_res.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; register_res.width = aligned_width; register_res.height = aligned_height; register_res.pitch = aligned_width; register_res.resourceToRegister = (void *) nv12_buffer; register_res.bufferFormat = NV_ENC_BUFFER_FORMAT_NV12; register_res.bufferUsage = NV_ENC_INPUT_IMAGE; if (rdp_nvenc->nvenc_api.nvEncRegisterResource ( encode_session->encoder, ®ister_res) != NV_ENC_SUCCESS) { g_warning ("[HWAccel.NVENC] Failed to register resource"); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); return FALSE; } map_input_res.version = NV_ENC_MAP_INPUT_RESOURCE_VER; map_input_res.registeredResource = register_res.registeredResource; if (rdp_nvenc->nvenc_api.nvEncMapInputResource ( encode_session->encoder, &map_input_res) != NV_ENC_SUCCESS) { g_warning ("[HWAccel.NVENC] Failed to map input resource"); rdp_nvenc->nvenc_api.nvEncUnregisterResource (encode_session->encoder, register_res.registeredResource); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); return FALSE; } pic_params.version = NV_ENC_PIC_PARAMS_VER; pic_params.inputWidth = aligned_width; pic_params.inputHeight = aligned_height; pic_params.inputPitch = aligned_width; pic_params.inputBuffer = map_input_res.mappedResource; pic_params.outputBitstream = encode_session->buffer_out; pic_params.bufferFmt = map_input_res.mappedBufferFmt; pic_params.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; if (rdp_nvenc->nvenc_api.nvEncEncodePicture ( encode_session->encoder, &pic_params) != NV_ENC_SUCCESS) { g_warning ("[HWAccel.NVENC] Failed to encode frame"); rdp_nvenc->nvenc_api.nvEncUnmapInputResource (encode_session->encoder, map_input_res.mappedResource); rdp_nvenc->nvenc_api.nvEncUnregisterResource (encode_session->encoder, register_res.registeredResource); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); return FALSE; } lock_bitstream.version = NV_ENC_LOCK_BITSTREAM_VER; lock_bitstream.outputBitstream = encode_session->buffer_out; if (rdp_nvenc->nvenc_api.nvEncLockBitstream ( encode_session->encoder, &lock_bitstream) != NV_ENC_SUCCESS) { g_warning ("[HWAccel.NVENC] Failed to lock bitstream"); rdp_nvenc->nvenc_api.nvEncUnmapInputResource (encode_session->encoder, map_input_res.mappedResource); rdp_nvenc->nvenc_api.nvEncUnregisterResource (encode_session->encoder, register_res.registeredResource); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); return FALSE; } *bitstream_size = lock_bitstream.bitstreamSizeInBytes; *bitstream = g_memdup2 (lock_bitstream.bitstreamBufferPtr, *bitstream_size); rdp_nvenc->nvenc_api.nvEncUnlockBitstream (encode_session->encoder, lock_bitstream.outputBitstream); rdp_nvenc->nvenc_api.nvEncUnmapInputResource (encode_session->encoder, map_input_res.mappedResource); rdp_nvenc->nvenc_api.nvEncUnregisterResource (encode_session->encoder, register_res.registeredResource); rdp_nvenc->cuda_funcs->cuMemFree (nv12_buffer); return TRUE; } GrdRdpNvenc * grd_rdp_nvenc_new (void) { GrdRdpNvenc *rdp_nvenc; gboolean nvenc_device_found = FALSE; CUdevice cu_device = 0; int cu_device_count = 0; g_autofree char *avc_ptx_path = NULL; g_autofree char *avc_ptx_instructions = NULL; g_autoptr (GError) error = NULL; int i; rdp_nvenc = g_object_new (GRD_TYPE_RDP_NVENC, NULL); cuda_load_functions (&rdp_nvenc->cuda_funcs, NULL); nvenc_load_functions (&rdp_nvenc->nvenc_funcs, NULL); if (!rdp_nvenc->cuda_funcs || !rdp_nvenc->nvenc_funcs) { g_debug ("[HWAccel.CUDA] Failed to load CUDA or NVENC library"); g_clear_object (&rdp_nvenc); return NULL; } rdp_nvenc->cuda_funcs->cuInit (0); rdp_nvenc->cuda_funcs->cuDeviceGetCount (&cu_device_count); g_debug ("[HWAccel.CUDA] Found %i CUDA devices", cu_device_count); for (i = 0; i < cu_device_count; ++i) { int cc_major = 0, cc_minor = 0; rdp_nvenc->cuda_funcs->cuDeviceGet (&cu_device, i); rdp_nvenc->cuda_funcs->cuDeviceComputeCapability (&cc_major, &cc_minor, cu_device); g_debug ("[HWAccel.CUDA] Device %i compute capability: [%i, %i]", i, cc_major, cc_minor); if (cc_major >= 3) { g_debug ("[HWAccel.NVENC] Choosing CUDA device with id %i", i); nvenc_device_found = TRUE; break; } } if (!cu_device_count || !nvenc_device_found) { g_debug ("[HWAccel.NVENC] No NVENC capable gpu found"); g_clear_object (&rdp_nvenc); return NULL; } rdp_nvenc->cu_device = cu_device; if (rdp_nvenc->cuda_funcs->cuDevicePrimaryCtxRetain ( &rdp_nvenc->cu_context, rdp_nvenc->cu_device) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to retain CUDA context"); g_clear_object (&rdp_nvenc); return NULL; } rdp_nvenc->nvenc_api.version = NV_ENCODE_API_FUNCTION_LIST_VER; if (rdp_nvenc->nvenc_funcs->NvEncodeAPICreateInstance (&rdp_nvenc->nvenc_api) != NV_ENC_SUCCESS) { g_warning ("[HWAccel.NVENC] Could not create NVENC API instance"); rdp_nvenc->cuda_funcs->cuDevicePrimaryCtxRelease (rdp_nvenc->cu_device); g_clear_object (&rdp_nvenc); return NULL; } if (rdp_nvenc->cuda_funcs->cuCtxPushCurrent (rdp_nvenc->cu_context) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to push CUDA context"); rdp_nvenc->cuda_funcs->cuDevicePrimaryCtxRelease (rdp_nvenc->cu_device); g_clear_object (&rdp_nvenc); return NULL; } rdp_nvenc->initialized = TRUE; avc_ptx_path = g_strdup_printf ("%s/grd-cuda-avc-utils_30.ptx", GRD_DATA_DIR); if (!g_file_get_contents (avc_ptx_path, &avc_ptx_instructions, NULL, &error)) g_error ("[HWAccel.CUDA] Failed to read PTX instructions: %s", error->message); if (rdp_nvenc->cuda_funcs->cuModuleLoadData ( &rdp_nvenc->cu_module_avc_utils, avc_ptx_instructions) != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to load CUDA module"); g_clear_object (&rdp_nvenc); return NULL; } if (rdp_nvenc->cuda_funcs->cuModuleGetFunction ( &rdp_nvenc->cu_bgrx_to_yuv420, rdp_nvenc->cu_module_avc_utils, "convert_2x2_bgrx_area_to_yuv420_nv12") != CUDA_SUCCESS) { g_warning ("[HWAccel.CUDA] Failed to get AVC CUDA kernel"); g_clear_object (&rdp_nvenc); return NULL; } return rdp_nvenc; } static void grd_rdp_nvenc_dispose (GObject *object) { GrdRdpNvenc *rdp_nvenc = GRD_RDP_NVENC (object); if (rdp_nvenc->initialized) { rdp_nvenc->cuda_funcs->cuCtxPopCurrent (&rdp_nvenc->cu_context); rdp_nvenc->cuda_funcs->cuDevicePrimaryCtxRelease (rdp_nvenc->cu_device); rdp_nvenc->initialized = FALSE; } g_clear_pointer (&rdp_nvenc->cu_module_avc_utils, rdp_nvenc->cuda_funcs->cuModuleUnload); nvenc_free_functions (&rdp_nvenc->nvenc_funcs); cuda_free_functions (&rdp_nvenc->cuda_funcs); g_assert (g_hash_table_size (rdp_nvenc->encode_sessions) == 0); g_clear_pointer (&rdp_nvenc->encode_sessions, g_hash_table_destroy); G_OBJECT_CLASS (grd_rdp_nvenc_parent_class)->dispose (object); } static void grd_rdp_nvenc_init (GrdRdpNvenc *rdp_nvenc) { rdp_nvenc->encode_sessions = g_hash_table_new (NULL, NULL); } static void grd_rdp_nvenc_class_init (GrdRdpNvencClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_nvenc_dispose; } 0707010000005E000081A40000000000000000000000016293A07000000969000000000000000000000000000000000000002E00000000gnome-remote-desktop-41.3/src/grd-rdp-nvenc.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_NVENC_H #define GRD_RDP_NVENC_H #include <glib-object.h> #include <stdint.h> #define GRD_TYPE_RDP_NVENC (grd_rdp_nvenc_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpNvenc, grd_rdp_nvenc, GRD, RDP_NVENC, GObject); GrdRdpNvenc *grd_rdp_nvenc_new (void); void grd_rdp_nvenc_push_cuda_context (GrdRdpNvenc *rdp_nvenc); void grd_rdp_nvenc_pop_cuda_context (GrdRdpNvenc *rdp_nvenc); gboolean grd_rdp_nvenc_create_encode_session (GrdRdpNvenc *rdp_nvenc, uint32_t *encode_session_id, uint16_t surface_width, uint16_t surface_height, uint16_t refresh_rate); void grd_rdp_nvenc_free_encode_session (GrdRdpNvenc *rdp_nvenc, uint32_t encode_session_id); gboolean grd_rdp_nvenc_avc420_encode_bgrx_frame (GrdRdpNvenc *rdp_nvenc, uint32_t encode_session_id, uint8_t *src_data, uint16_t src_width, uint16_t src_height, uint16_t aligned_width, uint16_t aligned_height, uint8_t **bitstream, uint32_t *bitstream_size); #endif /* GRD_RDP_NVENC_H */ 0707010000005F000081A40000000000000000000000016293A07000004FEB000000000000000000000000000000000000003800000000gnome-remote-desktop-41.3/src/grd-rdp-pipewire-stream.c/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-pipewire-stream.h" #include <linux/dma-buf.h> #include <pipewire/pipewire.h> #include <spa/param/props.h> #include <spa/param/format-utils.h> #include <spa/param/video/format-utils.h> #include <spa/utils/result.h> #include <sys/mman.h> #include <sys/syscall.h> #include "grd-pipewire-utils.h" enum { CLOSED, N_SIGNALS }; static guint signals[N_SIGNALS]; typedef struct _GrdRdpFrame { void *data; uint16_t width; uint16_t height; gboolean has_pointer_data; uint8_t *pointer_bitmap; uint16_t pointer_hotspot_x; uint16_t pointer_hotspot_y; uint16_t pointer_width; uint16_t pointer_height; gboolean pointer_is_hidden; } GrdRdpFrame; struct _GrdRdpPipeWireStream { GObject parent; GrdSessionRdp *session_rdp; GSource *pipewire_source; struct pw_context *pipewire_context; struct pw_core *pipewire_core; struct spa_hook pipewire_core_listener; GSource *render_source; GMutex frame_mutex; GrdRdpFrame *pending_frame; struct pw_stream *pipewire_stream; struct spa_hook pipewire_stream_listener; uint32_t src_node_id; struct spa_video_info_raw spa_format; }; G_DEFINE_TYPE (GrdRdpPipeWireStream, grd_rdp_pipewire_stream, G_TYPE_OBJECT) static gboolean do_render (gpointer user_data) { GrdRdpPipeWireStream *stream = GRD_RDP_PIPEWIRE_STREAM (user_data); GrdRdpFrame *frame; g_mutex_lock (&stream->frame_mutex); frame = g_steal_pointer (&stream->pending_frame); g_mutex_unlock (&stream->frame_mutex); if (!frame) return G_SOURCE_CONTINUE; if (frame->data) { grd_session_rdp_take_buffer (stream->session_rdp, frame->data, frame->width, frame->height); } if (frame->pointer_bitmap) { grd_session_rdp_update_pointer (stream->session_rdp, frame->pointer_hotspot_x, frame->pointer_hotspot_y, frame->pointer_width, frame->pointer_height, frame->pointer_bitmap); } else if (frame->pointer_is_hidden) { grd_session_rdp_hide_pointer (stream->session_rdp); } g_free (frame); return G_SOURCE_CONTINUE; } static gboolean render_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs render_source_funcs = { .dispatch = render_source_dispatch, }; static void create_render_source (GrdRdpPipeWireStream *stream, GMainContext *render_context) { stream->render_source = g_source_new (&render_source_funcs, sizeof (GSource)); g_source_set_callback (stream->render_source, do_render, stream, NULL); g_source_set_ready_time (stream->render_source, -1); g_source_attach (stream->render_source, render_context); } static gboolean pipewire_loop_source_prepare (GSource *base, int *timeout) { *timeout = -1; return FALSE; } static gboolean pipewire_loop_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) source; int result; result = pw_loop_iterate (pipewire_source->pipewire_loop, 0); if (result < 0) g_warning ("pipewire_loop_iterate failed: %s", spa_strerror (result)); return TRUE; } static void pipewire_loop_source_finalize (GSource *source) { GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) source; pw_loop_leave (pipewire_source->pipewire_loop); pw_loop_destroy (pipewire_source->pipewire_loop); } static GSourceFuncs pipewire_source_funcs = { pipewire_loop_source_prepare, NULL, pipewire_loop_source_dispatch, pipewire_loop_source_finalize }; static GrdPipeWireSource * create_pipewire_source (void) { GrdPipeWireSource *pipewire_source; pipewire_source = (GrdPipeWireSource *) g_source_new (&pipewire_source_funcs, sizeof (GrdPipeWireSource)); pipewire_source->pipewire_loop = pw_loop_new (NULL); if (!pipewire_source->pipewire_loop) { g_source_destroy ((GSource *) pipewire_source); return NULL; } g_source_add_unix_fd (&pipewire_source->base, pw_loop_get_fd (pipewire_source->pipewire_loop), G_IO_IN | G_IO_ERR); pw_loop_enter (pipewire_source->pipewire_loop); g_source_attach (&pipewire_source->base, NULL); return pipewire_source; } static void on_stream_state_changed (void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { g_debug ("PipeWire stream state changed from %s to %s", pw_stream_state_as_string (old), pw_stream_state_as_string (state)); switch (state) { case PW_STREAM_STATE_ERROR: g_warning ("PipeWire stream error: %s", error); break; case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_STREAMING: case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: break; } } static void on_stream_param_changed (void *user_data, uint32_t id, const struct spa_pod *format) { GrdRdpPipeWireStream *stream = GRD_RDP_PIPEWIRE_STREAM (user_data); uint8_t params_buffer[1024]; struct spa_pod_builder pod_builder; const struct spa_pod *params[3]; if (!format || id != SPA_PARAM_Format) return; spa_format_video_raw_parse (format, &stream->spa_format); pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer)); params[0] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int (8, 1, 8), 0); params[1] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int (sizeof (struct spa_meta_header)), 0); params[2] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int (CURSOR_META_SIZE (384, 384), CURSOR_META_SIZE (1, 1), CURSOR_META_SIZE (384, 384)), 0); pw_stream_update_params (stream->pipewire_stream, params, G_N_ELEMENTS (params)); } static GrdRdpFrame * process_buffer (GrdRdpPipeWireStream *stream, struct spa_buffer *buffer) { size_t size; uint8_t *map; void *src_data; struct spa_meta_cursor *spa_meta_cursor; g_autofree GrdRdpFrame *frame = NULL; frame = g_new0 (GrdRdpFrame, 1); if (buffer->datas[0].chunk->size == 0) { map = NULL; src_data = NULL; } else if (buffer->datas[0].type == SPA_DATA_MemFd) { size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset; map = mmap (NULL, size, PROT_READ, MAP_PRIVATE, buffer->datas[0].fd, 0); if (map == MAP_FAILED) { g_warning ("Failed to mmap buffer: %s", g_strerror (errno)); return NULL; } src_data = SPA_MEMBER (map, buffer->datas[0].mapoffset, uint8_t); } else if (buffer->datas[0].type == SPA_DATA_DmaBuf) { int fd; fd = buffer->datas[0].fd; size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset; map = mmap (NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { g_warning ("Failed to mmap DMA buffer: %s", g_strerror (errno)); return NULL; } grd_sync_dma_buf (fd, DMA_BUF_SYNC_START); src_data = SPA_MEMBER (map, buffer->datas[0].mapoffset, uint8_t); } else if (buffer->datas[0].type == SPA_DATA_MemPtr) { size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset; map = NULL; src_data = buffer->datas[0].data; } else { return NULL; } if (src_data) { int src_stride; int dst_stride; int height; int width; int y; height = stream->spa_format.size.height; width = stream->spa_format.size.width; src_stride = buffer->datas[0].chunk->stride; dst_stride = grd_session_rdp_get_stride_for_width (stream->session_rdp, width); frame->data = g_malloc (height * dst_stride); for (y = 0; y < height; ++y) { memcpy (((uint8_t *) frame->data) + y * dst_stride, ((uint8_t *) src_data) + y * src_stride, width * 4); } frame->width = width; frame->height = height; } if (map) { if (buffer->datas[0].type == SPA_DATA_DmaBuf) grd_sync_dma_buf (buffer->datas[0].fd, DMA_BUF_SYNC_END); munmap (map, size); } spa_meta_cursor = spa_buffer_find_meta_data (buffer, SPA_META_Cursor, sizeof *spa_meta_cursor); if (spa_meta_cursor && spa_meta_cursor_is_valid (spa_meta_cursor)) { struct spa_meta_bitmap *spa_meta_bitmap = NULL; GrdPixelFormat format; if (spa_meta_cursor->bitmap_offset) { spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor, spa_meta_cursor->bitmap_offset, struct spa_meta_bitmap); } if (spa_meta_bitmap && spa_meta_bitmap->size.width > 0 && spa_meta_bitmap->size.height > 0 && grd_spa_pixel_format_to_grd_pixel_format (spa_meta_bitmap->format, &format)) { uint8_t *buf; buf = SPA_MEMBER (spa_meta_bitmap, spa_meta_bitmap->offset, uint8_t); frame->pointer_bitmap = g_memdup2 (buf, spa_meta_bitmap->size.height * spa_meta_bitmap->stride); frame->pointer_hotspot_x = spa_meta_cursor->hotspot.x; frame->pointer_hotspot_y = spa_meta_cursor->hotspot.y; frame->pointer_width = spa_meta_bitmap->size.width; frame->pointer_height = spa_meta_bitmap->size.height; frame->has_pointer_data = TRUE; } else if (spa_meta_bitmap) { frame->pointer_is_hidden = TRUE; frame->has_pointer_data = TRUE; } } return g_steal_pointer (&frame); } static void take_frame_data_from (GrdRdpFrame *src_frame, GrdRdpFrame *dst_frame) { dst_frame->data = g_steal_pointer (&src_frame->data); dst_frame->width = src_frame->width; dst_frame->height = src_frame->height; } static void take_pointer_data_from (GrdRdpFrame *src_frame, GrdRdpFrame *dst_frame) { g_assert (!dst_frame->pointer_bitmap); dst_frame->pointer_bitmap = g_steal_pointer (&src_frame->pointer_bitmap); dst_frame->pointer_hotspot_x = src_frame->pointer_hotspot_x; dst_frame->pointer_hotspot_y = src_frame->pointer_hotspot_y; dst_frame->pointer_width = src_frame->pointer_width; dst_frame->pointer_height = src_frame->pointer_height; dst_frame->pointer_is_hidden = src_frame->pointer_is_hidden; dst_frame->has_pointer_data = TRUE; } static void on_stream_process (void *user_data) { GrdRdpPipeWireStream *stream = GRD_RDP_PIPEWIRE_STREAM (user_data); struct pw_buffer *next_buffer; struct pw_buffer *buffer = NULL; GrdRdpFrame *frame; next_buffer = pw_stream_dequeue_buffer (stream->pipewire_stream); while (next_buffer) { buffer = next_buffer; next_buffer = pw_stream_dequeue_buffer (stream->pipewire_stream); if (next_buffer) pw_stream_queue_buffer (stream->pipewire_stream, buffer); } if (!buffer) return; frame = process_buffer (stream, buffer->buffer); g_assert (frame); g_mutex_lock (&stream->frame_mutex); if (stream->pending_frame) { if (!frame->data && stream->pending_frame->data) take_frame_data_from (stream->pending_frame, frame); if (!frame->has_pointer_data && stream->pending_frame->has_pointer_data) take_pointer_data_from (stream->pending_frame, frame); g_free (stream->pending_frame->data); g_free (stream->pending_frame->pointer_bitmap); g_clear_pointer (&stream->pending_frame, g_free); } stream->pending_frame = frame; g_mutex_unlock (&stream->frame_mutex); pw_stream_queue_buffer (stream->pipewire_stream, buffer); g_source_set_ready_time (stream->render_source, 0); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .state_changed = on_stream_state_changed, .param_changed = on_stream_param_changed, .process = on_stream_process, }; static gboolean connect_to_stream (GrdRdpPipeWireStream *stream, uint32_t refresh_rate, GError **error) { struct pw_stream *pipewire_stream; uint8_t params_buffer[1024]; struct spa_pod_builder pod_builder; struct spa_rectangle min_rect; struct spa_rectangle max_rect; struct spa_fraction min_framerate; struct spa_fraction max_framerate; const struct spa_pod *params[2]; int ret; pipewire_stream = pw_stream_new (stream->pipewire_core, "grd-rdp-pipewire-stream", NULL); min_rect = SPA_RECTANGLE (1, 1); max_rect = SPA_RECTANGLE (INT32_MAX, INT32_MAX); min_framerate = SPA_FRACTION (1, 1); max_framerate = SPA_FRACTION (refresh_rate, 1); pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer)); params[0] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id (SPA_VIDEO_FORMAT_BGRx), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle (&min_rect, &min_rect, &max_rect), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction (&SPA_FRACTION (0, 1)), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction (&min_framerate, &min_framerate, &max_framerate), 0); stream->pipewire_stream = pipewire_stream; pw_stream_add_listener (pipewire_stream, &stream->pipewire_stream_listener, &stream_events, stream); ret = pw_stream_connect (stream->pipewire_stream, PW_DIRECTION_INPUT, stream->src_node_id, (PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_AUTOCONNECT), params, 1); if (ret < 0) { g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (-ret), strerror (-ret)); return FALSE; } return TRUE; } static void on_core_error (void *user_data, uint32_t id, int seq, int res, const char *message) { GrdRdpPipeWireStream *stream = GRD_RDP_PIPEWIRE_STREAM (user_data); g_warning ("PipeWire core error: id:%u %s", id, message); if (id == PW_ID_CORE && res == -EPIPE) g_signal_emit (stream, signals[CLOSED], 0); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; GrdRdpPipeWireStream * grd_rdp_pipewire_stream_new (GrdSessionRdp *session_rdp, GMainContext *render_context, uint32_t src_node_id, uint32_t refresh_rate, GError **error) { g_autoptr (GrdRdpPipeWireStream) stream = NULL; GrdPipeWireSource *pipewire_source; grd_maybe_initialize_pipewire (); stream = g_object_new (GRD_TYPE_RDP_PIPEWIRE_STREAM, NULL); stream->session_rdp = session_rdp; stream->src_node_id = src_node_id; create_render_source (stream, render_context); pipewire_source = create_pipewire_source (); if (!pipewire_source) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create PipeWire source"); return NULL; } stream->pipewire_source = (GSource *) pipewire_source; stream->pipewire_context = pw_context_new (pipewire_source->pipewire_loop, NULL, 0); if (!stream->pipewire_context) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create PipeWire context"); return NULL; } stream->pipewire_core = pw_context_connect (stream->pipewire_context, NULL, 0); if (!stream->pipewire_core) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to connect PipeWire context"); return NULL; } pw_core_add_listener (stream->pipewire_core, &stream->pipewire_core_listener, &core_events, stream); if (!connect_to_stream (stream, refresh_rate, error)) return NULL; return g_steal_pointer (&stream); } static void grd_rdp_pipewire_stream_finalize (GObject *object) { GrdRdpPipeWireStream *stream = GRD_RDP_PIPEWIRE_STREAM (object); /* * We can't clear stream->pipewire_stream before destroying it, as the data * thread in PipeWire might access the variable during destruction. */ if (stream->pipewire_stream) pw_stream_destroy (stream->pipewire_stream); g_clear_pointer (&stream->pipewire_core, pw_core_disconnect); g_clear_pointer (&stream->pipewire_context, pw_context_destroy); if (stream->pipewire_source) { g_source_destroy (stream->pipewire_source); g_clear_pointer (&stream->pipewire_source, g_source_unref); } if (stream->render_source) { g_source_destroy (stream->render_source); g_clear_pointer (&stream->render_source, g_source_unref); } if (stream->pending_frame) { g_free (stream->pending_frame->data); g_free (stream->pending_frame->pointer_bitmap); g_clear_pointer (&stream->pending_frame, g_free); } g_mutex_clear (&stream->frame_mutex); G_OBJECT_CLASS (grd_rdp_pipewire_stream_parent_class)->finalize (object); } static void grd_rdp_pipewire_stream_init (GrdRdpPipeWireStream *stream) { g_mutex_init (&stream->frame_mutex); } static void grd_rdp_pipewire_stream_class_init (GrdRdpPipeWireStreamClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = grd_rdp_pipewire_stream_finalize; signals[CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } 07070100000060000081A40000000000000000000000016293A07000000622000000000000000000000000000000000000003800000000gnome-remote-desktop-41.3/src/grd-rdp-pipewire-stream.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_PIPEWIRE_STREAM_H #define GRD_RDP_PIPEWIRE_STREAM_H #include <glib-object.h> #include <stdint.h> #include "grd-session-rdp.h" #define GRD_TYPE_RDP_PIPEWIRE_STREAM grd_rdp_pipewire_stream_get_type () G_DECLARE_FINAL_TYPE (GrdRdpPipeWireStream, grd_rdp_pipewire_stream, GRD, RDP_PIPEWIRE_STREAM, GObject) GrdRdpPipeWireStream *grd_rdp_pipewire_stream_new (GrdSessionRdp *session_rdp, GMainContext *render_context, uint32_t src_node_id, uint32_t refresh_rate, GError **error); #endif /* GRD_RDP_PIPEWIRE_STREAM_H */ 07070100000061000081A40000000000000000000000016293A0700000050E000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-rdp-private.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_PRIVATE_H #define GRD_RDP_PRIVATE_H #include <freerdp/freerdp.h> #include "grd-types.h" typedef struct _RdpPeerContext { rdpContext rdp_context; GrdSessionRdp *session_rdp; uint32_t frame_id; uint16_t planar_flags; RFX_CONTEXT *rfx_context; wStream *encode_stream; GrdRdpNetworkAutodetection *network_autodetection; /* Virtual Channel Manager */ HANDLE vcm; GrdClipboardRdp *clipboard_rdp; GrdRdpGraphicsPipeline *graphics_pipeline; } RdpPeerContext; #endif /* GRD_RDP_PRIVATE_H */ 07070100000062000081A40000000000000000000000016293A07000000B7B000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-rdp-sam.c/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-sam.h" #include <fcntl.h> #include <gio/gio.h> #include <glib/gstdio.h> #include <stdint.h> #include <sys/stat.h> #include <winpr/ntlm.h> static char * create_sam_string (const char *username, const char *password) { uint32_t username_length; uint32_t password_length; uint32_t i; char *sam_string; uint8_t nt_hash[16]; username_length = strlen (username); password_length = strlen (password); sam_string = g_malloc0 ((username_length + 3 + 32 + 3 + 1 + 1) * sizeof (char)); NTOWFv1A ((LPSTR) password, password_length, nt_hash); sprintf (sam_string, "%s:::", username); for (i = 0; i < 16; ++i) sprintf (sam_string + strlen (sam_string), "%02" PRIx8 "", nt_hash[i]); sprintf (sam_string + strlen (sam_string), ":::\n"); return sam_string; } GrdRdpSAMFile * grd_rdp_sam_create_sam_file (const char *username, const char *password) { const char *grd_path = "/gnome-remote-desktop"; const char *template = "/rdp-sam-XXXXXX"; const char *path; char *filename; char *sam_string; int fd_flags; GrdRdpSAMFile *rdp_sam_file; FILE *sam_file; rdp_sam_file = g_malloc0 (sizeof (GrdRdpSAMFile)); path = getenv ("XDG_RUNTIME_DIR"); filename = g_malloc0 (strlen (path) + strlen (grd_path) + strlen (template) + 1); strcpy (filename, path); strcat (filename, grd_path); if (g_access (filename, F_OK)) mkdir (filename, 0700); strcat (filename, template); rdp_sam_file->fd = mkstemp (filename); rdp_sam_file->filename = filename; fd_flags = fcntl (rdp_sam_file->fd, F_GETFD); fcntl (rdp_sam_file->fd, F_SETFD, fd_flags | FD_CLOEXEC); sam_string = create_sam_string (username, password); sam_file = fdopen (rdp_sam_file->fd, "w+"); fputs (sam_string, sam_file); fclose (sam_file); g_free (sam_string); return rdp_sam_file; } void grd_rdp_sam_maybe_close_and_free_sam_file (GrdRdpSAMFile *rdp_sam_file) { if (rdp_sam_file->fd >= 0) { unlink (rdp_sam_file->filename); close (rdp_sam_file->fd); } g_free (rdp_sam_file->filename); g_free (rdp_sam_file); } 07070100000063000081A40000000000000000000000016293A07000000469000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-rdp-sam.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_SAM_H #define GRD_RDP_SAM_H #include "grd-types.h" struct _GrdRdpSAMFile { int fd; char *filename; }; GrdRdpSAMFile *grd_rdp_sam_create_sam_file (const char *username, const char *password); void grd_rdp_sam_maybe_close_and_free_sam_file (GrdRdpSAMFile *rdp_sam_file); #endif /* GRD_RDP_SAM_H */ 07070100000064000081A40000000000000000000000016293A07000001D67000000000000000000000000000000000000002F00000000gnome-remote-desktop-41.3/src/grd-rdp-server.c/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-server.h" #include <freerdp/channels/channels.h> #include <freerdp/freerdp.h> #include <freerdp/primitives.h> #include <gio/gio.h> #include <winpr/ssl.h> #include "grd-context.h" #include "grd-rdp-nvenc.h" #include "grd-session-rdp.h" enum { PROP_0, PROP_CONTEXT, }; struct _GrdRdpServer { GSocketService parent; GList *sessions; GList *stopped_sessions; guint idle_task; GrdContext *context; #ifdef HAVE_NVENC GrdRdpNvenc *rdp_nvenc; #endif /* HAVE_NVENC */ }; G_DEFINE_TYPE (GrdRdpServer, grd_rdp_server, G_TYPE_SOCKET_SERVICE); GrdContext * grd_rdp_server_get_context (GrdRdpServer *rdp_server) { return rdp_server->context; } GrdRdpServer * grd_rdp_server_new (GrdContext *context) { GrdRdpServer *rdp_server; rdp_server = g_object_new (GRD_TYPE_RDP_SERVER, "context", context, NULL); return rdp_server; } static void grd_rdp_server_cleanup_stopped_sessions (GrdRdpServer *rdp_server) { g_list_free_full (rdp_server->stopped_sessions, g_object_unref); rdp_server->stopped_sessions = NULL; } static gboolean cleanup_stopped_sessions_idle (GrdRdpServer *rdp_server) { grd_rdp_server_cleanup_stopped_sessions (rdp_server); rdp_server->idle_task = 0; return G_SOURCE_REMOVE; } static void on_session_stopped (GrdSession *session, GrdRdpServer *rdp_server) { g_debug ("RDP session stopped"); rdp_server->stopped_sessions = g_list_append (rdp_server->stopped_sessions, session); rdp_server->sessions = g_list_remove (rdp_server->sessions, session); if (!rdp_server->idle_task) { rdp_server->idle_task = g_idle_add ((GSourceFunc) cleanup_stopped_sessions_idle, rdp_server); } } static gboolean on_incoming (GSocketService *service, GSocketConnection *connection) { GrdRdpServer *rdp_server = GRD_RDP_SERVER (service); GrdSessionRdp *session_rdp; g_debug ("New incoming RDP connection"); if (!(session_rdp = grd_session_rdp_new (rdp_server, connection, #ifdef HAVE_NVENC rdp_server->rdp_nvenc, #endif /* HAVE_NVENC */ 0))) return TRUE; rdp_server->sessions = g_list_append (rdp_server->sessions, session_rdp); grd_context_add_session (rdp_server->context, GRD_SESSION (session_rdp)); g_signal_connect (session_rdp, "stopped", G_CALLBACK (on_session_stopped), rdp_server); return TRUE; } gboolean grd_rdp_server_start (GrdRdpServer *rdp_server, GError **error) { GrdSettings *settings = grd_context_get_settings (rdp_server->context); if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (rdp_server), grd_settings_get_rdp_port (settings), NULL, error)) return FALSE; g_signal_connect (rdp_server, "incoming", G_CALLBACK (on_incoming), NULL); return TRUE; } static void stop_and_unref_session (GrdSession *session) { grd_session_stop (session); g_object_unref (session); } static void grd_rdp_server_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GrdRdpServer *rdp_server = GRD_RDP_SERVER (object); switch (prop_id) { case PROP_CONTEXT: rdp_server->context = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void grd_rdp_server_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GrdRdpServer *rdp_server = GRD_RDP_SERVER (object); switch (prop_id) { case PROP_CONTEXT: g_value_set_object (value, rdp_server->context); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void grd_rdp_server_dispose (GObject *object) { GrdRdpServer *rdp_server = GRD_RDP_SERVER (object); #ifdef HAVE_NVENC g_clear_object (&rdp_server->rdp_nvenc); #endif /* HAVE_NVENC */ if (rdp_server->idle_task) { g_source_remove (rdp_server->idle_task); rdp_server->idle_task = 0; } if (rdp_server->stopped_sessions) { grd_rdp_server_cleanup_stopped_sessions (rdp_server); } if (rdp_server->sessions) { g_list_free_full (rdp_server->sessions, (GDestroyNotify) stop_and_unref_session); rdp_server->sessions = NULL; } G_OBJECT_CLASS (grd_rdp_server_parent_class)->dispose (object); } static void grd_rdp_server_constructed (GObject *object) { G_OBJECT_CLASS (grd_rdp_server_parent_class)->constructed (object); } static void grd_rdp_server_init (GrdRdpServer *rdp_server) { winpr_InitializeSSL (WINPR_SSL_INIT_DEFAULT); WTSRegisterWtsApiFunctionTable (FreeRDP_InitWtsApi ()); /* * Run the primitives benchmark here to save time, when initializing a session */ primitives_get (); #ifdef HAVE_NVENC rdp_server->rdp_nvenc = grd_rdp_nvenc_new (); if (rdp_server->rdp_nvenc) { g_debug ("[RDP] Initialization of NVENC was successful"); } else { g_message ("[RDP] Initialization of NVENC failed. " "No hardware acceleration available"); } #else g_message ("[RDP] RDP backend is built WITHOUT support for NVENC and CUDA. " "No hardware acceleration available"); #endif /* HAVE_NVENC */ } static void grd_rdp_server_class_init (GrdRdpServerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = grd_rdp_server_set_property; object_class->get_property = grd_rdp_server_get_property; object_class->dispose = grd_rdp_server_dispose; object_class->constructed = grd_rdp_server_constructed; g_object_class_install_property (object_class, PROP_CONTEXT, g_param_spec_object ("context", "GrdContext", "The GrdContext instance", GRD_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } 07070100000065000081A40000000000000000000000016293A07000000554000000000000000000000000000000000000002F00000000gnome-remote-desktop-41.3/src/grd-rdp-server.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_SERVER_H #define GRD_RDP_SERVER_H #include <gio/gio.h> #include <glib-object.h> #include "grd-types.h" #define GRD_TYPE_RDP_SERVER (grd_rdp_server_get_type ()) G_DECLARE_FINAL_TYPE (GrdRdpServer, grd_rdp_server, GRD, RDP_SERVER, GSocketService); GrdContext *grd_rdp_server_get_context (GrdRdpServer *rdp_server); gboolean grd_rdp_server_start (GrdRdpServer *rdp_server, GError **error); GrdRdpServer *grd_rdp_server_new (GrdContext *context); #endif /* GRD_RDP_SERVER_H */ 07070100000066000081A40000000000000000000000016293A0700000041A000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-rdp-surface.c/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-rdp-surface.h" void grd_rdp_surface_free (GrdRdpSurface *rdp_surface) { g_assert (!rdp_surface->gfx_surface); g_clear_pointer (&rdp_surface->last_frame, g_free); g_clear_pointer (&rdp_surface->pending_frame, g_free); g_free (rdp_surface); } 07070100000067000081A40000000000000000000000016293A070000004E3000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-rdp-surface.h/* * Copyright (C) 2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_RDP_SURFACE_H #define GRD_RDP_SURFACE_H #include <gio/gio.h> #include <stdint.h> #include "grd-types.h" struct _GrdRdpSurface { uint16_t output_origin_x; uint16_t output_origin_y; uint16_t width; uint16_t height; uint8_t *last_frame; uint8_t *pending_frame; gboolean valid; GrdRdpGfxSurface *gfx_surface; uint16_t refresh_rate; gboolean encoding_suspended; }; void grd_rdp_surface_free (GrdRdpSurface *rdp_surface); #endif /* GRD_RDP_SURFACE_H */ 07070100000068000081A40000000000000000000000016293A07000012404000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-session-rdp.c/* * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "grd-session-rdp.h" #include <freerdp/channels/wtsvc.h> #include <freerdp/freerdp.h> #include <freerdp/peer.h> #include <gio/gio.h> #include <linux/input-event-codes.h> #include <xkbcommon/xkbcommon.h> #include "grd-clipboard-rdp.h" #include "grd-context.h" #include "grd-damage-utils.h" #include "grd-rdp-event-queue.h" #include "grd-rdp-graphics-pipeline.h" #include "grd-rdp-network-autodetection.h" #include "grd-rdp-pipewire-stream.h" #include "grd-rdp-private.h" #include "grd-rdp-sam.h" #include "grd-rdp-server.h" #include "grd-rdp-surface.h" #include "grd-settings.h" #include "grd-stream.h" #ifdef HAVE_NVENC #include "grd-rdp-nvenc.h" #endif /* HAVE_NVENC */ #define DISCRETE_SCROLL_STEP 10.0 typedef enum _RdpPeerFlag { RDP_PEER_ACTIVATED = 1 << 0, RDP_PEER_OUTPUT_ENABLED = 1 << 1, RDP_PEER_ALL_SURFACES_INVALID = 1 << 2, RDP_PEER_PENDING_GFX_INIT = 1 << 3, RDP_PEER_PENDING_GFX_GRAPHICS_RESET = 1 << 4, } RdpPeerFlag; typedef enum _PointerType { POINTER_TYPE_DEFAULT = 0, POINTER_TYPE_HIDDEN = 1 << 0, POINTER_TYPE_NORMAL = 1 << 1, } PointerType; typedef enum _PauseKeyState { PAUSE_KEY_STATE_NONE, PAUSE_KEY_STATE_CTRL_DOWN, PAUSE_KEY_STATE_NUMLOCK_DOWN, PAUSE_KEY_STATE_CTRL_UP, } PauseKeyState; typedef struct _Pointer { uint8_t *bitmap; uint16_t hotspot_x; uint16_t hotspot_y; uint16_t width; uint16_t height; uint16_t cache_index; int64_t last_used; } Pointer; typedef struct _NSCThreadPoolContext { uint32_t pending_job_count; GCond *pending_jobs_cond; GMutex *pending_jobs_mutex; uint32_t src_stride; uint8_t *src_data; rdpSettings *rdp_settings; } NSCThreadPoolContext; typedef struct _NSCEncodeContext { cairo_rectangle_int_t cairo_rect; wStream *stream; } NSCEncodeContext; typedef struct _RawThreadPoolContext { uint32_t pending_job_count; GCond *pending_jobs_cond; GMutex *pending_jobs_mutex; uint16_t planar_flags; uint32_t src_stride; uint8_t *src_data; } RawThreadPoolContext; struct _GrdSessionRdp { GrdSession parent; GSocketConnection *connection; freerdp_peer *peer; GrdRdpSAMFile *sam_file; uint32_t rdp_error_info; GMutex rdp_flags_mutex; RdpPeerFlag rdp_flags; GThread *socket_thread; HANDLE start_event; HANDLE stop_event; GThread *graphics_thread; GMainContext *graphics_context; GrdRdpSurface *rdp_surface; Pointer *last_pointer; GHashTable *pointer_cache; PointerType pointer_type; GHashTable *pressed_keys; GHashTable *pressed_unicode_keys; PauseKeyState pause_key_state; GrdRdpEventQueue *rdp_event_queue; GThreadPool *thread_pool; GCond pending_jobs_cond; GMutex pending_jobs_mutex; NSCThreadPoolContext nsc_thread_pool_context; RawThreadPoolContext raw_thread_pool_context; #ifdef HAVE_NVENC GrdRdpNvenc *rdp_nvenc; #endif /* HAVE_NVENC */ GSource *pending_encode_source; GMutex close_session_mutex; unsigned int close_session_idle_id; GrdRdpPipeWireStream *pipewire_stream; }; G_DEFINE_TYPE (GrdSessionRdp, grd_session_rdp, GRD_TYPE_SESSION); static gboolean close_session_idle (gpointer user_data); static void rdp_peer_refresh_region (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *data); static gboolean are_pointer_bitmaps_equal (gconstpointer a, gconstpointer b); static gboolean is_rdp_peer_flag_set (GrdSessionRdp *session_rdp, RdpPeerFlag flag) { gboolean state; g_mutex_lock (&session_rdp->rdp_flags_mutex); state = !!(session_rdp->rdp_flags & flag); g_mutex_unlock (&session_rdp->rdp_flags_mutex); return state; } static void set_rdp_peer_flag (GrdSessionRdp *session_rdp, RdpPeerFlag flag) { g_mutex_lock (&session_rdp->rdp_flags_mutex); session_rdp->rdp_flags |= flag; g_mutex_unlock (&session_rdp->rdp_flags_mutex); } static void unset_rdp_peer_flag (GrdSessionRdp *session_rdp, RdpPeerFlag flag) { g_mutex_lock (&session_rdp->rdp_flags_mutex); session_rdp->rdp_flags &= ~flag; g_mutex_unlock (&session_rdp->rdp_flags_mutex); } void grd_session_rdp_notify_graphics_pipeline_reset (GrdSessionRdp *session_rdp) { set_rdp_peer_flag (session_rdp, RDP_PEER_PENDING_GFX_INIT); } void grd_session_rdp_notify_graphics_pipeline_ready (GrdSessionRdp *session_rdp) { set_rdp_peer_flag (session_rdp, RDP_PEER_ALL_SURFACES_INVALID); set_rdp_peer_flag (session_rdp, RDP_PEER_PENDING_GFX_GRAPHICS_RESET); unset_rdp_peer_flag (session_rdp, RDP_PEER_PENDING_GFX_INIT); g_source_set_ready_time (session_rdp->pending_encode_source, 0); } static void maybe_resize_graphics_output_buffer (GrdSessionRdp *session_rdp, uint32_t width, uint32_t height) { freerdp_peer *peer = session_rdp->peer; rdpSettings *rdp_settings = peer->settings; if (rdp_settings->DesktopWidth == width && rdp_settings->DesktopHeight == height) return; rdp_settings->DesktopWidth = width; rdp_settings->DesktopHeight = height; if (rdp_settings->SupportGraphicsPipeline) set_rdp_peer_flag (session_rdp, RDP_PEER_PENDING_GFX_GRAPHICS_RESET); else peer->update->DesktopResize (peer->context); set_rdp_peer_flag (session_rdp, RDP_PEER_ALL_SURFACES_INVALID); } void grd_session_rdp_take_buffer (GrdSessionRdp *session_rdp, void *data, uint16_t width, uint16_t height) { GrdRdpSurface *rdp_surface = session_rdp->rdp_surface; uint32_t stride; cairo_region_t *region; g_clear_pointer (&rdp_surface->pending_frame, g_free); maybe_resize_graphics_output_buffer (session_rdp, width, height); if (is_rdp_peer_flag_set (session_rdp, RDP_PEER_ALL_SURFACES_INVALID)) { rdp_surface->valid = FALSE; unset_rdp_peer_flag (session_rdp, RDP_PEER_ALL_SURFACES_INVALID); } if (rdp_surface->width != width || rdp_surface->height != height) { rdp_surface->output_origin_x = rdp_surface->output_origin_y = 0; rdp_surface->width = width; rdp_surface->height = height; rdp_surface->valid = FALSE; } if (!rdp_surface->valid) g_clear_pointer (&rdp_surface->last_frame, g_free); if (is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) && is_rdp_peer_flag_set (session_rdp, RDP_PEER_OUTPUT_ENABLED) && !is_rdp_peer_flag_set (session_rdp, RDP_PEER_PENDING_GFX_INIT) && !rdp_surface->encoding_suspended) { stride = grd_session_rdp_get_stride_for_width (session_rdp, rdp_surface->width); region = grd_get_damage_region ((uint8_t *) data, rdp_surface->last_frame, rdp_surface->width, rdp_surface->height, 64, 64, stride, 4); if (!cairo_region_is_empty (region)) rdp_peer_refresh_region (session_rdp, rdp_surface, region, (uint8_t *) data); g_clear_pointer (&rdp_surface->last_frame, g_free); rdp_surface->last_frame = data; cairo_region_destroy (region); } else { rdp_surface->pending_frame = data; } } void grd_session_rdp_maybe_encode_pending_frame (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface) { g_assert (session_rdp->peer); if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) || !is_rdp_peer_flag_set (session_rdp, RDP_PEER_OUTPUT_ENABLED) || is_rdp_peer_flag_set (session_rdp, RDP_PEER_PENDING_GFX_INIT)) return; if (!rdp_surface->pending_frame) return; grd_session_rdp_take_buffer (session_rdp, g_steal_pointer (&rdp_surface->pending_frame), rdp_surface->width, rdp_surface->height); } static gboolean is_mouse_pointer_hidden (uint32_t width, uint32_t height, uint8_t *data) { uint8_t *src_data; uint32_t i; for (i = 0, src_data = data; i < height * width; ++i, ++src_data) { src_data += 3; if (*src_data) return FALSE; } return TRUE; } static gboolean find_equal_pointer_bitmap (gpointer key, gpointer value, gpointer user_data) { return are_pointer_bitmaps_equal (value, user_data); } void grd_session_rdp_update_pointer (GrdSessionRdp *session_rdp, uint16_t hotspot_x, uint16_t hotspot_y, uint16_t width, uint16_t height, uint8_t *data) { freerdp_peer *peer = session_rdp->peer; rdpSettings *rdp_settings = peer->settings; rdpUpdate *rdp_update = peer->update; POINTER_SYSTEM_UPDATE pointer_system = {0}; POINTER_NEW_UPDATE pointer_new = {0}; POINTER_LARGE_UPDATE pointer_large = {0}; POINTER_CACHED_UPDATE pointer_cached = {0}; POINTER_COLOR_UPDATE *pointer_color; cairo_rectangle_int_t cairo_rect; Pointer *new_pointer; void *key, *value; uint32_t stride; uint32_t xor_mask_length; uint8_t *xor_mask; uint8_t *src_data, *dst_data; uint8_t r, g, b, a; uint32_t x, y; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED)) { g_free (data); return; } if (is_mouse_pointer_hidden (width, height, data)) { if (session_rdp->pointer_type != POINTER_TYPE_HIDDEN) { session_rdp->last_pointer = NULL; session_rdp->pointer_type = POINTER_TYPE_HIDDEN; pointer_system.type = SYSPTR_NULL; rdp_update->pointer->PointerSystem (peer->context, &pointer_system); } g_free (data); return; } /* RDP only handles pointer bitmaps up to 384x384 pixels */ if (width > 384 || height > 384) { if (session_rdp->pointer_type != POINTER_TYPE_DEFAULT) { session_rdp->last_pointer = NULL; session_rdp->pointer_type = POINTER_TYPE_DEFAULT; pointer_system.type = SYSPTR_DEFAULT; rdp_update->pointer->PointerSystem (peer->context, &pointer_system); } g_free (data); return; } session_rdp->pointer_type = POINTER_TYPE_NORMAL; stride = width * 4; if (session_rdp->last_pointer && session_rdp->last_pointer->hotspot_x == hotspot_x && session_rdp->last_pointer->hotspot_y == hotspot_y && session_rdp->last_pointer->width == width && session_rdp->last_pointer->height == height) { cairo_rect.x = cairo_rect.y = 0; cairo_rect.width = width; cairo_rect.height = height; if (!grd_is_tile_dirty (&cairo_rect, data, session_rdp->last_pointer->bitmap, stride, 4)) { session_rdp->last_pointer->last_used = g_get_monotonic_time (); g_free (data); return; } } new_pointer = g_malloc0 (sizeof (Pointer)); new_pointer->bitmap = data; new_pointer->hotspot_x = hotspot_x; new_pointer->hotspot_y = hotspot_y; new_pointer->width = width; new_pointer->height = height; if ((value = g_hash_table_find (session_rdp->pointer_cache, find_equal_pointer_bitmap, new_pointer))) { session_rdp->last_pointer = (Pointer *) value; session_rdp->last_pointer->last_used = g_get_monotonic_time (); pointer_cached.cacheIndex = session_rdp->last_pointer->cache_index; rdp_update->pointer->PointerCached (peer->context, &pointer_cached); g_free (new_pointer); g_free (data); return; } xor_mask_length = height * stride * sizeof (uint8_t); xor_mask = g_malloc0 (xor_mask_length); for (y = 0; y < height; ++y) { src_data = &data[stride * (height - 1 - y)]; dst_data = &xor_mask[stride * y]; for (x = 0; x < width; ++x) { r = *src_data++; g = *src_data++; b = *src_data++; a = *src_data++; *dst_data++ = b; *dst_data++ = g; *dst_data++ = r; *dst_data++ = a; } } new_pointer->cache_index = g_hash_table_size (session_rdp->pointer_cache); if (g_hash_table_size (session_rdp->pointer_cache) >= rdp_settings->PointerCacheSize) { /* Least recently used pointer */ Pointer *lru_pointer = NULL; GHashTableIter iter; g_hash_table_iter_init (&iter, session_rdp->pointer_cache); while (g_hash_table_iter_next (&iter, &key, &value)) { if (!lru_pointer || lru_pointer->last_used > ((Pointer *) key)->last_used) lru_pointer = (Pointer *) key; } g_hash_table_steal (session_rdp->pointer_cache, lru_pointer); new_pointer->cache_index = lru_pointer->cache_index; g_free (lru_pointer->bitmap); g_free (lru_pointer); } new_pointer->last_used = g_get_monotonic_time (); if (width <= 96 && height <= 96) { pointer_new.xorBpp = 32; pointer_color = &pointer_new.colorPtrAttr; pointer_color->cacheIndex = new_pointer->cache_index; /* xPos and yPos actually represent the hotspot coordinates of the pointer * instead of the actual pointer position. * FreeRDP just uses a confusing naming convention here. * See also 2.2.9.1.1.4.4 Color Pointer Update (TS_COLORPOINTERATTRIBUTE) * for reference */ pointer_color->xPos = hotspot_x; pointer_color->yPos = hotspot_y; pointer_color->width = width; pointer_color->height = height; pointer_color->lengthAndMask = 0; pointer_color->lengthXorMask = xor_mask_length; pointer_color->andMaskData = NULL; pointer_color->xorMaskData = xor_mask; pointer_cached.cacheIndex = pointer_color->cacheIndex; rdp_update->pointer->PointerNew (peer->context, &pointer_new); } else { pointer_large.xorBpp = 32; pointer_large.cacheIndex = new_pointer->cache_index; pointer_large.hotSpotX = hotspot_x; pointer_large.hotSpotY = hotspot_y; pointer_large.width = width; pointer_large.height = height; pointer_large.lengthAndMask = 0; pointer_large.lengthXorMask = xor_mask_length; pointer_large.andMaskData = NULL; pointer_large.xorMaskData = xor_mask; pointer_cached.cacheIndex = pointer_large.cacheIndex; rdp_update->pointer->PointerLarge (peer->context, &pointer_large); } rdp_update->pointer->PointerCached (peer->context, &pointer_cached); g_hash_table_add (session_rdp->pointer_cache, new_pointer); session_rdp->last_pointer = new_pointer; g_free (xor_mask); } void grd_session_rdp_hide_pointer (GrdSessionRdp *session_rdp) { freerdp_peer *peer = session_rdp->peer; rdpUpdate *rdp_update = peer->update; POINTER_SYSTEM_UPDATE pointer_system = {0}; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED)) return; if (session_rdp->pointer_type == POINTER_TYPE_HIDDEN) return; session_rdp->last_pointer = NULL; session_rdp->pointer_type = POINTER_TYPE_HIDDEN; pointer_system.type = SYSPTR_NULL; rdp_update->pointer->PointerSystem (peer->context, &pointer_system); } static void maybe_queue_close_session_idle (GrdSessionRdp *session_rdp) { g_mutex_lock (&session_rdp->close_session_mutex); if (session_rdp->close_session_idle_id) { g_mutex_unlock (&session_rdp->close_session_mutex); return; } session_rdp->close_session_idle_id = g_idle_add (close_session_idle, session_rdp); g_mutex_unlock (&session_rdp->close_session_mutex); SetEvent (session_rdp->stop_event); } void grd_session_rdp_notify_error (GrdSessionRdp *session_rdp, uint32_t error_info) { session_rdp->rdp_error_info = error_info; unset_rdp_peer_flag (session_rdp, RDP_PEER_ACTIVATED); maybe_queue_close_session_idle (session_rdp); } static void handle_client_gone (GrdSessionRdp *session_rdp) { g_debug ("RDP client gone"); unset_rdp_peer_flag (session_rdp, RDP_PEER_ACTIVATED); maybe_queue_close_session_idle (session_rdp); } static gboolean is_view_only (GrdSessionRdp *session_rdp) { GrdContext *context = grd_session_get_context (GRD_SESSION (session_rdp)); GrdSettings *settings = grd_context_get_settings (context); return grd_settings_get_rdp_view_only (settings); } static void get_current_monitor_config (GrdSessionRdp *session_rdp, MONITOR_DEF **monitors, uint32_t *n_monitors) { GrdRdpSurface *rdp_surface = session_rdp->rdp_surface; *n_monitors = 1; *monitors = g_new0 (MONITOR_DEF, *n_monitors); (*monitors)[0].left = rdp_surface->output_origin_x; (*monitors)[0].top = rdp_surface->output_origin_y; (*monitors)[0].right = rdp_surface->output_origin_x + rdp_surface->width - 1; (*monitors)[0].bottom = rdp_surface->output_origin_y + rdp_surface->height - 1; (*monitors)[0].flags = MONITOR_PRIMARY; } static void rdp_peer_refresh_gfx (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *data) { freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; rdpSettings *rdp_settings = peer->settings; GrdRdpGraphicsPipeline *graphics_pipeline = rdp_peer_context->graphics_pipeline; if (is_rdp_peer_flag_set (session_rdp, RDP_PEER_PENDING_GFX_GRAPHICS_RESET)) { MONITOR_DEF *monitors; uint32_t n_monitors; get_current_monitor_config (session_rdp, &monitors, &n_monitors); grd_rdp_graphics_pipeline_reset_graphics (graphics_pipeline, rdp_settings->DesktopWidth, rdp_settings->DesktopHeight, monitors, n_monitors); g_free (monitors); unset_rdp_peer_flag (session_rdp, RDP_PEER_PENDING_GFX_GRAPHICS_RESET); } grd_rdp_graphics_pipeline_refresh_gfx (graphics_pipeline, rdp_surface, region, data); } static void rdp_peer_refresh_rfx (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *data) { freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; rdpSettings *rdp_settings = peer->settings; rdpUpdate *rdp_update = peer->update; uint32_t src_stride = grd_session_rdp_get_stride_for_width (session_rdp, rdp_surface->width); SURFACE_BITS_COMMAND cmd = {0}; cairo_rectangle_int_t cairo_rect; RFX_RECT *rfx_rects, *rfx_rect; int n_rects; RFX_MESSAGE *rfx_messages; size_t n_messages; BOOL first, last; size_t i; rdp_peer_context->rfx_context->mode = RLGR3; if (!rdp_surface->valid) { rfx_context_reset (rdp_peer_context->rfx_context, rdp_surface->width, rdp_surface->height); rdp_surface->valid = TRUE; } n_rects = cairo_region_num_rectangles (region); rfx_rects = g_malloc0 (n_rects * sizeof (RFX_RECT)); for (i = 0; i < n_rects; ++i) { cairo_region_get_rectangle (region, i, &cairo_rect); rfx_rect = &rfx_rects[i]; rfx_rect->x = cairo_rect.x; rfx_rect->y = cairo_rect.y; rfx_rect->width = cairo_rect.width; rfx_rect->height = cairo_rect.height; } rfx_messages = rfx_encode_messages_ex (rdp_peer_context->rfx_context, rfx_rects, n_rects, data, rdp_surface->width, rdp_surface->height, src_stride, &n_messages, rdp_settings->MultifragMaxRequestSize); cmd.cmdType = CMDTYPE_STREAM_SURFACE_BITS; cmd.bmp.codecID = rdp_settings->RemoteFxCodecId; cmd.destLeft = rdp_surface->output_origin_x; cmd.destTop = rdp_surface->output_origin_y; cmd.destRight = cmd.destLeft + rdp_surface->width; cmd.destBottom = cmd.destTop + rdp_surface->height; cmd.bmp.bpp = 32; cmd.bmp.flags = 0; cmd.bmp.width = rdp_surface->width; cmd.bmp.height = rdp_surface->height; for (i = 0; i < n_messages; ++i) { Stream_SetPosition (rdp_peer_context->encode_stream, 0); if (!rfx_write_message (rdp_peer_context->rfx_context, rdp_peer_context->encode_stream, &rfx_messages[i])) { g_warning ("rfx_write_message failed"); for (; i < n_messages; ++i) rfx_message_free (rdp_peer_context->rfx_context, &rfx_messages[i]); break; } rfx_message_free (rdp_peer_context->rfx_context, &rfx_messages[i]); cmd.bmp.bitmapDataLength = Stream_GetPosition (rdp_peer_context->encode_stream); cmd.bmp.bitmapData = Stream_Buffer (rdp_peer_context->encode_stream); first = i == 0 ? TRUE : FALSE; last = i + 1 == n_messages ? TRUE : FALSE; if (!rdp_settings->SurfaceFrameMarkerEnabled) rdp_update->SurfaceBits (rdp_update->context, &cmd); else rdp_update->SurfaceFrameBits (rdp_update->context, &cmd, first, last, rdp_peer_context->frame_id); } g_free (rfx_messages); g_free (rfx_rects); } static void rdp_peer_encode_nsc_rect (gpointer data, gpointer user_data) { NSCThreadPoolContext *thread_pool_context = (NSCThreadPoolContext *) user_data; NSCEncodeContext *encode_context = (NSCEncodeContext *) data; cairo_rectangle_int_t *cairo_rect = &encode_context->cairo_rect; uint32_t src_stride = thread_pool_context->src_stride; uint8_t *src_data = thread_pool_context->src_data; rdpSettings *rdp_settings = thread_pool_context->rdp_settings; NSC_CONTEXT *nsc_context; uint8_t *src_data_at_pos; nsc_context = nsc_context_new (); nsc_context_set_parameters (nsc_context, NSC_COLOR_LOSS_LEVEL, rdp_settings->NSCodecColorLossLevel); nsc_context_set_parameters (nsc_context, NSC_ALLOW_SUBSAMPLING, rdp_settings->NSCodecAllowSubsampling); nsc_context_set_parameters (nsc_context, NSC_DYNAMIC_COLOR_FIDELITY, rdp_settings->NSCodecAllowDynamicColorFidelity); nsc_context_set_parameters (nsc_context, NSC_COLOR_FORMAT, PIXEL_FORMAT_BGRX32); nsc_context_reset (nsc_context, cairo_rect->width, cairo_rect->height); encode_context->stream = Stream_New (NULL, 64 * 64 * 4); src_data_at_pos = &src_data[cairo_rect->y * src_stride + cairo_rect->x * 4]; Stream_SetPosition (encode_context->stream, 0); nsc_compose_message (nsc_context, encode_context->stream, src_data_at_pos, cairo_rect->width, cairo_rect->height, src_stride); nsc_context_free (nsc_context); g_mutex_lock (thread_pool_context->pending_jobs_mutex); --thread_pool_context->pending_job_count; if (!thread_pool_context->pending_job_count) g_cond_signal (thread_pool_context->pending_jobs_cond); g_mutex_unlock (thread_pool_context->pending_jobs_mutex); } static void rdp_peer_refresh_nsc (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *data) { freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; rdpSettings *rdp_settings = peer->settings; rdpUpdate *rdp_update = peer->update; uint32_t src_stride = grd_session_rdp_get_stride_for_width (session_rdp, rdp_surface->width); NSCThreadPoolContext *thread_pool_context = &session_rdp->nsc_thread_pool_context; g_autoptr (GError) error = NULL; cairo_rectangle_int_t *cairo_rect; int n_rects; NSCEncodeContext *encode_contexts; NSCEncodeContext *encode_context; SURFACE_BITS_COMMAND cmd = {0}; BOOL first, last; int i; rdp_surface->valid = TRUE; n_rects = cairo_region_num_rectangles (region); encode_contexts = g_malloc0 (n_rects * sizeof (NSCEncodeContext)); thread_pool_context->pending_job_count = 0; thread_pool_context->pending_jobs_cond = &session_rdp->pending_jobs_cond; thread_pool_context->pending_jobs_mutex = &session_rdp->pending_jobs_mutex; thread_pool_context->src_stride = src_stride; thread_pool_context->src_data = data; thread_pool_context->rdp_settings = rdp_settings; if (!session_rdp->thread_pool) session_rdp->thread_pool = g_thread_pool_new (rdp_peer_encode_nsc_rect, thread_pool_context, g_get_num_processors (), TRUE, &error); if (!session_rdp->thread_pool) { g_free (encode_contexts); g_error ("Couldn't create thread pool: %s", error->message); } g_mutex_lock (&session_rdp->pending_jobs_mutex); for (i = 0; i < n_rects; ++i) { encode_context = &encode_contexts[i]; cairo_region_get_rectangle (region, i, &encode_context->cairo_rect); ++thread_pool_context->pending_job_count; g_thread_pool_push (session_rdp->thread_pool, encode_context, NULL); } while (thread_pool_context->pending_job_count) { g_cond_wait (&session_rdp->pending_jobs_cond, &session_rdp->pending_jobs_mutex); } g_mutex_unlock (&session_rdp->pending_jobs_mutex); cmd.cmdType = CMDTYPE_SET_SURFACE_BITS; cmd.bmp.bpp = 32; cmd.bmp.codecID = rdp_settings->NSCodecId; for (i = 0; i < n_rects; ++i) { encode_context = &encode_contexts[i]; cairo_rect = &encode_context->cairo_rect; cmd.destLeft = rdp_surface->output_origin_x + cairo_rect->x; cmd.destTop = rdp_surface->output_origin_y + cairo_rect->y; cmd.destRight = cmd.destLeft + cairo_rect->width; cmd.destBottom = cmd.destTop + cairo_rect->height; cmd.bmp.width = cairo_rect->width; cmd.bmp.height = cairo_rect->height; cmd.bmp.bitmapDataLength = Stream_GetPosition (encode_context->stream); cmd.bmp.bitmapData = Stream_Buffer (encode_context->stream); first = i == 0 ? TRUE : FALSE; last = i + 1 == n_rects ? TRUE : FALSE; if (!rdp_settings->SurfaceFrameMarkerEnabled) rdp_update->SurfaceBits (rdp_update->context, &cmd); else rdp_update->SurfaceFrameBits (rdp_update->context, &cmd, first, last, rdp_peer_context->frame_id); Stream_Free (encode_context->stream, TRUE); } g_free (encode_contexts); } static void rdp_peer_compress_raw_tile (gpointer data, gpointer user_data) { RawThreadPoolContext *thread_pool_context = (RawThreadPoolContext *) user_data; BITMAP_DATA *dst_bitmap = (BITMAP_DATA *) data; uint16_t planar_flags = thread_pool_context->planar_flags; uint32_t dst_bpp = dst_bitmap->bitsPerPixel; uint32_t dst_Bpp = (dst_bpp + 7) / 8; BITMAP_PLANAR_CONTEXT *planar_context; BITMAP_INTERLEAVED_CONTEXT *interleaved_context; uint32_t src_stride; uint32_t src_data_pos; uint8_t *src_data; uint8_t *src_data_at_pos; dst_bitmap->bitmapLength = 64 * 64 * 4 * sizeof (uint8_t); dst_bitmap->bitmapDataStream = g_malloc0 (dst_bitmap->bitmapLength); src_stride = thread_pool_context->src_stride; src_data_pos = dst_bitmap->destTop * src_stride + dst_bitmap->destLeft * dst_Bpp; src_data = thread_pool_context->src_data; src_data_at_pos = &src_data[src_data_pos]; switch (dst_bpp) { case 32: planar_context = freerdp_bitmap_planar_context_new (planar_flags, 64, 64); freerdp_bitmap_planar_context_reset (planar_context, 64, 64); dst_bitmap->bitmapDataStream = freerdp_bitmap_compress_planar (planar_context, src_data_at_pos, PIXEL_FORMAT_BGRX32, dst_bitmap->width, dst_bitmap->height, src_stride, dst_bitmap->bitmapDataStream, &dst_bitmap->bitmapLength); freerdp_bitmap_planar_context_free (planar_context); break; case 24: case 16: case 15: interleaved_context = bitmap_interleaved_context_new (TRUE); bitmap_interleaved_context_reset (interleaved_context); interleaved_compress (interleaved_context, dst_bitmap->bitmapDataStream, &dst_bitmap->bitmapLength, dst_bitmap->width, dst_bitmap->height, src_data, PIXEL_FORMAT_BGRX32, src_stride, dst_bitmap->destLeft, dst_bitmap->destTop, NULL, dst_bpp); bitmap_interleaved_context_free (interleaved_context); break; } dst_bitmap->cbScanWidth = dst_bitmap->width * dst_Bpp; dst_bitmap->cbUncompressedSize = dst_bitmap->width * dst_bitmap->height * dst_Bpp; dst_bitmap->cbCompFirstRowSize = 0; dst_bitmap->cbCompMainBodySize = dst_bitmap->bitmapLength; dst_bitmap->compressed = TRUE; dst_bitmap->flags = 0; g_mutex_lock (thread_pool_context->pending_jobs_mutex); --thread_pool_context->pending_job_count; if (!thread_pool_context->pending_job_count) g_cond_signal (thread_pool_context->pending_jobs_cond); g_mutex_unlock (thread_pool_context->pending_jobs_mutex); } static void rdp_peer_refresh_raw_rect (freerdp_peer *peer, GrdRdpSurface *rdp_surface, GThreadPool *thread_pool, uint32_t *pending_job_count, BITMAP_DATA *bitmap_data, uint32_t *n_bitmaps, cairo_rectangle_int_t *cairo_rect, uint8_t *data) { rdpSettings *rdp_settings = peer->settings; uint32_t dst_bits_per_pixel = rdp_settings->ColorDepth; uint32_t cols, rows; uint32_t x, y; BITMAP_DATA *bitmap; cols = cairo_rect->width / 64 + (cairo_rect->width % 64 ? 1 : 0); rows = cairo_rect->height / 64 + (cairo_rect->height % 64 ? 1 : 0); /* Both x- and y-position of each bitmap need to be a multiple of 4 */ if (cairo_rect->x % 4) { cairo_rect->width += cairo_rect->x % 4; cairo_rect->x -= cairo_rect->x % 4; } if (cairo_rect->y % 4) { cairo_rect->height += cairo_rect->y % 4; cairo_rect->y -= cairo_rect->y % 4; } /* Both width and height of each bitmap need to be a multiple of 4 */ if (cairo_rect->width % 4) cairo_rect->width += 4 - cairo_rect->width % 4; if (cairo_rect->height % 4) cairo_rect->height += 4 - cairo_rect->height % 4; for (y = 0; y < rows; ++y) { for (x = 0; x < cols; ++x) { bitmap = &bitmap_data[(*n_bitmaps)++]; bitmap->width = 64; bitmap->height = 64; bitmap->destLeft = rdp_surface->output_origin_x + cairo_rect->x + x * 64; bitmap->destTop = rdp_surface->output_origin_y + cairo_rect->y + y * 64; if (bitmap->destLeft + bitmap->width > cairo_rect->x + cairo_rect->width) bitmap->width = cairo_rect->x + cairo_rect->width - bitmap->destLeft; if (bitmap->destTop + bitmap->height > cairo_rect->y + cairo_rect->height) bitmap->height = cairo_rect->y + cairo_rect->height - bitmap->destTop; bitmap->destRight = bitmap->destLeft + bitmap->width - 1; bitmap->destBottom = bitmap->destTop + bitmap->height - 1; bitmap->bitsPerPixel = dst_bits_per_pixel; /* Both width and height of each bitmap need to be a multiple of 4 */ if (bitmap->width < 4 || bitmap->height < 4) continue; ++*pending_job_count; g_thread_pool_push (thread_pool, bitmap, NULL); } } } static void rdp_peer_refresh_raw (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *data) { freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; rdpSettings *rdp_settings = peer->settings; rdpUpdate *rdp_update = peer->update; uint32_t src_stride = grd_session_rdp_get_stride_for_width (session_rdp, rdp_surface->width); RawThreadPoolContext *thread_pool_context = &session_rdp->raw_thread_pool_context; g_autoptr (GError) error = NULL; uint32_t bitmap_data_count = 0; uint32_t n_bitmaps = 0; uint32_t update_size = 0; cairo_rectangle_int_t cairo_rect; int n_rects; BITMAP_DATA *bitmap_data; uint32_t cols, rows; SURFACE_FRAME_MARKER marker; BITMAP_UPDATE bitmap_update = {0}; uint32_t max_update_size; uint32_t next_size; int i; rdp_surface->valid = TRUE; n_rects = cairo_region_num_rectangles (region); for (i = 0; i < n_rects; ++i) { cairo_region_get_rectangle (region, i, &cairo_rect); cols = cairo_rect.width / 64 + (cairo_rect.width % 64 ? 1 : 0); rows = cairo_rect.height / 64 + (cairo_rect.height % 64 ? 1 : 0); bitmap_data_count += cols * rows; } bitmap_data = g_malloc0 (bitmap_data_count * sizeof (BITMAP_DATA)); thread_pool_context->pending_job_count = 0; thread_pool_context->pending_jobs_cond = &session_rdp->pending_jobs_cond; thread_pool_context->pending_jobs_mutex = &session_rdp->pending_jobs_mutex; thread_pool_context->planar_flags = rdp_peer_context->planar_flags; thread_pool_context->src_stride = src_stride; thread_pool_context->src_data = data; if (!session_rdp->thread_pool) session_rdp->thread_pool = g_thread_pool_new (rdp_peer_compress_raw_tile, thread_pool_context, g_get_num_processors (), TRUE, &error); if (!session_rdp->thread_pool) { g_free (bitmap_data); g_error ("Couldn't create thread pool: %s", error->message); } g_mutex_lock (&session_rdp->pending_jobs_mutex); for (i = 0; i < n_rects; ++i) { cairo_region_get_rectangle (region, i, &cairo_rect); rdp_peer_refresh_raw_rect (peer, rdp_surface, session_rdp->thread_pool, &thread_pool_context->pending_job_count, bitmap_data, &n_bitmaps, &cairo_rect, data); } while (thread_pool_context->pending_job_count) { g_cond_wait (&session_rdp->pending_jobs_cond, &session_rdp->pending_jobs_mutex); } g_mutex_unlock (&session_rdp->pending_jobs_mutex); /* We send 2 additional Bytes for the update header * (See also update_write_bitmap_update () in FreeRDP) */ max_update_size = rdp_settings->MultifragMaxRequestSize - 2; bitmap_update.count = bitmap_update.number = 0; bitmap_update.rectangles = bitmap_data; bitmap_update.skipCompression = FALSE; marker.frameId = rdp_peer_context->frame_id; marker.frameAction = SURFACECMD_FRAMEACTION_BEGIN; if (rdp_settings->SurfaceFrameMarkerEnabled) rdp_update->SurfaceFrameMarker (peer->context, &marker); for (i = 0; i < n_bitmaps; ++i) { /* We send 26 additional Bytes for each bitmap * (See also update_write_bitmap_data () in FreeRDP) */ update_size += bitmap_data[i].bitmapLength + 26; ++bitmap_update.count; ++bitmap_update.number; next_size = i + 1 < n_bitmaps ? bitmap_data[i + 1].bitmapLength + 26 : 0; if (!next_size || update_size + next_size > max_update_size) { rdp_update->BitmapUpdate (peer->context, &bitmap_update); bitmap_update.rectangles += bitmap_update.count; bitmap_update.count = bitmap_update.number = 0; update_size = 0; } } marker.frameAction = SURFACECMD_FRAMEACTION_END; if (rdp_settings->SurfaceFrameMarkerEnabled) rdp_update->SurfaceFrameMarker (peer->context, &marker); for (i = 0; i < n_bitmaps; ++i) g_free (bitmap_data[i].bitmapDataStream); g_free (bitmap_data); } static void rdp_peer_refresh_region (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface, cairo_region_t *region, uint8_t *data) { freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; rdpSettings *rdp_settings = peer->settings; if (rdp_settings->SupportGraphicsPipeline) rdp_peer_refresh_gfx (session_rdp, rdp_surface, region, data); else if (rdp_settings->RemoteFxCodec) rdp_peer_refresh_rfx (session_rdp, rdp_surface, region, data); else if (rdp_settings->NSCodec) rdp_peer_refresh_nsc (session_rdp, rdp_surface, region, data); else rdp_peer_refresh_raw (session_rdp, rdp_surface, region, data); ++rdp_peer_context->frame_id; } static gboolean notify_keycode_released (gpointer key, gpointer value, gpointer user_data) { GrdSessionRdp *session_rdp = user_data; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; uint32_t keycode = GPOINTER_TO_UINT (key); grd_rdp_event_queue_add_input_event_keyboard_keycode (rdp_event_queue, keycode, GRD_KEY_STATE_RELEASED); return TRUE; } static gboolean notify_keysym_released (gpointer key, gpointer value, gpointer user_data) { GrdSessionRdp *session_rdp = user_data; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; xkb_keysym_t keysym = GPOINTER_TO_UINT (key); grd_rdp_event_queue_add_input_event_keyboard_keysym (rdp_event_queue, keysym, GRD_KEY_STATE_RELEASED); return TRUE; } static BOOL rdp_input_synchronize_event (rdpInput *rdp_input, uint32_t flags) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_input->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) || is_view_only (session_rdp)) return TRUE; g_hash_table_foreach_remove (session_rdp->pressed_keys, notify_keycode_released, session_rdp); g_hash_table_foreach_remove (session_rdp->pressed_unicode_keys, notify_keysym_released, session_rdp); grd_rdp_event_queue_add_synchronization_event (rdp_event_queue, !!(flags & KBD_SYNC_CAPS_LOCK), !!(flags & KBD_SYNC_NUM_LOCK)); return TRUE; } static BOOL rdp_input_mouse_event (rdpInput *rdp_input, uint16_t flags, uint16_t x, uint16_t y) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_input->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; GrdButtonState button_state; int32_t button = 0; uint16_t axis_value; double axis_step; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) || is_view_only (session_rdp)) return TRUE; if (flags & PTR_FLAGS_MOVE) { grd_rdp_event_queue_add_input_event_pointer_motion_abs (rdp_event_queue, x, y); } button_state = flags & PTR_FLAGS_DOWN ? GRD_BUTTON_STATE_PRESSED : GRD_BUTTON_STATE_RELEASED; if (flags & PTR_FLAGS_BUTTON1) button = BTN_LEFT; else if (flags & PTR_FLAGS_BUTTON2) button = BTN_RIGHT; else if (flags & PTR_FLAGS_BUTTON3) button = BTN_MIDDLE; if (button) { grd_rdp_event_queue_add_input_event_pointer_button (rdp_event_queue, button, button_state); } if (!(flags & PTR_FLAGS_WHEEL) && !(flags & PTR_FLAGS_HWHEEL)) return TRUE; axis_value = flags & WheelRotationMask; if (axis_value & PTR_FLAGS_WHEEL_NEGATIVE) { axis_value = ~axis_value & WheelRotationMask; ++axis_value; } axis_step = -axis_value / 120.0; if (flags & PTR_FLAGS_WHEEL_NEGATIVE) axis_step = -axis_step; if (flags & PTR_FLAGS_WHEEL) { grd_rdp_event_queue_add_input_event_pointer_axis ( rdp_event_queue, 0, axis_step * DISCRETE_SCROLL_STEP, GRD_POINTER_AXIS_FLAGS_SOURCE_WHEEL); } if (flags & PTR_FLAGS_HWHEEL) { grd_rdp_event_queue_add_input_event_pointer_axis ( rdp_event_queue, -axis_step * DISCRETE_SCROLL_STEP, 0, GRD_POINTER_AXIS_FLAGS_SOURCE_WHEEL); } return TRUE; } static BOOL rdp_input_extended_mouse_event (rdpInput *rdp_input, uint16_t flags, uint16_t x, uint16_t y) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_input->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; GrdButtonState button_state; int32_t button = 0; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) || is_view_only (session_rdp)) return TRUE; if (flags & PTR_FLAGS_MOVE) rdp_input_mouse_event (rdp_input, PTR_FLAGS_MOVE, x, y); button_state = flags & PTR_XFLAGS_DOWN ? GRD_BUTTON_STATE_PRESSED : GRD_BUTTON_STATE_RELEASED; if (flags & PTR_XFLAGS_BUTTON1) button = BTN_SIDE; else if (flags & PTR_XFLAGS_BUTTON2) button = BTN_EXTRA; if (button) { grd_rdp_event_queue_add_input_event_pointer_button (rdp_event_queue, button, button_state); } return TRUE; } static gboolean is_pause_key_sequence (GrdSessionRdp *session_rdp, uint16_t vkcode, uint16_t flags) { GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; switch (session_rdp->pause_key_state) { case PAUSE_KEY_STATE_NONE: if (vkcode == VK_LCONTROL && flags & KBD_FLAGS_DOWN && flags & KBD_FLAGS_EXTENDED1) { session_rdp->pause_key_state = PAUSE_KEY_STATE_CTRL_DOWN; return TRUE; } return FALSE; case PAUSE_KEY_STATE_CTRL_DOWN: if (vkcode == VK_NUMLOCK && flags & KBD_FLAGS_DOWN) { session_rdp->pause_key_state = PAUSE_KEY_STATE_NUMLOCK_DOWN; return TRUE; } break; case PAUSE_KEY_STATE_NUMLOCK_DOWN: if (vkcode == VK_LCONTROL && !(flags & KBD_FLAGS_DOWN) && flags & KBD_FLAGS_EXTENDED1) { session_rdp->pause_key_state = PAUSE_KEY_STATE_CTRL_UP; return TRUE; } break; case PAUSE_KEY_STATE_CTRL_UP: if (vkcode == VK_NUMLOCK && !(flags & KBD_FLAGS_DOWN)) { session_rdp->pause_key_state = PAUSE_KEY_STATE_NONE; grd_rdp_event_queue_add_input_event_keyboard_keysym ( rdp_event_queue, XKB_KEY_Pause, GRD_KEY_STATE_PRESSED); grd_rdp_event_queue_add_input_event_keyboard_keysym ( rdp_event_queue, XKB_KEY_Pause, GRD_KEY_STATE_RELEASED); return TRUE; } break; } g_warning ("Received invalid pause key sequence"); session_rdp->pause_key_state = PAUSE_KEY_STATE_NONE; return FALSE; } static BOOL rdp_input_keyboard_event (rdpInput *rdp_input, uint16_t flags, uint16_t code) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_input->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; GrdKeyState key_state; uint16_t fullcode; uint16_t vkcode; uint16_t keycode; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) || is_view_only (session_rdp)) return TRUE; fullcode = flags & KBD_FLAGS_EXTENDED ? code | KBDEXT : code; vkcode = GetVirtualKeyCodeFromVirtualScanCode (fullcode, 4); vkcode = flags & KBD_FLAGS_EXTENDED ? vkcode | KBDEXT : vkcode; /** * Although the type is declared as an evdev keycode, FreeRDP actually returns * a XKB keycode */ keycode = GetKeycodeFromVirtualKeyCode (vkcode, KEYCODE_TYPE_EVDEV) - 8; key_state = flags & KBD_FLAGS_DOWN ? GRD_KEY_STATE_PRESSED : GRD_KEY_STATE_RELEASED; if (is_pause_key_sequence (session_rdp, vkcode, flags)) return TRUE; if (flags & KBD_FLAGS_DOWN) { if (!g_hash_table_add (session_rdp->pressed_keys, GUINT_TO_POINTER (keycode))) return TRUE; } else { if (!g_hash_table_remove (session_rdp->pressed_keys, GUINT_TO_POINTER (keycode))) return TRUE; } grd_rdp_event_queue_add_input_event_keyboard_keycode (rdp_event_queue, keycode, key_state); return TRUE; } static BOOL rdp_input_unicode_keyboard_event (rdpInput *rdp_input, uint16_t flags, uint16_t code_utf16) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_input->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; uint32_t *code_utf32; xkb_keysym_t keysym; GrdKeyState key_state; if (!is_rdp_peer_flag_set (session_rdp, RDP_PEER_ACTIVATED) || is_view_only (session_rdp)) return TRUE; code_utf32 = g_utf16_to_ucs4 (&code_utf16, 1, NULL, NULL, NULL); if (!code_utf32) return TRUE; keysym = xkb_utf32_to_keysym (*code_utf32); g_free (code_utf32); key_state = flags & KBD_FLAGS_DOWN ? GRD_KEY_STATE_PRESSED : GRD_KEY_STATE_RELEASED; if (flags & KBD_FLAGS_DOWN) { if (!g_hash_table_add (session_rdp->pressed_unicode_keys, GUINT_TO_POINTER (keysym))) return TRUE; } else { if (!g_hash_table_remove (session_rdp->pressed_unicode_keys, GUINT_TO_POINTER (keysym))) return TRUE; } grd_rdp_event_queue_add_input_event_keyboard_keysym (rdp_event_queue, keysym, key_state); return TRUE; } static BOOL rdp_suppress_output (rdpContext *rdp_context, uint8_t allow, const RECTANGLE_16 *area) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) rdp_context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; rdpSettings *rdp_settings = session_rdp->peer->settings; if (allow) set_rdp_peer_flag (session_rdp, RDP_PEER_OUTPUT_ENABLED); else unset_rdp_peer_flag (session_rdp, RDP_PEER_OUTPUT_ENABLED); if (rdp_settings->SupportGraphicsPipeline && rdp_peer_context->network_autodetection) { if (allow) { grd_rdp_network_autodetection_ensure_rtt_consumer ( rdp_peer_context->network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX); } else { grd_rdp_network_autodetection_remove_rtt_consumer ( rdp_peer_context->network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX); } } if (allow) g_source_set_ready_time (session_rdp->pending_encode_source, 0); return TRUE; } static BOOL rdp_peer_capabilities (freerdp_peer *peer) { rdpSettings *rdp_settings = peer->settings; switch (rdp_settings->ColorDepth) { case 32: break; case 24: /* Using the interleaved codec with a colour depth of 24 * leads to bitmaps with artifacts. */ g_message ("Downgrading colour depth from 24 to 16 due to issues with " "the interleaved codec"); rdp_settings->ColorDepth = 16; case 16: case 15: break; default: g_warning ("Invalid colour depth, closing connection"); return FALSE; } if (!rdp_settings->DesktopResize) { g_warning ("Client doesn't support resizing, closing connection"); return FALSE; } if (rdp_settings->PointerCacheSize <= 0) { g_warning ("Client doesn't have a pointer cache, closing connection"); return FALSE; } return TRUE; } static BOOL rdp_peer_post_connect (freerdp_peer *peer) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; rdpSettings *rdp_settings = peer->settings; GrdRdpSAMFile *sam_file; g_debug ("New RDP client"); if (!rdp_settings->SupportGraphicsPipeline && !rdp_settings->RemoteFxCodec && rdp_settings->MultifragMaxRequestSize < 0x3F0000) { g_message ("Disabling NSCodec since it does not support fragmentation"); rdp_settings->NSCodec = FALSE; } if (!rdp_settings->SupportGraphicsPipeline && !rdp_settings->RemoteFxCodec) { g_warning ("[RDP] Client does neither support RFX nor GFX. This is will " "result in heavy performance and heavy bandwidth usage " "regressions. The legacy path is deprecated!"); } rdp_settings->PointerCacheSize = MIN (rdp_settings->PointerCacheSize, 100); session_rdp->rdp_surface = g_malloc0 (sizeof (GrdRdpSurface)); session_rdp->rdp_surface->refresh_rate = rdp_settings->SupportGraphicsPipeline ? 60 : 30; if (rdp_settings->SupportGraphicsPipeline && !rdp_settings->NetworkAutoDetect) { g_warning ("Client does not support autodetecting network characteristics " "(RTT detection, Bandwidth measurement). " "High latency connections will suffer!"); } if (rdp_settings->NetworkAutoDetect) { rdp_peer_context->network_autodetection = grd_rdp_network_autodetection_new (peer->context); } if (rdp_settings->SupportGraphicsPipeline) { set_rdp_peer_flag (session_rdp, RDP_PEER_PENDING_GFX_INIT); rdp_peer_context->graphics_pipeline = grd_rdp_graphics_pipeline_new (session_rdp, session_rdp->graphics_context, rdp_peer_context->vcm, session_rdp->stop_event, peer->context, rdp_peer_context->network_autodetection, rdp_peer_context->encode_stream, rdp_peer_context->rfx_context); #ifdef HAVE_NVENC grd_rdp_graphics_pipeline_set_nvenc (rdp_peer_context->graphics_pipeline, session_rdp->rdp_nvenc); #endif /* HAVE_NVENC */ } grd_session_start (GRD_SESSION (session_rdp)); sam_file = g_steal_pointer (&session_rdp->sam_file); grd_rdp_sam_maybe_close_and_free_sam_file (sam_file); if (rdp_settings->SupportGraphicsPipeline && rdp_peer_context->network_autodetection) { grd_rdp_network_autodetection_ensure_rtt_consumer ( rdp_peer_context->network_autodetection, GRD_RDP_NW_AUTODETECT_RTT_CONSUMER_RDPGFX); } set_rdp_peer_flag (session_rdp, RDP_PEER_OUTPUT_ENABLED); set_rdp_peer_flag (session_rdp, RDP_PEER_ACTIVATED); return TRUE; } static BOOL rdp_peer_activate (freerdp_peer *peer) { RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; GrdSessionRdp *session_rdp = rdp_peer_context->session_rdp; rdpSettings *rdp_settings = peer->settings; g_debug ("Activating client"); if (rdp_peer_context->graphics_pipeline && !rdp_settings->SupportGraphicsPipeline) { g_warning ("Client disabled support for the graphics pipeline during the " "Deactivation-Reactivation sequence. This is not supported. " "Closing connection"); return FALSE; } if (rdp_peer_context->network_autodetection && !rdp_settings->NetworkAutoDetect) { g_warning ("Client disabled support for network autodetection during the " "Deactivation-Reactivation sequence. This is not supported. " "Closing connection"); return FALSE; } set_rdp_peer_flag (session_rdp, RDP_PEER_ALL_SURFACES_INVALID); return TRUE; } static BOOL rdp_peer_context_new (freerdp_peer *peer, RdpPeerContext *rdp_peer_context) { rdpSettings *rdp_settings = peer->settings; rdp_peer_context->frame_id = 0; rdp_peer_context->rfx_context = rfx_context_new (TRUE); rfx_context_set_pixel_format (rdp_peer_context->rfx_context, PIXEL_FORMAT_BGRX32); rdp_peer_context->encode_stream = Stream_New (NULL, 64 * 64 * 4); rdp_peer_context->planar_flags = 0; if (rdp_settings->DrawAllowSkipAlpha) rdp_peer_context->planar_flags |= PLANAR_FORMAT_HEADER_NA; rdp_peer_context->planar_flags |= PLANAR_FORMAT_HEADER_RLE; rdp_peer_context->vcm = WTSOpenServerA ((LPSTR) peer->context); return TRUE; } static void rdp_peer_context_free (freerdp_peer *peer, RdpPeerContext *rdp_peer_context) { if (!rdp_peer_context) return; g_clear_pointer (&rdp_peer_context->vcm, WTSCloseServer); if (rdp_peer_context->encode_stream) Stream_Free (rdp_peer_context->encode_stream, TRUE); if (rdp_peer_context->rfx_context) rfx_context_free (rdp_peer_context->rfx_context); } int grd_session_rdp_get_stride_for_width (GrdSessionRdp *session_rdp, int width) { return width * 4; } static void init_rdp_session (GrdSessionRdp *session_rdp, const char *username, const char *password) { GrdContext *context = grd_session_get_context (GRD_SESSION (session_rdp)); GrdSettings *settings = grd_context_get_settings (context); GSocket *socket = g_socket_connection_get_socket (session_rdp->connection); freerdp_peer *peer; rdpInput *rdp_input; RdpPeerContext *rdp_peer_context; rdpSettings *rdp_settings; g_debug ("Initialize RDP session"); peer = freerdp_peer_new (g_socket_get_fd (socket)); peer->ContextSize = sizeof (RdpPeerContext); peer->ContextNew = (psPeerContextNew) rdp_peer_context_new; peer->ContextFree = (psPeerContextFree) rdp_peer_context_free; freerdp_peer_context_new (peer); rdp_peer_context = (RdpPeerContext *) peer->context; rdp_peer_context->session_rdp = session_rdp; session_rdp->sam_file = grd_rdp_sam_create_sam_file (username, password); rdp_settings = peer->settings; freerdp_settings_set_string (rdp_settings, FreeRDP_NtlmSamFile, session_rdp->sam_file->filename); rdp_settings->CertificateFile = strdup (grd_settings_get_rdp_server_cert (settings)); rdp_settings->PrivateKeyFile = strdup (grd_settings_get_rdp_server_key (settings)); rdp_settings->RdpSecurity = FALSE; rdp_settings->TlsSecurity = FALSE; rdp_settings->NlaSecurity = TRUE; rdp_settings->OsMajorType = OSMAJORTYPE_UNIX; rdp_settings->OsMinorType = OSMINORTYPE_PSEUDO_XSERVER; rdp_settings->ColorDepth = 32; rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 = FALSE; rdp_settings->GfxH264 = FALSE; rdp_settings->GfxSmallCache = FALSE; rdp_settings->GfxThinClient = FALSE; rdp_settings->HasExtendedMouseEvent = TRUE; rdp_settings->HasHorizontalWheel = TRUE; rdp_settings->NetworkAutoDetect = TRUE; rdp_settings->RefreshRect = TRUE; rdp_settings->RemoteFxCodec = TRUE; rdp_settings->SupportGraphicsPipeline = TRUE; rdp_settings->NSCodec = TRUE; rdp_settings->FrameMarkerCommandEnabled = TRUE; rdp_settings->SurfaceFrameMarkerEnabled = TRUE; rdp_settings->UnicodeInput = TRUE; peer->Capabilities = rdp_peer_capabilities; peer->PostConnect = rdp_peer_post_connect; peer->Activate = rdp_peer_activate; peer->update->SuppressOutput = rdp_suppress_output; rdp_input = peer->input; rdp_input->SynchronizeEvent = rdp_input_synchronize_event; rdp_input->MouseEvent = rdp_input_mouse_event; rdp_input->ExtendedMouseEvent = rdp_input_extended_mouse_event; rdp_input->KeyboardEvent = rdp_input_keyboard_event; rdp_input->UnicodeKeyboardEvent = rdp_input_unicode_keyboard_event; peer->Initialize (peer); session_rdp->peer = peer; session_rdp->last_pointer = NULL; session_rdp->thread_pool = NULL; SetEvent (session_rdp->start_event); } gpointer socket_thread_func (gpointer data) { GrdSessionRdp *session_rdp = data; freerdp_peer *peer; RdpPeerContext *rdp_peer_context; rdpSettings *rdp_settings; HANDLE vcm; HANDLE channel_event; HANDLE events[32]; uint32_t n_events; uint32_t n_freerdp_handles; WaitForSingleObject (session_rdp->start_event, INFINITE); peer = session_rdp->peer; rdp_peer_context = (RdpPeerContext *) peer->context; rdp_settings = peer->settings; vcm = rdp_peer_context->vcm; channel_event = WTSVirtualChannelManagerGetEventHandle (vcm); while (TRUE) { n_events = 0; events[n_events++] = session_rdp->stop_event; n_freerdp_handles = peer->GetEventHandles (peer, &events[n_events], 32 - n_events); if (!n_freerdp_handles) { g_warning ("Failed to get FreeRDP transport event handles"); handle_client_gone (session_rdp); break; } n_events += n_freerdp_handles; events[n_events++] = channel_event; WaitForMultipleObjects (n_events, events, FALSE, INFINITE); if (WaitForSingleObject (session_rdp->stop_event, 0) == WAIT_OBJECT_0) break; if (!peer->CheckFileDescriptor (peer)) { g_message ("Unable to check file descriptor, closing connection"); handle_client_gone (session_rdp); break; } if (WTSVirtualChannelManagerIsChannelJoined (vcm, "drdynvc")) { GrdRdpGraphicsPipeline *graphics_pipeline; switch (WTSVirtualChannelManagerGetDrdynvcState (vcm)) { case DRDYNVC_STATE_NONE: /* * This ensures that WTSVirtualChannelManagerCheckFileDescriptor() * will be called, which initializes the drdynvc channel */ SetEvent (channel_event); break; case DRDYNVC_STATE_READY: graphics_pipeline = rdp_peer_context->graphics_pipeline; if (rdp_settings->SupportGraphicsPipeline) grd_rdp_graphics_pipeline_maybe_init (graphics_pipeline); break; } if (WaitForSingleObject (session_rdp->stop_event, 0) == WAIT_OBJECT_0) break; } if (WaitForSingleObject (channel_event, 0) == WAIT_OBJECT_0 && !WTSVirtualChannelManagerCheckFileDescriptor (vcm)) { g_message ("Unable to check VCM file descriptor, closing connection"); handle_client_gone (session_rdp); break; } } return NULL; } static gpointer graphics_thread_func (gpointer data) { GrdSessionRdp *session_rdp = data; #ifdef HAVE_NVENC if (session_rdp->rdp_nvenc) grd_rdp_nvenc_push_cuda_context (session_rdp->rdp_nvenc); #endif /* HAVE_NVENC */ while (WaitForSingleObject (session_rdp->stop_event, 0) == WAIT_TIMEOUT) g_main_context_iteration (session_rdp->graphics_context, TRUE); #ifdef HAVE_NVENC if (session_rdp->rdp_nvenc) grd_rdp_nvenc_pop_cuda_context (session_rdp->rdp_nvenc); #endif /* HAVE_NVENC */ return NULL; } GrdSessionRdp * grd_session_rdp_new (GrdRdpServer *rdp_server, GSocketConnection *connection, #ifdef HAVE_NVENC GrdRdpNvenc *rdp_nvenc, #endif /* HAVE_NVENC */ int reserved) { GrdSessionRdp *session_rdp; GrdContext *context; GrdSettings *settings; char *username; char *password; g_autoptr (GError) error = NULL; context = grd_rdp_server_get_context (rdp_server); settings = grd_context_get_settings (context); username = grd_settings_get_rdp_username (settings, &error); if (!username) { g_warning ("Couldn't retrieve RDP username: %s", error->message); return NULL; } password = grd_settings_get_rdp_password (settings, &error); if (!password) { g_warning ("Couldn't retrieve RDP password: %s", error->message); g_free (username); return NULL; } session_rdp = g_object_new (GRD_TYPE_SESSION_RDP, "context", context, NULL); session_rdp->connection = g_object_ref (connection); #ifdef HAVE_NVENC session_rdp->rdp_nvenc = rdp_nvenc; #endif /* HAVE_NVENC */ session_rdp->socket_thread = g_thread_new ("RDP socket thread", socket_thread_func, session_rdp); session_rdp->graphics_thread = g_thread_new ("RDP graphics thread", graphics_thread_func, session_rdp); init_rdp_session (session_rdp, username, password); g_free (password); g_free (username); return session_rdp; } static gboolean clear_pointer_bitmap (gpointer key, gpointer value, gpointer user_data) { Pointer *pointer = (Pointer *) key; g_free (pointer->bitmap); g_free (pointer); return TRUE; } static void grd_session_rdp_stop (GrdSession *session) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (session); freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; g_debug ("Stopping RDP session"); unset_rdp_peer_flag (session_rdp, RDP_PEER_ACTIVATED); if (WaitForSingleObject (session_rdp->stop_event, 0) == WAIT_TIMEOUT) { freerdp_set_error_info (peer->context->rdp, ERRINFO_RPC_INITIATED_DISCONNECT); SetEvent (session_rdp->stop_event); } else if (session_rdp->rdp_error_info) { freerdp_set_error_info (peer->context->rdp, session_rdp->rdp_error_info); } if (rdp_peer_context->network_autodetection) { grd_rdp_network_autodetection_invoke_shutdown ( rdp_peer_context->network_autodetection); } if (session_rdp->graphics_thread) { g_assert (session_rdp->graphics_context); g_assert (WaitForSingleObject (session_rdp->stop_event, 0) != WAIT_TIMEOUT); g_main_context_wakeup (session_rdp->graphics_context); g_clear_pointer (&session_rdp->graphics_thread, g_thread_join); } g_clear_object (&session_rdp->pipewire_stream); g_clear_object (&rdp_peer_context->clipboard_rdp); g_clear_object (&rdp_peer_context->graphics_pipeline); g_clear_pointer (&session_rdp->socket_thread, g_thread_join); peer->Close (peer); g_clear_object (&session_rdp->connection); if (session_rdp->sam_file) grd_rdp_sam_maybe_close_and_free_sam_file (session_rdp->sam_file); g_clear_object (&rdp_peer_context->network_autodetection); if (session_rdp->thread_pool) g_thread_pool_free (session_rdp->thread_pool, FALSE, TRUE); peer->Disconnect (peer); freerdp_peer_context_free (peer); freerdp_peer_free (peer); g_hash_table_foreach_remove (session_rdp->pressed_keys, notify_keycode_released, session_rdp); g_hash_table_foreach_remove (session_rdp->pressed_unicode_keys, notify_keysym_released, session_rdp); g_clear_object (&session_rdp->rdp_event_queue); g_clear_pointer (&session_rdp->rdp_surface, grd_rdp_surface_free); g_hash_table_foreach_remove (session_rdp->pointer_cache, clear_pointer_bitmap, NULL); g_clear_handle_id (&session_rdp->close_session_idle_id, g_source_remove); } static gboolean close_session_idle (gpointer user_data) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (user_data); grd_session_stop (GRD_SESSION (session_rdp)); session_rdp->close_session_idle_id = 0; return G_SOURCE_REMOVE; } static void on_pipewire_stream_closed (GrdRdpPipeWireStream *stream, GrdSessionRdp *session_rdp) { g_warning ("PipeWire stream closed, closing client"); maybe_queue_close_session_idle (session_rdp); } static void grd_session_rdp_remote_desktop_session_ready (GrdSession *session) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (session); freerdp_peer *peer = session_rdp->peer; RdpPeerContext *rdp_peer_context = (RdpPeerContext *) peer->context; rdp_peer_context->clipboard_rdp = grd_clipboard_rdp_new (session_rdp, rdp_peer_context->vcm, session_rdp->stop_event); } static void grd_session_rdp_stream_ready (GrdSession *session, GrdStream *stream) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (session); GMainContext *graphics_context = session_rdp->graphics_context; uint32_t pipewire_node_id; uint16_t refresh_rate; g_autoptr (GError) error = NULL; pipewire_node_id = grd_stream_get_pipewire_node_id (stream); refresh_rate = session_rdp->rdp_surface->refresh_rate; session_rdp->pipewire_stream = grd_rdp_pipewire_stream_new (session_rdp, graphics_context, pipewire_node_id, refresh_rate, &error); if (!session_rdp->pipewire_stream) { g_warning ("Failed to establish PipeWire stream: %s", error->message); return; } g_signal_connect (session_rdp->pipewire_stream, "closed", G_CALLBACK (on_pipewire_stream_closed), session_rdp); } static void grd_session_rdp_on_caps_lock_state_changed (GrdSession *session, gboolean state) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (session); GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; grd_rdp_event_queue_update_caps_lock_state (rdp_event_queue, state); } static void grd_session_rdp_on_num_lock_state_changed (GrdSession *session, gboolean state) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (session); GrdRdpEventQueue *rdp_event_queue = session_rdp->rdp_event_queue; grd_rdp_event_queue_update_num_lock_state (rdp_event_queue, state); } static void grd_session_rdp_dispose (GObject *object) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (object); if (session_rdp->pending_encode_source) { g_source_destroy (session_rdp->pending_encode_source); g_clear_pointer (&session_rdp->pending_encode_source, g_source_unref); } g_assert (!session_rdp->graphics_thread); g_clear_pointer (&session_rdp->graphics_context, g_main_context_unref); g_clear_pointer (&session_rdp->pressed_unicode_keys, g_hash_table_unref); g_clear_pointer (&session_rdp->pressed_keys, g_hash_table_unref); g_clear_pointer (&session_rdp->pointer_cache, g_hash_table_unref); g_clear_pointer (&session_rdp->stop_event, CloseHandle); g_clear_pointer (&session_rdp->start_event, CloseHandle); G_OBJECT_CLASS (grd_session_rdp_parent_class)->dispose (object); } static void grd_session_rdp_finalize (GObject *object) { GrdSessionRdp *session_rdp = GRD_SESSION_RDP (object); g_mutex_clear (&session_rdp->close_session_mutex); g_mutex_clear (&session_rdp->rdp_flags_mutex); g_mutex_clear (&session_rdp->pending_jobs_mutex); g_cond_clear (&session_rdp->pending_jobs_cond); G_OBJECT_CLASS (grd_session_rdp_parent_class)->finalize (object); } static gboolean are_pointer_bitmaps_equal (gconstpointer a, gconstpointer b) { Pointer *first = (Pointer *) a; Pointer *second = (Pointer *) b; cairo_rectangle_int_t cairo_rect; if (first->hotspot_x != second->hotspot_x || first->hotspot_y != second->hotspot_y || first->width != second->width || first->height != second->height) return FALSE; cairo_rect.x = cairo_rect.y = 0; cairo_rect.width = first->width; cairo_rect.height = first->height; if (grd_is_tile_dirty (&cairo_rect, first->bitmap, second->bitmap, first->width * 4, 4)) return FALSE; return TRUE; } static gboolean encode_pending_frames (gpointer user_data) { GrdSessionRdp *session_rdp = user_data; GrdRdpSurface *rdp_surface; rdp_surface = session_rdp->rdp_surface; grd_session_rdp_maybe_encode_pending_frame (session_rdp, rdp_surface); return G_SOURCE_CONTINUE; } static gboolean pending_encode_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { g_source_set_ready_time (source, -1); return callback (user_data); } static GSourceFuncs pending_encode_source_funcs = { .dispatch = pending_encode_source_dispatch, }; static void grd_session_rdp_init (GrdSessionRdp *session_rdp) { session_rdp->start_event = CreateEvent (NULL, TRUE, FALSE, NULL); session_rdp->stop_event = CreateEvent (NULL, TRUE, FALSE, NULL); session_rdp->pointer_cache = g_hash_table_new (NULL, are_pointer_bitmaps_equal); session_rdp->pressed_keys = g_hash_table_new (NULL, NULL); session_rdp->pressed_unicode_keys = g_hash_table_new (NULL, NULL); g_cond_init (&session_rdp->pending_jobs_cond); g_mutex_init (&session_rdp->pending_jobs_mutex); g_mutex_init (&session_rdp->rdp_flags_mutex); g_mutex_init (&session_rdp->close_session_mutex); session_rdp->rdp_event_queue = grd_rdp_event_queue_new (session_rdp); session_rdp->graphics_context = g_main_context_new (); session_rdp->pending_encode_source = g_source_new (&pending_encode_source_funcs, sizeof (GSource)); g_source_set_callback (session_rdp->pending_encode_source, encode_pending_frames, session_rdp, NULL); g_source_set_ready_time (session_rdp->pending_encode_source, -1); g_source_attach (session_rdp->pending_encode_source, session_rdp->graphics_context); } static void grd_session_rdp_class_init (GrdSessionRdpClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GrdSessionClass *session_class = GRD_SESSION_CLASS (klass); object_class->dispose = grd_session_rdp_dispose; object_class->finalize = grd_session_rdp_finalize; session_class->remote_desktop_session_ready = grd_session_rdp_remote_desktop_session_ready; session_class->stop = grd_session_rdp_stop; session_class->stream_ready = grd_session_rdp_stream_ready; session_class->on_caps_lock_state_changed = grd_session_rdp_on_caps_lock_state_changed; session_class->on_num_lock_state_changed = grd_session_rdp_on_num_lock_state_changed; } 07070100000069000081A40000000000000000000000016293A07000000A9C000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-session-rdp.h/* * Copyright (C) 2020 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #ifndef GRD_SESSION_RDP_H #define GRD_SESSION_RDP_H #include <gio/gio.h> #include <glib-object.h> #include "grd-session.h" #include "grd-types.h" #define GRD_TYPE_SESSION_RDP (grd_session_rdp_get_type ()) G_DECLARE_FINAL_TYPE (GrdSessionRdp, grd_session_rdp, GRD, SESSION_RDP, GrdSession); GrdSessionRdp *grd_session_rdp_new (GrdRdpServer *rdp_server, GSocketConnection *connection, #ifdef HAVE_NVENC GrdRdpNvenc *rdp_nvenc, #endif /* HAVE_NVENC */ int reserved); void grd_session_rdp_notify_error (GrdSessionRdp *session_rdp, uint32_t error_info); void grd_session_rdp_notify_graphics_pipeline_reset (GrdSessionRdp *session_rdp); void grd_session_rdp_notify_graphics_pipeline_ready (GrdSessionRdp *session_rdp); int grd_session_rdp_get_stride_for_width (GrdSessionRdp *session_rdp, int width); void grd_session_rdp_take_buffer (GrdSessionRdp *session_rdp, void *data, uint16_t width, uint16_t height); void grd_session_rdp_maybe_encode_pending_frame (GrdSessionRdp *session_rdp, GrdRdpSurface *rdp_surface); void grd_session_rdp_update_pointer (GrdSessionRdp *session_rdp, uint16_t hotspot_x, uint16_t hotspot_y, uint16_t width, uint16_t height, uint8_t *data); void grd_session_rdp_hide_pointer (GrdSessionRdp *session_rdp); #endif /* GRD_SESSION_RDP_H */ 0707010000006A000081A40000000000000000000000016293A0700000556B000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-session-vnc.c/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #include "config.h" #include "grd-session-vnc.h" #include <gio/gio.h> #include <linux/input.h> #include <rfb/rfb.h> #include "grd-clipboard-vnc.h" #include "grd-context.h" #include "grd-prompt.h" #include "grd-settings.h" #include "grd-stream.h" #include "grd-vnc-server.h" #include "grd-vnc-pipewire-stream.h" /* BGRx */ #define BGRX_BITS_PER_SAMPLE 8 #define BGRX_SAMPLES_PER_PIXEL 3 #define BGRX_BYTES_PER_PIXEL 4 struct _GrdSessionVnc { GrdSession parent; GSocketConnection *connection; GSource *source; rfbScreenInfoPtr rfb_screen; rfbClientPtr rfb_client; gboolean pending_framebuffer_resize; int pending_framebuffer_width; int pending_framebuffer_height; guint close_session_idle_id; GrdVncAuthMethod auth_method; GrdPrompt *prompt; GCancellable *prompt_cancellable; GrdVncPipeWireStream *pipewire_stream; int prev_x; int prev_y; int prev_button_mask; GHashTable *pressed_keys; GrdClipboardVnc *clipboard_vnc; }; G_DEFINE_TYPE (GrdSessionVnc, grd_session_vnc, GRD_TYPE_SESSION); static void grd_session_vnc_detach_source (GrdSessionVnc *session_vnc); static gboolean close_session_idle (gpointer user_data); static rfbBool check_rfb_password (rfbClientPtr rfb_client, const char *response_encrypted, int len); static void swap_uint8 (uint8_t *a, uint8_t *b) { uint8_t tmp; tmp = *a; *a = *b; *b = tmp; } static void update_server_format (GrdSessionVnc *session_vnc) { rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; /* * Our format is hard coded to BGRX but LibVNCServer assumes it's RGBX; * lets override that. */ swap_uint8 (&rfb_screen->serverFormat.redShift, &rfb_screen->serverFormat.blueShift); } static void resize_vnc_framebuffer (GrdSessionVnc *session_vnc, int width, int height) { rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; uint8_t *framebuffer; if (!session_vnc->rfb_client->useNewFBSize) g_warning ("Client does not support NewFBSize"); g_free (rfb_screen->frameBuffer); framebuffer = g_malloc0 (width * height * BGRX_BYTES_PER_PIXEL); rfbNewFramebuffer (rfb_screen, (char *) framebuffer, width, height, BGRX_BITS_PER_SAMPLE, BGRX_SAMPLES_PER_PIXEL, BGRX_BYTES_PER_PIXEL); update_server_format (session_vnc); rfb_screen->setTranslateFunction (session_vnc->rfb_client); } void grd_session_vnc_queue_resize_framebuffer (GrdSessionVnc *session_vnc, int width, int height) { rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; if (rfb_screen->width == width && rfb_screen->height == height) return; if (session_vnc->rfb_client->preferredEncoding == -1) { session_vnc->pending_framebuffer_resize = TRUE; session_vnc->pending_framebuffer_width = width; session_vnc->pending_framebuffer_height = height; return; } resize_vnc_framebuffer (session_vnc, width, height); } void grd_session_vnc_take_buffer (GrdSessionVnc *session_vnc, void *data) { if (session_vnc->pending_framebuffer_resize) { free (data); return; } free (session_vnc->rfb_screen->frameBuffer); session_vnc->rfb_screen->frameBuffer = data; rfbMarkRectAsModified (session_vnc->rfb_screen, 0, 0, session_vnc->rfb_screen->width, session_vnc->rfb_screen->height); rfbProcessEvents (session_vnc->rfb_screen, 0); } void grd_session_vnc_flush (GrdSessionVnc *session_vnc) { rfbProcessEvents (session_vnc->rfb_screen, 0); } void grd_session_vnc_set_cursor (GrdSessionVnc *session_vnc, rfbCursorPtr rfb_cursor) { rfbSetCursor (session_vnc->rfb_screen, rfb_cursor); } void grd_session_vnc_move_cursor (GrdSessionVnc *session_vnc, int x, int y) { if (session_vnc->rfb_screen->cursorX == x || session_vnc->rfb_screen->cursorY == y) return; LOCK (session_vnc->rfb_screen->cursorMutex); session_vnc->rfb_screen->cursorX = x; session_vnc->rfb_screen->cursorY = y; UNLOCK (session_vnc->rfb_screen->cursorMutex); session_vnc->rfb_client->cursorWasMoved = TRUE; } void grd_session_vnc_set_client_clipboard_text (GrdSessionVnc *session_vnc, char *text, int text_length) { rfbSendServerCutText(session_vnc->rfb_screen, text, text_length); } static void maybe_queue_close_session_idle (GrdSessionVnc *session_vnc) { if (session_vnc->close_session_idle_id) return; session_vnc->close_session_idle_id = g_idle_add (close_session_idle, session_vnc); } gboolean grd_session_vnc_is_client_gone (GrdSessionVnc *session_vnc) { return !session_vnc->rfb_client; } static void handle_client_gone (rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; g_debug ("VNC client gone"); grd_session_vnc_detach_source (session_vnc); maybe_queue_close_session_idle (session_vnc); session_vnc->rfb_client = NULL; } static void prompt_response_callback (GObject *source_object, GAsyncResult *async_result, gpointer user_data) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (user_data); g_autoptr(GError) error = NULL; GrdPromptResponse response; if (!grd_prompt_query_finish (session_vnc->prompt, async_result, &response, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to query user about session: %s", error->message); rfbRefuseOnHoldClient (session_vnc->rfb_client); return; } else { return; } } switch (response) { case GRD_PROMPT_RESPONSE_ACCEPT: grd_session_start (GRD_SESSION (session_vnc)); rfbStartOnHoldClient (session_vnc->rfb_client); return; case GRD_PROMPT_RESPONSE_REFUSE: rfbRefuseOnHoldClient (session_vnc->rfb_client); return; } g_assert_not_reached (); } static enum rfbNewClientAction handle_new_client (rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (rfb_client->screen->screenData); GrdContext *context = grd_session_get_context (GRD_SESSION (session_vnc)); GrdSettings *settings = grd_context_get_settings (context); g_debug ("New VNC client"); session_vnc->auth_method = grd_settings_get_vnc_auth_method (settings); session_vnc->rfb_client = rfb_client; rfb_client->clientGoneHook = handle_client_gone; switch (session_vnc->auth_method) { case GRD_VNC_AUTH_METHOD_PROMPT: session_vnc->prompt = g_object_new (GRD_TYPE_PROMPT, NULL); session_vnc->prompt_cancellable = g_cancellable_new (); grd_prompt_query_async (session_vnc->prompt, rfb_client->host, session_vnc->prompt_cancellable, prompt_response_callback, session_vnc); grd_session_vnc_detach_source (session_vnc); return RFB_CLIENT_ON_HOLD; case GRD_VNC_AUTH_METHOD_PASSWORD: session_vnc->rfb_screen->passwordCheck = check_rfb_password; /* * authPasswdData needs to be non NULL in libvncserver to trigger * password authentication. */ session_vnc->rfb_screen->authPasswdData = (gpointer) 1; return RFB_CLIENT_ACCEPT; } g_assert_not_reached (); } static gboolean is_view_only (GrdSessionVnc *session_vnc) { GrdContext *context = grd_session_get_context (GRD_SESSION (session_vnc)); GrdSettings *settings = grd_context_get_settings (context); return grd_settings_get_vnc_view_only (settings); } static void handle_key_event (rfbBool down, rfbKeySym keysym, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (rfb_client->screen->screenData); GrdSession *session = GRD_SESSION (session_vnc); if (is_view_only (session_vnc)) return; if (down) { if (g_hash_table_add (session_vnc->pressed_keys, GUINT_TO_POINTER (keysym))) { grd_session_notify_keyboard_keysym (session, keysym, GRD_KEY_STATE_PRESSED); } } else { if (g_hash_table_remove (session_vnc->pressed_keys, GUINT_TO_POINTER (keysym))) { grd_session_notify_keyboard_keysym (session, keysym, GRD_KEY_STATE_RELEASED); } } } static gboolean notify_keyboard_key_released (gpointer key, gpointer value, gpointer user_data) { GrdSession *session = user_data; uint32_t keysym = GPOINTER_TO_UINT (key); grd_session_notify_keyboard_keysym (session, keysym, GRD_KEY_STATE_RELEASED); return TRUE; } static void handle_release_all_keys (rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; g_hash_table_foreach_remove (session_vnc->pressed_keys, notify_keyboard_key_released, session_vnc); } static void handle_set_clipboard_text (char *text, int text_length, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdClipboardVnc *clipboard_vnc = session_vnc->clipboard_vnc; grd_clipboard_vnc_set_clipboard_text (clipboard_vnc, text, text_length); } static void grd_session_vnc_notify_axis (GrdSessionVnc *session_vnc, int button_mask_bit_index) { GrdSession *session = GRD_SESSION (session_vnc); GrdPointerAxis axis; int steps; switch (button_mask_bit_index) { case 3: axis = GRD_POINTER_AXIS_VERTICAL; steps = -1; break; case 4: axis = GRD_POINTER_AXIS_VERTICAL; steps = 1; break; case 5: axis = GRD_POINTER_AXIS_HORIZONTAL; steps = -1; break; case 6: axis = GRD_POINTER_AXIS_HORIZONTAL; steps = 1; break; default: return; } grd_session_notify_pointer_axis_discrete (session, axis, steps); } static void handle_pointer_event (int button_mask, int x, int y, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdSession *session = GRD_SESSION (session_vnc); if (is_view_only (session_vnc)) return; if (x != session_vnc->prev_x || y != session_vnc->prev_y) { grd_session_notify_pointer_motion_absolute (session, x, y); session_vnc->prev_x = x; session_vnc->prev_y = y; } if (button_mask != session_vnc->prev_button_mask) { unsigned int i; int buttons[] = { BTN_LEFT, /* 0 */ BTN_MIDDLE, /* 1 */ BTN_RIGHT, /* 2 */ 0, /* 3 - vertical scroll: up */ 0, /* 4 - vertical scroll: down */ 0, /* 5 - horizontal scroll: left */ 0, /* 6 - horizontal scroll: right */ BTN_SIDE, /* 7 */ BTN_EXTRA, /* 8 */ }; for (i = 0; i < G_N_ELEMENTS (buttons); i++) { int button = buttons[i]; int prev_button_state = (session_vnc->prev_button_mask >> i) & 0x01; int cur_button_state = (button_mask >> i) & 0x01; if (prev_button_state != cur_button_state) { if (button) { grd_session_notify_pointer_button (session, button, cur_button_state); } else { grd_session_vnc_notify_axis (session_vnc, i); } } } session_vnc->prev_button_mask = button_mask; } rfbDefaultPtrAddEvent (button_mask, x, y, rfb_client); } static rfbBool check_rfb_password (rfbClientPtr rfb_client, const char *response_encrypted, int len) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdContext *context = grd_session_get_context (GRD_SESSION (session_vnc)); GrdSettings *settings = grd_context_get_settings (context); g_autofree char *password = NULL; g_autoptr(GError) error = NULL; uint8_t challenge_encrypted[CHALLENGESIZE]; switch (session_vnc->auth_method) { case GRD_VNC_AUTH_METHOD_PROMPT: return TRUE; case GRD_VNC_AUTH_METHOD_PASSWORD: break; } password = grd_settings_get_vnc_password (settings, &error); if (!password) { g_warning ("Couldn't retrieve VNC password: %s", error->message); return FALSE; } if (strlen (password) == 0) { g_warning ("VNC password was empty, denying"); return FALSE; } memcpy(challenge_encrypted, rfb_client->authChallenge, CHALLENGESIZE); rfbEncryptBytes(challenge_encrypted, password); if (memcmp (challenge_encrypted, response_encrypted, len) == 0) { grd_session_start (GRD_SESSION (session_vnc)); grd_session_vnc_detach_source (session_vnc); return TRUE; } else { return FALSE; } } int grd_session_vnc_get_stride_for_width (GrdSessionVnc *session_vnc, int width) { return width * BGRX_BYTES_PER_PIXEL; } static void init_vnc_session (GrdSessionVnc *session_vnc) { GSocket *socket; int screen_width; int screen_height; rfbScreenInfoPtr rfb_screen; /* Arbitrary framebuffer size, will get the proper size from the stream. */ screen_width = 800; screen_height = 600; rfb_screen = rfbGetScreen (0, NULL, screen_width, screen_height, 8, 3, 4); session_vnc->rfb_screen = rfb_screen; update_server_format (session_vnc); session_vnc->clipboard_vnc = grd_clipboard_vnc_new (session_vnc); socket = g_socket_connection_get_socket (session_vnc->connection); rfb_screen->inetdSock = g_socket_get_fd (socket); rfb_screen->desktopName = "GNOME"; rfb_screen->versionString = "GNOME Remote Desktop (VNC)"; rfb_screen->neverShared = TRUE; rfb_screen->newClientHook = handle_new_client; rfb_screen->screenData = session_vnc; /* Amount of milliseconds to wait before sending an update */ rfb_screen->deferUpdateTime = 0; rfb_screen->kbdAddEvent = handle_key_event; rfb_screen->kbdReleaseAllKeys = handle_release_all_keys; rfb_screen->setXCutText = handle_set_clipboard_text; rfb_screen->ptrAddEvent = handle_pointer_event; rfb_screen->frameBuffer = g_malloc0 (screen_width * screen_height * 4); memset (rfb_screen->frameBuffer, 0x1f, screen_width * screen_height * 4); rfbInitServer (rfb_screen); rfbProcessEvents (rfb_screen, 0); } static gboolean handle_socket_data (GSocket *socket, GIOCondition condition, gpointer user_data) { GrdSessionVnc *session_vnc = user_data; if (condition & G_IO_IN) { if (rfbIsActive (session_vnc->rfb_screen)) { rfbProcessEvents (session_vnc->rfb_screen, 0); if (session_vnc->pending_framebuffer_resize && session_vnc->rfb_client->preferredEncoding != -1) { resize_vnc_framebuffer (session_vnc, session_vnc->pending_framebuffer_width, session_vnc->pending_framebuffer_height); session_vnc->pending_framebuffer_resize = FALSE; /** * This is a workaround. libvncserver is unable to handle clipboard * changes early and either disconnects the client or crashes g-r-d * if it receives rfbSendServerCutText too early altough the * authentification process is already done. * Doing this after resizing the framebuffer, seems to work fine, * so enable the clipboard here and not when the remote desktop * session proxy is acquired. */ grd_clipboard_vnc_maybe_enable_clipboard (session_vnc->clipboard_vnc); } } } else { g_debug ("Unhandled socket condition %d\n", condition); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static void grd_session_vnc_attach_source (GrdSessionVnc *session_vnc) { GSocket *socket; socket = g_socket_connection_get_socket (session_vnc->connection); session_vnc->source = g_socket_create_source (socket, G_IO_IN | G_IO_PRI, NULL); g_source_set_callback (session_vnc->source, (GSourceFunc) handle_socket_data, session_vnc, NULL); g_source_attach (session_vnc->source, NULL); } static void grd_session_vnc_detach_source (GrdSessionVnc *session_vnc) { g_clear_pointer (&session_vnc->source, g_source_destroy); } GrdSessionVnc * grd_session_vnc_new (GrdVncServer *vnc_server, GSocketConnection *connection) { GrdSessionVnc *session_vnc; GrdContext *context; context = grd_vnc_server_get_context (vnc_server); session_vnc = g_object_new (GRD_TYPE_SESSION_VNC, "context", context, NULL); session_vnc->connection = g_object_ref (connection); grd_session_vnc_attach_source (session_vnc); init_vnc_session (session_vnc); return session_vnc; } static void grd_session_vnc_dispose (GObject *object) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (object); g_assert (!session_vnc->rfb_screen); g_clear_pointer (&session_vnc->pressed_keys, g_hash_table_unref); G_OBJECT_CLASS (grd_session_vnc_parent_class)->dispose (object); } static void grd_session_vnc_stop (GrdSession *session) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (session); g_debug ("Stopping VNC session"); g_clear_object (&session_vnc->pipewire_stream); grd_session_vnc_detach_source (session_vnc); g_clear_object (&session_vnc->connection); g_clear_object (&session_vnc->clipboard_vnc); g_clear_pointer (&session_vnc->rfb_screen->frameBuffer, g_free); g_clear_pointer (&session_vnc->rfb_screen, rfbScreenCleanup); g_clear_handle_id (&session_vnc->close_session_idle_id, g_source_remove); } static gboolean close_session_idle (gpointer user_data) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (user_data); grd_session_stop (GRD_SESSION (session_vnc)); session_vnc->close_session_idle_id = 0; return G_SOURCE_REMOVE; } static void on_pipewire_stream_closed (GrdVncPipeWireStream *stream, GrdSessionVnc *session_vnc) { g_warning ("PipeWire stream closed, closing client"); maybe_queue_close_session_idle (session_vnc); } static void grd_session_vnc_stream_ready (GrdSession *session, GrdStream *stream) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (session); uint32_t pipewire_node_id; g_autoptr (GError) error = NULL; pipewire_node_id = grd_stream_get_pipewire_node_id (stream); session_vnc->pipewire_stream = grd_vnc_pipewire_stream_new (session_vnc, pipewire_node_id, &error); if (!session_vnc->pipewire_stream) { g_warning ("Failed to establish PipeWire stream: %s", error->message); return; } g_signal_connect (session_vnc->pipewire_stream, "closed", G_CALLBACK (on_pipewire_stream_closed), session_vnc); if (!session_vnc->source) grd_session_vnc_attach_source (session_vnc); } static void grd_session_vnc_init (GrdSessionVnc *session_vnc) { session_vnc->pressed_keys = g_hash_table_new (NULL, NULL); } static void grd_session_vnc_class_init (GrdSessionVncClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GrdSessionClass *session_class = GRD_SESSION_CLASS (klass); object_class->dispose = grd_session_vnc_dispose; session_class->stop = grd_session_vnc_stop; session_class->stream_ready = grd_session_vnc_stream_ready; } 0707010000006B000081A40000000000000000000000016293A070000009B2000000000000000000000000000000000000003000000000gnome-remote-desktop-41.3/src/grd-session-vnc.h/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_SESSION_VNC_H #define GRD_SESSION_VNC_H #include <gio/gio.h> #include <glib-object.h> #include <rfb/rfb.h> #include "grd-session.h" #include "grd-types.h" #define GRD_TYPE_SESSION_VNC (grd_session_vnc_get_type ()) G_DECLARE_FINAL_TYPE (GrdSessionVnc, grd_session_vnc, GRD, SESSION_VNC, GrdSession); GrdSessionVnc *grd_session_vnc_new (GrdVncServer *vnc_server, GSocketConnection *connection); void grd_session_vnc_queue_resize_framebuffer (GrdSessionVnc *session_vnc, int width, int height); void grd_session_vnc_take_buffer (GrdSessionVnc *session_vnc, void *data); void grd_session_vnc_flush (GrdSessionVnc *session_vnc); void grd_session_vnc_set_cursor (GrdSessionVnc *session_vnc, rfbCursorPtr rfb_cursor); void grd_session_vnc_move_cursor (GrdSessionVnc *session_vnc, int x, int y); void grd_session_vnc_set_client_clipboard_text (GrdSessionVnc *session_vnc, char *text, int text_length); int grd_session_vnc_get_stride_for_width (GrdSessionVnc *session_vnc, int width); gboolean grd_session_vnc_is_client_gone (GrdSessionVnc *session_vnc); #endif /* GRD_SESSION_VNC_H */ 0707010000006C000081A40000000000000000000000016293A0700000801F000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-session.c/* * Copyright (C) 2015 Red Hat Inc. * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #include "config.h" #include "grd-session.h" #include <gio/gunixfdlist.h> #include <glib-object.h> #include <glib-unix.h> #include <sys/mman.h> #include "grd-clipboard.h" #include "grd-dbus-remote-desktop.h" #include "grd-context.h" #include "grd-private.h" #include "grd-stream.h" enum { PROP_0, PROP_CONTEXT, }; enum { STOPPED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; typedef enum _GrdScreenCastCursorMode { GRD_SCREEN_CAST_CURSOR_MODE_HIDDEN = 0, GRD_SCREEN_CAST_CURSOR_MODE_EMBEDDED = 1, GRD_SCREEN_CAST_CURSOR_MODE_METADATA = 2, } GrdScreenCastCursorMode; typedef struct _GrdSessionPrivate { GrdContext *context; GrdDBusRemoteDesktopSession *remote_desktop_session; GrdDBusScreenCastSession *screen_cast_session; GrdStream *stream; GrdClipboard *clipboard; GCancellable *cancellable; gboolean started; gulong caps_lock_state_changed_id; gulong num_lock_state_changed_id; } GrdSessionPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GrdSession, grd_session, G_TYPE_OBJECT); GrdContext * grd_session_get_context (GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); return priv->context; } void grd_session_stop (GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); if (priv->cancellable && g_cancellable_is_cancelled (priv->cancellable)) return; GRD_SESSION_GET_CLASS (session)->stop (session); if (priv->remote_desktop_session && priv->started) { GrdDBusRemoteDesktopSession *proxy = priv->remote_desktop_session; GError *error = NULL; if (!grd_dbus_remote_desktop_session_call_stop_sync (proxy, NULL, &error)) { g_warning ("Failed to stop: %s\n", error->message); g_error_free (error); } } if (priv->cancellable) g_cancellable_cancel (priv->cancellable); g_clear_signal_handler (&priv->caps_lock_state_changed_id, priv->remote_desktop_session); g_clear_signal_handler (&priv->num_lock_state_changed_id, priv->remote_desktop_session); if (priv->stream) grd_stream_disconnect_proxy_signals (priv->stream); g_clear_object (&priv->remote_desktop_session); g_clear_object (&priv->screen_cast_session); g_signal_emit (session, signals[STOPPED], 0); } void grd_session_notify_keyboard_keycode (GrdSession *session, uint32_t keycode, GrdKeyState state) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktopSession *session_proxy = priv->remote_desktop_session; grd_dbus_remote_desktop_session_call_notify_keyboard_keycode (session_proxy, keycode, state, NULL, NULL, NULL); } void grd_session_notify_keyboard_keysym (GrdSession *session, uint32_t keysym, GrdKeyState state) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktopSession *session_proxy = priv->remote_desktop_session; grd_dbus_remote_desktop_session_call_notify_keyboard_keysym (session_proxy, keysym, state, NULL, NULL, NULL); } void grd_session_notify_pointer_button (GrdSession *session, int32_t button, GrdButtonState state) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktopSession *session_proxy = priv->remote_desktop_session; grd_dbus_remote_desktop_session_call_notify_pointer_button (session_proxy, button, state, NULL, NULL, NULL); } void grd_session_notify_pointer_axis (GrdSession *session, double dx, double dy, GrdPointerAxisFlags flags) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktopSession *session_proxy = priv->remote_desktop_session; grd_dbus_remote_desktop_session_call_notify_pointer_axis ( session_proxy, dx, dy, flags, NULL, NULL, NULL); } void grd_session_notify_pointer_axis_discrete (GrdSession *session, GrdPointerAxis axis, int steps) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktopSession *session_proxy = priv->remote_desktop_session; grd_dbus_remote_desktop_session_call_notify_pointer_axis_discrete ( session_proxy, axis, steps, NULL, NULL, NULL); } void grd_session_notify_pointer_motion_absolute (GrdSession *session, double x, double y) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); const char *stream_path; if (!priv->stream) return; stream_path = grd_stream_get_object_path (priv->stream); grd_dbus_remote_desktop_session_call_notify_pointer_motion_absolute ( priv->remote_desktop_session, stream_path, x, y, NULL, NULL, NULL); } static GVariant * serialize_mime_type_tables (GList *mime_type_tables) { GVariantBuilder builder; GList *l; g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); for (l = mime_type_tables; l; l = l->next) { GrdMimeTypeTable *mime_type_table = l->data; GrdMimeType mime_type; const char *mime_type_string; mime_type = mime_type_table->mime_type; mime_type_string = grd_mime_type_to_string (mime_type); g_variant_builder_add (&builder, "s", mime_type_string); } return g_variant_builder_end (&builder); } static GVariant * serialize_clipboard_options (GList *mime_type_tables) { GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); if (mime_type_tables) { g_variant_builder_add (&builder, "{sv}", "mime-types", serialize_mime_type_tables (mime_type_tables)); } return g_variant_builder_end (&builder); } gboolean grd_session_enable_clipboard (GrdSession *session, GrdClipboard *clipboard, GList *mime_type_tables) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GVariant *options_variant; g_autoptr (GError) error = NULL; if (!priv->remote_desktop_session) return FALSE; priv->clipboard = clipboard; options_variant = serialize_clipboard_options (mime_type_tables); if (!grd_dbus_remote_desktop_session_call_enable_clipboard_sync ( priv->remote_desktop_session, options_variant, NULL, &error)) { g_warning ("Failed to enable clipboard: %s", error->message); return FALSE; } return TRUE; } void grd_session_disable_clipboard (GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); priv->clipboard = NULL; if (!priv->remote_desktop_session) return; grd_dbus_remote_desktop_session_call_disable_clipboard ( priv->remote_desktop_session, NULL, NULL, NULL); } void grd_session_set_selection (GrdSession *session, GList *mime_type_tables) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GVariant *options_variant; g_autoptr (GError) error = NULL; options_variant = serialize_clipboard_options (mime_type_tables); if (!grd_dbus_remote_desktop_session_call_set_selection_sync ( priv->remote_desktop_session, options_variant, NULL, &error)) g_warning ("Failed to set selection: %s", error->message); } static int acquire_fd_from_list (GUnixFDList *fd_list, int fd_idx, GError **error) { int fd; int fd_flags; fd = g_unix_fd_list_get (fd_list, fd_idx, error); if (fd == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "fcntl: %s", g_strerror (errno)); return -1; } fd_flags = fcntl (fd, F_GETFD); if (fd_flags == -1) { close (fd); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "fcntl: %s", g_strerror (errno)); return -1; } if (fcntl (fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) { close (fd); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "fcntl: %s", g_strerror (errno)); return -1; } return fd; } void grd_session_selection_write (GrdSession *session, unsigned int serial, const uint8_t *data, uint32_t size) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); g_autoptr (GError) error = NULL; g_autoptr (GVariant) fd_variant = NULL; g_autoptr (GUnixFDList) fd_list = NULL; int fd_idx; int fd; if (!data || !size) { grd_dbus_remote_desktop_session_call_selection_write_done ( priv->remote_desktop_session, serial, FALSE, NULL, NULL, NULL); return; } if (!grd_dbus_remote_desktop_session_call_selection_write_sync ( priv->remote_desktop_session, serial, NULL, &fd_variant, &fd_list, NULL, &error)) { g_warning ("Failed to write selection for serial %u: %s", serial, error->message); return; } g_variant_get (fd_variant, "h", &fd_idx); fd = acquire_fd_from_list (fd_list, fd_idx, &error); if (fd == -1) { g_warning ("Failed to acquire file descriptor for serial %u: %s", serial, error->message); return; } if (write (fd, data, size) < 0) { grd_dbus_remote_desktop_session_call_selection_write_done ( priv->remote_desktop_session, serial, FALSE, NULL, NULL, NULL); close (fd); return; } grd_dbus_remote_desktop_session_call_selection_write_done ( priv->remote_desktop_session, serial, TRUE, NULL, NULL, NULL); close (fd); } int grd_session_selection_read (GrdSession *session, GrdMimeType mime_type) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); g_autoptr (GError) error = NULL; g_autoptr (GVariant) fd_variant = NULL; g_autoptr (GUnixFDList) fd_list = NULL; int fd_idx; int fd; const char *mime_type_string; mime_type_string = grd_mime_type_to_string (mime_type); if (!grd_dbus_remote_desktop_session_call_selection_read_sync ( priv->remote_desktop_session, mime_type_string, NULL, &fd_variant, &fd_list, NULL, &error)) { g_warning ("Failed to read selection: %s", error->message); return -1; } g_variant_get (fd_variant, "h", &fd_idx); fd = acquire_fd_from_list (fd_list, fd_idx, &error); if (fd == -1) { g_warning ("Failed to acquire file descriptor: %s", error->message); return -1; } return fd; } static void on_stream_ready (GrdStream *stream, GrdSession *session) { GRD_SESSION_GET_CLASS (session)->stream_ready (session, stream); } static void on_stream_closed (GrdStream *stream, GrdSession *session) { grd_session_stop (session); } static void on_session_start_finished (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDBusRemoteDesktopSession *proxy; GrdSession *session; GrdSessionPrivate *priv; g_autoptr (GError) error = NULL; proxy = GRD_DBUS_REMOTE_DESKTOP_SESSION (object); if (!grd_dbus_remote_desktop_session_call_start_finish (proxy, result, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to start session: %s", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); priv->started = TRUE; } static void start_session (GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktopSession *proxy = priv->remote_desktop_session; grd_dbus_remote_desktop_session_call_start (proxy, priv->cancellable, on_session_start_finished, session); } static void on_screen_cast_stream_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDBusScreenCastStream *stream_proxy; GrdSession *session; GrdSessionPrivate *priv; g_autoptr (GError) error = NULL; GrdStream *stream; stream_proxy = grd_dbus_screen_cast_stream_proxy_new_finish (result, &error); if (!stream_proxy) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to acquire stream proxy: %s", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); stream = grd_stream_new (priv->context, stream_proxy); g_signal_connect (stream, "ready", G_CALLBACK (on_stream_ready), session); g_signal_connect (stream, "closed", G_CALLBACK (on_stream_closed), session); priv->stream = stream; start_session (session); } static void on_record_monitor_finished (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDBusScreenCastSession *proxy; GrdSession *session; GrdSessionPrivate *priv; GDBusConnection *connection; g_autofree char *stream_path = NULL; g_autoptr (GError) error = NULL; proxy = GRD_DBUS_SCREEN_CAST_SESSION (object); if (!grd_dbus_screen_cast_session_call_record_monitor_finish (proxy, &stream_path, result, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to record monitor: %s", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy)); grd_dbus_screen_cast_stream_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, MUTTER_SCREEN_CAST_BUS_NAME, stream_path, priv->cancellable, on_screen_cast_stream_proxy_acquired, session); } static void on_screen_cast_session_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDBusScreenCastSession *session_proxy; GrdSession *session; GrdSessionPrivate *priv; GVariantBuilder properties_builder; g_autoptr (GError) error = NULL; session_proxy = grd_dbus_screen_cast_session_proxy_new_finish (result, &error); if (!session_proxy) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to acquire screen cast session proxy: %s\n", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); priv->screen_cast_session = session_proxy; g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&properties_builder, "{sv}", "cursor-mode", g_variant_new_uint32 (GRD_SCREEN_CAST_CURSOR_MODE_METADATA)); /* TODO: Support something other than primary monitor */ grd_dbus_screen_cast_session_call_record_monitor (session_proxy, "", g_variant_builder_end (&properties_builder), priv->cancellable, on_record_monitor_finished, session); } static void on_screen_cast_session_created (GObject *source_object, GAsyncResult *res, gpointer user_data) { GrdDBusScreenCast *screen_cast_proxy; GrdSession *session; GrdSessionPrivate *priv; GDBusConnection *connection; g_autofree char *session_path = NULL; g_autoptr (GError) error = NULL; screen_cast_proxy = GRD_DBUS_SCREEN_CAST (source_object); if (!grd_dbus_screen_cast_call_create_session_finish (screen_cast_proxy, &session_path, res, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to start screen cast session: %s\n", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (screen_cast_proxy)); grd_dbus_screen_cast_session_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, MUTTER_SCREEN_CAST_BUS_NAME, session_path, priv->cancellable, on_screen_cast_session_proxy_acquired, session); } static void on_remote_desktop_session_closed (GrdDBusRemoteDesktopSession *session_proxy, GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); g_clear_object (&priv->remote_desktop_session); g_clear_object (&priv->screen_cast_session); grd_session_stop (session); } static void on_remote_desktop_session_selection_owner_changed (GrdDBusRemoteDesktopSession *session_proxy, GVariant *options_variant, GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GVariant *is_owner_variant, *mime_types_variant; GVariantIter iter; const char *mime_string; GrdMimeType mime_type; GList *mime_type_list = NULL; if (!priv->clipboard) return; is_owner_variant = g_variant_lookup_value (options_variant, "session-is-owner", G_VARIANT_TYPE ("b")); if (is_owner_variant && g_variant_get_boolean (is_owner_variant)) return; mime_types_variant = g_variant_lookup_value (options_variant, "mime-types", G_VARIANT_TYPE ("(as)")); if (!mime_types_variant) return; g_variant_iter_init (&iter, g_variant_get_child_value (mime_types_variant, 0)); while (g_variant_iter_loop (&iter, "s", &mime_string)) { mime_type = grd_mime_type_from_string (mime_string); if (mime_type != GRD_MIME_TYPE_NONE) { mime_type_list = g_list_append (mime_type_list, GUINT_TO_POINTER (mime_type)); g_debug ("Clipboard[SelectionOwnerChanged]: Server advertises mime " "type %s", mime_string); } else { g_debug ("Clipboard[SelectionOwnerChanged]: Server advertised unknown " "mime type: %s", mime_string); } } if (mime_type_list) grd_clipboard_update_client_mime_type_list (priv->clipboard, mime_type_list); } static void on_remote_desktop_session_selection_transfer (GrdDBusRemoteDesktopSession *session_proxy, char *mime_type_string, unsigned int serial, GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdMimeType mime_type; if (!priv->clipboard) return; mime_type = grd_mime_type_from_string (mime_type_string); if (mime_type == GRD_MIME_TYPE_NONE) { grd_dbus_remote_desktop_session_call_selection_write_done ( priv->remote_desktop_session, serial, FALSE, NULL, NULL, NULL); return; } grd_clipboard_request_client_content_for_mime_type (priv->clipboard, mime_type, serial); } static void on_caps_lock_state_changed (GrdDBusRemoteDesktopSession *session_proxy, GParamSpec *param_spec, GrdSession *session) { GrdSessionClass *klass = GRD_SESSION_GET_CLASS (session); gboolean state; state = grd_dbus_remote_desktop_session_get_caps_lock_state (session_proxy); g_debug ("Caps lock state: %s", state ? "locked" : "unlocked"); if (klass->on_caps_lock_state_changed) klass->on_caps_lock_state_changed (session, state); } static void on_num_lock_state_changed (GrdDBusRemoteDesktopSession *session_proxy, GParamSpec *param_spec, GrdSession *session) { GrdSessionClass *klass = GRD_SESSION_GET_CLASS (session); gboolean state; state = grd_dbus_remote_desktop_session_get_num_lock_state (session_proxy); g_debug ("Num lock state: %s", state ? "locked" : "unlocked"); if (klass->on_num_lock_state_changed) klass->on_num_lock_state_changed (session, state); } static void on_remote_desktop_session_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) { GrdDBusRemoteDesktopSession *session_proxy; GrdSession *session; GrdSessionPrivate *priv; GrdSessionClass *klass; g_autoptr (GError) error = NULL; const char *remote_desktop_session_id; GrdDBusScreenCast *screen_cast_proxy; GVariantBuilder properties_builder; GVariant *properties_variant; session_proxy = grd_dbus_remote_desktop_session_proxy_new_finish (result, &error); if (!session_proxy) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to acquire remote desktop session proxy: %s\n", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); klass = GRD_SESSION_GET_CLASS (session); g_signal_connect (session_proxy, "closed", G_CALLBACK (on_remote_desktop_session_closed), session); g_signal_connect (session_proxy, "selection-owner-changed", G_CALLBACK (on_remote_desktop_session_selection_owner_changed), session); g_signal_connect (session_proxy, "selection-transfer", G_CALLBACK (on_remote_desktop_session_selection_transfer), session); priv->remote_desktop_session = session_proxy; remote_desktop_session_id = grd_dbus_remote_desktop_session_get_session_id (session_proxy); g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&properties_builder, "{sv}", "remote-desktop-session-id", g_variant_new_string (remote_desktop_session_id)); g_variant_builder_add (&properties_builder, "{sv}", "disable-animations", g_variant_new_boolean (TRUE)); properties_variant = g_variant_builder_end (&properties_builder); screen_cast_proxy = grd_context_get_screen_cast_proxy (priv->context); grd_dbus_screen_cast_call_create_session (screen_cast_proxy, properties_variant, priv->cancellable, on_screen_cast_session_created, session); priv->caps_lock_state_changed_id = g_signal_connect (session_proxy, "notify::caps-lock-state", G_CALLBACK (on_caps_lock_state_changed), session); priv->num_lock_state_changed_id = g_signal_connect (session_proxy, "notify::num-lock-state", G_CALLBACK (on_num_lock_state_changed), session); if (klass->remote_desktop_session_ready) klass->remote_desktop_session_ready (session); on_caps_lock_state_changed (session_proxy, NULL, session); on_num_lock_state_changed (session_proxy, NULL, session); } static void on_remote_desktop_session_created (GObject *source_object, GAsyncResult *res, gpointer user_data) { GrdDBusRemoteDesktop *remote_desktop_proxy; GrdSession *session; GrdSessionPrivate *priv; GDBusConnection *connection; g_autofree char *session_path = NULL; g_autoptr (GError) error = NULL; remote_desktop_proxy = GRD_DBUS_REMOTE_DESKTOP (source_object); if (!grd_dbus_remote_desktop_call_create_session_finish (remote_desktop_proxy, &session_path, res, &error)) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; g_warning ("Failed to start remote desktop session: %s\n", error->message); grd_session_stop (GRD_SESSION (user_data)); return; } session = GRD_SESSION (user_data); priv = grd_session_get_instance_private (session); connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (remote_desktop_proxy)); grd_dbus_remote_desktop_session_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, MUTTER_REMOTE_DESKTOP_BUS_NAME, session_path, priv->cancellable, on_remote_desktop_session_proxy_acquired, session); } void grd_session_start (GrdSession *session) { GrdSessionPrivate *priv = grd_session_get_instance_private (session); GrdDBusRemoteDesktop *remote_desktop_proxy; priv->cancellable = g_cancellable_new (); remote_desktop_proxy = grd_context_get_remote_desktop_proxy (priv->context); grd_dbus_remote_desktop_call_create_session (remote_desktop_proxy, priv->cancellable, on_remote_desktop_session_created, session); } static void grd_session_dispose (GObject *object) { GrdSession *session = GRD_SESSION (object); GrdSessionPrivate *priv = grd_session_get_instance_private (session); g_clear_object (&priv->stream); G_OBJECT_CLASS (grd_session_parent_class)->dispose (object); } static void grd_session_finalize (GObject *object) { GrdSession *session = GRD_SESSION (object); GrdSessionPrivate *priv = grd_session_get_instance_private (session); g_assert (!priv->remote_desktop_session); if (priv->cancellable) g_assert (g_cancellable_is_cancelled (priv->cancellable)); g_clear_object (&priv->cancellable); G_OBJECT_CLASS (grd_session_parent_class)->finalize (object); } static void grd_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GrdSession *session = GRD_SESSION (object); GrdSessionPrivate *priv = grd_session_get_instance_private (session); switch (prop_id) { case PROP_CONTEXT: priv->context = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void grd_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GrdSession *session = GRD_SESSION (object); GrdSessionPrivate *priv = grd_session_get_instance_private (session); switch (prop_id) { case PROP_CONTEXT: g_value_set_object (value, priv->context); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void grd_session_init (GrdSession *session) { } static void grd_session_class_init (GrdSessionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_session_dispose; object_class->finalize = grd_session_finalize; object_class->set_property = grd_session_set_property; object_class->get_property = grd_session_get_property; g_object_class_install_property (object_class, PROP_CONTEXT, g_param_spec_object ("context", "GrdContext", "The GrdContext instance", GRD_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); signals[STOPPED] = g_signal_new ("stopped", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } 0707010000006D000081A40000000000000000000000016293A070000010DD000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/src/grd-session.h/* * Copyright (C) 2015 Red Hat Inc. * Copyright (C) 2020-2021 Pascal Nowack * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_SESSION_H #define GRD_SESSION_H #include <glib-object.h> #include <stdint.h> #include "grd-mime-type.h" #include "grd-types.h" #define GRD_TYPE_SESSION (grd_session_get_type ()) G_DECLARE_DERIVABLE_TYPE (GrdSession, grd_session, GRD, SESSION, GObject); typedef enum _GrdKeyState { GRD_KEY_STATE_RELEASED, GRD_KEY_STATE_PRESSED } GrdKeyState; typedef enum _GrdButtonState { GRD_BUTTON_STATE_RELEASED, GRD_BUTTON_STATE_PRESSED } GrdButtonState; typedef enum _GrdPointerAxisFlags { GRD_POINTER_AXIS_FLAGS_FINISH = 1 << 0, GRD_POINTER_AXIS_FLAGS_SOURCE_WHEEL = 1 << 1, GRD_POINTER_AXIS_FLAGS_SOURCE_FINGER = 1 << 2, GRD_POINTER_AXIS_FLAGS_SOURCE_CONTINUOUS = 1 << 3, } GrdPointerAxisFlags; typedef enum _GrdPointerAxis { GRD_POINTER_AXIS_VERTICAL, GRD_POINTER_AXIS_HORIZONTAL } GrdPointerAxis; struct _GrdSessionClass { GObjectClass parent_class; void (*remote_desktop_session_ready) (GrdSession *session); void (*stream_ready) (GrdSession *session, GrdStream *stream); void (*stop) (GrdSession *session); void (*on_caps_lock_state_changed) (GrdSession *session, gboolean state); void (*on_num_lock_state_changed) (GrdSession *session, gboolean state); }; GrdContext *grd_session_get_context (GrdSession *session); void grd_session_notify_keyboard_keycode (GrdSession *session, uint32_t keycode, GrdKeyState state); void grd_session_notify_keyboard_keysym (GrdSession *session, uint32_t keysym, GrdKeyState state); void grd_session_notify_pointer_button (GrdSession *session, int32_t button, GrdButtonState state); void grd_session_notify_pointer_axis (GrdSession *session, double dx, double dy, GrdPointerAxisFlags flags); void grd_session_notify_pointer_axis_discrete (GrdSession *session, GrdPointerAxis axis, int steps); void grd_session_notify_pointer_motion_absolute (GrdSession *session, double x, double y); gboolean grd_session_enable_clipboard (GrdSession *session, GrdClipboard *clipboard, GList *mime_type_tables); void grd_session_disable_clipboard (GrdSession *session); void grd_session_set_selection (GrdSession *session, GList *mime_type_tables); void grd_session_selection_write (GrdSession *session, unsigned int serial, const uint8_t *data, uint32_t size); int grd_session_selection_read (GrdSession *session, GrdMimeType mime_type); void grd_session_start (GrdSession *session); void grd_session_stop (GrdSession *session); #endif /* GRD_SESSION_H */ 0707010000006E000081A40000000000000000000000016293A07000002B1E000000000000000000000000000000000000002D00000000gnome-remote-desktop-41.3/src/grd-settings.c/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "grd-settings.h" #include <gio/gio.h> #include <string.h> #define GRD_RDP_SCHEMA_ID "org.gnome.desktop.remote-desktop.rdp" #define GRD_VNC_SCHEMA_ID "org.gnome.desktop.remote-desktop.vnc" #define GRD_RDP_SERVER_PORT 3389 #define GRD_VNC_SERVER_PORT 5900 enum { RDP_SERVER_CERT_CHANGED, RDP_SERVER_KEY_CHANGED, RDP_VIEW_ONLY_CHANGED, VNC_VIEW_ONLY_CHANGED, VNC_AUTH_METHOD_CHANGED, VNC_ENCRYPTION_CHANGED, N_SIGNALS }; static guint signals[N_SIGNALS]; struct _GrdSettings { GObject parent; struct { GSettings *settings; char *server_cert; char *server_key; gboolean view_only; int port; } rdp; struct { GSettings *settings; gboolean view_only; GrdVncAuthMethod auth_method; int port; } vnc; }; G_DEFINE_TYPE (GrdSettings, grd_settings, G_TYPE_OBJECT) const SecretSchema * grd_rdp_credentials_get_schema (void) { static const SecretSchema grd_rdp_credentials_schema = { .name = "org.gnome.RemoteDesktop.RdpCredentials", .flags = SECRET_SCHEMA_NONE, .attributes = { { "credentials", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "NULL", 0 }, }, }; return &grd_rdp_credentials_schema; } const SecretSchema * grd_vnc_password_get_schema (void) { static const SecretSchema grd_vnc_password_schema = { .name = "org.gnome.RemoteDesktop.VncPassword", .flags = SECRET_SCHEMA_NONE, .attributes = { { "password", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "NULL", 0 }, }, }; return &grd_vnc_password_schema; } int grd_settings_get_rdp_port (GrdSettings *settings) { return settings->rdp.port; } int grd_settings_get_vnc_port (GrdSettings *settings) { return settings->vnc.port; } void grd_settings_override_rdp_port (GrdSettings *settings, int port) { settings->rdp.port = port; } void grd_settings_override_vnc_port (GrdSettings *settings, int port) { settings->vnc.port = port; } char * grd_settings_get_rdp_server_cert (GrdSettings *settings) { return settings->rdp.server_cert; } char * grd_settings_get_rdp_server_key (GrdSettings *settings) { return settings->rdp.server_key; } char * grd_settings_get_rdp_username (GrdSettings *settings, GError **error) { const char *test_password_override; g_autofree char *credentials_string = NULL; g_autoptr (GVariant) credentials = NULL; char *username = NULL; test_password_override = g_getenv ("GNOME_REMOTE_DESKTOP_TEST_RDP_PASSWORD"); if (test_password_override) return g_strdup ("TEST"); credentials_string = secret_password_lookup_sync (GRD_RDP_CREDENTIALS_SCHEMA, NULL, error, NULL); if (!credentials_string) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Credentials not set"); return NULL; } credentials = g_variant_parse (NULL, credentials_string, NULL, NULL, NULL); g_variant_lookup (credentials, "username", "s", &username); if (!username) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Username not set"); return NULL; } return username; } char * grd_settings_get_rdp_password (GrdSettings *settings, GError **error) { const char *test_password_override; g_autofree char *credentials_string = NULL; g_autoptr (GVariant) credentials = NULL; char *password = NULL; test_password_override = g_getenv ("GNOME_REMOTE_DESKTOP_TEST_RDP_PASSWORD"); if (test_password_override) return g_strdup (test_password_override); credentials_string = secret_password_lookup_sync (GRD_RDP_CREDENTIALS_SCHEMA, NULL, error, NULL); if (!credentials_string) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Credentials not set"); return NULL; } credentials = g_variant_parse (NULL, credentials_string, NULL, NULL, NULL); g_variant_lookup (credentials, "password", "s", &password); if (!password) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Password not set"); return NULL; } return password; } char * grd_settings_get_vnc_password (GrdSettings *settings, GError **error) { const char *test_password_override; char *password; test_password_override = g_getenv ("GNOME_REMOTE_DESKTOP_TEST_VNC_PASSWORD"); if (test_password_override) return g_strdup (test_password_override); password = secret_password_lookup_sync (GRD_VNC_PASSWORD_SCHEMA, NULL, error, NULL); if (!password) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Password not set"); return NULL; } return password; } gboolean grd_settings_get_rdp_view_only (GrdSettings *settings) { return settings->rdp.view_only; } gboolean grd_settings_get_vnc_view_only (GrdSettings *settings) { return settings->vnc.view_only; } GrdVncAuthMethod grd_settings_get_vnc_auth_method (GrdSettings *settings) { if (g_getenv ("GNOME_REMOTE_DESKTOP_TEST_VNC_PASSWORD")) return GRD_VNC_AUTH_METHOD_PASSWORD; else return settings->vnc.auth_method; } static void update_rdp_tls_cert (GrdSettings *settings) { g_clear_pointer (&settings->rdp.server_cert, g_free); settings->rdp.server_cert = g_settings_get_string (settings->rdp.settings, "tls-cert"); } static void update_rdp_tls_key (GrdSettings *settings) { g_clear_pointer (&settings->rdp.server_key, g_free); settings->rdp.server_key = g_settings_get_string (settings->rdp.settings, "tls-key"); } static void update_rdp_view_only (GrdSettings *settings) { settings->rdp.view_only = g_settings_get_boolean (settings->rdp.settings, "view-only"); } static void update_vnc_view_only (GrdSettings *settings) { settings->vnc.view_only = g_settings_get_boolean (settings->vnc.settings, "view-only"); } static void update_vnc_auth_method (GrdSettings *settings) { settings->vnc.auth_method = g_settings_get_enum (settings->vnc.settings, "auth-method"); } static void on_rdp_settings_changed (GSettings *rdp_settings, const char *key, GrdSettings *settings) { if (strcmp (key, "tls-cert") == 0) { update_rdp_tls_cert (settings); g_signal_emit (settings, signals[RDP_SERVER_CERT_CHANGED], 0); } else if (strcmp (key, "tls-key") == 0) { update_rdp_tls_key (settings); g_signal_emit (settings, signals[RDP_SERVER_KEY_CHANGED], 0); } else if (strcmp (key, "view-only") == 0) { update_rdp_view_only (settings); g_signal_emit (settings, signals[RDP_VIEW_ONLY_CHANGED], 0); } } static void on_vnc_settings_changed (GSettings *vnc_settings, const char *key, GrdSettings *settings) { if (strcmp (key, "view-only") == 0) { update_vnc_view_only (settings); g_signal_emit (settings, signals[VNC_VIEW_ONLY_CHANGED], 0); } else if (strcmp (key, "auth-method") == 0) { update_vnc_auth_method (settings); g_signal_emit (settings, signals[VNC_AUTH_METHOD_CHANGED], 0); } } static void grd_settings_finalize (GObject *object) { GrdSettings *settings = GRD_SETTINGS (object); g_clear_pointer (&settings->rdp.server_cert, g_free); g_clear_pointer (&settings->rdp.server_key, g_free); g_clear_object (&settings->rdp.settings); g_clear_object (&settings->vnc.settings); G_OBJECT_CLASS (grd_settings_parent_class)->finalize (object); } static void grd_settings_init (GrdSettings *settings) { settings->rdp.settings = g_settings_new (GRD_RDP_SCHEMA_ID); settings->vnc.settings = g_settings_new (GRD_VNC_SCHEMA_ID); g_signal_connect (settings->rdp.settings, "changed", G_CALLBACK (on_rdp_settings_changed), settings); g_signal_connect (settings->vnc.settings, "changed", G_CALLBACK (on_vnc_settings_changed), settings); update_rdp_tls_cert (settings); update_rdp_tls_key (settings); update_rdp_view_only (settings); update_vnc_view_only (settings); update_vnc_auth_method (settings); settings->rdp.port = GRD_RDP_SERVER_PORT; settings->vnc.port = GRD_VNC_SERVER_PORT; } static void grd_settings_class_init (GrdSettingsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = grd_settings_finalize; signals[RDP_SERVER_CERT_CHANGED] = g_signal_new ("rdp-tls-cert-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[RDP_SERVER_KEY_CHANGED] = g_signal_new ("rdp-tls-key-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[RDP_VIEW_ONLY_CHANGED] = g_signal_new ("rdp-view-only-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[VNC_VIEW_ONLY_CHANGED] = g_signal_new ("vnc-view-only-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[VNC_AUTH_METHOD_CHANGED] = g_signal_new ("vnc-auth-method-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } 0707010000006F000081A40000000000000000000000016293A0700000094E000000000000000000000000000000000000002D00000000gnome-remote-desktop-41.3/src/grd-settings.h/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef GRD_SETTINGS_H #define GRD_SETTINGS_H #include <glib-object.h> #include <libsecret/secret.h> #include "grd-enums.h" #define GRD_TYPE_SETTINGS (grd_settings_get_type ()) G_DECLARE_FINAL_TYPE (GrdSettings, grd_settings, GRD, SETTINGS, GObject) const SecretSchema * cc_grd_rdp_credentials_get_schema (void); const SecretSchema * cc_grd_vnc_password_get_schema (void); #define GRD_RDP_CREDENTIALS_SCHEMA grd_rdp_credentials_get_schema () #define GRD_VNC_PASSWORD_SCHEMA grd_vnc_password_get_schema () int grd_settings_get_rdp_port (GrdSettings *settings); int grd_settings_get_vnc_port (GrdSettings *settings); void grd_settings_override_rdp_port (GrdSettings *settings, int port); void grd_settings_override_vnc_port (GrdSettings *settings, int port); char * grd_settings_get_rdp_server_cert (GrdSettings *settings); char * grd_settings_get_rdp_server_key (GrdSettings *settings); char * grd_settings_get_rdp_password (GrdSettings *settings, GError **error); char * grd_settings_get_vnc_password (GrdSettings *settings, GError **error); char * grd_settings_get_rdp_username (GrdSettings *settings, GError **error); gboolean grd_settings_get_rdp_view_only (GrdSettings *settings); gboolean grd_settings_get_vnc_view_only (GrdSettings *settings); GrdVncAuthMethod grd_settings_get_vnc_auth_method (GrdSettings *settings); #endif /* GRD_SETTINGS_H */ 07070100000070000081A40000000000000000000000016293A07000000E6E000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/src/grd-stream.c/* * Copyright (C) 2017 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "grd-stream.h" #include "grd-context.h" enum { READY, CLOSED, N_SIGNALS }; static guint signals[N_SIGNALS]; typedef struct _GrdStreamPrivate { GrdContext *context; uint32_t pipewire_node_id; GrdDBusScreenCastStream *proxy; unsigned long pipewire_stream_added_id; } GrdStreamPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GrdStream, grd_stream, G_TYPE_OBJECT) uint32_t grd_stream_get_pipewire_node_id (GrdStream *stream) { GrdStreamPrivate *priv = grd_stream_get_instance_private (stream); return priv->pipewire_node_id; } const char * grd_stream_get_object_path (GrdStream *stream) { GrdStreamPrivate *priv = grd_stream_get_instance_private (stream); return g_dbus_proxy_get_object_path (G_DBUS_PROXY (priv->proxy)); } void grd_stream_disconnect_proxy_signals (GrdStream *stream) { GrdStreamPrivate *priv = grd_stream_get_instance_private (stream); g_clear_signal_handler (&priv->pipewire_stream_added_id, priv->proxy); } static void on_pipewire_stream_added (GrdDBusScreenCastStream *proxy, unsigned int node_id, GrdStream *stream) { GrdStreamPrivate *priv = grd_stream_get_instance_private (stream); priv->pipewire_node_id = (uint32_t) node_id; g_signal_emit (stream, signals[READY], 0); } GrdStream * grd_stream_new (GrdContext *context, GrdDBusScreenCastStream *proxy) { GrdStream *stream; GrdStreamPrivate *priv; stream = g_object_new (GRD_TYPE_STREAM, NULL); priv = grd_stream_get_instance_private (stream); priv->context = context; priv->proxy = proxy; priv->pipewire_stream_added_id = g_signal_connect (proxy, "pipewire-stream-added", G_CALLBACK (on_pipewire_stream_added), stream); return stream; } static void grd_stream_finalize (GObject *object) { GrdStream *stream = GRD_STREAM (object); GrdStreamPrivate *priv = grd_stream_get_instance_private (stream); g_clear_object (&priv->proxy); G_OBJECT_CLASS (grd_stream_parent_class)->finalize (object); } static void grd_stream_init (GrdStream *stream) { } static void grd_stream_class_init (GrdStreamClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = grd_stream_finalize; signals[READY] = g_signal_new ("ready", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); signals[CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } 07070100000071000081A40000000000000000000000016293A07000000596000000000000000000000000000000000000002B00000000gnome-remote-desktop-41.3/src/grd-stream.h/* * Copyright (C) 2017 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef GRD_STREAM_H #define GRD_STREAM_H #include <glib-object.h> #include <stdint.h> #include "grd-dbus-screen-cast.h" #include "grd-types.h" #define GRD_TYPE_STREAM (grd_stream_get_type ()) G_DECLARE_DERIVABLE_TYPE (GrdStream, grd_stream, GRD, STREAM, GObject) struct _GrdStreamClass { GObjectClass parent_class; }; uint32_t grd_stream_get_pipewire_node_id (GrdStream *stream); const char * grd_stream_get_object_path (GrdStream *stream); void grd_stream_disconnect_proxy_signals (GrdStream *stream); GrdStream * grd_stream_new (GrdContext *context, GrdDBusScreenCastStream *proxy); #endif /* GRD_STREAM_H */ 07070100000072000081A40000000000000000000000016293A07000000792000000000000000000000000000000000000002A00000000gnome-remote-desktop-41.3/src/grd-types.h/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_TYPES_H #define GRD_TYPES_H typedef struct _GrdContext GrdContext; typedef struct _GrdClipboard GrdClipboard; typedef struct _GrdClipboardRdp GrdClipboardRdp; typedef struct _GrdClipboardVnc GrdClipboardVnc; typedef struct _GrdRdpEventQueue GrdRdpEventQueue; typedef struct _GrdRdpGfxFrameLog GrdRdpGfxFrameLog; typedef struct _GrdRdpGfxSurface GrdRdpGfxSurface; typedef struct _GrdRdpGraphicsPipeline GrdRdpGraphicsPipeline; typedef struct _GrdRdpNetworkAutodetection GrdRdpNetworkAutodetection; typedef struct _GrdRdpNvenc GrdRdpNvenc; typedef struct _GrdRdpSAMFile GrdRdpSAMFile; typedef struct _GrdRdpServer GrdRdpServer; typedef struct _GrdRdpSurface GrdRdpSurface; typedef struct _GrdSession GrdSession; typedef struct _GrdSessionRdp GrdSessionRdp; typedef struct _GrdSessionVnc GrdSessionVnc; typedef struct _GrdStream GrdStream; typedef struct _GrdPipeWireStream GrdPipeWireStream; typedef struct _GrdPipeWireStreamMonitor GrdPipeWireStreamMonitor; typedef struct _GrdVncServer GrdVncServer; typedef enum _GrdPixelFormat { GRD_PIXEL_FORMAT_RGBA8888, } GrdPixelFormat; #endif /* GRD_TYPES_H */ 07070100000073000081A40000000000000000000000016293A07000000C6C000000000000000000000000000000000000002F00000000gnome-remote-desktop-41.3/src/grd-vnc-cursor.c/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "grd-vnc-cursor.h" #include <glib.h> #include <stdint.h> static void get_pixel_components (uint32_t pixel, GrdPixelFormat format, uint8_t *a, uint8_t *r, uint8_t *g, uint8_t *b) { g_assert (format == GRD_PIXEL_FORMAT_RGBA8888); *a = (pixel & 0xff000000) >> 24; *b = (pixel & 0xff0000) >> 16; *g = (pixel & 0xff00) >> 8; *r = pixel & 0xff; } static gboolean is_practically_black (uint8_t r, uint8_t g, uint8_t b) { if (r <= 0x62 && g <= 0x62 && b <= 0x62) return TRUE; else return FALSE; } static gboolean is_practically_opaque (uint8_t a) { return a > 0xe0; } rfbCursorPtr grd_vnc_create_cursor (int width, int height, int stride, GrdPixelFormat format, uint8_t *buf) { g_autofree char *cursor = NULL; g_autofree char *mask = NULL; int y; g_return_val_if_fail (format == GRD_PIXEL_FORMAT_RGBA8888, NULL); cursor = g_new0 (char, width * height); mask = g_new0 (char, width * height); for (y = 0; y < height; y++) { uint32_t *pixel_row; int x; pixel_row = (uint32_t *) &buf[y * stride]; for (x = 0; x < width; x++) { uint32_t pixel = pixel_row[x]; uint8_t a, r, g, b; get_pixel_components (pixel, format, &a, &r, &g, &b); if (is_practically_opaque (a)) { if (is_practically_black (r, g, b)) cursor[y * width + x] = ' '; else cursor[y * width + x] = 'x'; mask[y * width + x] = 'x'; } else { cursor[y * width + x] = ' '; mask[y * width + x] = ' '; } } } return rfbMakeXCursor (width, height, cursor, mask); } rfbCursorPtr grd_vnc_create_empty_cursor (int width, int height) { g_autofree char *cursor = NULL; cursor = g_new0 (char, width * height); memset (cursor, ' ', width * height); return rfbMakeXCursor (width, height, cursor, cursor); } 07070100000074000081A40000000000000000000000016293A07000000514000000000000000000000000000000000000002F00000000gnome-remote-desktop-41.3/src/grd-vnc-cursor.h/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef GRD_VNC_CURSOR_H #define GRD_VNC_CURSOR_H #include <rfb/rfb.h> #include "grd-types.h" rfbCursorPtr grd_vnc_create_cursor (int width, int height, int stride, GrdPixelFormat format, uint8_t *buf); rfbCursorPtr grd_vnc_create_empty_cursor (int width, int height); #endif /* GRD_VNC_CURSOR_H */ 07070100000075000081A40000000000000000000000016293A07000004A3D000000000000000000000000000000000000003800000000gnome-remote-desktop-41.3/src/grd-vnc-pipewire-stream.c/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include "grd-vnc-pipewire-stream.h" #include <linux/dma-buf.h> #include <pipewire/pipewire.h> #include <spa/param/props.h> #include <spa/param/format-utils.h> #include <spa/param/video/format-utils.h> #include <spa/utils/result.h> #include <sys/mman.h> #include <sys/syscall.h> #include "grd-pipewire-utils.h" #include "grd-vnc-cursor.h" enum { CLOSED, N_SIGNALS }; static guint signals[N_SIGNALS]; typedef struct _GrdVncFrame { void *data; rfbCursorPtr rfb_cursor; gboolean cursor_moved; int cursor_x; int cursor_y; } GrdVncFrame; struct _GrdVncPipeWireStream { GObject parent; GrdSessionVnc *session; GSource *pipewire_source; struct pw_context *pipewire_context; struct pw_core *pipewire_core; struct spa_hook pipewire_core_listener; GMutex frame_mutex; GrdVncFrame *pending_frame; struct pw_stream *pipewire_stream; struct spa_hook pipewire_stream_listener; uint32_t src_node_id; struct spa_video_info_raw spa_format; }; G_DEFINE_TYPE (GrdVncPipeWireStream, grd_vnc_pipewire_stream, G_TYPE_OBJECT) static gboolean pipewire_loop_source_prepare (GSource *base, int *timeout) { *timeout = -1; return FALSE; } static gboolean pipewire_loop_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) source; int result; result = pw_loop_iterate (pipewire_source->pipewire_loop, 0); if (result < 0) g_warning ("pipewire_loop_iterate failed: %s", spa_strerror (result)); return TRUE; } static void pipewire_loop_source_finalize (GSource *source) { GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) source; pw_loop_leave (pipewire_source->pipewire_loop); pw_loop_destroy (pipewire_source->pipewire_loop); } static GSourceFuncs pipewire_source_funcs = { pipewire_loop_source_prepare, NULL, pipewire_loop_source_dispatch, pipewire_loop_source_finalize }; static GrdPipeWireSource * create_pipewire_source (void) { GrdPipeWireSource *pipewire_source; pipewire_source = (GrdPipeWireSource *) g_source_new (&pipewire_source_funcs, sizeof (GrdPipeWireSource)); pipewire_source->pipewire_loop = pw_loop_new (NULL); if (!pipewire_source->pipewire_loop) { g_source_destroy ((GSource *) pipewire_source); return NULL; } g_source_add_unix_fd (&pipewire_source->base, pw_loop_get_fd (pipewire_source->pipewire_loop), G_IO_IN | G_IO_ERR); pw_loop_enter (pipewire_source->pipewire_loop); g_source_attach (&pipewire_source->base, NULL); return pipewire_source; } static void on_stream_state_changed (void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { g_debug ("Pipewire stream state changed from %s to %s", pw_stream_state_as_string (old), pw_stream_state_as_string (state)); switch (state) { case PW_STREAM_STATE_ERROR: g_warning ("PipeWire stream error: %s", error); break; case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_STREAMING: case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: break; } } static void on_stream_param_changed (void *user_data, uint32_t id, const struct spa_pod *format) { GrdVncPipeWireStream *stream = GRD_VNC_PIPEWIRE_STREAM (user_data); uint8_t params_buffer[1024]; struct spa_pod_builder pod_builder; int width; int height; const struct spa_pod *params[3]; if (!format || id != SPA_PARAM_Format) return; spa_format_video_raw_parse (format, &stream->spa_format); pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer)); width = stream->spa_format.size.width; height = stream->spa_format.size.height; grd_session_vnc_queue_resize_framebuffer (stream->session, width, height); params[0] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int (8, 1, 8), 0); params[1] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int (sizeof (struct spa_meta_header)), 0); params[2] = spa_pod_builder_add_object( &pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id (SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int (CURSOR_META_SIZE (384, 384), CURSOR_META_SIZE (1,1), CURSOR_META_SIZE (384, 384)), 0); pw_stream_update_params (stream->pipewire_stream, params, G_N_ELEMENTS (params)); } static int do_render (struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { GrdVncPipeWireStream *stream = GRD_VNC_PIPEWIRE_STREAM (user_data); GrdVncFrame *frame; g_mutex_lock (&stream->frame_mutex); frame = g_steal_pointer (&stream->pending_frame); g_mutex_unlock (&stream->frame_mutex); if (!frame) return 0; if (grd_session_vnc_is_client_gone (stream->session)) { g_free (frame->data); g_clear_pointer (&frame->rfb_cursor, rfbFreeCursor); g_free (frame); return 0; } if (frame->rfb_cursor) grd_session_vnc_set_cursor (stream->session, frame->rfb_cursor); if (frame->cursor_moved) { grd_session_vnc_move_cursor (stream->session, frame->cursor_x, frame->cursor_y); } if (frame->data) grd_session_vnc_take_buffer (stream->session, frame->data); else grd_session_vnc_flush (stream->session); g_free (frame); return 0; } static GrdVncFrame * process_buffer (GrdVncPipeWireStream *stream, struct spa_buffer *buffer) { size_t size; uint8_t *map; void *src_data; struct spa_meta_cursor *spa_meta_cursor; g_autofree GrdVncFrame *frame = NULL; frame = g_new0 (GrdVncFrame, 1); if (buffer->datas[0].chunk->size == 0) { map = NULL; src_data = NULL; } else if (buffer->datas[0].type == SPA_DATA_MemFd) { size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset; map = mmap (NULL, size, PROT_READ, MAP_PRIVATE, buffer->datas[0].fd, 0); if (map == MAP_FAILED) { g_warning ("Failed to mmap buffer: %s", g_strerror (errno)); return NULL; } src_data = SPA_MEMBER (map, buffer->datas[0].mapoffset, uint8_t); } else if (buffer->datas[0].type == SPA_DATA_DmaBuf) { int fd; fd = buffer->datas[0].fd; size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset; map = mmap (NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { g_warning ("Failed to mmap DMA buffer: %s", g_strerror (errno)); return NULL; } grd_sync_dma_buf (fd, DMA_BUF_SYNC_START); src_data = SPA_MEMBER (map, buffer->datas[0].mapoffset, uint8_t); } else if (buffer->datas[0].type == SPA_DATA_MemPtr) { size = buffer->datas[0].maxsize + buffer->datas[0].mapoffset; map = NULL; src_data = buffer->datas[0].data; } else { return NULL; } if (src_data) { int src_stride; int dst_stride; int width; int height; int y; height = stream->spa_format.size.height; width = stream->spa_format.size.width; src_stride = buffer->datas[0].chunk->stride; dst_stride = grd_session_vnc_get_stride_for_width (stream->session, width); frame->data = g_malloc (height * dst_stride); for (y = 0; y < height; y++) { memcpy (((uint8_t *) frame->data) + y * dst_stride, ((uint8_t *) src_data) + y * src_stride, width * 4); } } if (map) { if (buffer->datas[0].type == SPA_DATA_DmaBuf) grd_sync_dma_buf (buffer->datas[0].fd, DMA_BUF_SYNC_END); munmap (map, size); } spa_meta_cursor = spa_buffer_find_meta_data (buffer, SPA_META_Cursor, sizeof *spa_meta_cursor); if (spa_meta_cursor && spa_meta_cursor_is_valid (spa_meta_cursor)) { struct spa_meta_bitmap *spa_meta_bitmap; GrdPixelFormat format; if (spa_meta_cursor->bitmap_offset) { spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor, spa_meta_cursor->bitmap_offset, struct spa_meta_bitmap); } else { spa_meta_bitmap = NULL; } if (spa_meta_bitmap && spa_meta_bitmap->size.width > 0 && spa_meta_bitmap->size.height > 0 && grd_spa_pixel_format_to_grd_pixel_format (spa_meta_bitmap->format, &format)) { uint8_t *buf; rfbCursorPtr rfb_cursor; buf = SPA_MEMBER (spa_meta_bitmap, spa_meta_bitmap->offset, uint8_t); rfb_cursor = grd_vnc_create_cursor (spa_meta_bitmap->size.width, spa_meta_bitmap->size.height, spa_meta_bitmap->stride, format, buf); rfb_cursor->xhot = spa_meta_cursor->hotspot.x; rfb_cursor->yhot = spa_meta_cursor->hotspot.y; frame->rfb_cursor = rfb_cursor; } else if (spa_meta_bitmap) { frame->rfb_cursor = grd_vnc_create_empty_cursor (1, 1); } frame->cursor_moved = TRUE; frame->cursor_x = spa_meta_cursor->position.x; frame->cursor_y = spa_meta_cursor->position.y; } return g_steal_pointer (&frame); } static void on_stream_process (void *user_data) { GrdVncPipeWireStream *stream = GRD_VNC_PIPEWIRE_STREAM (user_data); GrdPipeWireSource *pipewire_source = (GrdPipeWireSource *) stream->pipewire_source; struct pw_buffer *next_buffer; struct pw_buffer *buffer = NULL; GrdVncFrame *frame; next_buffer = pw_stream_dequeue_buffer (stream->pipewire_stream); while (next_buffer) { buffer = next_buffer; next_buffer = pw_stream_dequeue_buffer (stream->pipewire_stream); if (next_buffer) pw_stream_queue_buffer (stream->pipewire_stream, buffer); } if (!buffer) return; frame = process_buffer (stream, buffer->buffer); g_assert (frame); g_mutex_lock (&stream->frame_mutex); if (stream->pending_frame) { if (!frame->data && stream->pending_frame->data) frame->data = g_steal_pointer (&stream->pending_frame->data); if (!frame->rfb_cursor && stream->pending_frame->rfb_cursor) frame->rfb_cursor = g_steal_pointer (&stream->pending_frame->rfb_cursor); if (!frame->cursor_moved && stream->pending_frame->cursor_moved) { frame->cursor_x = stream->pending_frame->cursor_x; frame->cursor_y = stream->pending_frame->cursor_y; frame->cursor_moved = TRUE; } g_free (stream->pending_frame->data); g_clear_pointer (&stream->pending_frame, g_free); } stream->pending_frame = frame; g_mutex_unlock (&stream->frame_mutex); pw_stream_queue_buffer (stream->pipewire_stream, buffer); pw_loop_invoke (pipewire_source->pipewire_loop, do_render, SPA_ID_INVALID, NULL, 0, false, stream); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .state_changed = on_stream_state_changed, .param_changed = on_stream_param_changed, .process = on_stream_process, }; static gboolean connect_to_stream (GrdVncPipeWireStream *stream, GError **error) { struct pw_stream *pipewire_stream; uint8_t params_buffer[1024]; struct spa_pod_builder pod_builder; struct spa_rectangle min_rect; struct spa_rectangle max_rect; struct spa_fraction min_framerate; struct spa_fraction max_framerate; const struct spa_pod *params[2]; int ret; pipewire_stream = pw_stream_new (stream->pipewire_core, "grd-vnc-pipewire-stream", NULL); min_rect = SPA_RECTANGLE (1, 1); max_rect = SPA_RECTANGLE (INT32_MAX, INT32_MAX); min_framerate = SPA_FRACTION (1, 1); max_framerate = SPA_FRACTION (30, 1); pod_builder = SPA_POD_BUILDER_INIT (params_buffer, sizeof (params_buffer)); params[0] = spa_pod_builder_add_object ( &pod_builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id (SPA_VIDEO_FORMAT_BGRx), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle (&min_rect, &min_rect, &max_rect), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction (&SPA_FRACTION(0, 1)), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction (&min_framerate, &min_framerate, &max_framerate), 0); stream->pipewire_stream = pipewire_stream; pw_stream_add_listener (pipewire_stream, &stream->pipewire_stream_listener, &stream_events, stream); ret = pw_stream_connect (stream->pipewire_stream, PW_DIRECTION_INPUT, stream->src_node_id, (PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_AUTOCONNECT), params, 1); if (ret < 0) { g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (-ret), strerror (-ret)); return FALSE; } return TRUE; } static void on_core_error (void *user_data, uint32_t id, int seq, int res, const char *message) { GrdVncPipeWireStream *stream = GRD_VNC_PIPEWIRE_STREAM (user_data); g_warning ("Pipewire core error: id:%u %s", id, message); if (id == PW_ID_CORE && res == -EPIPE) g_signal_emit (stream, signals[CLOSED], 0); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; GrdVncPipeWireStream * grd_vnc_pipewire_stream_new (GrdSessionVnc *session_vnc, uint32_t src_node_id, GError **error) { g_autoptr (GrdVncPipeWireStream) stream = NULL; GrdPipeWireSource *pipewire_source; grd_maybe_initialize_pipewire (); stream = g_object_new (GRD_TYPE_VNC_PIPEWIRE_STREAM, NULL); stream->session = session_vnc; stream->src_node_id = src_node_id; pipewire_source = create_pipewire_source (); if (!pipewire_source) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create PipeWire source"); return NULL; } stream->pipewire_source = (GSource *) pipewire_source; stream->pipewire_context = pw_context_new (pipewire_source->pipewire_loop, NULL, 0); if (!stream->pipewire_context) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to create pipewire context"); return NULL; } stream->pipewire_core = pw_context_connect (stream->pipewire_context, NULL, 0); if (!stream->pipewire_core) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to connect pipewire context"); return NULL; } pw_core_add_listener (stream->pipewire_core, &stream->pipewire_core_listener, &core_events, stream); if (!connect_to_stream (stream, error)) return NULL; return g_steal_pointer (&stream); } static void grd_vnc_pipewire_stream_finalize (GObject *object) { GrdVncPipeWireStream *stream = GRD_VNC_PIPEWIRE_STREAM (object); /* * We can't clear stream->pipewire_stream before destroying it, as the data * thread in PipeWire might access the variable during destruction. */ if (stream->pipewire_stream) pw_stream_destroy (stream->pipewire_stream); g_clear_pointer (&stream->pipewire_core, pw_core_disconnect); g_clear_pointer (&stream->pipewire_context, pw_context_destroy); if (stream->pipewire_source) { g_source_destroy (stream->pipewire_source); g_clear_pointer (&stream->pipewire_source, g_source_unref); } g_mutex_clear (&stream->frame_mutex); G_OBJECT_CLASS (grd_vnc_pipewire_stream_parent_class)->finalize (object); } static void grd_vnc_pipewire_stream_init (GrdVncPipeWireStream *stream) { g_mutex_init (&stream->frame_mutex); } static void grd_vnc_pipewire_stream_class_init (GrdVncPipeWireStreamClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = grd_vnc_pipewire_stream_finalize; signals[CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } 07070100000076000081A40000000000000000000000016293A07000000583000000000000000000000000000000000000003800000000gnome-remote-desktop-41.3/src/grd-vnc-pipewire-stream.h/* * Copyright (C) 2018 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef GRD_VNC_PIPEWIRE_STREAM_H #define GRD_VNC_PIPEWIRE_STREAM_H #include <glib-object.h> #include <stdint.h> #include "grd-session-vnc.h" #define GRD_TYPE_VNC_PIPEWIRE_STREAM grd_vnc_pipewire_stream_get_type () G_DECLARE_FINAL_TYPE (GrdVncPipeWireStream, grd_vnc_pipewire_stream, GRD, VNC_PIPEWIRE_STREAM, GObject) GrdVncPipeWireStream * grd_vnc_pipewire_stream_new (GrdSessionVnc *session_vnc, uint32_t src_node_id, GError **error); #endif /* GRD_VNC_PIPEWIRE_STREAM_H */ 07070100000077000081A40000000000000000000000016293A07000001AAC000000000000000000000000000000000000002F00000000gnome-remote-desktop-41.3/src/grd-vnc-server.c/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #include "config.h" #include "grd-vnc-server.h" #include <gio/gio.h> #include <rfb/rfb.h> #include "grd-context.h" #include "grd-session-vnc.h" enum { PROP_0, PROP_CONTEXT, }; struct _GrdVncServer { GSocketService parent; GList *sessions; GList *stopped_sessions; guint idle_task; GrdContext *context; }; G_DEFINE_TYPE (GrdVncServer, grd_vnc_server, G_TYPE_SOCKET_SERVICE); GrdContext * grd_vnc_server_get_context (GrdVncServer *vnc_server) { return vnc_server->context; } GrdVncServer * grd_vnc_server_new (GrdContext *context) { GrdVncServer *vnc_server; vnc_server = g_object_new (GRD_TYPE_VNC_SERVER, "context", context, NULL); return vnc_server; } static void grd_vnc_server_cleanup_stopped_sessions (GrdVncServer *vnc_server) { g_list_free_full (vnc_server->stopped_sessions, g_object_unref); vnc_server->stopped_sessions = NULL; } static gboolean cleanup_stopped_sessions_idle (GrdVncServer *vnc_server) { grd_vnc_server_cleanup_stopped_sessions (vnc_server); vnc_server->idle_task = 0; return G_SOURCE_REMOVE; } static void on_session_stopped (GrdSession *session, GrdVncServer *vnc_server) { g_debug ("VNC session stopped"); vnc_server->stopped_sessions = g_list_append (vnc_server->stopped_sessions, session); vnc_server->sessions = g_list_remove (vnc_server->sessions, session); if (!vnc_server->idle_task) { vnc_server->idle_task = g_idle_add ((GSourceFunc) cleanup_stopped_sessions_idle, vnc_server); } } static gboolean on_incoming (GSocketService *service, GSocketConnection *connection) { GrdVncServer *vnc_server = GRD_VNC_SERVER (service); GrdSessionVnc *session_vnc; g_debug ("New incoming VNC connection"); if (vnc_server->sessions) { /* TODO: Add the rfbScreen instance to GrdVncServer to support multiple * sessions. */ g_debug ("Refusing new VNC connection: already an active session"); return TRUE; } session_vnc = grd_session_vnc_new (vnc_server, connection); vnc_server->sessions = g_list_append (vnc_server->sessions, session_vnc); grd_context_add_session (vnc_server->context, GRD_SESSION (session_vnc)); g_signal_connect (session_vnc, "stopped", G_CALLBACK (on_session_stopped), vnc_server); return TRUE; } gboolean grd_vnc_server_start (GrdVncServer *vnc_server, GError **error) { GrdSettings *settings = grd_context_get_settings (vnc_server->context); if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (vnc_server), grd_settings_get_vnc_port (settings), NULL, error)) return FALSE; g_signal_connect (vnc_server, "incoming", G_CALLBACK (on_incoming), NULL); return TRUE; } static void stop_and_unref_session (GrdSession *session) { grd_session_stop (session); g_object_unref (session); } static void grd_vnc_server_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GrdVncServer *vnc_server = GRD_VNC_SERVER (object); switch (prop_id) { case PROP_CONTEXT: vnc_server->context = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void grd_vnc_server_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GrdVncServer *vnc_server = GRD_VNC_SERVER (object); switch (prop_id) { case PROP_CONTEXT: g_value_set_object (value, vnc_server->context); default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void grd_vnc_server_dispose (GObject *object) { GrdVncServer *vnc_server = GRD_VNC_SERVER (object); if (vnc_server->idle_task) { g_source_remove (vnc_server->idle_task); vnc_server->idle_task = 0; } if (vnc_server->stopped_sessions) { grd_vnc_server_cleanup_stopped_sessions (vnc_server); } if (vnc_server->sessions) { g_list_free_full (vnc_server->sessions, (GDestroyNotify) stop_and_unref_session); vnc_server->sessions = NULL; } G_OBJECT_CLASS (grd_vnc_server_parent_class)->dispose (object); } static void grd_vnc_server_constructed (GObject *object) { GrdVncServer *vnc_server = GRD_VNC_SERVER (object); if (grd_context_get_debug_flags (vnc_server->context) & GRD_DEBUG_VNC) rfbLogEnable (1); else rfbLogEnable (0); G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object); } static void grd_vnc_server_init (GrdVncServer *vnc_server) { } static void grd_vnc_server_class_init (GrdVncServerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = grd_vnc_server_set_property; object_class->get_property = grd_vnc_server_get_property; object_class->dispose = grd_vnc_server_dispose; object_class->constructed = grd_vnc_server_constructed; g_object_class_install_property (object_class, PROP_CONTEXT, g_param_spec_object ("context", "GrdContext", "The GrdContext instance", GRD_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } 07070100000078000081A40000000000000000000000016293A07000000566000000000000000000000000000000000000002F00000000gnome-remote-desktop-41.3/src/grd-vnc-server.h/* * Copyright (C) 2015 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * Written by: * Jonas Ådahl <jadahl@gmail.com> */ #ifndef GRD_VNC_SERVER_H #define GRD_VNC_SERVER_H #include <gio/gio.h> #include <glib-object.h> #include "grd-types.h" #define GRD_TYPE_VNC_SERVER (grd_vnc_server_get_type ()) G_DECLARE_FINAL_TYPE (GrdVncServer, grd_vnc_server, GRD, VNC_SERVER, GSocketService); GrdContext *grd_vnc_server_get_context (GrdVncServer *vnc_server); gboolean grd_vnc_server_start (GrdVncServer *vnc_server, GError **error); GrdVncServer *grd_vnc_server_new (GrdContext *context); #endif /* GRD_VNC_SERVER_H */ 07070100000079000081A40000000000000000000000016293A07000001131000000000000000000000000000000000000002A00000000gnome-remote-desktop-41.3/src/meson.builddeps = [ cairo_dep, glib_dep, gio_dep, gio_unix_dep, pipewire_dep, libsecret_dep, libnotify_dep, ] daemon_sources = files([ 'grd-clipboard.c', 'grd-clipboard.h', 'grd-context.c', 'grd-context.h', 'grd-daemon.c', 'grd-daemon.h', 'grd-damage-utils.c', 'grd-damage-utils.h', 'grd-mime-type.c', 'grd-mime-type.h', 'grd-pipewire-utils.c', 'grd-pipewire-utils.h', 'grd-private.h', 'grd-prompt.c', 'grd-prompt.h', 'grd-session.c', 'grd-session.h', 'grd-settings.c', 'grd-settings.h', 'grd-stream.c', 'grd-stream.h', 'grd-types.h', ]) if have_rdp daemon_sources += files([ 'grd-clipboard-rdp.c', 'grd-clipboard-rdp.h', 'grd-rdp-event-queue.c', 'grd-rdp-event-queue.h', 'grd-rdp-frame-info.h', 'grd-rdp-fuse-clipboard.c', 'grd-rdp-fuse-clipboard.h', 'grd-rdp-gfx-frame-log.c', 'grd-rdp-gfx-frame-log.h', 'grd-rdp-gfx-surface.c', 'grd-rdp-gfx-surface.h', 'grd-rdp-graphics-pipeline.c', 'grd-rdp-graphics-pipeline.h', 'grd-rdp-network-autodetection.c', 'grd-rdp-network-autodetection.h', 'grd-rdp-pipewire-stream.c', 'grd-rdp-pipewire-stream.h', 'grd-rdp-private.h', 'grd-rdp-sam.c', 'grd-rdp-sam.h', 'grd-rdp-server.c', 'grd-rdp-server.h', 'grd-rdp-surface.c', 'grd-rdp-surface.h', 'grd-session-rdp.c', 'grd-session-rdp.h', ]) deps += [ freerdp_dep, freerdp_client_dep, freerdp_server_dep, fuse_dep, winpr_dep, xkbcommon_dep, ] if have_nvenc daemon_sources += files([ 'grd-rdp-nvenc.c', 'grd-rdp-nvenc.h', ]) deps += [ dl_dep, nvenc_dep, ] endif endif if have_vnc daemon_sources += files([ 'grd-clipboard-vnc.c', 'grd-clipboard-vnc.h', 'grd-session-vnc.c', 'grd-session-vnc.h', 'grd-vnc-cursor.c', 'grd-vnc-cursor.h', 'grd-vnc-pipewire-stream.c', 'grd-vnc-pipewire-stream.h', 'grd-vnc-server.c', 'grd-vnc-server.h', ]) deps += [ libvncserver_dep, ] endif gen_daemon_sources = [] gen_daemon_sources += gnome.gdbus_codegen('grd-dbus-screen-cast', 'org.gnome.Mutter.ScreenCast.xml', interface_prefix: 'org.gnome.Mutter.', namespace: 'GrdDBus') gen_daemon_sources += gnome.gdbus_codegen('grd-dbus-remote-desktop', 'org.gnome.Mutter.RemoteDesktop.xml', interface_prefix: 'org.gnome.Mutter.', namespace: 'GrdDBus') daemon_sources += gen_daemon_sources control_sources = ([ 'grd-control.c' ]) executable('gnome-remote-desktop-daemon', daemon_sources, dependencies: deps, include_directories: [configinc], install: true, install_dir: libexecdir) executable('gnome-remote-desktop-control', control_sources, dependencies: [glib_dep, gio_dep], include_directories: [configinc], install : false) service_config = configuration_data() service_config.set('libexecdir', libexecdir) configure_file(input: 'gnome-remote-desktop.service.in', output: 'gnome-remote-desktop.service', configuration: service_config, install_dir: servicedir) custom_target('gsettings-enums', input: 'grd-enums.h', output: 'org.gnome.desktop.remote-desktop.enums.xml', install: true, install_dir: join_paths(datadir, 'glib-2.0', 'schemas'), capture: true, command: ['glib-mkenums', '--comments', '<!-- @comment@ -->', '--fhead', '<schemalist>', '--vhead', ' <@type@ id="org.gnome.desktop.remote-desktop.@EnumName@">', '--vprod', ' <value nick="@valuenick@" value="@valuenum@"/>', '--vtail', ' </@type@>', '--ftail', '</schemalist>', '@INPUT@']) schema = 'org.gnome.desktop.remote-desktop.gschema.xml' generated_schema = configure_file(output: schema, input: schema + '.in', copy: true) install_data(generated_schema, install_dir: schemadir) gnome.compile_schemas() 0707010000007A000081A40000000000000000000000016293A07000002C5A000000000000000000000000000000000000004100000000gnome-remote-desktop-41.3/src/org.gnome.Mutter.RemoteDesktop.xml<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'> <node> <!-- org.gnome.Mutter.RemoteDesktop: @short_description: Remote desktop interface This API is private and not intended to be used outside of the integrated system that uses libmutter. No compatibility between versions are promised. --> <interface name="org.gnome.Mutter.RemoteDesktop"> <!-- CreateSession: @session_path: Path to the new session object --> <method name="CreateSession"> <arg name="session_path" type="o" direction="out" /> </method> <!-- SupportedDeviceTypes: @short_description: Bit mask of supported device types Device types: 1: keyboard 2: pointer 4: touchscreen --> <property name="SupportedDeviceTypes" type="u" access="read" /> <!-- Version: @short_description: API version --> <property name="Version" type="i" access="read" /> </interface> <!-- org.gnome.Mutter.RemoteDesktop.Session: @short_description: Remote desktop session --> <interface name="org.gnome.Mutter.RemoteDesktop.Session"> <!-- SessionId: An identification string used for identifying a remote desktop session. It can be used to associate screen cast sessions with a remote desktop session. --> <property name="SessionId" type="s" access="read" /> <!-- Start: Start the remote desktop session --> <method name="Start" /> <!-- Stop: Stop the remote desktop session --> <method name="Stop" /> <!-- Closed: The session has closed. A session doesn't have to have been started before it may be closed. After it being closed, it can no longer be used. --> <signal name="Closed" /> <!-- NotifyKeyboardKeycode: A key identified by an evdev keycode was pressed or released --> <method name="NotifyKeyboardKeycode"> <arg name="keycode" type="u" direction="in" /> <arg name="state" type="b" direction="in" /> </method> <!-- NotifyKeyboardKeysym: A key identified by a keysym was pressed or released --> <method name="NotifyKeyboardKeysym"> <arg name="keysym" type="u" direction="in" /> <arg name="state" type="b" direction="in" /> </method> <!-- NotifyPointerButton: A pointer button was pressed or released --> <method name="NotifyPointerButton"> <arg name="button" type="i" direction="in" /> <arg name="state" type="b" direction="in" /> </method> <!-- NotifyPointerAxis: A smooth pointer axis event notification. Relative motion deltas are to be interpreted as pixel movement of a standardized mouse. Additionally to the smooth pointer axis event notification, an emulated discrete pointer axis event notification is emitted based on the submitted accumulated smooth scrolling steps. The base for these emulated discrete pointer axis event is the discrete step with the value 10.0. This means that for a delta dx (or dy) with the value 10.0 one emulated discrete scrolling event is emitted. For a high resolution smooth pointer axis event, a smaller value is submitted for each scrolling step. This means: For a double resolution mouse wheel one emulated discrete event is emitted for 2 smooth pointer axis events with each having the value 5.0. Possible @flags: 1: finish - scroll motion was finished (e.g. fingers lifted) 2: source_wheel - The scroll event is originated by a mouse wheel. 4: source_finger - The scroll event is originated by one or more fingers on the device (eg. touchpads). 8: source_continuous - The scroll event is originated by the motion of some device (eg. a scroll button is set). Maximum one of the @flags 'source_wheel', 'source_finger', 'source_continuous' may be specified. If no source flag is specified, `source_finger` is assumed. --> <method name="NotifyPointerAxis"> <arg name="dx" type="d" direction="in" /> <arg name="dy" type="d" direction="in" /> <arg name="flags" type="u" direction="in" /> </method> <!-- NotifyPointerAxisDiscrete: A discrete pointer axis event notification --> <method name="NotifyPointerAxisDiscrete"> <arg name="axis" type="u" direction="in" /> <arg name="steps" type="i" direction="in" /> </method> <!-- NotifyPointerMotionRelative: A relative pointer motion event notification --> <method name="NotifyPointerMotionRelative"> <arg name="dx" type="d" direction="in" /> <arg name="dy" type="d" direction="in" /> </method> <!-- NotifyPointerMotionAbsolute: A absolute pointer motion event notification --> <method name="NotifyPointerMotionAbsolute"> <arg name="stream" type="s" direction="in" /> <arg name="x" type="d" direction="in" /> <arg name="y" type="d" direction="in" /> </method> <!-- NotifyTouchDown: A absolute pointer motion event notification --> <method name="NotifyTouchDown"> <arg name="stream" type="s" direction="in" /> <arg name="slot" type="u" direction="in" /> <arg name="x" type="d" direction="in" /> <arg name="y" type="d" direction="in" /> </method> <!-- NotifyTouchMotion: A absolute pointer motion event notification --> <method name="NotifyTouchMotion"> <arg name="stream" type="s" direction="in" /> <arg name="slot" type="u" direction="in" /> <arg name="x" type="d" direction="in" /> <arg name="y" type="d" direction="in" /> </method> <!-- NotifyTouchUp: A absolute pointer motion event notification --> <method name="NotifyTouchUp"> <arg name="slot" type="u" direction="in" /> </method> <!-- EnableClipboard: @options: Options for the clipboard Available @options include: * "mime-types" (as): List of mime types, for which the clipboard of the remote desktop client has content. Each mime-type is in string form, e.g. "image/jpeg", "text/plain", etc.. If this list is included in @options, then this call is equivalent to calling 'EnableClipboard' and 'SetSelection' atomically. Enables the clipboard for the remote desktop client which will allow it to call the methods 'SetSelection', 'DisableClipboard', 'SelectionWrite', 'SelectionWriteDone', 'SelectionRead'. The 'SelectionOwnerChanged' signal will also be emitted when the selection owner changes to inform the API user of new clipboard mime types, and the 'SelectionTransfer' signal will be emitted to request the advertised clipboard content of a mime type. --> <method name="EnableClipboard"> <arg name="options" type="a{sv}" direction="in" /> </method> <!-- DisableClipboard: Unregisters all clipboard types that were advertised by the remote desktop client. The 'SelectionOwnerChanged' or 'SelectionTransfer' signals will not be emitted any more. Any 'SelectionTransfer' signals that weren't answered yet with a 'SelectionWriteDone' call, will be answered with a 'SelectionWriteDone' call where 'success' is 'false'. --> <method name="DisableClipboard" /> <!-- SetSelection: @options: Options for the clipboard selection Available @options include: * "mime-types" (as): List of mime types, for which the clipboard of the remote desktop client has content. Each mime-type is in string form, e.g. "image/jpeg", "text/plain", etc.. Sets the owner of the clipboard formats in 'mime-types' in @options to the remote desktop client, i.e. the remote desktop client has data for these advertised clipboard formats. --> <method name="SetSelection"> <arg name="options" type="a{sv}" direction="in" /> </method> <!-- SelectionWrite: @serial: The serial of the request where this answer is directed to @fd: The file descriptor where the data will be written to Answer to 'SelectionTransfer' signal. Contains the fd where the clipboard content will be written to. --> <method name="SelectionWrite"> <arg name="serial" type="u" direction="in" /> <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/> <arg name="fd" type="h" direction="out" /> </method> <!-- SelectionWriteDone: @serial: The serial of the request where this answer is directed to @success: A boolean which indicates whether the transfer of the clipboard data was successful ('true') or not ('false'). Notifies that the transfer of the clipboard data has either completed successfully, or failed. --> <method name="SelectionWriteDone"> <arg name="serial" type="u" direction="in" /> <arg name="success" type="b" direction="in" /> </method> <!-- SelectionRead: @mime_type: The mime-type string of the requested format @fd: The file descriptor where the data will be written to Transfer the clipboard content given the specified mime type to the method caller via a file descriptor. It is the callee that creates the file descriptor. --> <method name="SelectionRead"> <arg name="mime_type" type="s" direction="in" /> <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/> <arg name="fd" type="h" direction="out" /> </method> <!-- SelectionOwnerChanged: @options: Options for the clipboard selection Available @options include: * "mime-types" (as): List of mime types, for which the clipboard of the remote desktop client has content. Each mime-type is in string form, e.g. "image/jpeg", "text/plain", etc.. * "session-is-owner" (b): 'true', if the remote desktop clients clipboard is already owner of these types, else 'false'. Informs the remote desktop client of new clipboard formats that are available. --> <signal name="SelectionOwnerChanged"> <arg name="options" type="a{sv}" direction="in" /> </signal> <!-- SelectionTransfer: @mime_type: The mime-type string of the requested format @serial: The serial, that the answer of this particular request, MUST use Requests the data for a clipboard format from the remote desktop client. MUST NOT be called when the remote desktop clients clipboard is (already) disabled. --> <signal name="SelectionTransfer"> <arg name="mime_type" type="s" direction="in" /> <arg name="serial" type="u" direction="in" /> </signal> <property name="CapsLockState" type="b" access="read" /> <property name="NumLockState" type="b" access="read" /> </interface> </node> 0707010000007B000081A40000000000000000000000016293A07000000922000000000000000000000000000000000000003E00000000gnome-remote-desktop-41.3/src/org.gnome.Mutter.ScreenCast.xml<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'> <node> <!-- org.gnome.Mutter.ScreenCast: @short_description: Screen cast interface Available @properties (see RecordMonitor and RecordWindow) --> <interface name="org.gnome.Mutter.ScreenCast"> <!-- CreateSession: @properties: Properties @session: Path to the new session object * "remote-desktop-session-id" (s): The ID of a remote desktop session. Remote desktop driven screen casts are started and stopped by the remote desktop session. --> <method name="CreateSession"> <arg name="properties" type="a{sv}" direction="in" /> <arg name="session_path" type="o" direction="out" /> </method> </interface> <interface name="org.gnome.Mutter.ScreenCast.Session"> <!-- Start: Start the screen cast session --> <method name="Start" /> <!-- Stop: Stop the screen cast session --> <method name="Stop" /> <!-- Closed: The session has closed. --> <signal name="Closed" /> <!-- RecordMonitor: @connector: Connector of the monitor to record @properties: Properties @session: Path to the new session object Record a single monitor. Available @properties include: (none) --> <method name="RecordMonitor"> <arg name="connector" type="s" direction="in" /> <arg name="properties" type="a{sv}" direction="in" /> <arg name="stream_path" type="o" direction="out" /> </method> <!-- RecordWindow: @properties: Properties @session: Path to the new session object Record a single monitor. Available @properties include: (none) --> <method name="RecordWindow"> <arg name="properties" type="a{sv}" direction="in" /> <arg name="stream_path" type="o" direction="out" /> </method> </interface> <!-- org.gnome.Mutter.ScreenCast.Session: @short_description: Screen cast session --> <interface name="org.gnome.Mutter.ScreenCast.Stream"> <!-- PipeWireStreamAdded: --> <signal name="PipeWireStreamAdded"> <annotation name="org.gtk.GDBus.C.Name" value="pipewire-stream-added"/> <arg name="node_id" type="u" direction="out" /> </signal> </interface> </node> 0707010000007C000081A40000000000000000000000016293A070000008D2000000000000000000000000000000000000004E00000000gnome-remote-desktop-41.3/src/org.gnome.desktop.remote-desktop.gschema.xml.in<schemalist> <schema id='org.gnome.desktop.remote-desktop' path='/org/gnome/desktop/remote-desktop/'> </schema> <schema id='org.gnome.desktop.remote-desktop.rdp' path='/org/gnome/desktop/remote-desktop/rdp/'> <key name='tls-cert' type='s'> <default>''</default> <summary>Path to the certificate file</summary> <description> In order to be able to use RDP with TLS Security, both the private key file and the certificate file need to be provided to the RDP server. </description> </key> <key name='tls-key' type='s'> <default>''</default> <summary>Path to the private key file</summary> <description> In order to be able to use RDP with TLS Security, both the private key file and the certificate file need to be provided to the RDP server. </description> </key> <key name='view-only' type='b'> <default>true</default> <summary>Only allow remote connections to view the screen content</summary> <description> When view-only is true, remote RDP connections cannot manipulate input devices (e.g. mouse and keyboard). </description> </key> </schema> <schema id='org.gnome.desktop.remote-desktop.vnc' path='/org/gnome/desktop/remote-desktop/vnc/'> <key name='view-only' type='b'> <default>true</default> <summary>Only allow remote connections to view the screen content</summary> <description> When view-only is true, remote VNC connections cannot manipulate input devices (e.g. mouse and keyboard). </description> </key> <key name='auth-method' enum='org.gnome.desktop.remote-desktop.GrdVncAuthMethod'> <default>'prompt'</default> <summary>Method used to authenticate VNC connections</summary> <description> The VNC authentication method describes how a remote connection is authenticated. It can currently be done in two different ways: * prompt - by prompting the user for each new connection, requiring a person with physical access to the workstation to explicitly approve the new connection. * password - by requiring the remote client to provide a known password </description> </key> </schema> </schemalist> 0707010000007D000041ED0000000000000000000000016293A07000000000000000000000000000000000000000000000002000000000gnome-remote-desktop-41.3/tests0707010000007E000081A40000000000000000000000016293A07000000212000000000000000000000000000000000000002C00000000gnome-remote-desktop-41.3/tests/meson.buildif have_vnc test_client_vnc = executable( 'test-client-vnc', files(['test-client-vnc.c']), dependencies: [glib_dep, libvncclient_dep], include_directories: [configinc], install: false) test_runner = find_program('vnc-test-runner.sh') test_env = environment() test_env.set('TEST_SRCDIR', top_srcdir) test_env.set('TEST_BUILDDIR', builddir) test_env.set('NO_AT_BRIDGE', '1') test('gnome-remote-desktop/vnc', test_runner, env: test_env, is_parallel: false, timeout: 10, ) endif 0707010000007F000081ED0000000000000000000000016293A07000000A22000000000000000000000000000000000000003100000000gnome-remote-desktop-41.3/tests/run-vnc-tests.py#!/usr/bin/python3 import dbus import os import time import os.path import subprocess import sys from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop mutter = None DBusGMainLoop(set_as_default=True) loop = GLib.MainLoop() bus = dbus.SessionBus() vnc_client_failed = None vnc_server_failed = None os.environ['GNOME_REMOTE_DESKTOP_TEST_VNC_PASSWORD'] = 'secret' os.environ['MUTTER_DEBUG_DUMMY_MODE_SPECS'] = '1024x768' def run_vnc_test_client(): print("Running VNC test client") global vnc_client_failed builddir=os.getenv("TEST_BUILDDIR") vnc_test_client_path = os.path.join(builddir, 'tests', 'test-client-vnc') vnc_test_client = subprocess.Popen([vnc_test_client_path, 'localhost:5912'], stderr=subprocess.STDOUT) vnc_test_client.wait() if vnc_test_client.wait() != 0: print("VNC test client exited incorrectly") vnc_client_failed = True else: vnc_client_failed = False def start_vnc_server(): print("Starting VNC server") global vnc_server builddir=os.getenv("TEST_BUILDDIR") vnc_server_path = os.path.join(builddir, 'src', 'gnome-remote-desktop-daemon') vnc_server = subprocess.Popen([vnc_server_path, '--vnc-port', '5912'], stderr=subprocess.STDOUT) time.sleep(5) def stop_vnc_server(): print("Stopping VNC server") global vnc_server global vnc_server_failed vnc_server.terminate() vnc_server.wait() if vnc_server.returncode != -15 and vnc_server.returncode != 0: print("VNC server exited incorrectly: %d"%(vnc_server.returncode)) vnc_server_failed = True else: vnc_server_failed = False def remote_desktop_name_appeared_cb(name): if name == '': return print("Remote desktop capable display server appeared") start_vnc_server() run_vnc_test_client() stop_vnc_server() stop_mutter() def start_mutter(): global mutter print("Starting mutter") mutter = subprocess.Popen(['mutter', '--nested', '--wayland'], stderr=subprocess.STDOUT) def stop_mutter(): global mutter global loop if mutter == None: print("no mutter") return print("Stopping mutter") mutter.terminate() print("Waiting for mutter to terminate") if mutter.wait() != 0: print("Mutter exited incorrectly") sys.exit(1) print("Done") loop.quit() bus.watch_name_owner('org.gnome.Mutter.RemoteDesktop', remote_desktop_name_appeared_cb) start_mutter() loop.run() if vnc_server_failed != False or vnc_client_failed != False: sys.exit(1) else: sys.exit(0) 07070100000080000081A40000000000000000000000016293A07000000C73000000000000000000000000000000000000003200000000gnome-remote-desktop-41.3/tests/test-client-vnc.c/* * Copyright (C) 2019 Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #include "config.h" #include <glib.h> #include <rfb/rfbclient.h> #include <stdio.h> static gboolean saw_correct_size = FALSE; static rfbBool handle_malloc_framebuffer (rfbClient *rfb_client) { if (rfb_client->width == 1024 && rfb_client->height == 768) saw_correct_size = TRUE; g_clear_pointer (&rfb_client->frameBuffer, g_free); rfb_client->frameBuffer = g_malloc0 (rfb_client->width * rfb_client->height * rfb_client->format.bitsPerPixel / 4); return TRUE; } static void handle_got_framebuffer_update (rfbClient *rfb_client, int x, int y, int width, int height) { if (saw_correct_size) exit(EXIT_SUCCESS); } static char * handle_get_password (rfbClient *rfb_client) { const char *test_password; test_password = g_getenv ("GNOME_REMOTE_DESKTOP_TEST_VNC_PASSWORD"); g_assert (test_password); return g_strdup (test_password); } int main (int argc, char **argv) { rfbClient *rfb_client; const int bits_per_sample = 8; const int samples_per_pixel = 3; const int bytes_per_pixel = 4; rfb_client = rfbGetClient (bits_per_sample, samples_per_pixel, bytes_per_pixel); rfb_client->MallocFrameBuffer = handle_malloc_framebuffer; rfb_client->canHandleNewFBSize = TRUE; rfb_client->GotFrameBufferUpdate = handle_got_framebuffer_update; rfb_client->GetPassword = handle_get_password; rfb_client->listenPort = LISTEN_PORT_OFFSET; rfb_client->listen6Port = LISTEN_PORT_OFFSET; if (!rfbInitClient (rfb_client, &argc, argv)) { g_warning ("Failed to initialize VNC client"); return EXIT_FAILURE; } while (TRUE) { int ret; const int timeout_us = 500; ret = WaitForMessage (rfb_client, timeout_us); if (ret < 0) { g_warning ("WaitForMessage failed"); rfbClientCleanup (rfb_client); return EXIT_FAILURE; } else if (ret > 0) { if (!HandleRFBServerMessage (rfb_client)) { g_warning ("HandleRFBServerMessage failed"); rfbClientCleanup (rfb_client); return EXIT_FAILURE; } } } } 07070100000081000081ED0000000000000000000000016293A0700000006A000000000000000000000000000000000000003300000000gnome-remote-desktop-41.3/tests/vnc-test-runner.sh#!/usr/bin/env bash dbus-run-session -- xvfb-run -s '+iglx -noreset' $TEST_SRCDIR/tests/run-vnc-tests.py 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1626 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor