File gnome-remote-desktop-41.3.obscpio of Package gnome-remote-desktop.26665

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, &region_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, &register_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
openSUSE Build Service is sponsored by