Full Code of H-M-H/Weylus for AI

master 38a01a8f8e42 cached
69 files
414.1 KB
106.5k tokens
760 symbols
1 requests
Download .txt
Showing preview only (435K chars total). Download the full file or copy to clipboard to get everything.
Repository: H-M-H/Weylus
Branch: master
Commit: 38a01a8f8e42
Files: 69
Total size: 414.1 KB

Directory structure:
gitextract_cqulqu0f/

├── .clang-format
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── CONTRIBUTORS
├── Cargo.toml
├── LICENSE
├── Readme.md
├── build.rs
├── build_in_local_container.sh
├── compile_flags.txt
├── deps/
│   ├── awk.patch
│   ├── build.sh
│   ├── clean.sh
│   ├── clean_all.sh
│   ├── command_limit.patch
│   ├── download.sh
│   ├── ffmpeg.sh
│   ├── hashes.sh
│   ├── libva.sh
│   ├── nv-codec-headers.sh
│   ├── refs.sh
│   ├── update_hashes.sh
│   └── x264.sh
├── docker/
│   ├── Dockerfile
│   └── Dockerfile_alpine
├── docker_build.sh
├── lib/
│   ├── encode_video.c
│   ├── error.c
│   ├── error.h
│   ├── linux/
│   │   ├── uinput.c
│   │   ├── uinput_info.md
│   │   ├── xcapture.c
│   │   ├── xhelper.c
│   │   └── xhelper.h
│   ├── log.c
│   └── log.h
├── src/
│   ├── capturable/
│   │   ├── captrs_capture.rs
│   │   ├── core_graphics.rs
│   │   ├── mod.rs
│   │   ├── pipewire.rs
│   │   ├── remote_desktop_dbus.rs
│   │   ├── testsrc.rs
│   │   ├── win_ctx.rs
│   │   └── x11.rs
│   ├── cerror.rs
│   ├── config.rs
│   ├── gui.rs
│   ├── input/
│   │   ├── autopilot_device.rs
│   │   ├── autopilot_device_win.rs
│   │   ├── device.rs
│   │   ├── mod.rs
│   │   ├── uinput_device.rs
│   │   └── uinput_keys.rs
│   ├── log.rs
│   ├── main.rs
│   ├── protocol.rs
│   ├── strings/
│   │   └── uinput_error.txt
│   ├── video.rs
│   ├── web.rs
│   ├── websocket.rs
│   └── weylus.rs
├── ts/
│   └── lib.ts
├── tsconfig.json
├── weylus.desktop
├── weylus_tls.sh
└── www/
    ├── static/
    │   ├── access_code.html
    │   └── style.css
    └── templates/
        └── index.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
Language: Cpp
Standard: Latest
BasedOnStyle: LLVM

ColumnLimit: 100
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation

BreakBeforeBraces: Allman

AccessModifierOffset: -4

PointerAlignment: Left

AlignAfterOpenBracket: AlwaysBreak
BinPackArguments: false
BinPackParameters: false


================================================
FILE: .github/FUNDING.yml
================================================
github: H-M-H
liberapay: HMH


================================================
FILE: .github/workflows/build.yml
================================================
name: Build

on:
  push:
    branches: [ '*' ]
    tags:
      - v*
  pull_request:
    branches: [ master ]

jobs:
  build-docker:
    runs-on: ubuntu-latest
    container: docker://hhmhh/weylus_build:latest
    steps:
    - uses: actions/checkout@v6
    - uses: actions/cache@v5
      with:
        path: deps/dist*
        key: ${{ runner.os }}-deps-${{ hashFiles('deps/*.sh', 'deps/*.patch') }}
    - uses: actions/cache@v5
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
    - name: Build
      run: ./docker_build.sh
      shell: bash
    - name: Artifacts1
      uses: actions/upload-artifact@v6
      with:
        name: linux
        path: packages/weylus-linux.zip
    - name: Artifacts2
      uses: actions/upload-artifact@v6
      with:
        name: linux-deb
        path: packages/Weylus*.deb
    - name: Artifacts3
      uses: actions/upload-artifact@v6
      with:
        name: windows
        path: packages/weylus-windows.zip
    - name: Publish
      uses: softprops/action-gh-release@v2
      if: startsWith(github.ref, 'refs/tags/')
      with:
          files: |
            packages/weylus-linux.zip
            packages/Weylus*.deb
            packages/weylus-windows.zip
          prerelease: false
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  build-docker-alpine:
    runs-on: ubuntu-latest
    container: docker://hhmhh/weylus_build_alpine:latest
    steps:
    - uses: actions/checkout@v6
    - uses: actions/cache@v5
      with:
        path: deps/dist*
        key: ${{ runner.os }}-alpine-deps-${{ hashFiles('deps/*.sh', 'deps/*.patch') }}
    - uses: actions/cache@v5
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-alpine-cargo-${{ hashFiles('Cargo.lock') }}
    - name: Build
      run: RUSTFLAGS='-C target-feature=-crt-static' cargo build --release && cd target/release && tar czf weylus-linux-alpine-musl.tar.gz weylus
      shell: bash
    - name: Artifacts1
      uses: actions/upload-artifact@v6
      with:
        name: linux-alpine-musl
        path: target/release/weylus-linux-alpine-musl.tar.gz
    - name: Publish
      uses: softprops/action-gh-release@v2
      if: startsWith(github.ref, 'refs/tags/')
      with:
          files: |
            target/release/weylus-linux-alpine-musl.tar.gz
          prerelease: false
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  build-macos:
    strategy:
      matrix:
        os: [macos-latest, macos-15-intel] # -latest is for Apple Silicon, -15-intel is for Intel
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v6
    - uses: actions/cache@v5
      with:
        path: deps/dist
        key: ${{ runner.os }}-deps-${{ hashFiles('deps/*.sh', 'deps/*.patch') }}
    - uses: actions/cache@v5
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
    - name: Download deps
      run: |
        npm install -g typescript
        brew install nasm
        cargo install cargo-bundle
      shell: bash
    - name: Build
      # run: MACOSX_DEPLOYMENT_TARGET=10.13 cargo bundle --release
      run: cargo bundle --release
    - name: Package
      run: |
        MACOS_BUILD_NAME=macos-$([ "${{ matrix.os }}" == "macos-latest" ] && echo "arm" || echo "intel")
        echo "MACOS_BUILD_NAME=$MACOS_BUILD_NAME" >> $GITHUB_ENV
        cd target/release/bundle/osx/ && zip -r ${MACOS_BUILD_NAME}.zip Weylus.app
    - name: Artifacts
      uses: actions/upload-artifact@v6
      with:
        name: ${{ env.MACOS_BUILD_NAME }}
        path: target/release/bundle/osx/${{ env.MACOS_BUILD_NAME }}.zip
    - name: ArtifactsDebug
      if: failure()
      uses: actions/upload-artifact@v6
      with:
        name: ${{ runner.os }}-ffbuild
        path: |
          deps/ffmpeg/ffbuild
    - name: Debug via SSH
      if: failure()
      uses: luchihoratiu/debug-via-ssh@main
      with:
        NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }}
        SSH_PASS: ${{ secrets.SSH_PASS }}
        NGROK_REGION: eu
    - name: Publish
      uses: softprops/action-gh-release@v2
      if: startsWith(github.ref, 'refs/tags/')
      with:
          files: |
            target/release/bundle/osx/macOS.zip
          prerelease: false
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
/.vscode
/target
/deps/ffmpeg
/deps/x264
/deps/dist*
/deps/nv-codec-headers
/deps/libva
c_helper/target
c_helper/Cargo.lock
*.js
*.js.map
*.tar.gz


================================================
FILE: CONTRIBUTORS
================================================
If you want to contribute to Weylus you have to agree to license your
contributions under the 3-Clause BSD License. To do so please add yourself to
the list of contributors below.

List of Contributors:

**************************
Robert Schroll
Daniel Rutz
Philipp Urlbauer
OmegaRogue
**************************

3-Clause BSD License

Copyright 2020-Present above Contributors.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: Cargo.toml
================================================
[package]
name = "weylus"
version = "0.11.4"
authors = ["HMH <henry@freedesk.net>"]
license = "AGPL-3.0-or-later"
edition = "2021"
description = "Use your iPad or Android tablet as graphic tablet."

[dependencies]
autopilot = { git = "https://github.com/H-M-H/autopilot-rs.git", rev = "63eed09c715bfb665bb23172a3930a528e11691c" }
bitflags = { version = "^2.6", features = ["serde"] }
bytes = "1.7.1"
clap = { version = "4.5.18", features = ["derive"] }
clap_complete = "4.5.29"
dirs = "^5.0"
fastwebsockets = { version = "0.8.0", features = ["upgrade", "unstable-split"] }
fltk = { version = "^1.5", features = ["use-wayland"] }
fltk-theme = "^0.7.9"
handlebars = "^6.1"
http-body-util = "0.1.2"
hyper = { version = "^1.4", features = ["server", "http1", "http2"] }
hyper-util = { version = "0.1.8", features = ["tokio"] }
image = { version = "^0.25", features = ["png"], default-features = false }
image_autopilot = { package = "image", version = "0.22.5", features = [], default-features = false }
percent-encoding = "2.1.0"
qrcode = "0.14.0"
rand = "0.8.5"
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
signal-hook = "0.3.17"
tokio = { version = "^1", features = ["fs", "macros", "rt-multi-thread", "sync", "net"] }
toml = "^0.9"
tracing = "^0.1"
tracing-subscriber = { version = "^0.3", features = ["ansi", "json"], default-features = false }
url = "^2.5"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["d3d11", "d3dcommon", "dxgi", "dxgi1_2", "dxgitype"] }
wio = "0.2.2"
captrs = "^0.3.1"

[build-dependencies]
cc = "^1.1"
num_cpus = "^1.16"

[target.'cfg(target_os = "linux")'.dependencies]
dbus = "^0.9"
gstreamer = "^0.24"
gstreamer-app = { version = "^0.24", features = ["v1_16"] }
gstreamer-video = "^0.24"

[target.'cfg(not(target_os = "windows"))'.dependencies]
pnet_datalink = "^0.35"

[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "^0.10"
core-graphics = "^0.24"

[features]
bench = []
ffmpeg-system = []
va-static = []

[package.metadata.bundle]
name = "Weylus"
identifier = "io.github.h-m-h.weylus"

[package.metadata.deb]
name = "Weylus"
section = "graphics"
priority = "optional"
assets = [
    ["target/release/weylus", "usr/bin/weylus", "755"],
    ["weylus.desktop", "usr/share/applications/weylus.desktop", "755"],
    ["Readme.md", "usr/share/doc/weylus/README", "644"],
]

[profile.release]
lto = true
opt-level = 3


================================================
FILE: LICENSE
================================================
Weylus is licensed under the GNU Affero General Public License, version
3 or later. Contributed work is licensed under the 3-Clause BSD License.
See CONTRIBUTORS for details.

    Copyright (C) 2020-Present  H-M-H

                    GNU AFFERO GENERAL PUBLIC LICENSE
                       Version 3, 19 November 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.

  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
them 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.

  Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.

  A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate.  Many developers of free software are heartened and
encouraged by the resulting cooperation.  However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.

  The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community.  It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server.  Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.

  An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals.  This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU Affero General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey 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;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If 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 convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Remote Network Interaction; Use with the GNU General Public License.

  Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software.  This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU Affero 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 that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  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.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
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.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     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
state 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 Affero General Public License as published by
    the Free Software Foundation, either version 3 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source.  For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code.  There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.


================================================
FILE: Readme.md
================================================
# Weylus
![Build](https://github.com/H-M-H/Weylus/workflows/Build/badge.svg)

Weylus turns your tablet or smart phone into a graphic tablet/touch screen for your computer!

Weylus in action with [Xournal++](https://github.com/xournalpp/xournalpp):

![Weylus in action](In_action.gif)

## Table of Contents
* [Features](#features)
* [Installation](#installation)
    * [Packages](#packages)
* [Running](#running)
    * [Fullscreen](#fullscreen)
    * [Keyboard Input](#keyboard-input)
    * [Automation](#automation)
    * [Linux](#linux)
        * [Wayland](#wayland)
        * [Hardware Acceleration](#hardware-acceleration)
        * [Weylus as Second Screen](#weylus-as-second-screen)
            * [Intel GPU on Xorg with Intel drivers](#intel-gpu-on-xorg-with-intel-drivers)
            * [Dummy Plugs](#dummy-plugs)
            * [Other Options](#other-options)
        * [Encryption](#encryption)
    * [macOS](#macos)
        * [Hardware Acceleration](#hardware-acceleration-1)
    * [Windows](#windows)
        * [Hardware Acceleration](#hardware-acceleration-2)
* [Building](#building)
    * [Docker](#docker)
* [How does this work?](#how-does-this-work)
    * [Stylus/Touch](#stylustouch)
    * [Screen mirroring & window capturing](#screen-mirroring--window-capturing)
* [FAQ](#faq)

## Features
- Control your mouse with your tablet
- Mirror your screen to your tablet
- Send keyboard input using physical keyboards
- Hardware accelerated video encoding

The above features are available on all Operating Systems but Weylus works best on Linux. Additional
features on Linux are:
- Support for a stylus/pen (supports pressure and tilt)
- Multi-touch: Try it with software that supports multi-touch, like Krita, and see for yourself!
- Capturing specific windows and only drawing to them
- Faster screen mirroring
- Tablet as second screen

## Installation
Just grab the latest release for your OS from the
[releases page](https://github.com/H-M-H/Weylus/releases) and install it on your computer. No apps
except a modern browser (Firefox 80+, iOS/iPadOS 13+) are required on your tablet. **If you run
Linux make sure to follow the instructions described [here](#linux) to enable uinput for features
like pressure sensitivity and multitouch!**

### Packages
AUR packages for Weylus are available here:
- From source: [weylus](https://aur.archlinux.org/packages/weylus/)
- Prebuilt binary: [weylus-bin](https://aur.archlinux.org/packages/weylus-bin/)

## Running
Start Weylus, preferably set an access code in the access code box and press the Start button. This
will start a webserver running on your computer. To control your computer with your tablet you need
to open the url `http://<address of your computer>:<port set in the menu, default is 1701>`, if
possible Weylus will display to you the url you need to open and show a QR code with the encoded
address. If you have a firewall running make sure to open a TCP port for the webserver (1701 by
default) and the websocket connection (9001 by default).

On many Linux distributions this is done with ufw:
```
sudo ufw allow 1701/tcp
sudo ufw allow 9001/tcp
```

Please only run Weylus in networks you trust as there is no encryption to enable minimal latencies.

### Fullscreen
You may want to add a bookmark to your home screen on your tablet as this enables running Weylus in
full screen mode (on iOS/iPadOS this needs to be done with Safari). If you are not on iOS/iPadOS
there is a button to toggle full screen mode.

### Keyboard Input
Weylus supports keyboard input for physical keyboards, so if you have a Bluetooth keyboard, just
connect it to your tablet and start typing. Due to technical limitations onscreen keyboards are not
supported.

### Automation
Weylus provides some features to make automation as convenient as possible. There is a command-line
interface; `--no-gui` for example starts Weylus in headless mode without a gui. For more options see
`weylus --help`. If you want to run a specific script e.g., once a client connects to your computer
you can do so by parsing the log Weylus generates. You may want to enable more verbose logging by
setting the environment variable `WEYLUS_LOG_LEVEL` to `DEBUG` or `TRACE` as well as
`WEYLUS_LOG_JSON` to `true` to enable easily parseable JSON logging.

### Linux
Weylus uses the `uinput` interface to simulate input events on Linux. **To enable stylus and
multi-touch support `/dev/uinput` needs to be writable by Weylus.** To make `/dev/uinput`
permanently writable by your user, run:
```sh
sudo groupadd -r uinput
sudo usermod -aG uinput $USER
echo 'KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"' \
| sudo tee /etc/udev/rules.d/60-weylus.rules
```

Then, either reboot, or run

```sh
sudo udevadm control --reload
sudo udevadm trigger
```

then log out and log in again. To undo this, run:

```sh
sudo rm /etc/udev/rules.d/60-weylus.rules
```

This allows your user to synthesize input events system-wide, even when another user is logged in.
Therefore, untrusted users should not be added to the uinput group.

#### Wayland
Weylus offers experimental support for Wayland. Installing `pipewire` and `xdg-desktop-portal` as
well as one of:
- `xdg-desktop-portal-gtk` for GNOME
- `xdg-desktop-portal-kde` for KDE
- `xdg-desktop-portal-wlr` for wlroots-based compositors like Sway
is required.

There are still some things that do not work:
- input mapping for windows
- displaying proper window names
- capturing the cursor

#### Hardware Acceleration
On Linux Weylus supports hardware accelerated video encoding through the Video Acceleration API
(VAAPI) or Nvidia's NVENC. By default hardware acceleration is disabled as quality and stability of
the hardware encoded video stream varies widely among different hardware and sufficient quality can
not be guaranteed. If VAAPI is used it is possible to select a specific driver by setting the
environment variable `LIBVA_DRIVER_NAME`. You can find possible values with the command
`ls /usr/lib/dri/ | sed -n 's/^\(\S*\)_drv_video.so$/\1/p'`. On some distributions the drivers may
not reside in `/usr/lib/dri` but for example in `/usr/lib/x86_64-linux-gnu/dri` and may not be found
by Weylus. To force Weylus to search another directory for drivers, the environment variable
`LIBVA_DRIVERS_PATH` can be set.
Additionally you can specify the VAAPI device to use by setting `WEYLUS_VAAPI_DEVICE`; by default
devices can be found in `/dev/dri`. On some systems this is not optional and this variable must be
set. If VAAPI doesn't work out of the box for you, have a look into `/dev/dri`, often setting
`WEYLUS_VAAPI_DEVICE=/dev/dri/renderD129` is already the solution. Note that you may need to install
the driver(s) first.

Nvidias NVENC is very fast but delivers a video stream of noticeably lower quality (at least on my
GeForce GTX 1050 Mobile GPU) but more recent GPUs should provide higher quality. For this to work
nvidia drivers need to be installed.

#### Weylus as Second Screen
There are a few possibilities to use Weylus to turn your tablet into a second screen.

##### Intel GPU on Xorg with Intel drivers
Intel's drivers support creating virtual outputs that can be configured via xrandr.

But first a word of warning: The following configuration may break starting the X server. This means
you might end up without a graphical login or X may get stuck and just display a black screen. So
make sure you know what you are doing or are at least able to recover from a broken X server.

You will need to install the `xf86-video-intel` driver and create the file
`/etc/X11/xorg.conf.d/20-intel.conf` with the following contents:
```text
Section "Device"
    Identifier "intelgpu0"
    Driver "intel"

    # this adds two virtual monitors / devices
    Option "VirtualHeads" "2"

    # if your screen is flickering one of the following options might help
    # Option "TripleBuffer" "true"
    # Option "TearFree"     "true"
    # Option "DRI"          "false"
EndSection
```
After a reboot `xrandr` will show two additional monitors `VIRTUAL1` and `VIRTUAL2` and can be used
to configure them. To activate `VIRTUAL1` with a screen size of 1112x834 and a refresh rate of 60
fps the following commands can be used:
```console
> # this generates all input parameters xrandr needs
> #from a given screen resolution and refresh rate
> gtf 1112 834 60

  # 1112x834 @ 60.00 Hz (GTF) hsync: 51.78 kHz; pclk: 75.81 MHz
  Modeline "1112x834_60.00"  75.81  1112 1168 1288 1464  834 835 838 863  -HSync +Vsync
> # setup the monitor
> xrandr --newmode "1112x834_60.00"  75.81  1112 1168 1288 1464  834 835 838 863  -HSync +Vsync
> xrandr --addmode VIRTUAL1 1112x834_60.00
> xrandr --output VIRTUAL1 --mode 1112x834_60.00
> # check if everything is in order
> xrandr
```
Now you should be able to configure this monitor in your system setting like a regular second
monitor and for example set its position relative to your primary monitor.

After setting up the virtual monitor start Weylus and select it in the capture menu. You may want to
enable displaying the cursor in this case. That is it!

##### Dummy Plugs
Weylus detects if you use multiple monitors and you can select the one you want to mirror. So if you
want to use Weylus as a second screen you could just buy another monitor. Obviously this is
pointless as if you already bought that monitor, there is no need to use Weylus! This is where so
called **HDMI/Displayport/VGA Dummy Plugs** come in handy. These are small devices that pretend to
be a monitor but only cost a fraction of the price of an actual monitor.

Once you have bought one and plugged it into your computer you can configure an additional screen
just like you would do with an actual one and then use Weylus to mirror this virtual screen.

##### Other Options
The following is untested/incomplete, feel free to do more research and open a pull request to
expand documentation on this!
- On Wayland with sway there is `create_output` which can be used to [create headless
  outputs](https://github.com/swaywm/sway/releases/tag/1.5), unfortunately it is not documented how
  to actually do that: https://github.com/swaywm/sway/issues/5553
- On Wayland with GNOME recently there has been added an option to [create virtual monitors with
  mutter](https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1698)

#### Encryption
By default Weylus comes without encryption and should only be run on networks you trust. If this is
not the case it's strongly advised to set up a TLS proxy. One option is to use
[hitch](https://hitch-tls.org/), an example script that sets up encryption is located at
`weylus_tls.sh`.
But any TLS proxy should work just fine.

Note that the mentioned script works by creating a self-signed certificate. This means your browser
will most likely display a scary looking but completely unfounded message telling you how incredibly
dangerous it is to trust the certificate you yourself just created; this can be safely ignored!

In case you are using Firefox: There is a [bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1187666)
that prevents users from accepting self-signed certificates for websocket connections. A workaround
is to directly open the websocket connection via the URL bar and accept the certificate there. After
accepting the connection will of course fail as the browser expects https and not wss as protocol.

Sadly this solution is anything but frictionless and I am unhappy with the current state of affairs.
This is also another reason why encryption is not enabled by default, self-signed certificates are
just too painful to handle nowadays. I'd gladly welcome any proposals to improve the situation!

### macOS
Weylus needs some permissions to work properly, make sure you enable:
- Incoming connections
- Screen capturing
- Controlling your desktop

#### Hardware Acceleration
Weylus can make use of the Videotoolbox framework on macOS for hardware acceleration. In my tests
the video quality has been considerably worse than that using software encoding and thus
Videotoolbox is disabled by default.

### Windows

#### Hardware Acceleration
Weylus can make use of Nvidias NVENC as well as Microsoft's MediaFoundation for hardware accelerated
video encoding. Due to widely varying quality it is disabled by default.

## Building
To build Weylus you need to install Rust, Typescript, make, git, a C compiler, nasm and bash. `cargo
build` builds the project. By default Weylus is build in debug mode, if you want a release build run
`cargo build --release`. On Linux some additional dependencies are required to build Weylus. On
Debian or Ubuntu they can be installed via:
```sh
apt-get install -y libx11-dev libxext-dev libxft-dev libxinerama-dev libxcursor-dev libxrender-dev \
libxfixes-dev libxtst-dev libxrandr-dev libxcomposite-dev libxi-dev libxv-dev autoconf libtool-bin \
nvidia-cuda-dev pkg-config libdrm-dev libpango1.0-dev libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev libdbus-1-dev
```

On Fedora, they can be installed via:
```sh
sudo dnf install libXext-devel libXft-devel libXinerama-devel libXcursor-devel libXrender-devel \
libXfixes-devel libXtst-devel libXrandr-devel libXcomposite-devel libXi-devel libXv-devel autoconf libtool \
pkg-config libdrm-devel pango-devel gstreamer1-devel \
gstreamer1-plugins-base-devel dbus-devel nasm npm
```
After npm is installed, typescript must be installed by:
```sh
sudo npm install typescript -g
```

Note that building for the first time may take a while as by default ffmpeg needs to be built. On
Windows only msvc is supported as C compiler; it is, however, possible to cross compile on Linux for
Windows using minGW.

In case you do not want to build ffmpeg and libx264 via the supplied build script you can create the
directory `deps/dist` yourself and copy static ffmpeg libraries built with support for libx264 and a
static version of libx264 into `deps/dist/lib`. Additional `deps/dist/include` needs to be filled
with ffmpeg's include header files. For hardware acceleration to work ffmpeg needs to be built with
additional flags depending on your OS: Consult the variable `FFMPEG_EXTRA_ARGS` in `deps/build.sh`
for details. Furthermore, for VAAPI on Linux a static version of libva is required as well.

The build script will only try to build ffmpeg if the directory `deps/dist` does not exist.

Alternatively passing `--features ffmpeg-system` to cargo will build Weylus using the system's
version of ffmpeg. This is disabled by default for compatibility reasons, on newer systems this
should not pose a problem and using the system libraries is advised.

### Docker
It is also possible to build the Linux version inside a docker container. The Dockerfile used is
located at [docker/Dockerfile](docker/Dockerfile). This is also how the official release is built.
Building works like
this:
```console
docker run -it hhmhh/weylus_build bash
root@f02164dbfa18:/# git clone https://github.com/H-M-H/Weylus
Cloning into 'Weylus'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 827 (delta 1), reused 6 (delta 0), pack-reused 817
Receiving objects: 100% (827/827), 5.38 MiB | 7.12 MiB/s, done.
Resolving deltas: 100% (431/431), done.
root@f02164dbfa18:/# cd Weylus/
root@f02164dbfa18:/Weylus# cargo deb
   Compiling
   ...
```
Once the build is finished you can for example copy the binary from the container to your file
system like this:
```sh
docker cp f02164dbfa18:/Weylus/target/release/weylus ~/some/path/weylus
```
The .deb is located at `/Weylus/target/debian/`.  Please note that the container ID will most likely
not be `f02164dbfa18` if you run this yourself, replace it accordingly.

## How does this work?
### Stylus/Touch
Modern browsers expose so called
[PointerEvents](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) that can convey not
only mouse but additionally stylus/pen and touch information. Weylus sets up a webserver with the
corresponding javascript code to capture these events. The events are sent back to the server using
websockets.
Weylus then processes these events using either the generic OS independent backend, which only
supports controlling the mouse or on Linux the uinput backend can be used. It makes use of the
uinput Linux kernel module which supports creating a wide range of input devices including mouse,
stylus and touch input devices.

### Screen mirroring & window capturing
Either the generic backend is used which is less efficient and only captures the whole screen or on
Linux xlib is used to connect to the X-server and do the necessary work of getting window
information and capturing the window/screen. To make things fast the "MIT-SHM - The MIT Shared
Memory Extension" is used to create shared memory images using `XShmCreateImage`. If Wayland instead
of X11 is running, PipeWire and GStreamer is used to capture the screen. The images captured are
then encoded to a video stream using ffmpeg. Fragmented MP4 is used as container format to enable
browsers to play the stream via the Media Source Extensions API. The video codec used is H.264 as
this is widely supported and allows very fast encoding as opposed to formats like AV1. To minimize
dependencies ffmpeg is statically linked into Weylus.

## FAQ
Q: Why does the page not load on my tablet and instead I get a timeout?<br>
A: There probably is some kind of firewall running, make sure the ports Weylus uses are opened.

Q: Why do I get the error `ERROR Failed to create uinput device: CError: code...`?<br>
A: uinput is probably misconfigured, have you made sure to follow all instructions and logged out
and in again? You may also be running a very old kernel that does not support the required features.
In that case try to upgrade your system or use a newer one.

Q: Why is the "Capture" drop down empty and the screen not mirrored?<br>
A: It is possible that only the port for the webserver but not the websocket has been opened, check
that both ports have been opened.

Q: Why can I not select any windows in the "Capture" drop down and only see the whole screen.<br>
A: If you are running Weylus on MacOS or Windows this feature is unfortunately not implemented. On
Linux it is possible that your window manager does not support
[Extended Window Manager Hints](https://specifications.freedesktop.org/wm-spec/latest/) or that you
need to activate them first, like for XMonad.

Q: Do I have to follow the instructions to setup Weylus as second screen too?<br>
A: No, this is strictly optional.

Q: Why am I unable to connect my tablet to the URL displayed by Weylus?<br>
A: It is possible that your computer and WiFi connected tablet are on different networks, make sure
they are on the same network.

Q: Why does this not run on Firefox for Android?<br>
A: Actually it does, just make sure Firefox version 80+ is installed.

Q: Why does this not run under Chrome on my iPad?<br>
A: Chrome lacks some features for video streaming on iPadOS/iOS, try Firefox or Safari.

Q: Why won't my cursor move in osu! ?<br>
A: Try disabling raw input.

Q: Can I use Weylus even if there is no WiFi?<br>
A: Probably yes! Most tablets permit setting up a WiFi hotspot that can be used to connect your
computer and tablet. Alternatively there is USB tethering too, which can be used to setup a peer to
peer connection between your tablet and computer over USB. Another method for Android devices is to
setup a socket connection with
[adb](https://developer.android.com/studio/command-line/adb#Enabling):
```console
adb reverse tcp:1701 tcp:1701
adb reverse tcp:9001 tcp:9001
```
Like that you can connect from your Android device to Weylus with the URL: `http://127.0.0.1:1701`.

Weylus only requires that your devices
are connected via the Internet Protocol and that doesn't necessarily imply WiFi.

---

[![Packaging status](
https://repology.org/badge/vertical-allrepos/weylus.svg
)](https://repology.org/project/weylus/versions)


================================================
FILE: build.rs
================================================
use std::env;
use std::path::Path;
use std::process::Command;

fn build_ffmpeg(dist_dir: &Path, enable_libnpp: bool) {
    if dist_dir.exists() {
        return;
    }

    Command::new("bash")
        .arg(Path::new("clean.sh"))
        .current_dir("deps")
        .status()
        .expect("Failed to clean ffmpeg build!");

    if !Command::new("bash")
        .arg(Path::new("build.sh"))
        .current_dir("deps")
        .env("DIST", dist_dir)
        .env("ENABLE_LIBNPP", if enable_libnpp { "y" } else { "n" })
        .status()
        .expect("Failed to run bash!")
        .success()
    {
        println!("cargo:warning=Failed to build ffmpeg!");
        std::process::exit(1);
    }
}

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();

    let dist_dir = Path::new("deps")
        .canonicalize()
        .unwrap()
        .join(format!("dist_{}", target_os));

    let enable_libnpp = env::var("I_AM_BUILDING_THIS_AT_HOME_AND_WANT_LIBNPP").map_or(false, |v| {
        ["y", "yes", "true", "1"].contains(&v.to_lowercase().as_str())
    });

    if env::var("CARGO_FEATURE_FFMPEG_SYSTEM").is_err() {
        build_ffmpeg(&dist_dir, enable_libnpp);
    }

    println!("cargo:rerun-if-changed=ts/lib.ts");

    #[cfg(not(target_os = "windows"))]
    let mut tsc_command = Command::new("tsc");

    #[cfg(target_os = "windows")]
    let mut tsc_command = Command::new("bash");
    #[cfg(target_os = "windows")]
    tsc_command.args(&["-c", "tsc"]);

    let js_needs_update = || -> Result<bool, Box<dyn std::error::Error>> {
        Ok(Path::new("ts/lib.ts").metadata()?.modified()?
            > Path::new("www/static/lib.js").metadata()?.modified()?)
    }()
    .unwrap_or(true);

    if js_needs_update {
        match tsc_command.status() {
            Err(err) => {
                println!("cargo:warning=Failed to call tsc: {}", err);
                std::process::exit(1);
            }
            Ok(status) => {
                if !status.success() {
                    match status.code() {
                        Some(code) => println!("cargo:warning=tsc failed with exitcode: {}", code),
                        None => println!("cargo:warning=tsc terminated by signal."),
                    };
                    std::process::exit(2);
                }
            }
        }
    }

    println!("cargo:rerun-if-changed=lib/encode_video.c");
    let mut cc_video = cc::Build::new();
    cc_video.file("lib/encode_video.c");
    cc_video.include(dist_dir.join("include"));
    if ["linux", "windows"].contains(&target_os.as_str()) {
        cc_video.define("HAS_NVENC", None);
    }
    if target_os == "linux" {
        cc_video.define("HAS_VAAPI", None);
    }
    if target_os == "macos" {
        cc_video.define("HAS_VIDEOTOOLBOX", None);
    }
    if target_os == "windows" {
        cc_video.define("HAS_MEDIAFOUNDATION", None);
    }
    if enable_libnpp {
        cc_video.define("HAS_LIBNPP", None);
    }
    cc_video.compile("video");

    println!("cargo:rerun-if-changed=lib/error.h");
    println!("cargo:rerun-if-changed=lib/error.c");
    println!("cargo:rerun-if-changed=lib/log.h");
    println!("cargo:rerun-if-changed=lib/log.c");
    cc::Build::new().file("lib/error.c").compile("error");
    cc::Build::new().file("lib/log.c").compile("log");

    let ffmpeg_link_kind =
        // https://github.com/rust-lang/rust/pull/72785
        // https://users.rust-lang.org/t/linking-on-windows-without-wholearchive/49846/3
        if cfg!(target_os = "windows") ||
            env::var("CARGO_FEATURE_FFMPEG_SYSTEM").is_ok() {
            "dylib"
        } else {
            "static"
        };
    println!("cargo:rustc-link-lib={}=avdevice", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=avformat", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=avfilter", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=avcodec", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=swresample", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=swscale", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=avutil", ffmpeg_link_kind);
    println!("cargo:rustc-link-lib={}=x264", ffmpeg_link_kind);
    if enable_libnpp {
        if let Ok(lib_paths) = env::var("LIBRARY_PATH") {
            for lib_path in lib_paths.split(':') {
                println!("cargo:rustc-link-search={}", lib_path);
            }
        }
        println!("cargo:rustc-link-lib=dylib=nppig");
        println!("cargo:rustc-link-lib=dylib=nppicc");
        println!("cargo:rustc-link-lib=dylib=nppc");
        println!("cargo:rustc-link-lib=dylib=nppidei");
        println!("cargo:rustc-link-lib=dylib=nppif");
    }
    if env::var("CARGO_FEATURE_FFMPEG_SYSTEM").is_err() {
        println!(
            "cargo:rustc-link-search={}",
            dist_dir.join("lib").to_string_lossy()
        );
    }

    if target_os == "linux" {
        linux();
    }

    if target_os == "macos" {
        println!("cargo:rustc-link-lib=framework=VideoToolbox");
        println!("cargo:rustc-link-lib=framework=CoreMedia");
    }

    if target_os == "windows" {
        println!("cargo:rustc-link-lib=dylib=mfplat");
        println!("cargo:rustc-link-lib=dylib=mfuuid");
        println!("cargo:rustc-link-lib=dylib=ole32");
        println!("cargo:rustc-link-lib=dylib=strmiids");
        println!("cargo:rustc-link-lib=dylib=vfw32");
        println!("cargo:rustc-link-lib=dylib=shlwapi");
        println!("cargo:rustc-link-lib=dylib=bcrypt");
    }
}

fn linux() {
    println!("cargo:rerun-if-changed=lib/linux/uniput.c");
    println!("cargo:rerun-if-changed=lib/linux/xcapture.c");
    println!("cargo:rerun-if-changed=lib/linux/xhelper.c");
    println!("cargo:rerun-if-changed=lib/linux/xhelper.h");

    cc::Build::new()
        .file("lib/linux/uinput.c")
        .file("lib/linux/xcapture.c")
        .file("lib/linux/xhelper.c")
        .compile("linux");

    println!("cargo:rustc-link-lib=X11");
    println!("cargo:rustc-link-lib=Xext");
    println!("cargo:rustc-link-lib=Xrandr");
    println!("cargo:rustc-link-lib=Xfixes");
    println!("cargo:rustc-link-lib=Xcomposite");
    println!("cargo:rustc-link-lib=Xi");
    let va_link_kind = if env::var("CARGO_FEATURE_VA_STATIC").is_ok() {
        "static"
    } else {
        "dylib"
    };
    println!("cargo:rustc-link-lib={}=va", va_link_kind);
    println!("cargo:rustc-link-lib={}=va-drm", va_link_kind);
    println!("cargo:rustc-link-lib={}=va-x11", va_link_kind);
    println!("cargo:rustc-link-lib=drm");
    println!("cargo:rustc-link-lib=xcb-dri3");
    println!("cargo:rustc-link-lib=X11-xcb");
    println!("cargo:rustc-link-lib=xcb");
}


================================================
FILE: build_in_local_container.sh
================================================
#!/usr/bin/env sh

set -ex

rm -f docker/archive.tar.gz
git ls-files | tar Tczf - docker/archive.tar.gz

podman run --replace -d --name weylus_build hhmhh/weylus_build:latest sleep infinity
podman cp docker/archive.tar.gz weylus_build:/
podman exec weylus_build sh -c "mkdir /weylus && tar xf archive.tar.gz --directory=/weylus && cd weylus && ./docker_build.sh"

podman run --replace -d --name weylus_build_alpine hhmhh/weylus_build_alpine:latest sleep infinity
podman cp docker/archive.tar.gz weylus_build_alpine:/
podman exec weylus_build_alpine sh -c "mkdir /weylus && tar xf archive.tar.gz --directory=/weylus && cd weylus && RUSTFLAGS='-C target-feature=-crt-static' cargo build --release"


================================================
FILE: compile_flags.txt
================================================
-lX11
-lXext
-Wall
-Wextra
-Ideps/dist/include
-DHAS_NVENC
-DHAS_LIBNPP
-DHAS_VAAPI


================================================
FILE: deps/awk.patch
================================================
diff --git a/configure b/configure
index 8569a60bf8..928b19db69 100755
--- a/configure
+++ b/configure
@@ -4690,7 +4690,7 @@ probe_cc(){
         else
             _ident=$($_cc --version 2>/dev/null | head -n1 | tr -d '\r')
         fi
-        _DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | awk '\''/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) print "$@:", $$0 }'\'' > $(@:.o=.d)'
+        _DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | awk -f ./msvc_dep.awk > $(@:.o=.d)'
         _DEPFLAGS='$(CPPFLAGS) $(CFLAGS) -showIncludes -Zs'
         _cflags_speed="-O2"
         _cflags_size="-O1"
diff --git a/msvc_dep.awk b/msvc_dep.awk
new file mode 100644
index 0000000000..a791efe000
--- /dev/null
+++ b/msvc_dep.awk
@@ -0,0 +1 @@
+/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) print "$@:", $$0 }


================================================
FILE: deps/build.sh
================================================
#!/usr/bin/env bash

set -ex

export TARGET_OS="$CARGO_CFG_TARGET_OS"

if [ "$OSTYPE" == "linux-gnu" ]; then
    export HOST_OS="linux"
fi

if [[ "$OSTYPE" == "darwin"* ]]; then
    export HOST_OS="macos"
fi

if [ "$OS" == "Windows_NT" ]; then
    export HOST_OS="windows"
fi

[ -z "$DIST" ] && export DIST="$PWD/dist"
[ -z "$TARGET_OS" ] && export TARGET_OS="$HOST_OS"

export NPROCS="$(nproc || echo 4)"

./download.sh

if [ "$TARGET_OS" == "windows" ]; then
    if [ "$HOST_OS" == "linux" ]; then
        export CROSS_COMPILE="x86_64-w64-mingw32-"
        export FFMPEG_EXTRA_ARGS="--arch=x86_64 --target-os=mingw64 \
            --cross-prefix=x86_64-w64-mingw32- --enable-nvenc --enable-ffnvcodec \
            --enable-cuda-llvm --enable-mediafoundation --pkg-config=pkg-config --enable-d3d11va"
        export FFMPEG_CFLAGS="-I$DIST/include"
        export FFMPEG_LIBRARY_PATH="-L$DIST/lib"
    else
        export CC="cl"
        export FFMPEG_EXTRA_ARGS="--toolchain=msvc --enable-nvenc --enable-ffnvcodec \
            --enable-cuda-llvm --enable-mediafoundation --enable-d3d11va"
        export FFMPEG_CFLAGS="-I$DIST/include"
        export FFMPEG_LIBRARY_PATH="-LIBPATH:$DIST/lib"
    fi
else
    export FFMPEG_CFLAGS="-I$DIST/include"
    export FFMPEG_LIBRARY_PATH="-L$DIST/lib"
    if [ "$TARGET_OS" == "linux" ]; then
        export FFMPEG_EXTRA_ARGS="--enable-nvenc \
            --enable-ffnvcodec \
            --enable-cuda-llvm \
            --enable-vaapi \
            --enable-libdrm \
            --enable-xlib"
    fi
    if [ "$TARGET_OS" == "macos" ]; then
        export FFMPEG_EXTRA_ARGS="--enable-videotoolbox"
    fi
fi

if [ "$ENABLE_LIBNPP" == "y" ]; then
    export FFMPEG_EXTRA_ARGS="$FFMPEG_EXTRA_ARGS --enable-libnpp --enable-nonfree"
fi

if [ "$TARGET_OS" == "windows" ] && [ "$HOST_OS" == "linux" ]; then
    export X264_EXTRA_ARGS="--cross-prefix=x86_64-w64-mingw32- --host=x86_64-w64-mingw32"
fi
./x264.sh
if [ "$TARGET_OS" == "linux" ]; then
    ./nv-codec-headers.sh
    ./libva.sh
fi
if [ "$TARGET_OS" == "windows" ]; then
    ./nv-codec-headers.sh
fi
./ffmpeg.sh

if [ "$TARGET_OS" == "windows" ] && [ "$HOST_OS" == "windows" ]; then
    cd "$DIST/lib"
    for l in *.a; do
        d=${l#lib}
        cp "$l" "${d%.a}.lib"
    done
    cp libx264.lib x264.lib
fi


================================================
FILE: deps/clean.sh
================================================
#!/usr/bin/env bash

set -x
for d in ffmpeg x264 nv-codec-headers libva; do
    test -d "$d" || continue
    (cd "$d" && git clean -dfx && git reset --hard HEAD)
done


================================================
FILE: deps/clean_all.sh
================================================
#!/usr/bin/env bash

set -ex
rm -rf ffmpeg x264 nv-codec-headers libva dist*


================================================
FILE: deps/command_limit.patch
================================================
diff --git a/ffbuild/library.mak b/ffbuild/library.mak
index ad09f20..e63196d 100644
--- a/ffbuild/library.mak
+++ b/ffbuild/library.mak
@@ -35,7 +35,9 @@ OBJS += $(SHLIBOBJS)
 endif
 $(SUBDIR)$(LIBNAME): $(OBJS) $(STLIBOBJS)
 	$(RM) $@
-	$(AR) $(ARFLAGS) $(AR_O) $^
+	$(file >$@.ar.txt, $^)
+	$(AR) $(ARFLAGS) $(AR_O) @$@.ar.txt
+	$(RM) $@.ar.txt
 	$(RANLIB) $@
 
 install-headers: install-lib$(NAME)-headers install-lib$(NAME)-pkgconfig


================================================
FILE: deps/download.sh
================================================
#!/usr/bin/env bash

set -ex

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/refs.sh"
source "$SCRIPT_DIR/hashes.sh"

clone_at_commit() {
    local url="$1" dir="$2" commit="$3"
    if [ ! -d "$dir" ]; then
        git clone --filter=blob:none "$url" "$dir"
        git -C "$dir" checkout "$commit"
    fi
}

clone_at_commit "$X264_URL" x264 "$X264_COMMIT"
clone_at_commit "$FFMPEG_URL" ffmpeg "$FFMPEG_COMMIT"

if [ "$TARGET_OS" == "linux" ]; then
    clone_at_commit "$NV_CODEC_URL" nv-codec-headers "$NV_CODEC_COMMIT"
    clone_at_commit "$LIBVA_URL" libva "$LIBVA_COMMIT"
fi
if [ "$TARGET_OS" == "windows" ]; then
    clone_at_commit "$NV_CODEC_URL" nv-codec-headers "$NV_CODEC_COMMIT"
fi

if [ "$TARGET_OS" == "windows" ] && [ "$HOST_OS" == "windows" ]; then
    cd ffmpeg
    git apply ../command_limit.patch
    git apply ../awk.patch
fi






================================================
FILE: deps/ffmpeg.sh
================================================
#!/usr/bin/env bash

set -ex

cd ffmpeg
PKG_CONFIG_PATH="$DIST/lib/pkgconfig" ./configure \
	--prefix="$DIST" \
	--disable-debug \
	--enable-static \
	--disable-shared \
	--enable-pic \
	--enable-stripping \
	--disable-programs \
	--enable-gpl \
	--enable-libx264 \
	--disable-autodetect \
	--extra-cflags="$FFMPEG_CFLAGS" \
	--extra-ldflags="$FFMPEG_LIBRARY_PATH" \
	$FFMPEG_EXTRA_ARGS

make -j$NPROCS
make install


================================================
FILE: deps/hashes.sh
================================================
#!/usr/bin/env bash

# Generated by update_hashes.sh — do not edit manually.
# Run ./update_hashes.sh to regenerate.

X264_COMMIT="b35605ace3ddf7c1a5d67a2eb553f034aef41d55"  # refs/heads/stable
FFMPEG_COMMIT="a4044e04486d1136022498891088a90baf5b2775"  # refs/tags/n8.0
NV_CODEC_COMMIT="876af32a202d0de83bd1d36fe74ee0f7fcf86b0d"  # HEAD
LIBVA_COMMIT="e85b1569b738fd8866cb9fa2452319f7148d663f"  # refs/tags/2.23.0


================================================
FILE: deps/libva.sh
================================================
#!/usr/bin/env bash

set -ex

cd libva

# required to make ffmpeg's configure work
sed -i -e "s/-lva$/-lva -ldrm -ldl/" pkgconfig/libva.pc.in
sed -i -e 's/-lva-\${display}$/-lva-\${display} -lX11 -lXext -lXfixes -ldrm/' pkgconfig/libva-x11.pc.in
sed -i -e 's/-lva-\${display}$/-lva-\${display} -ldrm/' pkgconfig/libva-drm.pc.in

./autogen.sh --prefix=$(readlink -f "$DIST") \
    --enable-static=yes \
    --enable-shared=yes \
    --enable-drm \
    --enable-x11 \
    --enable-glx \
    --with-drivers-path="/usr/lib/dri"

make -j$NPROCS
make install


================================================
FILE: deps/nv-codec-headers.sh
================================================
#!/usr/bin/env bash

set -ex

cd nv-codec-headers
make PREFIX="$DIST"
make install PREFIX="$DIST"


================================================
FILE: deps/refs.sh
================================================
#!/usr/bin/env bash

# Dependency URLs and the branch/tag/ref to pin.
# After editing, run ./update_hashes.sh to regenerate hashes.sh.

X264_URL="https://code.videolan.org/videolan/x264.git"
X264_REF="refs/heads/stable"

FFMPEG_URL="https://code.ffmpeg.org/FFmpeg/FFmpeg.git"
FFMPEG_REF="refs/tags/n8.0"

NV_CODEC_URL="https://git.videolan.org/git/ffmpeg/nv-codec-headers.git"
NV_CODEC_REF="HEAD"

LIBVA_URL="https://github.com/intel/libva"
LIBVA_REF="refs/tags/2.23.0"


================================================
FILE: deps/update_hashes.sh
================================================
#!/usr/bin/env bash

# Resolves each ref in refs.sh to a commit hash and writes hashes.sh.

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/refs.sh"

resolve() {
    local name="$1" url="$2" ref="$3"
    local hash
    hash=$(git ls-remote "$url" "$ref" | awk 'END { print $1 }')
    if [ -z "$hash" ]; then
        echo "ERROR: could not resolve $name $ref from $url" >&2
        exit 1
    fi
    echo "${name}_COMMIT=\"${hash}\"  # ${ref}"
}

cat > "$SCRIPT_DIR/hashes.sh" <<EOF
#!/usr/bin/env bash

# Generated by update_hashes.sh — do not edit manually.
# Run ./update_hashes.sh to regenerate.

$(resolve X264      "$X264_URL"     "$X264_REF")
$(resolve FFMPEG    "$FFMPEG_URL"   "$FFMPEG_REF")
$(resolve NV_CODEC  "$NV_CODEC_URL" "$NV_CODEC_REF")
$(resolve LIBVA     "$LIBVA_URL"    "$LIBVA_REF")
EOF

echo "hashes.sh updated."


================================================
FILE: deps/x264.sh
================================================
#!/usr/bin/env bash

set -ex

cd x264
./configure \
	--prefix="$DIST" \
	--exec-prefix="$DIST" \
	--enable-static \
	--enable-pic \
	--enable-strip \
	--disable-cli \
	--disable-opencl \
	$X264_EXTRA_ARGS

make -j$NPROCS
make install


================================================
FILE: docker/Dockerfile
================================================
FROM debian:bullseye
ENV RUSTUP_HOME="/usr/local/rustup" CARGO_HOME="/usr/local/cargo" PATH="/usr/local/cargo/bin:$PATH"
RUN apt-get update && \
   apt-get install -y libx11-dev libxext-dev libxft-dev libxinerama-dev libxcursor-dev \
   libxrender-dev libxfixes-dev libgl1-mesa-dev libglu1-mesa-dev libxtst-dev cmake git curl \
   software-properties-common zip libssl-dev libxrandr-dev libxcomposite-dev libxi-dev \
   gcc g++ autoconf libtool-bin libxv-dev libdrm-dev libpango1.0-dev pkg-config mingw-w64 \
   libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libdbus-1-dev libxcb-dri3-dev clang  \
   libwayland-dev libxkbcommon-dev
RUN apt-add-repository contrib
RUN apt-add-repository non-free
RUN apt-get update && apt-get install -y nvidia-cuda-dev
RUN curl -Lo cmake.tar.gz https://github.com/Kitware/CMake/releases/download/v4.1.1/cmake-4.1.1.tar.gz && tar xf cmake.tar.gz
RUN cd cmake-4.* && cmake . && make -j$(nproc) && make install
RUN rm -rf cmake*
RUN curl -LO "https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/nasm-2.16.03.tar.xz" && \
    tar xf "nasm-2.16.03.tar.xz" && cd "nasm-2.16.03" && \
    ./configure --prefix=/usr && make -j$(nproc) && make install && cd .. && rm -rf "nasm-2.16.03*"
RUN curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
    apt-get install -y nodejs && \
    npm install -g typescript
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
    sh -s -- -y --default-toolchain stable-x86_64-unknown-linux-gnu
RUN cargo install cargo-deb
RUN rustup target add x86_64-pc-windows-gnu


================================================
FILE: docker/Dockerfile_alpine
================================================
FROM alpine:latest
ENV RUSTUP_HOME="/usr/local/rustup" CARGO_HOME="/usr/local/cargo" PATH="/usr/local/cargo/bin:$PATH"

RUN apk add --no-cache libx11-dev libxext-dev libxft-dev libxinerama-dev libxcursor-dev \
    libxrender-dev libxfixes-dev libxtst-dev libxrandr-dev libxcomposite-dev libxi-dev libxv-dev \
    autoconf libtool pkgconfig libdrm-dev pango-dev gst-plugins-base-dev gstreamer-dev dbus-libs \
    dbus-dev cmake build-base nasm npm ffmpeg-dev libva-dev curl git bash automake tar clang \
    wayland-dev libxkbcommon-dev

RUN npm install --global typescript

RUN ln -s /usr/bin/x86_64-alpine-linux-musl-gcc /usr/bin/musl-gcc
RUN ln -s /usr/bin/x86_64-alpine-linux-musl-g++ /usr/bin/musl-g++

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
    sh -s -- -y --default-toolchain stable-x86_64-unknown-linux-musl


================================================
FILE: docker_build.sh
================================================
#!/usr/bin/env sh

set -ex

# cross compile windows version
cargo build --target x86_64-pc-windows-gnu --release

# cleanup cross compiled windows artifacts
(cd deps && ./clean.sh)

# build linux versions
cargo deb --  --features=va-static

# check if installing works
dpkg -i target/debian/Weylus*.deb
cp target/release/weylus target/release/weylus_va_static

# build version with dynamic libva
cargo build --release

mkdir packages

PKGDIR="$PWD/packages"

# package windows
(
  cd target/x86_64-pc-windows-gnu/release/
  zip weylus-windows.zip weylus.exe
  mv weylus-windows.zip "$PKGDIR/"
)

# package linux
(
  cp target/debian/Weylus*.deb "$PKGDIR/"
  cp weylus.desktop target/release/
  cd target/release/
  zip weylus-linux.zip weylus weylus_va_static weylus.desktop
  mv weylus-linux.zip "$PKGDIR/"
)


================================================
FILE: lib/encode_video.c
================================================
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/buffer.h>
#include <libavutil/dict.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/hwcontext.h>
#include <libavutil/imgutils.h>
#include <libavutil/mem.h>
#include <libavutil/opt.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>

#include "error.h"
#include "log.h"

#ifdef HAS_VAAPI
#include <libavutil/hwcontext_vaapi.h>
#include <va/va.h>
#endif

const AVRational TIME_BASE = (AVRational){1, 1000};

typedef struct ScaleContext
{
	AVFilterGraph* filter_graph_scale;
	AVFilterContext* buffersink_scale_ctx;
	AVFilterContext* buffersrc_scale_ctx;
	AVFrame* frame_in;
	AVFrame* frame_out;
} ScaleContext;

typedef struct Scalers
{
	ScaleContext bgr0;
	ScaleContext rgb0;
	ScaleContext rgb;
	AVBufferRef* hw_frames_ctx;
	AVFrame* frame_out;
} Scalers;

typedef struct VideoContext
{
	AVFormatContext* oc;
	AVCodecContext* c;

	// pointer to the frame to be encoded, one of frame_out in scalers.bgr0/rgb0/rgb
	AVFrame* frame;

	Scalers scalers;

	AVBufferRef* hw_device_ctx;

	AVPacket* pkt;
	AVStream* st;
	int width_out;
	int height_out;
	int width_in;
	int height_in;
	void* buf;
	void* rust_ctx;
	int pts;
	int initialized;
	int frame_allocated;
	int try_vaapi;
	int try_nvenc;
	int try_videotoolbox;
	int try_mediafoundation;
} VideoContext;

// this is a rust function and lives in src/video.rs
int write_video_packet(void* rust_ctx, const uint8_t* buf, int buf_size);

#if defined(__clang__) || defined(__GNUC__)
void log_callback(__attribute__((unused)) void* _ptr, int level, const char* fmt_orig, va_list args)
#else
void log_callback(void* _ptr, int level, const char* fmt_orig, va_list args)
#endif
{
	char fmt[256] = {0};
	strncpy(fmt, fmt_orig, sizeof(fmt) - 1);
	int done = 0;
	// strip whitespaces from end
	for (int i = sizeof(fmt) - 1; i >= 0 && !done; --i)
		switch (fmt[i])
		{
		case ' ':
		case '\n':
		case '\t':
		case '\r':
			fmt[i] = '\0';
			break;
		case '\0':
			break;
		default:
			done = 1;
		}
	char buf[2048];
	vsnprintf(buf, sizeof(buf), fmt, args);
	switch (level)
	{
	case AV_LOG_FATAL:
	case AV_LOG_ERROR:
	case AV_LOG_PANIC:
		log_error("%s", buf);
		break;
	case AV_LOG_INFO:
		log_info("%s", buf);
		break;
	case AV_LOG_WARNING:
		log_warn("%s", buf);
		break;
	case AV_LOG_QUIET:
		break;
	case AV_LOG_VERBOSE:
		log_debug("%s", buf);
		break;
	case AV_LOG_DEBUG:
		log_trace("%s", buf);
		break;
	}
}

// called in src/log.rs
void init_ffmpeg_logger() { av_log_set_callback(log_callback); }

void set_codec_params(VideoContext* ctx)
{
	/* resolution must be a multiple of two */
	ctx->c->width = ctx->width_out;
	ctx->c->height = ctx->height_out;
	ctx->c->time_base = TIME_BASE;
	ctx->c->framerate = (AVRational){0, 1};

	ctx->c->gop_size = 12;
	// no B-frames to reduce latency
	ctx->c->max_b_frames = 0;
	if (ctx->oc->oformat->flags & AVFMT_GLOBALHEADER)
		ctx->c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

void destroy_scale_ctx(ScaleContext* ctx)
{
	avfilter_graph_free(&ctx->filter_graph_scale);
	if (ctx->frame_in)
		av_frame_free(&ctx->frame_in);
}

void init_scaler(
	ScaleContext* ctx,
	int width_in,
	int height_in,
	int width_out,
	int height_out,
	enum AVPixelFormat pix_fmt_in,
	enum AVPixelFormat pix_fmt_out,
	AVBufferRef* hw_device_ctx,
	enum AVPixelFormat pix_fmt_sw_out,
	AVFrame* frame_out,
	Error* err)
{
	int ret = 0;

	ctx->frame_in = av_frame_alloc();
	if (!ctx->frame_in)
		ERROR(err, 1, "Failed to allocate frame_in for scale filter!");

	ctx->frame_out = frame_out;

	ctx->frame_in->format = pix_fmt_in;
	ctx->frame_in->width = width_in;
	ctx->frame_in->height = height_in;
	ret = av_frame_get_buffer(ctx->frame_in, 0);
	if (ret)
	{
		destroy_scale_ctx(ctx);
		ERROR(
			err,
			1,
			"Failed to allocate buffer for frame_in for scale filter: %s!",
			av_err2str(ret));
	}

	char args[512];
	const AVFilter* buffersrc = avfilter_get_by_name("buffer");
	const AVFilter* buffersink = avfilter_get_by_name("buffersink");
	AVFilterInOut* outputs = avfilter_inout_alloc();
	AVFilterInOut* inputs = avfilter_inout_alloc();

	ctx->filter_graph_scale = avfilter_graph_alloc();
	if (!outputs || !inputs || !ctx->filter_graph_scale)
	{
		ret = AVERROR(ENOMEM);
		goto end;
	}

	avfilter_graph_set_auto_convert(ctx->filter_graph_scale, AVFILTER_AUTO_CONVERT_NONE);

	/* buffer video source: the decoded frames from the decoder will be inserted here. */
	snprintf(
		args,
		sizeof(args),
		"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
		width_in,
		height_in,
		pix_fmt_in,
		TIME_BASE.num,
		TIME_BASE.den,
		1,
		1);

	ret = avfilter_graph_create_filter(
		&ctx->buffersrc_scale_ctx, buffersrc, "in", args, NULL, ctx->filter_graph_scale);
	if (ret < 0)
	{
		log_warn("Cannot create buffer source");
		goto end;
	}

	/* buffer video sink: to terminate the filter chain. */
	ctx->buffersink_scale_ctx =
		avfilter_graph_alloc_filter(ctx->filter_graph_scale, buffersink, "out");

	if (ctx->buffersink_scale_ctx == NULL)
	{
		log_warn("Cannot allocate buffer sink");
		goto end;
	}

	ret = av_opt_set_array(
		ctx->buffersink_scale_ctx,
		"pixel_formats",
		AV_OPT_SEARCH_CHILDREN,
		0,
		1,
		AV_OPT_TYPE_PIXEL_FMT,
		&pix_fmt_out);
	if (ret < 0)
	{
		log_warn("Cannot set output pixel format: %s", av_err2str(ret));
		goto end;
	}

	ret = avfilter_init_dict(ctx->buffersink_scale_ctx, NULL);
	if (ret < 0)
	{
		log_warn("Cannot init buffer sink");
		goto end;
	}

	outputs->name = av_strdup("in");
	outputs->filter_ctx = ctx->buffersrc_scale_ctx;
	outputs->pad_idx = 0;
	outputs->next = NULL;

	inputs->name = av_strdup("out");
	inputs->filter_ctx = ctx->buffersink_scale_ctx;
	inputs->pad_idx = 0;
	inputs->next = NULL;

	switch (pix_fmt_out)
	{
	case AV_PIX_FMT_CUDA:
		if (pix_fmt_in == AV_PIX_FMT_RGB24)
		{
			snprintf(
				args,
				sizeof(args),
				"scale=w=%d:h=%d:flags=fast_bilinear,hwupload_cuda",
				width_out,
				height_out);
		}
		else
		{
			snprintf(
				args,
				sizeof(args),
#ifdef HAS_LIBNPP
				"scale,format=nv12,hwupload_cuda,scale_npp=w=%d:h=%d:format=%s:interp_algo=nn",
#else
				"hwupload_cuda,scale_cuda=w=%d:h=%d:format=%s:interp_algo=nearest",
#endif
				width_out,
				height_out,
				av_get_pix_fmt_name(pix_fmt_sw_out));
		}
		break;
	case AV_PIX_FMT_VAAPI:
		if (pix_fmt_in == AV_PIX_FMT_RGB24)
			snprintf(
				args,
				sizeof(args),
				"scale=w=%d:h=%d:flags=fast_bilinear,hwupload",
				width_out,
				height_out);
		else
			snprintf(
				args,
				sizeof(args),
				"hwupload,scale_vaapi=w=%d:h=%d:format=%s:mode=fast",
				width_out,
				height_out,
				av_get_pix_fmt_name(pix_fmt_sw_out));
		break;
	default:
		snprintf(args, sizeof(args), "scale=w=%d:h=%d:flags=fast_bilinear", width_out, height_out);
	}

	if ((ret = avfilter_graph_parse_ptr(ctx->filter_graph_scale, args, &inputs, &outputs, NULL)) <
		0)
	{
		log_warn("Failed to parse filter");
		goto end;
	}

	for (unsigned int i = 0; i < ctx->filter_graph_scale->nb_filters; i++)
	{
		AVFilterContext* filt = ctx->filter_graph_scale->filters[i];
		if (strcmp(filt->filter->name, "hwupload") == 0)
		{
			filt->hw_device_ctx = av_buffer_ref(hw_device_ctx);
		}
	}

	if ((ret = avfilter_graph_config(ctx->filter_graph_scale, NULL)) < 0)
	{
		log_warn("Failed to configure filter graph");
		goto end;
	}

end:
	avfilter_inout_free(&inputs);
	avfilter_inout_free(&outputs);

	if (ret != 0)
	{
		destroy_scale_ctx(ctx);
		ERROR(
			err,
			1,
			"Setting up scale filter %s -> %s (sw: %s) failed!",
			av_get_pix_fmt_name(pix_fmt_in),
			av_get_pix_fmt_name(pix_fmt_out),
			av_get_pix_fmt_name(pix_fmt_sw_out));
	}
	else
	{
		log_debug(
			"Scale filter set %s -> %s (sw: %s) up!",
			av_get_pix_fmt_name(pix_fmt_in),
			av_get_pix_fmt_name(pix_fmt_out),
			av_get_pix_fmt_name(pix_fmt_sw_out));
	}
}

void destroy_scalers(Scalers* s)
{
	destroy_scale_ctx(&s->bgr0);
	destroy_scale_ctx(&s->rgb0);
	destroy_scale_ctx(&s->rgb);
	if (s->frame_out)
		av_frame_free(&s->frame_out);
}

void init_scalers(
	Scalers* ctx,
	int width_in,
	int height_in,
	int width_out,
	int height_out,
	enum AVPixelFormat pix_fmt_out,
	enum AVPixelFormat pix_fmt_sw_out,
	AVBufferRef* hw_device_ctx,
	Error* err)
{
	int ret;
	ctx->frame_out = av_frame_alloc();
	if (!ctx->frame_out)
	{
		destroy_scalers(ctx);
		ERROR(err, 1, "Failed to allocate frame_out for scale filter!");
	}

	if (hw_device_ctx != NULL)
	{

		AVBufferRef* hw_frames_ref;
		AVHWFramesContext* frames_ctx = NULL;
		if (!(hw_frames_ref = av_hwframe_ctx_alloc(hw_device_ctx)))
		{
			destroy_scalers(ctx);
			ERROR(err, 1, "Failed to create HW frame context.");
		}
		frames_ctx = (AVHWFramesContext*)(hw_frames_ref->data);
		frames_ctx->format = pix_fmt_out;
		frames_ctx->sw_format = pix_fmt_sw_out;
		frames_ctx->width = width_out;
		frames_ctx->height = height_out;
		frames_ctx->initial_pool_size = 20;
		if ((ret = av_hwframe_ctx_init(hw_frames_ref)) < 0)
		{
			av_buffer_unref(&hw_frames_ref);
			destroy_scalers(ctx);
			ERROR(
				err,
				1,
				"Failed to initialize HW frame context."
				"Error code: %s",
				av_err2str(ret));
		}

		ctx->hw_frames_ctx = av_buffer_ref(hw_frames_ref);
		ret = av_hwframe_get_buffer(ctx->hw_frames_ctx, ctx->frame_out, 0);
		if (ret < 0)
		{
			av_buffer_unref(&hw_frames_ref);
			destroy_scalers(ctx);
			ERROR(
				err,
				1,
				"Could not allocate video hardware frame data for scaling: %s",
				av_err2str(ret));
		}
		av_buffer_unref(&hw_frames_ref);
	}

	enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_BGR0, AV_PIX_FMT_RGB0, AV_PIX_FMT_RGB24};
	ScaleContext* scalers[] = {&ctx->bgr0, &ctx->rgb0, &ctx->rgb};
	for (int i = 0; i < 3; i++)
	{
		init_scaler(
			scalers[i],
			width_in,
			height_in,
			width_out,
			height_out,
			pix_fmts[i],
			pix_fmt_out,
			hw_device_ctx,
			pix_fmt_sw_out,
			ctx->frame_out,
			err);
		OK_OR_ABORT(err);
	}
}

void scale_frame(ScaleContext* ctx, Error* err)
{
	int ret;
	if ((ret = av_buffersrc_add_frame_flags(
				   ctx->buffersrc_scale_ctx, ctx->frame_in, AV_BUFFERSRC_FLAG_KEEP_REF) < 0))
	{
		ERROR(err, ret, "Error adding frame to buffer source: %s.", av_err2str(ret));
	}

	av_frame_unref(ctx->frame_out);

	while (1)
	{
		int ret = av_buffersink_get_frame(ctx->buffersink_scale_ctx, ctx->frame_out);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			break;
		if (ret < 0)
		{
			ERROR(err, ret, "Error reading frame from buffer sink: %s.", av_err2str(ret));
		}
	}
}

void open_video(VideoContext* ctx, Error* err)
{
	if (ctx->width_out <= 1 || ctx->height_out <= 1)
		ERROR(
			err,
			1,
			"Invalid size for video: width = %d, height = %d",
			ctx->width_out,
			ctx->height_out);

	const AVCodec* codec;
	int ret;

	avformat_alloc_output_context2(&ctx->oc, NULL, "mp4", NULL);
	if (!ctx->oc)
	{
		ERROR(err, 1, "Could not find output format mp4.");
	}

	int using_hw = 0;

#ifdef HAS_VAAPI
	char* vaapi_device = getenv("WEYLUS_VAAPI_DEVICE");

	if (ctx->try_vaapi &&
		av_hwdevice_ctx_create(
			&ctx->hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, vaapi_device, NULL, 0) == 0)
	{

		if (ctx->hw_device_ctx)
		{
			AVHWFramesConstraints* cst =
				av_hwdevice_get_hwframe_constraints(ctx->hw_device_ctx, NULL);
			if (cst)
			{
				for (enum AVPixelFormat* fmt = cst->valid_sw_formats; *fmt != AV_PIX_FMT_NONE;
					 ++fmt)
				{
					log_debug("VAAPI: valid pix_fmt: %s", av_get_pix_fmt_name(*fmt));
				}
				av_hwframe_constraints_free(&cst);
			}
		}

		codec = avcodec_find_encoder_by_name("h264_vaapi");
		if (codec)
		{
			ctx->c = avcodec_alloc_context3(codec);
			if (ctx->c)
			{
				Error err = {0};
				init_scalers(
					&ctx->scalers,
					ctx->width_in,
					ctx->height_in,
					ctx->width_out,
					ctx->height_out,
					AV_PIX_FMT_VAAPI,
					AV_PIX_FMT_NV12,
					ctx->hw_device_ctx,
					&err);
				if (err.code)
				{
					log_warn("Failed to initialize scaler: %s", err.error_str);
					avcodec_free_context(&ctx->c);
				}
				else
				{
					ctx->c->pix_fmt = AV_PIX_FMT_VAAPI;
					ctx->c->hw_frames_ctx = ctx->scalers.hw_frames_ctx;
					av_opt_set(ctx->c->priv_data, "quality", "7", 0);
					av_opt_set(ctx->c->priv_data, "qp", "23", 0);
					set_codec_params(ctx);

					if ((ret = avcodec_open2(ctx->c, codec, NULL) == 0))
						using_hw = 1;
					else
					{
						log_debug("Could not open codec: %s!", av_err2str(ret));
						avcodec_free_context(&ctx->c);
						av_buffer_unref(&ctx->hw_device_ctx);
						destroy_scalers(&ctx->scalers);
					}
				}
			}
		}
		else
			av_buffer_unref(&ctx->hw_device_ctx);
	}
#endif

#ifdef HAS_MEDIAFOUNDATION
	if (ctx->try_mediafoundation && !using_hw)
	{
		codec = avcodec_find_encoder_by_name("h264_mf");
		if (codec)
		{
			ctx->c = avcodec_alloc_context3(codec);
			if (ctx->c)
			{
				Error err = {0};
				init_scalers(
					&ctx->scalers,
					ctx->width_in,
					ctx->height_in,
					ctx->width_out,
					ctx->height_out,
					AV_PIX_FMT_NV12,
					AV_PIX_FMT_NV12,
					NULL,
					&err);
				if (err.code)
				{
					log_warn("Failed to initialize scaler: %s", err.error_str);
					avcodec_free_context(&ctx->c);
				}
				else
				{
					ctx->c->pix_fmt = AV_PIX_FMT_NV12;
					av_opt_set(ctx->c->priv_data, "rate_control", "ld_vbr", 0);
					av_opt_set(ctx->c->priv_data, "scenario", "display_remoting", 0);
					av_opt_set(ctx->c->priv_data, "quality", "100", 0);
					set_codec_params(ctx);
					int ret = avcodec_open2(ctx->c, codec, NULL);
					if (ret == 0)
						using_hw = 1;
					else
					{
						log_debug("Could not open codec: %s!", av_err2str(ret));
						avcodec_free_context(&ctx->c);
						destroy_scalers(&ctx->scalers);
					}
				}
			}
			else
				log_debug("Could not allocate video codec context for 'h264_mf'!");
		}
		else
			log_debug("Codec 'h264_mf' not found!");
	}
#endif

#ifdef HAS_NVENC
	if (ctx->try_nvenc && !using_hw &&
		av_hwdevice_ctx_create(&ctx->hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0) == 0)
	{
		codec = avcodec_find_encoder_by_name("h264_nvenc");
		if (codec)
		{
			ctx->c = avcodec_alloc_context3(codec);
			if (ctx->c)
			{
				Error err = {0};
				init_scalers(
					&ctx->scalers,
					ctx->width_in,
					ctx->height_in,
					ctx->width_out,
					ctx->height_out,
					AV_PIX_FMT_CUDA,
#ifdef HAS_LIBNPP
					AV_PIX_FMT_NV12,
#else
					AV_PIX_FMT_BGR0,
#endif
					ctx->hw_device_ctx,
					&err);
				if (err.code)
				{
					log_warn("Failed to initialize scaler: %s", err.error_str);
					avcodec_free_context(&ctx->c);
				}
				else
				{
					ctx->c->pix_fmt = AV_PIX_FMT_CUDA;
					ctx->c->hw_frames_ctx = ctx->scalers.hw_frames_ctx;
					av_opt_set(ctx->c->priv_data, "preset", "p1", 0);
					av_opt_set(ctx->c->priv_data, "zerolatency", "1", 0);
					av_opt_set(ctx->c->priv_data, "tune", "ull", 0);
					av_opt_set(ctx->c->priv_data, "rc", "cbr", 0);
					av_opt_set(ctx->c->priv_data, "cq", "21", 0);
					av_opt_set(ctx->c->priv_data, "delay", "0", 0);
					set_codec_params(ctx);

					int ret = avcodec_open2(ctx->c, codec, NULL);
					if (ret == 0)
						using_hw = 1;
					else
					{
						log_debug("Could not open codec: %s!", av_err2str(ret));
						avcodec_free_context(&ctx->c);
						destroy_scalers(&ctx->scalers);
					}
				}
			}
			else
				log_debug("Could not allocate video codec context for 'h264_nvenc'!");
		}
		else
			log_debug("Codec 'h264_nvenc' not found!");
	}
#endif

#ifdef HAS_VIDEOTOOLBOX
	if (ctx->try_videotoolbox && !using_hw)
	{
		codec = avcodec_find_encoder_by_name("h264_videotoolbox");
		if (codec)
		{
			ctx->c = avcodec_alloc_context3(codec);
			if (ctx->c)
			{
				Error err = {0};
				init_scalers(
					&ctx->scalers,
					ctx->width_in,
					ctx->height_in,
					ctx->width_out,
					ctx->height_out,
					AV_PIX_FMT_YUV420P,
					AV_PIX_FMT_YUV420P,
					ctx->hw_device_ctx,
					&err);
				if (err.code)
				{
					log_warn("Failed to initialize scaler: %s", err.error_str);
					avcodec_free_context(&ctx->c);
				}
				else
				{
					ctx->c->pix_fmt = AV_PIX_FMT_YUV420P;
					av_opt_set(ctx->c->priv_data, "realtime", "true", 0);
					av_opt_set(ctx->c->priv_data, "allow_sw", "true", 0);
					av_opt_set(ctx->c->priv_data, "profile", "extended", 0);
					av_opt_set(ctx->c->priv_data, "level", "5.2", 0);
					set_codec_params(ctx);
					if (avcodec_open2(ctx->c, codec, NULL) == 0)
						using_hw = 1;
					else
					{
						log_debug("Could not open codec: %s!", av_err2str(ret));
						avcodec_free_context(&ctx->c);
						destroy_scalers(&ctx->scalers);
					}
				}
			}
		}
	}
#endif

	if (!using_hw)
	{
		codec = avcodec_find_encoder_by_name("libx264");
		if (!codec)
		{
			ERROR(err, 1, "Codec 'libx264' not found");
		}

		ctx->c = avcodec_alloc_context3(codec);
		if (!ctx->c)
		{
			ERROR(err, 1, "Could not allocate video codec context");
		}

		init_scalers(
			&ctx->scalers,
			ctx->width_in,
			ctx->height_in,
			ctx->width_out,
			ctx->height_out,
			AV_PIX_FMT_YUV420P,
			AV_PIX_FMT_YUV420P,
			NULL,
			err);
		if (err->code)
		{
			avcodec_free_context(&ctx->c);
			return;
		}

		ctx->c->pix_fmt = AV_PIX_FMT_YUV420P;
		av_opt_set(ctx->c->priv_data, "preset", "ultrafast", 0);
		av_opt_set(ctx->c->priv_data, "tune", "zerolatency", 0);
		av_opt_set(ctx->c->priv_data, "crf", "23", 0);
		set_codec_params(ctx);

		ret = avcodec_open2(ctx->c, codec, NULL);
		if (ret < 0)
		{
			avcodec_free_context(&ctx->c);
			ERROR(err, 1, "Could not open codec: %s", av_err2str(ret));
		}
	}

	ctx->st = avformat_new_stream(ctx->oc, NULL);
	avcodec_parameters_from_context(ctx->st->codecpar, ctx->c);

	ctx->pkt = av_packet_alloc();
	if (!ctx->pkt)
		ERROR(err, 1, "Failed to allocate packet");

	int buf_size = 1024 * 1024;
	ctx->buf = av_malloc(buf_size);
	ctx->oc->pb = avio_alloc_context(
		ctx->buf, buf_size, AVIO_FLAG_WRITE, ctx->rust_ctx, NULL, write_video_packet, NULL);
	if (!ctx->oc->pb)
		ERROR(err, 1, "Failed to allocate avio context");

	AVDictionary* opt = NULL;

	// enable writing fragmented mp4
	av_dict_set(&opt, "movflags", "frag_custom+empty_moov+default_base_moof", 0);
	ret = avformat_write_header(ctx->oc, &opt);
	if (ret < 0)
		log_warn("Video: failed to write header!");
	av_dict_free(&opt);

	if (av_pix_fmt_desc_get(ctx->c->pix_fmt)->flags & AV_PIX_FMT_FLAG_HWACCEL &&
		ctx->c->hw_frames_ctx)
	{
		const char* pix_fmt_sw =
			av_get_pix_fmt_name(((AVHWFramesContext*)ctx->c->hw_frames_ctx->data)->sw_format);
		log_info(
			"Video: %dx%d@%s pix_fmt: %s (%s)",
			ctx->width_out,
			ctx->height_out,
			ctx->c->codec->name,
			av_get_pix_fmt_name(ctx->c->pix_fmt),
			pix_fmt_sw);
	}
	else
		log_info(
			"Video: %dx%d@%s pix_fmt: %s",
			ctx->width_out,
			ctx->height_out,
			ctx->c->codec->name,
			av_get_pix_fmt_name(ctx->c->pix_fmt));

	ctx->initialized = 1;
}

void destroy_video_encoder(VideoContext* ctx)
{
	if (ctx->initialized)
	{
		av_write_trailer(ctx->oc);
		avio_context_free(&ctx->oc->pb);
		avformat_free_context(ctx->oc);
		avcodec_free_context(&ctx->c);
		av_packet_free(&ctx->pkt);
		av_free(ctx->buf);
		destroy_scalers(&ctx->scalers);
	}
	if (ctx->hw_device_ctx)
		av_buffer_unref(&ctx->hw_device_ctx);
	free(ctx);
}

void encode_video_frame(VideoContext* ctx, int millis, Error* err)
{
	int ret;
	AVFrame* frame = ctx->frame;
	if (!frame)
		ERROR(err, 1, "Frame not initialized!");

	frame->pts = millis;

	ret = avcodec_send_frame(ctx->c, frame);
	if (ret < 0)
		ERROR(err, 1, "Error sending a frame for encoding: %s", av_err2str(ret));

	while (ret >= 0)
	{
		ret = avcodec_receive_packet(ctx->c, ctx->pkt);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0)
		{
			ERROR(err, 1, "Error during encoding");
		}

		av_packet_rescale_ts(ctx->pkt, ctx->c->time_base, ctx->st->time_base);
		av_write_frame(ctx->oc, ctx->pkt);
		av_packet_unref(ctx->pkt);

		// new fragment on every frame for lowest latency
		av_write_frame(ctx->oc, NULL);
	}
}

VideoContext* init_video_encoder(
	void* rust_ctx,
	int width_in,
	int height_in,
	int width_out,
	int height_out,
	int try_vaapi,
	int try_nvenc,
	int try_videotoolbox,
	int try_mediafoundation)
{
	VideoContext* ctx = malloc(sizeof(VideoContext));
	ctx->rust_ctx = rust_ctx;
	ctx->width_out = width_out - width_out % 2;
	ctx->height_out = height_out - height_out % 2;
	ctx->width_in = width_in;
	ctx->height_in = height_in;
	ctx->pts = 0;
	ctx->initialized = 0;
	ctx->frame_allocated = 0;
	ctx->try_vaapi = try_vaapi;
	ctx->try_nvenc = try_nvenc;
	ctx->try_videotoolbox = try_videotoolbox;
	ctx->try_mediafoundation = try_mediafoundation;
	ctx->hw_device_ctx = NULL;

	// make sure all scalers are zero initialized so that destroy can always be called
	memset(&ctx->scalers, 0, sizeof(Scalers));
	return ctx;
}

void fill_bgr0(VideoContext* ctx, const void* data, int stride, Error* err)
{
	ctx->frame = NULL;
	ScaleContext* scaler = &ctx->scalers.bgr0;
	scaler->frame_in->data[0] = (uint8_t*)data;
	scaler->frame_in->linesize[0] = stride;

	scale_frame(scaler, err);
	OK_OR_ABORT(err)
	ctx->frame = scaler->frame_out;
}

void fill_rgb(VideoContext* ctx, const void* data, Error* err)
{
	ctx->frame = NULL;
	ScaleContext* scaler = &ctx->scalers.rgb;
	ctx->frame = NULL;
	scaler->frame_in->data[0] = (uint8_t*)data;
	scaler->frame_in->linesize[0] = ctx->width_in * 3;

	scale_frame(scaler, err);
	OK_OR_ABORT(err)
	ctx->frame = scaler->frame_out;
}

void fill_rgb0(VideoContext* ctx, const void* data, Error* err)
{
	ctx->frame = NULL;
	ScaleContext* scaler = &ctx->scalers.rgb0;
	scaler->frame_in->data[0] = (uint8_t*)data;
	scaler->frame_in->linesize[0] = ctx->width_in * 4;

	scale_frame(scaler, err);
	OK_OR_ABORT(err)
	ctx->frame = scaler->frame_out;
}


================================================
FILE: lib/error.c
================================================
#include "error.h"

void fill_error(Error* err, int code, const char* fmt, ...)
{
	if (!err)
		return;
	err->code = code;
	va_list args;
	va_start(args, fmt);
	vsnprintf(err->error_str, sizeof(err->error_str), fmt, args);
}


================================================
FILE: lib/error.h
================================================
#pragma once

#include <stdarg.h>
#include <stdio.h>

struct Error
{
	int code;
	char error_str[1024];
};

typedef struct Error Error;

#if defined(__clang__) || defined(__GNUC__)
__attribute__((__format__ (__printf__, 3, 4)))
#endif
void fill_error(Error* err, int code, const char* fmt, ...);

#define ERROR(err, code, fmt, ...)                                                                 \
	{                                                                                              \
		fill_error(err, code, fmt, ##__VA_ARGS__);                                                 \
		return;                                                                                    \
	}

#define OK_OR_ABORT(err)                                                                           \
	{                                                                                              \
		if (err->code)                                                                             \
			return;                                                                                \
	}


================================================
FILE: lib/linux/uinput.c
================================================
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/input-event-codes.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../error.h"

#define ABS_MAXVAL 65535

#ifndef REL_WHEEL_HI_RES
#define REL_WHEEL_HI_RES	0x0b
#endif
#ifndef REL_HWHEEL_HI_RES
#define REL_HWHEEL_HI_RES	0x0c
#endif

void setup_abs(int fd, int code, int minimum, int maximum, int resolution, Error* err)
{
	if (ioctl(fd, UI_SET_ABSBIT, code) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_ABSBIT, code %#x", code);

	struct uinput_abs_setup abs_setup;
	memset(&abs_setup, 0, sizeof(abs_setup));
	abs_setup.code = code;
	abs_setup.absinfo.value = 0;
	abs_setup.absinfo.minimum = minimum;
	abs_setup.absinfo.maximum = maximum;
	abs_setup.absinfo.fuzz = 0;
	abs_setup.absinfo.flat = 0;
	// units/mm
	abs_setup.absinfo.resolution = resolution;
	if (ioctl(fd, UI_ABS_SETUP, &abs_setup) < 0)
		ERROR(err, 1, "error: UI_ABS_SETUP, code: %#x", code);
}

void setup(int fd, const char* name, Error* err)
{

	struct uinput_setup setup;
	memset(&setup, 0, sizeof(setup));
	strncpy(setup.name, name, UINPUT_MAX_NAME_SIZE - 1);
	setup.id.bustype = BUS_VIRTUAL;
	setup.id.vendor = 0x1701;
	setup.id.product = 0x1701;
	setup.id.version = 0x0001;
	setup.ff_effects_max = 0;
	if (ioctl(fd, UI_DEV_SETUP, &setup) < 0)
		ERROR(err, 1, "error: UI_DEV_SETUP");
}

void init_keyboard(int fd, const char* name, Error* err)
{
	// enable synchronization
	if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_SYN");

	// enable keys
	if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_KEY");

	// enable all the keys!
	for (int keycode = KEY_ESC; keycode <= KEY_MICMUTE; ++keycode)
		if (ioctl(fd, UI_SET_KEYBIT, keycode) < 0)
			ERROR(err, 1, "error: ioctl UI_SET_KEYBIT %x", keycode);

	// TODO: figure if scancodes are needed
	// if (ioctl(fd, UI_SET_EVBIT, EV_MSC) < 0)
	// 	ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_MSC");
	// if (ioctl(fd, UI_SET_MSCBIT, MSC_SCAN) < 0)
	// 	ERROR(err, 1, "error: ioctl UI_SET_MSCBIT MSC_SCAN");

	setup(fd, name, err);
	OK_OR_ABORT(err);

	if (ioctl(fd, UI_DEV_CREATE) < 0)
		ERROR(err, 1, "error: ioctl");
}

void init_mouse(int fd, const char* name, Error* err)
{
	// enable synchronization
	if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_SYN");

	if (ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_PROPBIT INPUT_PROP_DIRECT");

	// enable buttons
	if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_KEY");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT BTN_LEFT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT BTN_RIGHT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT BTN_MIDDLE");

	// enable scrolling
	if (ioctl(fd, UI_SET_EVBIT, EV_REL) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_REL");
	if (ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_RELBIT REL_WHEEL");
	if (ioctl(fd, UI_SET_RELBIT, REL_HWHEEL) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_RELBIT REL_HWHEEL");
	if (ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_RELBIT REL_WHEEL_HI_RES");
	if (ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_RELBIT REL_HWHEEL_HI_RES");

	// setup sending timestamps
	if (ioctl(fd, UI_SET_EVBIT, EV_MSC) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_MSC");
	if (ioctl(fd, UI_SET_MSCBIT, MSC_TIMESTAMP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_MSCBIT MSC_TIMESTAMP");

	if (ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_ABS");

	setup_abs(fd, ABS_X, 0, ABS_MAXVAL, 0, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_Y, 0, ABS_MAXVAL, 0, err);
	OK_OR_ABORT(err);

	setup(fd, name, err);
	OK_OR_ABORT(err);

	if (ioctl(fd, UI_DEV_CREATE) < 0)
		ERROR(err, 1, "error: ioctl");
}

void init_stylus(int fd, const char* name, Error* err)
{
	// enable synchronization
	if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_SYN");

	if (ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_PROPBIT INPUT_PROP_DIRECT");

	// enable buttons
	if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_KEY");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT BTN_TOOL_PEN");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT BTN_TOOL_RUBBER");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT BTN_TOUCH");

	// setup sending timestamps
	if (ioctl(fd, UI_SET_EVBIT, EV_MSC) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_MSC");
	if (ioctl(fd, UI_SET_MSCBIT, MSC_TIMESTAMP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_MSCBIT MSC_TIMESTAMP");

	if (ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_ABS");

	setup_abs(fd, ABS_X, 0, ABS_MAXVAL, 12, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_Y, 0, ABS_MAXVAL, 12, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_PRESSURE, 0, ABS_MAXVAL, 12, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_TILT_X, -90, 90, 12, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_TILT_Y, -90, 90, 12, err);
	OK_OR_ABORT(err);

	setup(fd, name, err);
	OK_OR_ABORT(err);

	if (ioctl(fd, UI_DEV_CREATE) < 0)
		ERROR(err, 1, "error: ioctl");
}

void init_touch(int fd, const char* name, Error* err)
{
	// enable synchronization
	if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_SYN");

	if (ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_PROPBIT INPUT_PROP_DIRECT");

	if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_KEY");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_FINGER) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_DOUBLETAP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_TRIPLETAP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_QUADTAP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT");
	if (ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_QUINTTAP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_KEYBIT");

	// setup sending timestamps
	if (ioctl(fd, UI_SET_EVBIT, EV_MSC) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_MSC");
	if (ioctl(fd, UI_SET_MSCBIT, MSC_TIMESTAMP) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_MSCBIT MSC_TIMESTAMP");

	if (ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0)
		ERROR(err, 1, "error: ioctl UI_SET_EVBIT EV_ABS");

	setup_abs(fd, ABS_X, 0, ABS_MAXVAL, 200, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_Y, 0, ABS_MAXVAL, 200, err);
	OK_OR_ABORT(err);

	// 5 fingers 5 multitouch slots.
	setup_abs(fd, ABS_MT_SLOT, 0, 4, 0, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_MT_TRACKING_ID, 0, 4, 0, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_MT_POSITION_X, 0, ABS_MAXVAL, 200, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_MT_POSITION_Y, 0, ABS_MAXVAL, 200, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_MT_PRESSURE, 0, ABS_MAXVAL, 0, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_MT_TOUCH_MAJOR, 0, ABS_MAXVAL, 12, err);
	OK_OR_ABORT(err);
	setup_abs(fd, ABS_MT_TOUCH_MINOR, 0, ABS_MAXVAL, 12, err);
	OK_OR_ABORT(err);
	// PointerEvent only gives partial orientation of the touch ellipse
	setup_abs(fd, ABS_MT_ORIENTATION, 0, 1, 0, err);
	OK_OR_ABORT(err);

	setup(fd, name, err);
	OK_OR_ABORT(err);

	if (ioctl(fd, UI_DEV_CREATE) < 0)
		ERROR(err, 1, "error: ioctl");
}

int init_uinput_keyboard(const char* name, Error* err)
{
	int device;

	if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0)
		fill_error(err, 101, "error: failed to open /dev/uinput");
	else
	{
		init_keyboard(device, name, err);
	}
	return device;
}

int init_uinput_stylus(const char* name, Error* err)
{
	int device;

	if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0)
		fill_error(err, 101, "error: failed to open /dev/uinput");
	else
	{
		init_stylus(device, name, err);
	}
	return device;
}

int init_uinput_mouse(const char* name, Error* err)
{
	int device;

	if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0)
		fill_error(err, 101, "error: failed to open /dev/uinput");
	else
	{
		init_mouse(device, name, err);
	}
	return device;
}

int init_uinput_touch(const char* name, Error* err)
{
	int device;

	if ((device = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0)
		fill_error(err, 101, "error: failed to open /dev/uinput");
	else
	{
		init_touch(device, name, err);
	}
	return device;
}

void destroy_uinput_device(int fd)
{
	ioctl(fd, UI_DEV_DESTROY);
	close(fd);
}

void send_uinput_event(int device, int type, int code, int value, Error* err)
{
	struct input_event ev;
	ev.type = type;
	ev.code = code;
	ev.value = value;
	if (write(device, &ev, sizeof(ev)) < 0)
		ERROR(err, 1, "error writing to device, filedescriptor: %d)", device);
}


================================================
FILE: lib/linux/uinput_info.md
================================================
# Some References on how to develop things using uinput

- uinput: https://www.kernel.org/doc/html/latest/input/uinput.html
- event codes: https://www.kernel.org/doc/html/latest/input/event-codes.html
- multi-touch protocol: https://www.kernel.org/doc/html/latest/input/multi-touch-protocol.html

## Other projects using uinput
- https://github.com/rfc2822/GfxTablet
- https://github.com/bsteinsbo/rpi_touch_driver

## Debugging
- libinput debug-events
- evtest



================================================
FILE: lib/linux/xcapture.c
================================================
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <X11/extensions/XShm.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xfixes.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <stdint.h>

#include "../error.h"
#include "../log.h"
#include "xhelper.h"

int clamp(int x, int lb, int ub)
{
	if (x < lb)
		return lb;
	if (x > ub)
		return ub;
	return x;
}

struct CaptureContext
{
	Capturable cap;
	XImage* ximg;
	XShmSegmentInfo shminfo;
	int has_xfixes;
	int has_offscreen;
	int wayland;
	Bool last_img_return;
};

typedef struct CaptureContext CaptureContext;

struct Image
{
	char* data;
	unsigned int width;
	unsigned int height;
};

void* start_capture(Capturable* cap, CaptureContext* ctx, Error* err)
{
	if (XShmQueryExtension(cap->disp) != True)
	{
		fill_error(err, 1, "XShmExtension is not available but required!");
		return NULL;
	}

	if (!ctx)
	{
		ctx = malloc(sizeof(CaptureContext));

		int major, minor;
		Bool pixmaps = False;
		XShmQueryVersion(cap->disp, &major, &minor, &pixmaps);
		ctx->has_offscreen = pixmaps == True;
		if (ctx->has_offscreen && cap->type == WINDOW && cap->c.winfo.is_regular_window)
		{
			int event_base, error_base;
			ctx->has_offscreen =
				XCompositeQueryExtension(cap->disp, &event_base, &error_base) == True;
			if (ctx->has_offscreen)
				XCompositeRedirectWindow(cap->disp, cap->c.winfo.win, False);
		}
		const char* session_type = getenv("XDG_SESSION_TYPE");
		if (session_type && strcmp(session_type, "wayland") == 0)
			ctx->wayland = 1;
		else
			ctx->wayland = 0;
	}
	ctx->cap = *cap;
	ctx->last_img_return = True;

	if (&ctx->cap != cap)
		strncpy(ctx->cap.name, cap->name, sizeof(ctx->cap.name));

	int event_base, error_base;
	ctx->has_xfixes = XFixesQueryExtension(cap->disp, &event_base, &error_base) == True;

	int x, y;
	unsigned int width, height;
	get_geometry(cap, &x, &y, &width, &height, err);
	ctx->ximg = XShmCreateImage(
		cap->disp,
		DefaultVisualOfScreen(cap->screen),
		DefaultDepthOfScreen(cap->screen),
		ZPixmap,
		NULL,
		&ctx->shminfo,
		width,
		height);

	ctx->shminfo.shmid =
		shmget(IPC_PRIVATE, ctx->ximg->bytes_per_line * ctx->ximg->height, IPC_CREAT | 0777);
	ctx->shminfo.shmaddr = ctx->ximg->data = (char*)shmat(ctx->shminfo.shmid, 0, 0);
	ctx->shminfo.readOnly = False;
	if (ctx->shminfo.shmid < 0)
	{
		fill_error(err, 1, "Fatal shminfo error!");
		free(ctx);
		return NULL;
	}
	if (!XShmAttach(cap->disp, &ctx->shminfo))
	{
		fill_error(err, 1, "XShmAttach() failed");
		free(ctx);
		return NULL;
	}

	return ctx;
}

void stop_capture(CaptureContext* ctx, Error* err)
{
	XShmDetach(ctx->cap.disp, &ctx->shminfo);
	XDestroyImage(ctx->ximg);
	if (shmdt(ctx->shminfo.shmaddr) != 0)
	{
		fill_error(err, 1, "Failed to detach shared memory!");
	}
	shmctl(ctx->shminfo.shmid, IPC_RMID, NULL);
	if (ctx->has_offscreen && ctx->cap.type == WINDOW && ctx->cap.c.winfo.is_regular_window)
		XCompositeUnredirectWindow(ctx->cap.disp, ctx->cap.c.winfo.win, False);
	free(ctx);
}

void capture_screen(CaptureContext* ctx, struct Image* img, int capture_cursor, Error* err)
{
	Window root = DefaultRootWindow(ctx->cap.disp);
	int x, y;
	unsigned int width, height;
	get_geometry(&ctx->cap, &x, &y, &width, &height, err);
	OK_OR_ABORT(err);
	// if window resized, create new cap...
	if (width != (unsigned int)ctx->ximg->width || height != (unsigned int)ctx->ximg->height)
	{
		XShmDetach(ctx->cap.disp, &ctx->shminfo);
		XDestroyImage(ctx->ximg);
		shmdt(ctx->shminfo.shmaddr);
		shmctl(ctx->shminfo.shmid, IPC_RMID, NULL);
		CaptureContext* new_ctx = start_capture(&ctx->cap, ctx, err);
		if (!new_ctx)
		{
			return;
		}
	}

	Bool get_img_ret = False;

	switch (ctx->cap.type)
	{
	case WINDOW:
	{
		Window* active_window;
		unsigned long size;

		int is_offscreen = ctx->cap.c.winfo.is_regular_window &&
						   (x < 0 || y < 0 || x + (int)width > ctx->cap.screen->width ||
							y + (int)height > ctx->cap.screen->height);

		active_window =
			(Window*)get_property(ctx->cap.disp, root, XA_WINDOW, "_NET_ACTIVE_WINDOW", &size, err);
		if (!ctx->wayland && *active_window == ctx->cap.c.winfo.win && !is_offscreen)
		{
			// cap window within its root so menus are visible as strictly speaking menus do not
			// belong to the window itself ...
			// But don't do this on (X)Wayland as the root window is just black in that case.
			get_img_ret = XShmGetImage(ctx->cap.disp, root, ctx->ximg, x, y, 0x00ffffff);
		}
		else
		{
			// ... but only if it is the active window as we might be recording the wrong thing
			// otherwise. If it is not active just record the window itself.
			// also if pixmaps are supported use those as they support capturing windows even if
			// they are offscreen
			if (is_offscreen)
			{
				if (ctx->has_offscreen)
				{
					Pixmap pm = XCompositeNameWindowPixmap(ctx->cap.disp, ctx->cap.c.winfo.win);
					get_img_ret = XShmGetImage(ctx->cap.disp, pm, ctx->ximg, 0, 0, 0x00ffffff);
					XFreePixmap(ctx->cap.disp, pm);
				}
				else
					ERROR(
						err,
						1,
						"Can not capture window as it is off screen and Xcomposite is "
						"unavailable!");
			}
			else
				get_img_ret =
					XShmGetImage(ctx->cap.disp, ctx->cap.c.winfo.win, ctx->ximg, 0, 0, 0x00ffffff);
		}
		free(active_window);
		break;
	}
	case RECT:
		get_img_ret = XShmGetImage(ctx->cap.disp, root, ctx->ximg, x, y, 0x00ffffff);
		break;
	}

	Bool last_img_return = ctx->last_img_return;
	ctx->last_img_return = get_img_ret;
	// only print an error once and do not repeat this message if consecutive calls to XShmGetImage
	// fail to avoid spamming the logs.
	if (get_img_ret != True)
	{
		if (last_img_return != get_img_ret)
		{
			ERROR(err, 1, "XShmGetImage failed!");
		}
		else
		{
			ERROR(err, 2, "XShmGetImage failed!");
		}
	}

	// capture cursor if requested and if XFixes is available
	if (capture_cursor && ctx->has_xfixes)
	{
		XFixesCursorImage* cursor_img = XFixesGetCursorImage(ctx->cap.disp);
		if (cursor_img != NULL)
		{
			uint32_t* data = (uint32_t*)ctx->ximg->data;

			// coordinates of cursor inside ximg
			int x0 = cursor_img->x - cursor_img->xhot - x;
			int y0 = cursor_img->y - cursor_img->yhot - y;

			// clamp part of cursor image to draw to the part of the cursor that is inside
			// the captured area
			int i0 = clamp(0, -x0, width - x0);
			int i1 = clamp(cursor_img->width, -x0, width - x0);
			int j0 = clamp(0, -y0, height - y0);
			int j1 = clamp(cursor_img->height, -y0, height - y0);
			// paint cursor image into captured image
			for (int j = j0; j < j1; ++j)
				for (int i = i0; i < i1; ++i)
				{
					uint32_t c_pixel = cursor_img->pixels[j * cursor_img->width + i];
					unsigned char a = (c_pixel & 0xff000000) >> 24;
					if (a)
					{
						uint32_t d_pixel = data[(j + y0) * width + i + x0];

						unsigned char c1 = (c_pixel & 0x00ff0000) >> 16;
						unsigned char c2 = (c_pixel & 0x0000ff00) >> 8;
						unsigned char c3 = (c_pixel & 0x000000ff) >> 0;
						unsigned char d1 = (d_pixel & 0x00ff0000) >> 16;
						unsigned char d2 = (d_pixel & 0x0000ff00) >> 8;
						unsigned char d3 = (d_pixel & 0x000000ff) >> 0;
						// colors from the cursor image are premultiplied with the alpha channel
						unsigned char f1 = c1 + d1 * (255 - a) / 255;
						unsigned char f2 = c2 + d2 * (255 - a) / 255;
						unsigned char f3 = c3 + d3 * (255 - a) / 255;
						data[(j + y0) * width + i + x0] = (f1 << 16) | (f2 << 8) | (f3 << 0);
					}
				}

			XFree(cursor_img);
		}
		else
		{
			log_warn(
				"Failed to obtain cursor image, XFixesGetCursorImage has returned a null pointer.");
		}
	}

	img->width = ctx->ximg->width;
	img->height = ctx->ximg->height;
	img->data = ctx->ximg->data;
}


================================================
FILE: lib/linux/xhelper.c
================================================
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/randr.h>
#include <stdlib.h>
#include <string.h>

#include "../error.h"
#include "../log.h"
#include "xhelper.h"

int x11_error_handler(Display* disp, XErrorEvent* err)
{
	char buf1[128], buf2[128], message_selector[64];
	XGetErrorText(disp, err->error_code, buf1, sizeof(buf1));
	snprintf(message_selector, sizeof(message_selector), "XRequest.%d", err->request_code);
	XGetErrorDatabaseText(disp, "", message_selector, message_selector, buf2, sizeof(buf2));
	log_debug("X11 error: %s: %s 0x%lx", buf1, buf2, err->resourceid);
	return 0;
}

void x11_set_error_handler()
{
	// setting an error handler is required as otherwise xlib may just exit the process, even though
	// the error was recoverable.
	XSetErrorHandler(x11_error_handler);
}

int locale_to_utf8(char* src, char* dest, size_t size)
{
	iconv_t icd = iconv_open("UTF-8//IGNORE", "");
	size_t src_size = size;
	size_t outbytes_left = MAX_PROPERTY_VALUE_LEN - 1;
	int ret = iconv(icd, &src, &src_size, &dest, &outbytes_left);
	iconv_close(icd);
	if (ret < 0)
	{
		return -1;
	}
	dest[src_size - 1 - outbytes_left] = '\0';
	return 0;
}

char* get_property(
	Display* disp, Window win, Atom xa_prop_type, char* prop_name, unsigned long* size, Error* err)
{
	Atom xa_prop_name;
	Atom xa_ret_type;
	int ret_format;
	unsigned long ret_nitems;
	unsigned long ret_bytes_after;
	unsigned long tmp_size;
	unsigned char* ret_prop;
	char* ret;

	xa_prop_name = XInternAtom(disp, prop_name, False);

	/* MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
	 *
	 * long_length = Specifies the length in 32-bit multiples of the
	 *               data to be retrieved.
	 */
	if (XGetWindowProperty(
			disp,
			win,
			xa_prop_name,
			0,
			MAX_PROPERTY_VALUE_LEN / 4,
			False,
			xa_prop_type,
			&xa_ret_type,
			&ret_format,
			&ret_nitems,
			&ret_bytes_after,
			&ret_prop) != Success)
	{
		fill_error(err, 1, "Cannot get %s property.", prop_name);
		return NULL;
	}

	if (xa_ret_type != xa_prop_type)
	{
		fill_error(err, 1, "Invalid type of %s property.", prop_name);
		XFree(ret_prop);
		return NULL;
	}

	/* null terminate the result to make string handling easier */
	tmp_size = (ret_format / 8) * ret_nitems;
	/* Correct 64 Architecture implementation of 32 bit data */
	if (ret_format == 32)
		tmp_size *= sizeof(long) / 4;
	ret = malloc(tmp_size + 1);
	memcpy(ret, ret_prop, tmp_size);
	ret[tmp_size] = '\0';

	if (size)
	{
		*size = tmp_size;
	}

	XFree(ret_prop);
	return ret;
}

char* get_window_title(Display* disp, Window win, Error* err)
{
	char* title_utf8;
	char* wm_name;
	char* net_wm_name;
	Error err_wm;
	Error err_net_wm;

	wm_name = get_property(disp, win, XA_STRING, "WM_NAME", NULL, &err_wm);
	net_wm_name = get_property(
		disp, win, XInternAtom(disp, "UTF8_STRING", False), "_NET_WM_NAME", NULL, &err_net_wm);

	if (net_wm_name)
	{
		title_utf8 = strdup(net_wm_name);
	}
	else
	{
		if (wm_name)
		{
			title_utf8 = malloc(MAX_PROPERTY_VALUE_LEN);
			if (locale_to_utf8(wm_name, title_utf8, MAX_PROPERTY_VALUE_LEN) != 0)
			{
				fill_error(err, 1, "Failed to convert windowname to UTF-8!");
				free(title_utf8);
				title_utf8 = NULL;
			}
		}
		else
		{
			fill_error(
				err,
				1,
				"Could not get window name: (%s) (%s)",
				err_net_wm.error_str,
				err_wm.error_str);
			title_utf8 = NULL;
		}
	}

	free(wm_name);
	free(net_wm_name);

	return title_utf8;
}

Window* get_client_list(Display* disp, unsigned long* size, Error* err)
{
	Window* client_list;
	Error err_net;
	Error err_win;
	if ((client_list = (Window*)get_property(
			 disp, DefaultRootWindow(disp), XA_WINDOW, "_NET_CLIENT_LIST", size, &err_net)) == NULL)
	{
		if ((client_list = (Window*)get_property(
				 disp, DefaultRootWindow(disp), XA_CARDINAL, "_WIN_CLIENT_LIST", size, &err_win)) ==
			NULL)
		{
			fill_error(
				err,
				2,
				"Cannot get client list properties. "
				"_NET_CLIENT_LIST: %s or _WIN_CLIENT_LIST: %s",
				err_net.error_str,
				err_win.error_str);
			return NULL;
		}
	}

	return client_list;
}

int create_capturables(
	Display* disp, Capturable** capturables, int* num_monitors, int size, Error* err)
{
	if (size <= 0)
		return 0;

	int screen = DefaultScreen(disp);
	Window root = RootWindow(disp, screen);

	int event_base, error_base, major, minor;
	*num_monitors = 0;
	XRRMonitorInfo* monitors = NULL;
	if (XRRQueryExtension(disp, &event_base, &error_base) && XRRQueryVersion(disp, &major, &minor))
	{
		monitors = XRRGetMonitors(disp, root, True, num_monitors);
		if (*num_monitors < 0)
		{
			*num_monitors = 0;
			fill_error(err, 2, "Failed to query monitor info via xrandr.");
		}
	}
	else
	{
		fill_error(err, 2, "Xrandr is unsupported on this X server.");
	}

	Window* client_list;
	unsigned long client_list_size;

	size_t num_windows = ((client_list = get_client_list(disp, &client_list_size, err)) == NULL)
							 ? 0
							 : client_list_size / sizeof(Window);

	size_t i = 0;
	Capturable* c = malloc(sizeof(Capturable));
	capturables[i] = c;
	c->disp = disp;
	c->screen = ScreenOfDisplay(disp, screen);
	strncpy(c->name, "Desktop", sizeof(c->name) - 1);
	c->type = WINDOW;
	c->c.winfo.win = root;
	c->c.winfo.is_regular_window = 0;
	++i;

	for (; i < (size_t)*num_monitors + 1 && i < (size_t)size; ++i)
	{
		Capturable* c = malloc(sizeof(Capturable));
		capturables[i] = c;
		XRRMonitorInfo* m = &monitors[i - 1];
		c->disp = disp;
		c->screen = ScreenOfDisplay(disp, screen);
		char* name = XGetAtomName(disp, m->name);
		snprintf(c->name, sizeof(c->name) - 1, "Monitor: %s", name);
		XFree(name);
		c->type = RECT;
		c->c.rinfo.x = m->x;
		c->c.rinfo.y = m->y;
		c->c.rinfo.width = m->width;
		c->c.rinfo.height = m->height;
	}

	for (; i < num_windows + *num_monitors + 1 && i < (size_t)size; ++i)
	{
		size_t j = i - *num_monitors - 1;
		char* title_utf8 = get_window_title(disp, client_list[j], NULL);
		if (title_utf8 == NULL)
		{
			title_utf8 = malloc(32);
			snprintf(title_utf8, 32, "UNKNOWN %lu", j);
		}

		Capturable* c = malloc(sizeof(Capturable));
		capturables[i] = c;
		c->disp = disp;
		c->screen = ScreenOfDisplay(disp, screen);
		c->type = WINDOW;
		strncpy(c->name, title_utf8, sizeof(c->name) - 1);
		c->c.winfo.win = client_list[j];
		c->c.winfo.is_regular_window = 1;
		free(title_utf8);
	}
	free(client_list);
	XRRFreeMonitors(monitors);
	return i;
}

void* clone_capturable(Capturable* c)
{
	Capturable* c2 = malloc(sizeof(Capturable));
	*c2 = *c;
	memcpy(c2->name, c->name, sizeof(c2->name));
	return c2;
}

void destroy_capturable(Capturable* c) { free(c); }

void get_window_geometry(
	Display* disp,
	Window win,
	int* x,
	int* y,
	unsigned int* width,
	unsigned int* height,
	Error* err)
{
	Window junkroot;
	int junkx, junky;
	unsigned int bw, depth;
	if (!XGetGeometry(disp, win, &junkroot, &junkx, &junky, width, height, &bw, &depth))
	{
		ERROR(err, 1, "Failed to get window geometry!");
	}
	XTranslateCoordinates(disp, win, junkroot, 0, 0, x, y, &junkroot);
}

void get_geometry(
	Capturable* cap, int* x, int* y, unsigned int* width, unsigned int* height, Error* err)
{
	switch (cap->type)
	{
	case WINDOW:
		get_window_geometry(cap->disp, cap->c.winfo.win, x, y, width, height, err);
		return;
	case RECT:
		*x = cap->c.rinfo.x;
		*y = cap->c.rinfo.y;
		*width = cap->c.rinfo.width;
		*height = cap->c.rinfo.height;
		return;
	}
}

void get_geometry_relative(
	Capturable* cap, float* x, float* y, float* width, float* height, Error* err)
{
	int x_tmp, y_tmp;
	unsigned int width_tmp, height_tmp;
	get_geometry(cap, &x_tmp, &y_tmp, &width_tmp, &height_tmp, err);
	OK_OR_ABORT(err);
	*x = x_tmp / (float)cap->screen->width;
	*y = y_tmp / (float)cap->screen->height;
	*width = width_tmp / (float)cap->screen->width;
	*height = height_tmp / (float)cap->screen->height;
}

void client_msg(
	Display* disp,
	Window win,
	char* msg,
	unsigned long data0,
	unsigned long data1,
	unsigned long data2,
	unsigned long data3,
	unsigned long data4,
	Error* err)
{
	XEvent event;
	long mask = SubstructureRedirectMask | SubstructureNotifyMask;

	event.xclient.type = ClientMessage;
	event.xclient.serial = 0;
	event.xclient.send_event = True;
	event.xclient.message_type = XInternAtom(disp, msg, False);
	event.xclient.window = win;
	event.xclient.format = 32;
	event.xclient.data.l[0] = data0;
	event.xclient.data.l[1] = data1;
	event.xclient.data.l[2] = data2;
	event.xclient.data.l[3] = data3;
	event.xclient.data.l[4] = data4;

	if (!XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event))
	{
		ERROR(err, 1, "Cannot send %s event.", msg);
	}
}

void activate_window(Display* disp, WindowInfo* winfo, Error* err)
{
	// do not activate windows like the root window or root windows of a screen
	if (!winfo->is_regular_window)
		return;

	Window* active_window = 0;
	unsigned long size;

	active_window = (Window*)get_property(
		disp, DefaultRootWindow(disp), XA_WINDOW, "_NET_ACTIVE_WINDOW", &size, err);
	if (*active_window == winfo->win)
	{
		// nothing to do window is active already
		free(active_window);
		return;
	}
	free(active_window);

	unsigned long* desktop;
	/* desktop ID */
	if ((desktop = (unsigned long*)get_property(
			 disp, winfo->win, XA_CARDINAL, "_NET_WM_DESKTOP", NULL, err)) == NULL)
	{
		if ((desktop = (unsigned long*)get_property(
				 disp, winfo->win, XA_CARDINAL, "_WIN_WORKSPACE", NULL, err)) == NULL)
		{
			ERROR(err, 1, "Cannot find desktop ID of the window.");
		}
	}
	client_msg(disp, DefaultRootWindow(disp), "_NET_CURRENT_DESKTOP", *desktop, 0, 0, 0, 0, err);
	free(desktop);
	OK_OR_ABORT(err);

	client_msg(disp, winfo->win, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0, err);
	OK_OR_ABORT(err);
	XMapRaised(disp, winfo->win);
}

void capturable_before_input(Capturable* cap, Error* err)
{
	switch (cap->type)
	{
	case WINDOW:
		activate_window(cap->disp, &cap->c.winfo, err);
		break;
	case RECT:
		break;
	}
}

const char* get_capturable_name(Capturable* c) { return c->name; }

void map_input_device_to_entire_screen(Display* disp, const char* device_name, int pen, Error* err)
{

	// for some reason a device simualting a stylus does NOT create a single device in
	// XListInputDevices but actually two: One with the original name and the other one with
	// "Pen (0)" appended to it. The problem is that the original device does NOT permit setting
	// "Coordinate Transformation Matrix". This can only be done for the device with "Pen (0)"
	// appended. So this here is a dirty workaround assuming the configurable stylus/pen device is
	// always called original name + "Pen" + whatever.
	char pen_name[256];
	if (pen)
		snprintf(pen_name, sizeof(pen_name), "%s Pen", device_name);
	XID device_id;
	int num_devices = 0;
	XDeviceInfo* devices = XListInputDevices(disp, &num_devices);

	int found = 0;
	for (int i = 0; i < num_devices; ++i)
	{
		if ((!pen && strcmp(device_name, devices[i].name) == 0) ||
			(pen && strncmp(pen_name, devices[i].name, strlen(pen_name)) == 0))
		{
			device_id = devices[i].id;
			found = 1;
			break;
		}
	}
	XFreeDeviceList(devices);

	if (!found)
		ERROR(err, 2, "Device with name: %s not found!", device_name);

	Atom prop_float, prop_matrix;

	union
	{
		unsigned char* c;
		float* f;
	} data;
	int format_return;
	Atom type_return;
	unsigned long nitems;
	unsigned long bytes_after;

	int rc;

	prop_float = XInternAtom(disp, "FLOAT", False);
	prop_matrix = XInternAtom(disp, "Coordinate Transformation Matrix", False);

	if (!prop_float)
	{
		ERROR(err, 1, "Float atom not found. This server is too old.");
	}
	if (!prop_matrix)
	{
		ERROR(
			err,
			1,
			"Coordinate transformation matrix not found. This "
			"server is too old.");
	}

	rc = XIGetProperty(
		disp,
		device_id,
		prop_matrix,
		0,
		9,
		False,
		prop_float,
		&type_return,
		&format_return,
		&nitems,
		&bytes_after,
		&data.c);
	if (rc != Success || prop_float != type_return || format_return != 32 || nitems != 9 ||
		bytes_after != 0)
	{
		ERROR(err, 1, "Failed to retrieve current property values.");
	}

	data.f[0] = 1.0;
	data.f[1] = 0.0;
	data.f[2] = 0.0;
	data.f[3] = 0.0;
	data.f[4] = 1.0;
	data.f[5] = 0.0;
	data.f[6] = 0.0;
	data.f[7] = 0.0;
	data.f[8] = 1.0;

	XIChangeProperty(
		disp, device_id, prop_matrix, prop_float, format_return, PropModeReplace, data.c, nitems);

	XFree(data.c);
}


================================================
FILE: lib/linux/xhelper.h
================================================
#pragma once

#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <iconv.h>
#include <malloc.h>
#include <string.h>

#include "../error.h"

#define MAX_PROPERTY_VALUE_LEN 4096

typedef struct WindowInfo
{
	Window win;
	int is_regular_window;
} WindowInfo;

typedef struct RectInfo
{
	int x;
	int y;
	unsigned int width;
	unsigned int height;
} RectInfo;

typedef enum CaptureType
{
	WINDOW,
	RECT
} CaptureType;

typedef struct Capturable
{
	CaptureType type;
	char name[128];
	Display* disp;
	Screen* screen;
	union
	{
		WindowInfo winfo;
		RectInfo rinfo;
	} c;
} Capturable;

char* get_property(
	Display* disp, Window win, Atom xa_prop_type, char* prop_name, unsigned long* size, Error* err);

void get_geometry(
	Capturable* cap, int* x, int* y, unsigned int* width, unsigned int* height, Error* err);

void get_geometry_relative(
	Capturable* cap, float* x, float* y, float* width, float* height, Error* err);


================================================
FILE: lib/log.c
================================================
#include "log.h"

// rust functions living in log.rs
void log_error_rust(const char*);
void log_debug_rust(const char*);
void log_info_rust(const char*);
void log_trace_rust(const char*);
void log_warn_rust(const char*);

void log_error(const char* fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	char buf[2048];
	vsnprintf(buf, sizeof(buf), fmt, args);
	log_error_rust(buf);
}

void log_debug(const char* fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	char buf[2048];
	vsnprintf(buf, sizeof(buf), fmt, args);
	log_debug_rust(buf);
}

void log_info(const char* fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	char buf[2048];
	vsnprintf(buf, sizeof(buf), fmt, args);
	log_info_rust(buf);
}

void log_trace(const char* fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	char buf[2048];
	vsnprintf(buf, sizeof(buf), fmt, args);
	log_trace_rust(buf);
}

void log_warn(const char* fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	char buf[2048];
	vsnprintf(buf, sizeof(buf), fmt, args);
	log_warn_rust(buf);
}


================================================
FILE: lib/log.h
================================================
#pragma once

#include <stdarg.h>
#include <stdio.h>

#if defined(__clang__) || defined(__GNUC__)
__attribute__((__format__ (__printf__, 1, 2)))
void log_error(const char* fmt, ...);
__attribute__((__format__ (__printf__, 1, 2)))
void log_debug(const char* fmt, ...);
__attribute__((__format__ (__printf__, 1, 2)))
void log_info(const char* fmt, ...);
__attribute__((__format__ (__printf__, 1, 2)))
void log_trace(const char* fmt, ...);
__attribute__((__format__ (__printf__, 1, 2)))
void log_warn(const char* fmt, ...);
#else
void log_error(const char* fmt, ...);
void log_debug(const char* fmt, ...);
void log_info(const char* fmt, ...);
void log_trace(const char* fmt, ...);
void log_warn(const char* fmt, ...);
#endif


================================================
FILE: src/capturable/captrs_capture.rs
================================================
use crate::capturable::{Capturable, Recorder};
use captrs::Capturer;
use std::boxed::Box;
use std::error::Error;
use winapi::shared::windef::RECT;

use super::Geometry;

#[derive(Clone)]
pub struct CaptrsCapturable {
    id: u8,
    name: String,
    screen: RECT,
    virtual_screen: RECT,
}

impl CaptrsCapturable {
    pub fn new(id: u8, name: String, screen: RECT, virtual_screen: RECT) -> CaptrsCapturable {
        CaptrsCapturable {
            id,
            name,
            screen,
            virtual_screen,
        }
    }
}

impl Capturable for CaptrsCapturable {
    fn name(&self) -> String {
        format!("Desktop {} (captrs)", self.name).into()
    }
    fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
        Ok(())
    }
    fn recorder(&self, _capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
        Ok(Box::new(CaptrsRecorder::new(self.id)?))
    }
    fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
        Ok(Geometry::VirtualScreen(
            self.screen.left - self.virtual_screen.left,
            self.screen.top - self.virtual_screen.top,
            (self.screen.right - self.screen.left) as u32,
            (self.screen.bottom - self.screen.top) as u32,
            self.screen.left,
            self.screen.top,
        ))
    }
}
#[derive(Debug)]
pub struct CaptrsError(String);

impl std::fmt::Display for CaptrsError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self(s) = self;
        write!(f, "{}", s)
    }
}

impl Error for CaptrsError {}
pub struct CaptrsRecorder {
    capturer: Capturer,
}

impl CaptrsRecorder {
    pub fn new(id: u8) -> Result<CaptrsRecorder, Box<dyn Error>> {
        Ok(CaptrsRecorder {
            capturer: Capturer::new(id.into())?,
        })
    }
}

impl Recorder for CaptrsRecorder {
    fn capture(&mut self) -> Result<crate::video::PixelProvider, Box<dyn Error>> {
        self.capturer
            .capture_store_frame()
            .map_err(|_e| CaptrsError("Captrs failed to capture frame".into()))?;
        let (w, h) = self.capturer.geometry();
        Ok(crate::video::PixelProvider::BGR0(
            w as usize,
            h as usize,
            unsafe { std::mem::transmute(self.capturer.get_stored_frame().unwrap()) },
        ))
    }
}


================================================
FILE: src/capturable/core_graphics.rs
================================================
use std::boxed::Box;
use std::error::Error;
use std::ffi::c_void;
use std::time::{Duration, Instant};

use core_foundation::{
    array::CFArray,
    base::{TCFType, ToVoid},
    data::CFData,
    dictionary::{CFDictionary, CFDictionaryRef},
    number::{CFNumber, CFNumberRef},
    string::{CFString, CFStringRef},
};
use core_graphics::{
    display,
    display::{CGDisplay, CGRect},
    image::CGImage,
    window,
    window::CGWindowID,
};

use crate::capturable::{Capturable, Geometry, Recorder};

#[derive(Debug)]
pub struct CGError(String);

impl std::fmt::Display for CGError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self(s) = self;
        write!(f, "{}", s)
    }
}

impl Error for CGError {}

#[derive(Clone)]
pub struct CGDisplayCapturable {
    display: CGDisplay,
}

impl CGDisplayCapturable {
    pub fn new(display: CGDisplay) -> Self {
        Self { display }
    }
}

impl Capturable for CGDisplayCapturable {
    fn name(&self) -> String {
        format!(
            "Monitor (CG, {}x{})",
            self.display.pixels_wide(),
            self.display.pixels_high()
        )
    }
    fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
        let bounds = self.display.bounds();
        let (x0, y0, w, h) = screen_coordsys()?;
        Ok(Geometry::Relative(
            (bounds.origin.x - x0) / w,
            (bounds.origin.y - y0) / h,
            bounds.size.width / w,
            bounds.size.height / h,
        ))
    }
    fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
        Ok(())
    }
    fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
        Ok(Box::new(RecorderCGDisplay::new(
            self.display,
            capture_cursor,
        )))
    }
}

pub struct RecorderCGDisplay {
    img_data: Option<CFData>,
    display: CGDisplay,
    capture_cursor: bool,
}

impl RecorderCGDisplay {
    pub fn new(display: CGDisplay, capture_cursor: bool) -> Self {
        Self {
            img_data: None,
            display,
            capture_cursor,
        }
    }
}

fn check_pixelformat(img: &CGImage) -> Result<(), Box<dyn Error>> {
    // for now assume that the pixels are always in BGR0 format
    // do some basic checks to verify this
    if img.bits_per_pixel() != 32 {
        Err(CGError(format!(
            "Only BGR0 with 32 bits per pixel is supported, not {} bits!",
            img.bits_per_pixel()
        )))?
    }
    if img.bits_per_component() != 8 {
        Err(CGError(format!(
            "Only BGR0 with 8 bits per component is supported, not {} bits!",
            img.bits_per_component()
        )))?
    }
    Ok(())
}

impl Recorder for RecorderCGDisplay {
    fn capture(&mut self) -> Result<crate::video::PixelProvider, Box<dyn Error>> {
        let img = if self.capture_cursor {
            CGDisplay::screenshot(self.display.bounds(), 0, 0, 0)
        } else {
            self.display.image()
        };
        if let Some(img) = img {
            check_pixelformat(&img)?;
            let w = img.width() as usize;
            let h = img.height() as usize;

            // extract raw image data
            self.img_data = Some(img.data());
            Ok(crate::video::PixelProvider::BGR0S(
                w,
                h,
                img.bytes_per_row(),
                self.img_data.as_ref().unwrap().bytes(),
            ))
        } else {
            Err(Box::new(CGError(
                "Failed to capture screen using CoreGraphics.".into(),
            )))
        }
    }
}

#[derive(Clone)]
pub struct CGWindowCapturable {
    id: CGWindowID,
    name: String,
    cursor_id: CGWindowID,
    bounds: CGRect,
    geometry_relative: (f64, f64, f64, f64),
    last_geometry_update: Instant,
}

impl CGWindowCapturable {
    fn update_geometry(&mut self) -> Result<(), Box<dyn Error>> {
        if Instant::now() - self.last_geometry_update > Duration::from_secs(1) {
            self.bounds = get_window_infos()
                .iter()
                .find(|w| w.id == self.id)
                .ok_or_else(|| {
                    CGError(format!(
                        "Could not find information for current window {}.",
                        self.id
                    ))
                })?
                .bounds;
            let (x0, y0, w, h) = screen_coordsys()?;
            self.geometry_relative = (
                (self.bounds.origin.x - x0) / w,
                (self.bounds.origin.y - y0) / h,
                self.bounds.size.width / w,
                self.bounds.size.height / h,
            );
            self.last_geometry_update = Instant::now();
        }
        Ok(())
    }
}

impl Capturable for CGWindowCapturable {
    fn name(&self) -> String {
        self.name.clone()
    }
    fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
        let (x, y, w, h) = self.geometry_relative;
        Ok(Geometry::Relative(x, y, w, h))
    }
    fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
        self.update_geometry()
    }
    fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
        Ok(Box::new(RecorderCGWindow {
            img_data: None,
            capture_cursor,
            win: self.clone(),
        }))
    }
}
pub struct RecorderCGWindow {
    img_data: Option<CFData>,
    capture_cursor: bool,
    win: CGWindowCapturable,
}

impl Recorder for RecorderCGWindow {
    fn capture(&mut self) -> Result<crate::video::PixelProvider, Box<dyn Error>> {
        self.win.update_geometry()?;
        let img = CGDisplay::screenshot_from_windows(
            self.win.bounds,
            if self.capture_cursor {
                CFArray::from_copyable(&[
                    self.win.cursor_id as *const c_void,
                    self.win.id as *const c_void,
                ])
            } else {
                CFArray::from_copyable(&[self.win.id as *const c_void])
            },
            0,
        );
        if let Some(img) = img {
            check_pixelformat(&img)?;
            let w = img.width() as usize;
            let h = img.height() as usize;

            // extract raw image data
            self.img_data = Some(img.data());
            Ok(crate::video::PixelProvider::BGR0S(
                w,
                h,
                img.bytes_per_row(),
                self.img_data.as_ref().unwrap().bytes(),
            ))
        } else {
            Err(Box::new(CGError(
                "Failed to capture window using CoreGraphics.".into(),
            )))
        }
    }
}

#[derive(Debug)]
struct WindowInfo {
    pub id: CGWindowID,
    pub name: String,
    pub bounds: CGRect,
}

fn get_window_infos() -> Vec<WindowInfo> {
    let mut win_infos = vec![];
    let wins = CGDisplay::window_list_info(
        display::kCGWindowListExcludeDesktopElements | display::kCGWindowListOptionOnScreenOnly,
        None,
    );
    if let Some(wins) = wins {
        for w in wins.iter() {
            let w: CFDictionary<*const c_void, *const c_void> =
                unsafe { CFDictionary::wrap_under_get_rule(*w as CFDictionaryRef) };
            let id = w.get(unsafe { window::kCGWindowNumber }.to_void());
            let id = unsafe { CFNumber::wrap_under_get_rule(*id as CFNumberRef) }
                .to_i64()
                .unwrap() as CGWindowID;

            let bounds = w.get(unsafe { window::kCGWindowBounds }.to_void());
            let bounds = unsafe { CFDictionary::wrap_under_get_rule(*bounds as CFDictionaryRef) };
            let bounds = CGRect::from_dict_representation(&bounds).unwrap();

            let name = match w.find(unsafe { window::kCGWindowName }.to_void()) {
                Some(n) => n,
                None => continue,
            };

            let name = unsafe { CFString::wrap_under_get_rule(*name as CFStringRef) };
            win_infos.push(WindowInfo {
                id,
                name: name.to_string(),
                bounds,
            });
        }
    }
    win_infos
}

pub fn screen_coordsys() -> Result<(f64, f64, f64, f64), Box<dyn Error>> {
    let display_ids = CGDisplay::active_displays()
        .map_err(|err| CGError(format!("Failed to obtain displays, CGError code: {}", err)))?;
    let rects: Vec<CGRect> = display_ids
        .iter()
        .map(|id| CGDisplay::new(*id).bounds())
        .collect();
    let mut x0 = 0.0;
    let mut x1 = 0.0;
    let mut y0 = 0.0;
    let mut y1 = 0.0;
    for r in rects.iter() {
        let r_x0 = r.origin.x;
        let r_x1 = r_x0 + r.size.width;
        let r_y0 = r.origin.y;
        let r_y1 = r_y0 + r.size.height;
        x0 = f64::min(x0, r_x0);
        x1 = f64::max(x1, r_x1);
        y0 = f64::min(y0, r_y0);
        y1 = f64::max(y1, r_y1);
    }
    Ok((x0, y0, x1 - x0, y1 - y0))
}

pub fn get_displays() -> Result<Vec<CGDisplayCapturable>, Box<dyn Error>> {
    let display_ids = CGDisplay::active_displays()
        .map_err(|err| CGError(format!("Failed to obtain displays, CGError code: {}", err)))?;
    Ok(display_ids
        .iter()
        .map(|id| CGDisplayCapturable::new(CGDisplay::new(*id)))
        .collect())
}

pub fn get_windows() -> Result<Vec<CGWindowCapturable>, Box<dyn Error>> {
    let window_infos = get_window_infos();
    let cursor_id = window_infos
        .iter()
        .find(|w| w.name == "Cursor")
        .ok_or_else(|| CGError("No Cursor found!".into()))?
        .id;
    Ok(window_infos
        .iter()
        .filter(|w| w.id != cursor_id)
        .map(|w| CGWindowCapturable {
            id: w.id,
            name: w.name.clone(),
            cursor_id,
            bounds: w.bounds,
            geometry_relative: (0.0, 0.0, 1.0, 1.0),
            last_geometry_update: Instant::now() - Duration::from_secs(2),
        })
        .collect())
}


================================================
FILE: src/capturable/mod.rs
================================================
use std::boxed::Box;
use std::error::Error;
use tracing::warn;

#[cfg(target_os = "macos")]
pub mod core_graphics;
#[cfg(target_os = "linux")]
pub mod pipewire;
#[cfg(target_os = "linux")]
#[allow(dead_code)]
pub mod remote_desktop_dbus;
pub mod testsrc;

#[cfg(target_os = "windows")]
pub mod captrs_capture;
#[cfg(target_os = "windows")]
pub mod win_ctx;
#[cfg(target_os = "linux")]
pub mod x11;
pub trait Recorder {
    fn capture(&mut self) -> Result<crate::video::PixelProvider<'_>, Box<dyn Error>>;
}

pub trait BoxCloneCapturable {
    fn box_clone(&self) -> Box<dyn Capturable>;
}

impl<T> BoxCloneCapturable for T
where
    T: Clone + Capturable + 'static,
{
    fn box_clone(&self) -> Box<dyn Capturable> {
        Box::new(self.clone())
    }
}
/// Relative: x, y, width, height of the Capturable as floats relative to the absolute size of the
/// screen. For example x=0.5, y=0.0, width=0.5, height=1.0 means the right half of the screen.
/// VirtualScreen: offset_x, offset_y, width, height for a capturable using a virtual screen. (Windows)
pub enum Geometry {
    Relative(f64, f64, f64, f64),
    #[cfg(target_os = "windows")]
    VirtualScreen(i32, i32, u32, u32, i32, i32),
}

pub trait Capturable: Send + BoxCloneCapturable {
    /// Name of the Capturable, for example the window title, if it is a window.
    fn name(&self) -> String;

    /// Return Geometry of the Capturable.
    fn geometry(&self) -> Result<Geometry, Box<dyn Error>>;

    /// Callback that is called right before input is simulated.
    /// Useful to focus the window on input.
    fn before_input(&mut self) -> Result<(), Box<dyn Error>>;

    /// Return a Recorder that can record the current capturable.
    fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>>;
}

impl Clone for Box<dyn Capturable> {
    fn clone(&self) -> Self {
        self.box_clone()
    }
}

pub fn get_capturables(
    #[cfg(target_os = "linux")] wayland_support: bool,
    #[cfg(target_os = "linux")] capture_cursor: bool,
) -> Vec<Box<dyn Capturable>> {
    let mut capturables: Vec<Box<dyn Capturable>> = vec![];
    #[cfg(target_os = "linux")]
    {
        if wayland_support {
            use crate::capturable::pipewire::get_capturables as get_capturables_pw;
            match get_capturables_pw(capture_cursor) {
                Ok(captrs) => {
                    for c in captrs {
                        capturables.push(Box::new(c));
                    }
                }
                Err(err) => warn!(
                    "Failed to get list of capturables via dbus/pipewire: {}",
                    err
                ),
            }
        }

        use crate::capturable::x11::X11Context;
        let x11ctx = X11Context::new();
        if let Some(mut x11ctx) = x11ctx {
            match x11ctx.capturables() {
                Ok(captrs) => {
                    for c in captrs {
                        capturables.push(Box::new(c));
                    }
                }
                Err(err) => warn!("Failed to get list of capturables via X11: {}", err),
            }
        };
    }

    #[cfg(target_os = "macos")]
    {
        use crate::capturable::core_graphics::get_displays as get_displays_cg;
        use crate::capturable::core_graphics::get_windows as get_windows_cg;
        match get_displays_cg() {
            Ok(captrs) => {
                for c in captrs {
                    capturables.push(Box::new(c));
                }
            }
            Err(err) => warn!("Failed to get list of displays via CoreGraphics: {}", err),
        }

        match get_windows_cg() {
            Ok(mut captrs) => {
                captrs.sort_by(|a, b| a.name().to_lowercase().cmp(&b.name().to_lowercase()));
                for c in captrs {
                    capturables.push(Box::new(c));
                }
            }
            Err(err) => warn!("Failed to get list of windows via CoreGraphics: {}", err),
        }
    }

    #[cfg(target_os = "windows")]
    {
        use crate::capturable::captrs_capture::CaptrsCapturable;
        use crate::capturable::win_ctx::WinCtx;
        let winctx = WinCtx::new();
        for (i, o) in winctx.get_outputs().iter().enumerate() {
            let captr = CaptrsCapturable::new(
                i as u8,
                String::from_utf16_lossy(o.DeviceName.as_ref()),
                o.DesktopCoordinates,
                winctx.get_union_rect().clone(),
            );
            capturables.push(Box::new(captr));
        }
    }

    if crate::log::get_log_level() >= tracing::Level::DEBUG {
        for (width, height) in [
            (200, 200),
            (800, 600),
            (1080, 720),
            (1920, 1080),
            (3840, 2160),
            (15360, 2160),
        ]
        .iter()
        {
            use testsrc::PixelFormat;
            for pixel_format in [PixelFormat::BGR0, PixelFormat::RGB0, PixelFormat::RGB] {
                capturables.push(Box::new(testsrc::TestCapturable {
                    width: *width,
                    height: *height,
                    pixel_format,
                }));
            }
        }
    }

    capturables
}


================================================
FILE: src/capturable/pipewire.rs
================================================
use std::collections::HashMap;
use std::error::Error;
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::{debug, trace, warn};

use dbus::{
    arg::{OwnedFd, PropMap, RefArg, Variant},
    blocking::{Proxy, SyncConnection},
    message::{MatchRule, MessageType},
    Message,
};

use gstreamer as gst;
use gstreamer::prelude::*;
use gstreamer_app::AppSink;

use crate::capturable::{Capturable, Geometry, Recorder};
use crate::video::PixelProvider;

use crate::capturable::remote_desktop_dbus::{
    OrgFreedesktopPortalRemoteDesktop, OrgFreedesktopPortalRequestResponse,
    OrgFreedesktopPortalScreenCast,
};

#[derive(Debug, Clone, Copy)]
struct PwStreamInfo {
    path: u64,
    source_type: u64,
}

#[derive(Debug)]
pub struct DBusError(String);

impl std::fmt::Display for DBusError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self(s) = self;
        write!(f, "{}", s)
    }
}

impl Error for DBusError {}

#[derive(Debug)]
pub struct GStreamerError(String);

impl std::fmt::Display for GStreamerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let Self(s) = self;
        write!(f, "{}", s)
    }
}

impl Error for GStreamerError {}

#[derive(Clone)]
pub struct PipeWireCapturable {
    // connection needs to be kept alive for recording
    dbus_conn: Arc<SyncConnection>,
    fd: OwnedFd,
    path: u64,
    source_type: u64,
}

impl PipeWireCapturable {
    fn new(conn: Arc<SyncConnection>, fd: OwnedFd, stream: PwStreamInfo) -> Self {
        Self {
            dbus_conn: conn,
            fd,
            path: stream.path,
            source_type: stream.source_type,
        }
    }
}

impl std::fmt::Debug for PipeWireCapturable {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "PipeWireCapturable {{dbus: {}, fd: {}, path: {}, source_type: {}}}",
            self.dbus_conn.unique_name(),
            self.fd.as_raw_fd(),
            self.path,
            self.source_type
        )
    }
}

impl Capturable for PipeWireCapturable {
    fn name(&self) -> String {
        let type_str = match self.source_type {
            1 => "Desktop",
            2 => "Window",
            _ => "Unknown",
        };
        format!("Pipewire {}, path: {}", type_str, self.path)
    }

    fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
        Ok(Geometry::Relative(0.0, 0.0, 1.0, 1.0))
    }

    fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
        Ok(())
    }

    fn recorder(&self, _capture_cursor: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
        Ok(Box::new(PipeWireRecorder::new(self.clone())?))
    }
}

pub struct PipeWireRecorder {
    buffer: Option<gst::MappedBuffer<gst::buffer::Readable>>,
    buffer_cropped: Vec<u8>,
    pix_fmt: String,
    is_cropped: bool,
    pipeline: gst::Pipeline,
    appsink: AppSink,
    width: usize,
    height: usize,
}

impl PipeWireRecorder {
    pub fn new(capturable: PipeWireCapturable) -> Result<Self, Box<dyn Error>> {
        let pipeline = gst::Pipeline::new();

        let src = gst::ElementFactory::make("pipewiresrc").build()?;
        src.set_property("fd", &capturable.fd.as_raw_fd());
        src.set_property("path", &format!("{}", capturable.path));

        // For some reason pipewire blocks on destruction of AppSink if this is not set to true,
        // see: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/982
        src.set_property("always-copy", &true);

        let sink = gst::ElementFactory::make("appsink").build()?;
        sink.set_property("drop", &true);
        sink.set_property("max-buffers", &1u32);

        pipeline.add_many(&[&src, &sink])?;
        src.link(&sink)?;
        let appsink = sink
            .dynamic_cast::<AppSink>()
            .map_err(|_| GStreamerError("Sink element is expected to be an appsink!".into()))?;
        let mut caps = gst::Caps::new_empty();
        caps.merge_structure(gst::structure::Structure::from_iter(
            "video/x-raw",
            [("format", "BGRx".into())],
        ));
        caps.merge_structure(gst::structure::Structure::from_iter(
            "video/x-raw",
            [("format", "RGBx".into())],
        ));
        appsink.set_caps(Some(&caps));

        pipeline.set_state(gst::State::Playing)?;
        Ok(Self {
            pipeline,
            appsink,
            buffer: None,
            pix_fmt: "".into(),
            width: 0,
            height: 0,
            buffer_cropped: vec![],
            is_cropped: false,
        })
    }
}

impl Recorder for PipeWireRecorder {
    fn capture(&mut self) -> Result<PixelProvider<'_>, Box<dyn Error>> {
        if let Some(sample) = self
            .appsink
            .try_pull_sample(gst::ClockTime::from_mseconds(16))
        {
            let cap = sample.caps().unwrap().structure(0).unwrap();
            let w: i32 = cap.value("width")?.get()?;
            let h: i32 = cap.value("height")?.get()?;
            self.pix_fmt = cap.value("format")?.get()?;
            let w = w as usize;
            let h = h as usize;
            let buf = sample
                .buffer_owned()
                .ok_or_else(|| GStreamerError("Failed to get owned buffer.".into()))?;
            let mut crop = buf
                .meta::<gstreamer_video::VideoCropMeta>()
                .map(|m| m.rect());
            // only crop if necessary
            if Some((0, 0, w as u32, h as u32)) == crop {
                crop = None;
            }
            let buf = buf
                .into_mapped_buffer_readable()
                .map_err(|_| GStreamerError("Failed to map buffer.".into()))?;
            let buf_size = buf.size();
            // BGRx is 4 bytes per pixel
            if buf_size != (w * h * 4) {
                // for some reason the width and height of the caps do not guarantee correct buffer
                // size, so ignore those buffers, see:
                // https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/985
                trace!(
                    "Size of mapped buffer: {} does NOT match size of capturable {}x{}@BGRx, \
                    dropping it!",
                    buf_size,
                    w,
                    h
                );
            } else {
                // Copy region specified by crop into self.buffer_cropped
                // TODO: Figure out if ffmpeg provides a zero copy alternative
                if let Some((x_off, y_off, w_crop, h_crop)) = crop {
                    let x_off = x_off as usize;
                    let y_off = y_off as usize;
                    let w_crop = w_crop as usize;
                    let h_crop = h_crop as usize;
                    self.buffer_cropped.clear();
                    let data = buf.as_slice();
                    // BGRx is 4 bytes per pixel
                    self.buffer_cropped.reserve(w_crop * h_crop * 4);
                    for y in y_off..(y_off + h_crop) {
                        let i = 4 * (w * y + x_off);
                        self.buffer_cropped.extend(&data[i..i + 4 * w_crop]);
                    }
                    self.width = w_crop;
                    self.height = h_crop;
                } else {
                    self.width = w;
                    self.height = h;
                }
                self.is_cropped = crop.is_some();
                self.buffer = Some(buf);
            }
        } else {
            trace!("No new buffer available, falling back to previous one.");
        }
        if self.buffer.is_none() {
            return Err(Box::new(GStreamerError("No buffer available!".into())));
        }
        let buf = if self.is_cropped {
            self.buffer_cropped.as_slice()
        } else {
            self.buffer.as_ref().unwrap().as_slice()
        };
        match self.pix_fmt.as_str() {
            "BGRx" => Ok(PixelProvider::BGR0(self.width, self.height, buf)),
            "RGBx" => Ok(PixelProvider::RGB0(self.width, self.height, buf)),
            _ => unreachable!(),
        }
    }
}

impl Drop for PipeWireRecorder {
    fn drop(&mut self) {
        if let Err(err) = self.pipeline.set_state(gst::State::Null) {
            warn!("Failed to stop GStreamer pipeline: {}.", err);
        }
    }
}

fn handle_response<F>(
    portal: Proxy<&SyncConnection>,
    path: dbus::Path<'static>,
    context: Arc<Mutex<CallBackContext>>,
    mut f: F,
) -> Result<dbus::channel::Token, dbus::Error>
where
    F: FnMut(
            OrgFreedesktopPortalRequestResponse,
            Proxy<&SyncConnection>,
            &Message,
            Arc<Mutex<CallBackContext>>,
        ) -> Result<(), Box<dyn Error>>
        + Send
        + Sync
        + 'static,
{
    let mut m = MatchRule::new();
    m.path = Some(path);
    m.msg_type = Some(MessageType::Signal);
    m.sender = Some("org.freedesktop.portal.Desktop".into());
    m.interface = Some("org.freedesktop.portal.Request".into());
    portal
        .connection
        .add_match(m, move |r: OrgFreedesktopPortalRequestResponse, c, m| {
            let portal = get_portal(c);
            debug!("Response from DBus: response: {:?}, message: {:?}", r, m);
            match r.response {
                0 => {}
                1 => {
                    context.lock().unwrap().failure = true;
                    warn!("DBus response: User cancelled interaction.");
                    return true;
                }
                c => {
                    context.lock().unwrap().failure = true;
                    warn!("DBus response: Unknown error, code: {}.", c);
                    return true;
                }
            }
            if let Err(err) = f(r, portal, m, context.clone()) {
                context.lock().unwrap().failure = true;
                warn!("Error requesting screen capture via dbus: {}", err);
            }
            true
        })
}

fn get_portal(conn: &SyncConnection) -> Proxy<'_, &SyncConnection> {
    conn.with_proxy(
        "org.freedesktop.portal.Desktop",
        "/org/freedesktop/portal/desktop",
        Duration::from_millis(1000),
    )
}

fn streams_from_response(response: &OrgFreedesktopPortalRequestResponse) -> Vec<PwStreamInfo> {
    (move || {
        Some(
            response
                .results
                .get("streams")?
                .as_iter()?
                .next()?
                .as_iter()?
                .filter_map(|stream| {
                    let mut itr = stream.as_iter()?;
                    let path = itr.next()?.as_u64()?;
                    let (keys, values): (Vec<(usize, &dyn RefArg)>, Vec<(usize, &dyn RefArg)>) =
                        itr.next()?
                            .as_iter()?
                            .enumerate()
                            .partition(|(i, _)| i % 2 == 0);
                    let attributes = keys
                        .iter()
                        .filter_map(|(_, key)| Some(key.as_str()?.to_owned()))
                        .zip(
                            values
                                .iter()
                                .map(|(_, arg)| *arg)
                                .collect::<Vec<&dyn RefArg>>(),
                        )
                        .collect::<HashMap<String, &dyn RefArg>>();
                    Some(PwStreamInfo {
                        path,
                        source_type: attributes
                            .get("source_type")
                            .map_or(Some(0), |v| v.as_u64())?,
                    })
                })
                .collect::<Vec<PwStreamInfo>>(),
        )
    })()
    .unwrap_or_default()
}

// mostly inspired by https://gitlab.gnome.org/snippets/19 and
// https://gitlab.gnome.org/-/snippets/39
struct CallBackContext {
    capture_cursor: bool,
    session: dbus::Path<'static>,
    streams: Vec<PwStreamInfo>,
    fd: Option<OwnedFd>,
    restore_token: Option<String>,
    has_remote_desktop: bool,
    failure: bool,
}

fn on_create_session_response(
    r: OrgFreedesktopPortalRequestResponse,
    portal: Proxy<&SyncConnection>,
    _msg: &Message,
    context: Arc<Mutex<CallBackContext>>,
) -> Result<(), Box<dyn Error>> {
    debug!("on_create_session_response");
    let session: dbus::Path = r
        .results
        .get("session_handle")
        .ok_or_else(|| {
            DBusError(format!(
                "Failed to obtain session_handle from response: {:?}",
                r
            ))
        })?
        .as_str()
        .ok_or_else(|| DBusError("Failed to convert session_handle to string.".into()))?
        .to_string()
        .into();

    context.lock().unwrap().session = session.clone();
    if context.lock().unwrap().has_remote_desktop {
        select_devices(portal, context)
    } else {
        select_sources(portal, context)
    }
}

fn select_devices(
    portal: Proxy<&SyncConnection>,
    context: Arc<Mutex<CallBackContext>>,
) -> Result<(), Box<dyn Error>> {
    let mut args: PropMap = HashMap::new();
    let t: usize = rand::random();
    args.insert(
        "handle_token".to_string(),
        Variant(Box::new(format!("weylus{t}"))),
    );

    // TODO
    //args.insert(
    //    "restore_token".to_string(),
    //    Variant(Box::new(format!("weylus{t}"))),
    //);

    // persist modes:
    // 0: Do not persist (default)
    // 1: Permissions persist as long as the application is running
    // 2: Permissions persist until explicitly revoked
    args.insert("persist_mode".to_string(), Variant(Box::new(2 as u32)));

    // device types
    // 1: KEYBOARD
    // 2: POINTER
    // 4: TOUCHSCREEN
    let device_types = portal.available_device_types()?;
    debug!("Available device types: {device_types}.");
    args.insert("types".to_string(), Variant(Box::new(device_types)));

    let path = portal.select_devices(context.lock().unwrap().session.clone(), args)?;
    handle_response(portal, path, context, |_, portal, _, context| {
        select_sources(portal, context)
    })?;
    Ok(())
}

fn select_sources(
    portal: Proxy<&SyncConnection>,
    context: Arc<Mutex<CallBackContext>>,
) -> Result<(), Box<dyn Error>> {
    debug!("select_sources");
    let mut args: PropMap = HashMap::new();

    let t: usize = rand::random();
    args.insert(
        "handle_token".to_string(),
        Variant(Box::new(format!("weylus{t}"))),
    );
    // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html#org-freedesktop-portal-screencast-selectsources
    // allow multiple sources
    args.insert("multiple".into(), Variant(Box::new(true)));

    // 1: MONITOR
    // 2: WINDOW
    // 4: VIRTUAL
    let source_types = portal.available_source_types()?;
    debug!("Available source types: {source_types}.");
    args.insert("types".into(), Variant(Box::new(source_types)));

    let capture_cursor = context.lock().unwrap().capture_cursor;
    // 1: Hidden. The cursor is not part of the screen cast stream.
    // 2: Embedded: The cursor is embedded as part of the stream buffers.
    // 4: Metadata: The cursor is not part of the screen cast stream, but sent as PipeWire stream metadata.
    let cursor_mode = if capture_cursor { 2u32 } else { 1u32 };

    let is_plasma = std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("plasma"));
    if is_plasma && capture_cursor {
        // Warn the user if capturing the cursor is tried on kde as this can crash
        // kwin_wayland and tear down the plasma desktop, see:
        // https://bugs.kde.org/show_bug.cgi?id=435042
        warn!(
            "You are attempting to capture the cursor under KDE Plasma, this may crash your \
                    desktop, see https://bugs.kde.org/show_bug.cgi?id=435042 for details! \
                    You have been warned."
        );
    }
    args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode)));

    let path = portal.select_sources(context.lock().unwrap().session.clone(), args)?;
    handle_response(portal, path, context, on_select_sources_response)?;
    Ok(())
}

fn on_select_sources_response(
    _r: OrgFreedesktopPortalRequestResponse,
    portal: Proxy<&SyncConnection>,
    _msg: &Message,
    context: Arc<Mutex<CallBackContext>>,
) -> Result<(), Box<dyn Error>> {
    debug!("on_select_sources_response");
    let mut args: PropMap = HashMap::new();
    let t: usize = rand::random();
    args.insert(
        "handle_token".to_string(),
        Variant(Box::new(format!("weylus{t}"))),
    );
    let path = if context.lock().unwrap().has_remote_desktop {
        OrgFreedesktopPortalRemoteDesktop::start(
            &portal,
            context.lock().unwrap().session.clone(),
            "",
            args,
        )?
    } else {
        OrgFreedesktopPortalScreenCast::start(
            &portal,
            context.lock().unwrap().session.clone(),
            "",
            args,
        )?
    };
    handle_response(portal, path, context, on_start_response)?;
    Ok(())
}

fn on_start_response(
    r: OrgFreedesktopPortalRequestResponse,
    portal: Proxy<&SyncConnection>,
    _msg: &Message,
    context: Arc<Mutex<CallBackContext>>,
) -> Result<(), Box<dyn Error>> {
    debug!("on_start_response");
    let mut context = context.lock().unwrap();
    context.streams.append(&mut streams_from_response(&r));
    let session = context.session.clone();
    context
        .fd
        .replace(portal.open_pipe_wire_remote(session.clone(), HashMap::new())?);
    if let Some(Some(t)) = r.results.get("restore_token").map(|t| t.as_str()) {
        context.restore_token = Some(t.to_string());
    }
    dbg!(&context.restore_token);
    if context.has_remote_desktop {
        debug!("Remote Desktop Session started");
    } else {
        debug!("Screen Cast Session started");
    }
    Ok(())
}

fn request_remote_desktop(
    capture_cursor: bool,
) -> Result<(SyncConnection, OwnedFd, Vec<PwStreamInfo>), Box<dyn Error>> {
    let conn = SyncConnection::new_session()?;
    let portal = get_portal(&conn);

    // Disabled for KDE plasma due to https://bugs.kde.org/show_bug.cgi?id=484996
    // List of supported DEs: https://wiki.archlinux.org/title/XDG_Desktop_Portal#List_of_backends_and_interfaces
    let has_remote_desktop =
        std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("gnome"));

    let context = CallBackContext {
        capture_cursor,
        session: Default::default(),
        streams: Default::default(),
        fd: None,
        restore_token: None,
        has_remote_desktop,
        failure: false,
    };
    let context = Arc::new(Mutex::new(context));

    let mut args: PropMap = HashMap::new();
    let t1: usize = rand::random();
    let t2: usize = rand::random();
    args.insert(
        "session_handle_token".to_string(),
        Variant(Box::new(format!("weylus{t1}"))),
    );
    args.insert(
        "handle_token".to_string(),
        Variant(Box::new(format!("weylus{t2}"))),
    );
    let path = if has_remote_desktop {
        OrgFreedesktopPortalRemoteDesktop::create_session(&portal, args)?
    } else {
        OrgFreedesktopPortalScreenCast::create_session(&portal, args)?
    };
    handle_response(portal, path, context.clone(), on_create_session_response)?;

    // wait 3 minutes for user interaction
    for _ in 0..1800 {
        conn.process(Duration::from_millis(100))?;
        let context = context.lock().unwrap();
        // Once we got a file descriptor we are done!
        if context.fd.is_some() {
            break;
        }

        if context.failure {
            break;
        }
    }
    let context = context.lock().unwrap();
    if context.fd.is_some() && !context.streams.is_empty() {
        Ok((conn, context.fd.clone().unwrap(), context.streams.clone()))
    } else {
        Err(Box::new(DBusError(
            "Failed to obtain screen capture.".into(),
        )))
    }
}

pub fn get_capturables(capture_cursor: bool) -> Result<Vec<PipeWireCapturable>, Box<dyn Error>> {
    let (conn, fd, streams) = request_remote_desktop(capture_cursor)?;
    let conn = Arc::new(conn);
    Ok(streams
        .into_iter()
        .map(|s| PipeWireCapturable::new(conn.clone(), fd.clone(), s))
        .collect())
}


================================================
FILE: src/capturable/remote_desktop_dbus.rs
================================================
// This code was autogenerated with `dbus-codegen-rust -c blocking -m None`, see https://github.com/diwic/dbus-rs
// XML source:
// https://github.com/flatpak/xdg-desktop-portal/raw/refs/tags/1.18.4/data/org.freedesktop.portal.Request.xml
// https://github.com/flatpak/xdg-desktop-portal/raw/refs/tags/1.18.4/data/org.freedesktop.portal.ScreenCast.xml
// https://github.com/flatpak/xdg-desktop-portal/raw/refs/tags/1.18.4/data/org.freedesktop.portal.RemoteDesktop.xml
use dbus;
#[allow(unused_imports)]
use dbus::arg;
use dbus::blocking;

pub trait OrgFreedesktopPortalRequest {
    fn close(&self) -> Result<(), dbus::Error>;
}

#[derive(Debug)]
pub struct OrgFreedesktopPortalRequestResponse {
    pub response: u32,
    pub results: arg::PropMap,
}

impl arg::AppendAll for OrgFreedesktopPortalRequestResponse {
    fn append(&self, i: &mut arg::IterAppend) {
        arg::RefArg::append(&self.response, i);
        arg::RefArg::append(&self.results, i);
    }
}

impl arg::ReadAll for OrgFreedesktopPortalRequestResponse {
    fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
        Ok(OrgFreedesktopPortalRequestResponse {
            response: i.read()?,
            results: i.read()?,
        })
    }
}

impl dbus::message::SignalArgs for OrgFreedesktopPortalRequestResponse {
    const NAME: &'static str = "Response";
    const INTERFACE: &'static str = "org.freedesktop.portal.Request";
}

impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> OrgFreedesktopPortalRequest
    for blocking::Proxy<'a, C>
{
    fn close(&self) -> Result<(), dbus::Error> {
        self.method_call("org.freedesktop.portal.Request", "Close", ())
    }
}

pub trait OrgFreedesktopPortalScreenCast {
    fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, dbus::Error>;
    fn select_sources(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error>;
    fn start(
        &self,
        session_handle: dbus::Path,
        parent_window: &str,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error>;
    fn open_pipe_wire_remote(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<arg::OwnedFd, dbus::Error>;
    fn available_source_types(&self) -> Result<u32, dbus::Error>;
    fn available_cursor_modes(&self) -> Result<u32, dbus::Error>;
    fn version(&self) -> Result<u32, dbus::Error>;
}

impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>>
    OrgFreedesktopPortalScreenCast for blocking::Proxy<'a, C>
{
    fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.ScreenCast",
            "CreateSession",
            (options,),
        )
        .and_then(|r: (dbus::Path<'static>,)| Ok(r.0))
    }

    fn select_sources(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.ScreenCast",
            "SelectSources",
            (session_handle, options),
        )
        .and_then(|r: (dbus::Path<'static>,)| Ok(r.0))
    }

    fn start(
        &self,
        session_handle: dbus::Path,
        parent_window: &str,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.ScreenCast",
            "Start",
            (session_handle, parent_window, options),
        )
        .and_then(|r: (dbus::Path<'static>,)| Ok(r.0))
    }

    fn open_pipe_wire_remote(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<arg::OwnedFd, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.ScreenCast",
            "OpenPipeWireRemote",
            (session_handle, options),
        )
        .and_then(|r: (arg::OwnedFd,)| Ok(r.0))
    }

    fn available_source_types(&self) -> Result<u32, dbus::Error> {
        <Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
            self,
            "org.freedesktop.portal.ScreenCast",
            "AvailableSourceTypes",
        )
    }

    fn available_cursor_modes(&self) -> Result<u32, dbus::Error> {
        <Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
            self,
            "org.freedesktop.portal.ScreenCast",
            "AvailableCursorModes",
        )
    }

    fn version(&self) -> Result<u32, dbus::Error> {
        <Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
            self,
            "org.freedesktop.portal.ScreenCast",
            "version",
        )
    }
}

pub trait OrgFreedesktopPortalRemoteDesktop {
    fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, dbus::Error>;
    fn select_devices(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error>;
    fn start(
        &self,
        session_handle: dbus::Path,
        parent_window: &str,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error>;
    fn notify_pointer_motion(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        dx: f64,
        dy: f64,
    ) -> Result<(), dbus::Error>;
    fn notify_pointer_motion_absolute(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        stream: u32,
        x_: f64,
        y_: f64,
    ) -> Result<(), dbus::Error>;
    fn notify_pointer_button(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        button: i32,
        state: u32,
    ) -> Result<(), dbus::Error>;
    fn notify_pointer_axis(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        dx: f64,
        dy: f64,
    ) -> Result<(), dbus::Error>;
    fn notify_pointer_axis_discrete(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        axis: u32,
        steps: i32,
    ) -> Result<(), dbus::Error>;
    fn notify_keyboard_keycode(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        keycode: i32,
        state: u32,
    ) -> Result<(), dbus::Error>;
    fn notify_keyboard_keysym(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        keysym: i32,
        state: u32,
    ) -> Result<(), dbus::Error>;
    fn notify_touch_down(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        stream: u32,
        slot: u32,
        x_: f64,
        y_: f64,
    ) -> Result<(), dbus::Error>;
    fn notify_touch_motion(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        stream: u32,
        slot: u32,
        x_: f64,
        y_: f64,
    ) -> Result<(), dbus::Error>;
    fn notify_touch_up(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        slot: u32,
    ) -> Result<(), dbus::Error>;
    fn connect_to_eis(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<arg::OwnedFd, dbus::Error>;
    fn available_device_types(&self) -> Result<u32, dbus::Error>;
    fn version(&self) -> Result<u32, dbus::Error>;
}

impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>>
    OrgFreedesktopPortalRemoteDesktop for blocking::Proxy<'a, C>
{
    fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "CreateSession",
            (options,),
        )
        .and_then(|r: (dbus::Path<'static>,)| Ok(r.0))
    }

    fn select_devices(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "SelectDevices",
            (session_handle, options),
        )
        .and_then(|r: (dbus::Path<'static>,)| Ok(r.0))
    }

    fn start(
        &self,
        session_handle: dbus::Path,
        parent_window: &str,
        options: arg::PropMap,
    ) -> Result<dbus::Path<'static>, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "Start",
            (session_handle, parent_window, options),
        )
        .and_then(|r: (dbus::Path<'static>,)| Ok(r.0))
    }

    fn notify_pointer_motion(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        dx: f64,
        dy: f64,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyPointerMotion",
            (session_handle, options, dx, dy),
        )
    }

    fn notify_pointer_motion_absolute(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        stream: u32,
        x_: f64,
        y_: f64,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyPointerMotionAbsolute",
            (session_handle, options, stream, x_, y_),
        )
    }

    fn notify_pointer_button(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        button: i32,
        state: u32,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyPointerButton",
            (session_handle, options, button, state),
        )
    }

    fn notify_pointer_axis(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        dx: f64,
        dy: f64,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyPointerAxis",
            (session_handle, options, dx, dy),
        )
    }

    fn notify_pointer_axis_discrete(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        axis: u32,
        steps: i32,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyPointerAxisDiscrete",
            (session_handle, options, axis, steps),
        )
    }

    fn notify_keyboard_keycode(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        keycode: i32,
        state: u32,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyKeyboardKeycode",
            (session_handle, options, keycode, state),
        )
    }

    fn notify_keyboard_keysym(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        keysym: i32,
        state: u32,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyKeyboardKeysym",
            (session_handle, options, keysym, state),
        )
    }

    fn notify_touch_down(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        stream: u32,
        slot: u32,
        x_: f64,
        y_: f64,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyTouchDown",
            (session_handle, options, stream, slot, x_, y_),
        )
    }

    fn notify_touch_motion(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        stream: u32,
        slot: u32,
        x_: f64,
        y_: f64,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyTouchMotion",
            (session_handle, options, stream, slot, x_, y_),
        )
    }

    fn notify_touch_up(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
        slot: u32,
    ) -> Result<(), dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "NotifyTouchUp",
            (session_handle, options, slot),
        )
    }

    fn connect_to_eis(
        &self,
        session_handle: dbus::Path,
        options: arg::PropMap,
    ) -> Result<arg::OwnedFd, dbus::Error> {
        self.method_call(
            "org.freedesktop.portal.RemoteDesktop",
            "ConnectToEIS",
            (session_handle, options),
        )
        .and_then(|r: (arg::OwnedFd,)| Ok(r.0))
    }

    fn available_device_types(&self) -> Result<u32, dbus::Error> {
        <Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
            self,
            "org.freedesktop.portal.RemoteDesktop",
            "AvailableDeviceTypes",
        )
    }

    fn version(&self) -> Result<u32, dbus::Error> {
        <Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
            self,
            "org.freedesktop.portal.RemoteDesktop",
            "version",
        )
    }
}


================================================
FILE: src/capturable/testsrc.rs
================================================
use crate::capturable::{Capturable, Geometry, Recorder};
use crate::video::PixelProvider;
use std::error::Error;

#[derive(Debug, Clone, Copy)]
pub enum PixelFormat {
    BGR0,
    RGB0,
    RGB,
}

#[derive(Debug, Clone, Copy)]
pub struct TestCapturable {
    pub width: usize,
    pub height: usize,
    pub pixel_format: PixelFormat,
}

impl TestCapturable {
    fn pixel_size(&self) -> usize {
        match self.pixel_format {
            PixelFormat::BGR0 => 4,
            PixelFormat::RGB0 => 4,
            PixelFormat::RGB => 3,
        }
    }
    fn set_default_pixel(&self, buf: &mut [u8], x: usize, y: usize) {
        let w = self.width;
        let i = x * 8 / w;
        let pos = (x + y * w) * self.pixel_size();
        let (pos_r, pos_g, pos_b) = match self.pixel_format {
            PixelFormat::BGR0 => (pos + 2, pos + 1, pos),
            PixelFormat::RGB0 | PixelFormat::RGB => (pos, pos + 1, pos + 2),
        };
        buf[pos_b] = if i & 1 != 0 { 255 } else { 0 };
        buf[pos_g] = if i & 2 != 0 { 255 } else { 0 };
        buf[pos_r] = if i & 4 != 0 { 255 } else { 0 };
    }
}

pub struct TestRecorder {
    capturable: TestCapturable,
    buf: Vec<u8>,
    i: usize,
}

impl TestRecorder {
    fn new(capturable: TestCapturable) -> Self {
        let mut buf = vec![0; capturable.width * capturable.height * capturable.pixel_size()];
        let buf_ref = buf.as_mut();
        for y in 0..capturable.height {
            for x in 0..capturable.width {
                capturable.set_default_pixel(buf_ref, x, y);
            }
        }
        Self {
            capturable,
            buf,
            i: 0,
        }
    }
}

impl Capturable for TestCapturable {
    fn name(&self) -> String {
        format!(
            "Test Source {}x{}@{:?}",
            self.width, self.height, self.pixel_format
        )
    }
    fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
        Ok(Geometry::Relative(0.0, 0.0, 1.0, 1.0))
    }
    fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
        Ok(())
    }
    fn recorder(&self, _: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
        Ok(Box::new(TestRecorder::new(*self)))
    }
}

impl Recorder for TestRecorder {
    fn capture(&mut self) -> Result<PixelProvider<'_>, Box<dyn Error>> {
        const N: usize = 120;
        let dh = self.capturable.height / N;
        let buf_ref = self.buf.as_mut();
        let w = self.capturable.width;
        for y in self.i * dh..(self.i + 1) * dh {
            for x in 0..w {
                self.capturable.set_default_pixel(buf_ref, x, y);
            }
        }
        self.i = (self.i + 1) % N;
        for y in self.i * dh..(self.i + 1) * dh {
            for x in 0..w {
                let pos = (x + y * w) * self.capturable.pixel_size();

                let (pos_r, pos_g, pos_b) = match self.capturable.pixel_format {
                    PixelFormat::BGR0 => (pos + 2, pos + 1, pos),
                    PixelFormat::RGB0 | PixelFormat::RGB => (pos, pos + 1, pos + 2),
                };
                buf_ref[pos_b] = ((self.i + N * x / w) % N * 256 / N) as u8;
                buf_ref[pos_g] = ((self.i + N * x / w + N / 3) % N * 256 / N) as u8;
                buf_ref[pos_r] = ((self.i + N * x / w + 2 * N / 3) % N * 256 / N) as u8;
            }
        }
        Ok(match self.capturable.pixel_format {
            PixelFormat::BGR0 => PixelProvider::BGR0(
                self.capturable.width,
                self.capturable.height,
                self.buf.as_slice(),
            ),
            PixelFormat::RGB0 => PixelProvider::RGB0(
                self.capturable.width,
                self.capturable.height,
                self.buf.as_slice(),
            ),
            PixelFormat::RGB => PixelProvider::RGB(
                self.capturable.width,
                self.capturable.height,
                self.buf.as_slice(),
            ),
        })
    }
}


================================================
FILE: src/capturable/win_ctx.rs
================================================
use std::mem::zeroed;
use std::{mem, ptr};
use winapi::shared::dxgi::{
    CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IID_IDXGIFactory1,
    DXGI_OUTPUT_DESC,
};

use winapi::shared::windef::*;
use winapi::shared::winerror::*;
use winapi::um::winuser::*;
use wio::com::ComPtr;

// from https://github.com/bryal/dxgcap-rs/blob/009b746d1c19c4c10921dd469eaee483db6aa002/src/lib.r
fn hr_failed(hr: HRESULT) -> bool {
    hr < 0
}

fn create_dxgi_factory_1() -> ComPtr<IDXGIFactory1> {
    unsafe {
        let mut factory = ptr::null_mut();
        let hr = CreateDXGIFactory1(&IID_IDXGIFactory1, &mut factory);
        if hr_failed(hr) {
            panic!("Failed to create DXGIFactory1, {:x}", hr)
        } else {
            ComPtr::from_raw(factory as *mut IDXGIFactory1)
        }
    }
}

fn get_adapter_outputs(adapter: &IDXGIAdapter1) -> Vec<ComPtr<IDXGIOutput>> {
    let mut outputs = Vec::new();
    for i in 0.. {
        unsafe {
            let mut output = ptr::null_mut();
            if hr_failed(adapter.EnumOutputs(i, &mut output)) {
                break;
            } else {
                let mut out_desc = zeroed();
                (*output).GetDesc(&mut out_desc);
                if out_desc.AttachedToDesktop != 0 {
                    outputs.push(ComPtr::from_raw(output))
            
Download .txt
gitextract_cqulqu0f/

├── .clang-format
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── CONTRIBUTORS
├── Cargo.toml
├── LICENSE
├── Readme.md
├── build.rs
├── build_in_local_container.sh
├── compile_flags.txt
├── deps/
│   ├── awk.patch
│   ├── build.sh
│   ├── clean.sh
│   ├── clean_all.sh
│   ├── command_limit.patch
│   ├── download.sh
│   ├── ffmpeg.sh
│   ├── hashes.sh
│   ├── libva.sh
│   ├── nv-codec-headers.sh
│   ├── refs.sh
│   ├── update_hashes.sh
│   └── x264.sh
├── docker/
│   ├── Dockerfile
│   └── Dockerfile_alpine
├── docker_build.sh
├── lib/
│   ├── encode_video.c
│   ├── error.c
│   ├── error.h
│   ├── linux/
│   │   ├── uinput.c
│   │   ├── uinput_info.md
│   │   ├── xcapture.c
│   │   ├── xhelper.c
│   │   └── xhelper.h
│   ├── log.c
│   └── log.h
├── src/
│   ├── capturable/
│   │   ├── captrs_capture.rs
│   │   ├── core_graphics.rs
│   │   ├── mod.rs
│   │   ├── pipewire.rs
│   │   ├── remote_desktop_dbus.rs
│   │   ├── testsrc.rs
│   │   ├── win_ctx.rs
│   │   └── x11.rs
│   ├── cerror.rs
│   ├── config.rs
│   ├── gui.rs
│   ├── input/
│   │   ├── autopilot_device.rs
│   │   ├── autopilot_device_win.rs
│   │   ├── device.rs
│   │   ├── mod.rs
│   │   ├── uinput_device.rs
│   │   └── uinput_keys.rs
│   ├── log.rs
│   ├── main.rs
│   ├── protocol.rs
│   ├── strings/
│   │   └── uinput_error.txt
│   ├── video.rs
│   ├── web.rs
│   ├── websocket.rs
│   └── weylus.rs
├── ts/
│   └── lib.ts
├── tsconfig.json
├── weylus.desktop
├── weylus_tls.sh
└── www/
    ├── static/
    │   ├── access_code.html
    │   └── style.css
    └── templates/
        └── index.html
Download .txt
SYMBOL INDEX (760 symbols across 33 files)

FILE: build.rs
  function build_ffmpeg (line 5) | fn build_ffmpeg(dist_dir: &Path, enable_libnpp: bool) {
  function main (line 30) | fn main() {
  function linux (line 164) | fn linux() {

FILE: lib/encode_video.c
  type ScaleContext (line 31) | typedef struct ScaleContext
  type Scalers (line 40) | typedef struct Scalers
  type VideoContext (line 49) | typedef struct VideoContext
  function log_callback (line 84) | void log_callback(void* _ptr, int level, const char* fmt_orig, va_list a...
  function init_ffmpeg_logger (line 132) | void init_ffmpeg_logger() { av_log_set_callback(log_callback); }
  function set_codec_params (line 134) | void set_codec_params(VideoContext* ctx)
  function destroy_scale_ctx (line 149) | void destroy_scale_ctx(ScaleContext* ctx)
  function init_scaler (line 156) | void init_scaler(
  function destroy_scalers (line 363) | void destroy_scalers(Scalers* s)
  function init_scalers (line 372) | void init_scalers(
  function scale_frame (line 454) | void scale_frame(ScaleContext* ctx, Error* err)
  function open_video (line 477) | void open_video(VideoContext* ctx, Error* err)
  function destroy_video_encoder (line 818) | void destroy_video_encoder(VideoContext* ctx)
  function encode_video_frame (line 835) | void encode_video_frame(VideoContext* ctx, int millis, Error* err)
  function VideoContext (line 867) | VideoContext* init_video_encoder(
  function fill_bgr0 (line 898) | void fill_bgr0(VideoContext* ctx, const void* data, int stride, Error* err)
  function fill_rgb (line 910) | void fill_rgb(VideoContext* ctx, const void* data, Error* err)
  function fill_rgb0 (line 923) | void fill_rgb0(VideoContext* ctx, const void* data, Error* err)

FILE: lib/error.c
  function fill_error (line 3) | void fill_error(Error* err, int code, const char* fmt, ...)

FILE: lib/error.h
  type Error (line 6) | struct Error
  type Error (line 12) | typedef struct Error Error;

FILE: lib/linux/uinput.c
  function setup_abs (line 25) | void setup_abs(int fd, int code, int minimum, int maximum, int resolutio...
  function setup (line 44) | void setup(int fd, const char* name, Error* err)
  function init_keyboard (line 59) | void init_keyboard(int fd, const char* name, Error* err)
  function init_mouse (line 87) | void init_mouse(int fd, const char* name, Error* err)
  function init_stylus (line 139) | void init_stylus(int fd, const char* name, Error* err)
  function init_touch (line 185) | void init_touch(int fd, const char* name, Error* err)
  function init_uinput_keyboard (line 249) | int init_uinput_keyboard(const char* name, Error* err)
  function init_uinput_stylus (line 262) | int init_uinput_stylus(const char* name, Error* err)
  function init_uinput_mouse (line 275) | int init_uinput_mouse(const char* name, Error* err)
  function init_uinput_touch (line 288) | int init_uinput_touch(const char* name, Error* err)
  function destroy_uinput_device (line 301) | void destroy_uinput_device(int fd)
  function send_uinput_event (line 307) | void send_uinput_event(int device, int type, int code, int value, Error*...

FILE: lib/linux/xcapture.c
  function clamp (line 19) | int clamp(int x, int lb, int ub)
  type CaptureContext (line 28) | struct CaptureContext
  type CaptureContext (line 39) | typedef struct CaptureContext CaptureContext;
  type Image (line 41) | struct Image
  function stop_capture (line 120) | void stop_capture(CaptureContext* ctx, Error* err)
  function capture_screen (line 134) | void capture_screen(CaptureContext* ctx, struct Image* img, int capture_...

FILE: lib/linux/xhelper.c
  function x11_error_handler (line 14) | int x11_error_handler(Display* disp, XErrorEvent* err)
  function x11_set_error_handler (line 24) | void x11_set_error_handler()
  function locale_to_utf8 (line 31) | int locale_to_utf8(char* src, char* dest, size_t size)
  function Window (line 154) | Window* get_client_list(Display* disp, unsigned long* size, Error* err)
  function create_capturables (line 180) | int create_capturables(
  function destroy_capturable (line 274) | void destroy_capturable(Capturable* c) { free(c); }
  function get_window_geometry (line 276) | void get_window_geometry(
  function get_geometry (line 295) | void get_geometry(
  function get_geometry_relative (line 312) | void get_geometry_relative(
  function client_msg (line 325) | void client_msg(
  function activate_window (line 357) | void activate_window(Display* disp, WindowInfo* winfo, Error* err)
  function capturable_before_input (line 396) | void capturable_before_input(Capturable* cap, Error* err)
  function map_input_device_to_entire_screen (line 410) | void map_input_device_to_entire_screen(Display* disp, const char* device...

FILE: lib/linux/xhelper.h
  type WindowInfo (line 16) | typedef struct WindowInfo
  type RectInfo (line 22) | typedef struct RectInfo
  type CaptureType (line 30) | typedef enum CaptureType
  type Capturable (line 36) | typedef struct Capturable

FILE: lib/log.c
  function log_error (line 10) | void log_error(const char* fmt, ...)
  function log_debug (line 19) | void log_debug(const char* fmt, ...)
  function log_info (line 28) | void log_info(const char* fmt, ...)
  function log_trace (line 37) | void log_trace(const char* fmt, ...)
  function log_warn (line 46) | void log_warn(const char* fmt, ...)

FILE: src/capturable/captrs_capture.rs
  type CaptrsCapturable (line 10) | pub struct CaptrsCapturable {
    method new (line 18) | pub fn new(id: u8, name: String, screen: RECT, virtual_screen: RECT) -...
  method name (line 29) | fn name(&self) -> String {
  method before_input (line 32) | fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
  method recorder (line 35) | fn recorder(&self, _capture_cursor: bool) -> Result<Box<dyn Recorder>, B...
  method geometry (line 38) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
  type CaptrsError (line 50) | pub struct CaptrsError(String);
    method fmt (line 53) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type CaptrsRecorder (line 60) | pub struct CaptrsRecorder {
    method new (line 65) | pub fn new(id: u8) -> Result<CaptrsRecorder, Box<dyn Error>> {
  method capture (line 73) | fn capture(&mut self) -> Result<crate::video::PixelProvider, Box<dyn Err...

FILE: src/capturable/core_graphics.rs
  type CGError (line 25) | pub struct CGError(String);
    method fmt (line 28) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type CGDisplayCapturable (line 37) | pub struct CGDisplayCapturable {
    method new (line 42) | pub fn new(display: CGDisplay) -> Self {
  method name (line 48) | fn name(&self) -> String {
  method geometry (line 55) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
  method before_input (line 65) | fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
  method recorder (line 68) | fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Bo...
  type RecorderCGDisplay (line 76) | pub struct RecorderCGDisplay {
    method new (line 83) | pub fn new(display: CGDisplay, capture_cursor: bool) -> Self {
  function check_pixelformat (line 92) | fn check_pixelformat(img: &CGImage) -> Result<(), Box<dyn Error>> {
  method capture (line 111) | fn capture(&mut self) -> Result<crate::video::PixelProvider, Box<dyn Err...
  type CGWindowCapturable (line 139) | pub struct CGWindowCapturable {
    method update_geometry (line 149) | fn update_geometry(&mut self) -> Result<(), Box<dyn Error>> {
  method name (line 175) | fn name(&self) -> String {
  method geometry (line 178) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
  method before_input (line 182) | fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
  method recorder (line 185) | fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Bo...
  type RecorderCGWindow (line 193) | pub struct RecorderCGWindow {
  method capture (line 200) | fn capture(&mut self) -> Result<crate::video::PixelProvider, Box<dyn Err...
  type WindowInfo (line 236) | struct WindowInfo {
  function get_window_infos (line 242) | fn get_window_infos() -> Vec<WindowInfo> {
  function screen_coordsys (line 277) | pub fn screen_coordsys() -> Result<(f64, f64, f64, f64), Box<dyn Error>> {
  function get_displays (line 301) | pub fn get_displays() -> Result<Vec<CGDisplayCapturable>, Box<dyn Error>> {
  function get_windows (line 310) | pub fn get_windows() -> Result<Vec<CGWindowCapturable>, Box<dyn Error>> {

FILE: src/capturable/mod.rs
  type Recorder (line 20) | pub trait Recorder {
    method capture (line 21) | fn capture(&mut self) -> Result<crate::video::PixelProvider<'_>, Box<d...
  type BoxCloneCapturable (line 24) | pub trait BoxCloneCapturable {
    method box_clone (line 25) | fn box_clone(&self) -> Box<dyn Capturable>;
    method box_clone (line 32) | fn box_clone(&self) -> Box<dyn Capturable> {
  type Geometry (line 39) | pub enum Geometry {
  type Capturable (line 45) | pub trait Capturable: Send + BoxCloneCapturable {
    method name (line 47) | fn name(&self) -> String;
    method geometry (line 50) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>>;
    method before_input (line 54) | fn before_input(&mut self) -> Result<(), Box<dyn Error>>;
    method recorder (line 57) | fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, ...
  method clone (line 61) | fn clone(&self) -> Self {
  function get_capturables (line 66) | pub fn get_capturables(

FILE: src/capturable/pipewire.rs
  type PwStreamInfo (line 28) | struct PwStreamInfo {
  type DBusError (line 34) | pub struct DBusError(String);
    method fmt (line 37) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type GStreamerError (line 46) | pub struct GStreamerError(String);
    method fmt (line 49) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type PipeWireCapturable (line 58) | pub struct PipeWireCapturable {
    method new (line 67) | fn new(conn: Arc<SyncConnection>, fd: OwnedFd, stream: PwStreamInfo) -...
    method fmt (line 78) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  method name (line 91) | fn name(&self) -> String {
  method geometry (line 100) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
  method before_input (line 104) | fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
  method recorder (line 108) | fn recorder(&self, _capture_cursor: bool) -> Result<Box<dyn Recorder>, B...
  type PipeWireRecorder (line 113) | pub struct PipeWireRecorder {
    method new (line 125) | pub fn new(capturable: PipeWireCapturable) -> Result<Self, Box<dyn Err...
  method capture (line 171) | fn capture(&mut self) -> Result<PixelProvider<'_>, Box<dyn Error>> {
  method drop (line 253) | fn drop(&mut self) {
  function handle_response (line 260) | fn handle_response<F>(
  function get_portal (line 308) | fn get_portal(conn: &SyncConnection) -> Proxy<'_, &SyncConnection> {
  function streams_from_response (line 316) | fn streams_from_response(response: &OrgFreedesktopPortalRequestResponse)...
  type CallBackContext (line 358) | struct CallBackContext {
  function on_create_session_response (line 368) | fn on_create_session_response(
  function select_devices (line 397) | fn select_devices(
  function select_sources (line 435) | fn select_sources(
  function on_select_sources_response (line 482) | fn on_select_sources_response(
  function on_start_response (line 514) | fn on_start_response(
  function request_remote_desktop (line 539) | fn request_remote_desktop(
  function get_capturables (line 602) | pub fn get_capturables(capture_cursor: bool) -> Result<Vec<PipeWireCaptu...

FILE: src/capturable/remote_desktop_dbus.rs
  type OrgFreedesktopPortalRequest (line 11) | pub trait OrgFreedesktopPortalRequest {
    method close (line 12) | fn close(&self) -> Result<(), dbus::Error>;
    method close (line 45) | fn close(&self) -> Result<(), dbus::Error> {
  type OrgFreedesktopPortalRequestResponse (line 16) | pub struct OrgFreedesktopPortalRequestResponse {
    method append (line 22) | fn append(&self, i: &mut arg::IterAppend) {
    method read (line 29) | fn read(i: &mut arg::Iter) -> Result<Self, arg::TypeMismatchError> {
    constant NAME (line 38) | const NAME: &'static str = "Response";
    constant INTERFACE (line 39) | const INTERFACE: &'static str = "org.freedesktop.portal.Request";
  type OrgFreedesktopPortalScreenCast (line 50) | pub trait OrgFreedesktopPortalScreenCast {
    method create_session (line 51) | fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'...
    method select_sources (line 52) | fn select_sources(
    method start (line 57) | fn start(
    method open_pipe_wire_remote (line 63) | fn open_pipe_wire_remote(
    method available_source_types (line 68) | fn available_source_types(&self) -> Result<u32, dbus::Error>;
    method available_cursor_modes (line 69) | fn available_cursor_modes(&self) -> Result<u32, dbus::Error>;
    method version (line 70) | fn version(&self) -> Result<u32, dbus::Error>;
    method create_session (line 76) | fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'...
    method select_sources (line 85) | fn select_sources(
    method start (line 98) | fn start(
    method open_pipe_wire_remote (line 112) | fn open_pipe_wire_remote(
    method available_source_types (line 125) | fn available_source_types(&self) -> Result<u32, dbus::Error> {
    method available_cursor_modes (line 133) | fn available_cursor_modes(&self) -> Result<u32, dbus::Error> {
    method version (line 141) | fn version(&self) -> Result<u32, dbus::Error> {
  type OrgFreedesktopPortalRemoteDesktop (line 150) | pub trait OrgFreedesktopPortalRemoteDesktop {
    method create_session (line 151) | fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'...
    method select_devices (line 152) | fn select_devices(
    method start (line 157) | fn start(
    method notify_pointer_motion (line 163) | fn notify_pointer_motion(
    method notify_pointer_motion_absolute (line 170) | fn notify_pointer_motion_absolute(
    method notify_pointer_button (line 178) | fn notify_pointer_button(
    method notify_pointer_axis (line 185) | fn notify_pointer_axis(
    method notify_pointer_axis_discrete (line 192) | fn notify_pointer_axis_discrete(
    method notify_keyboard_keycode (line 199) | fn notify_keyboard_keycode(
    method notify_keyboard_keysym (line 206) | fn notify_keyboard_keysym(
    method notify_touch_down (line 213) | fn notify_touch_down(
    method notify_touch_motion (line 222) | fn notify_touch_motion(
    method notify_touch_up (line 231) | fn notify_touch_up(
    method connect_to_eis (line 237) | fn connect_to_eis(
    method available_device_types (line 242) | fn available_device_types(&self) -> Result<u32, dbus::Error>;
    method version (line 243) | fn version(&self) -> Result<u32, dbus::Error>;
    method create_session (line 249) | fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'...
    method select_devices (line 258) | fn select_devices(
    method start (line 271) | fn start(
    method notify_pointer_motion (line 285) | fn notify_pointer_motion(
    method notify_pointer_motion_absolute (line 299) | fn notify_pointer_motion_absolute(
    method notify_pointer_button (line 314) | fn notify_pointer_button(
    method notify_pointer_axis (line 328) | fn notify_pointer_axis(
    method notify_pointer_axis_discrete (line 342) | fn notify_pointer_axis_discrete(
    method notify_keyboard_keycode (line 356) | fn notify_keyboard_keycode(
    method notify_keyboard_keysym (line 370) | fn notify_keyboard_keysym(
    method notify_touch_down (line 384) | fn notify_touch_down(
    method notify_touch_motion (line 400) | fn notify_touch_motion(
    method notify_touch_up (line 416) | fn notify_touch_up(
    method connect_to_eis (line 429) | fn connect_to_eis(
    method available_device_types (line 442) | fn available_device_types(&self) -> Result<u32, dbus::Error> {
    method version (line 450) | fn version(&self) -> Result<u32, dbus::Error> {

FILE: src/capturable/testsrc.rs
  type PixelFormat (line 6) | pub enum PixelFormat {
  type TestCapturable (line 13) | pub struct TestCapturable {
    method pixel_size (line 20) | fn pixel_size(&self) -> usize {
    method set_default_pixel (line 27) | fn set_default_pixel(&self, buf: &mut [u8], x: usize, y: usize) {
  type TestRecorder (line 41) | pub struct TestRecorder {
    method new (line 48) | fn new(capturable: TestCapturable) -> Self {
  method name (line 65) | fn name(&self) -> String {
  method geometry (line 71) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
  method before_input (line 74) | fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
  method recorder (line 77) | fn recorder(&self, _: bool) -> Result<Box<dyn Recorder>, Box<dyn Error>> {
  method capture (line 83) | fn capture(&mut self) -> Result<PixelProvider<'_>, Box<dyn Error>> {

FILE: src/capturable/win_ctx.rs
  function hr_failed (line 14) | fn hr_failed(hr: HRESULT) -> bool {
  function create_dxgi_factory_1 (line 18) | fn create_dxgi_factory_1() -> ComPtr<IDXGIFactory1> {
  function get_adapter_outputs (line 30) | fn get_adapter_outputs(adapter: &IDXGIAdapter1) -> Vec<ComPtr<IDXGIOutpu...
  type WinCtx (line 52) | pub struct WinCtx {
    method new (line 58) | pub fn new() -> WinCtx {
    method get_outputs (line 85) | pub fn get_outputs(&self) -> &Vec<DXGI_OUTPUT_DESC> {
    method get_union_rect (line 88) | pub fn get_union_rect(&self) -> &RECT {

FILE: src/capturable/x11.rs
  function XOpenDisplay (line 13) | fn XOpenDisplay(name: *const c_char) -> *mut c_void;
  function XCloseDisplay (line 14) | fn XCloseDisplay(disp: *mut c_void) -> c_int;
  function XInitThreads (line 15) | fn XInitThreads() -> c_int;
  function XLockDisplay (line 16) | fn XLockDisplay(disp: *mut c_void);
  function XUnlockDisplay (line 17) | fn XUnlockDisplay(disp: *mut c_void);
  function x11_set_error_handler (line 19) | fn x11_set_error_handler();
  function create_capturables (line 21) | fn create_capturables(
  function clone_capturable (line 29) | fn clone_capturable(handle: *const c_void) -> *mut c_void;
  function destroy_capturable (line 30) | fn destroy_capturable(handle: *mut c_void);
  function get_capturable_name (line 31) | fn get_capturable_name(handle: *const c_void) -> *const c_char;
  function capturable_before_input (line 32) | fn capturable_before_input(handle: *mut c_void, err: *mut CError);
  function get_geometry_relative (line 33) | fn get_geometry_relative(
  function map_input_device_to_entire_screen (line 42) | fn map_input_device_to_entire_screen(
  function start_capture (line 48) | fn start_capture(handle: *const c_void, ctx: *mut c_void, err: *mut CErr...
  function capture_screen (line 49) | fn capture_screen(
  function stop_capture (line 55) | fn stop_capture(handle: *mut c_void, err: *mut CError);
  function x11_init (line 58) | pub fn x11_init() {
  type X11Capturable (line 65) | pub struct X11Capturable {
    method handle (line 84) | pub unsafe fn handle(&mut self) -> *mut c_void {
    method fmt (line 148) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  method clone (line 72) | fn clone(&self) -> Self {
  method name (line 90) | fn name(&self) -> String {
  method geometry (line 98) | fn geometry(&self) -> Result<Geometry, Box<dyn Error>> {
  method before_input (line 127) | fn before_input(&mut self) -> Result<(), Box<dyn Error>> {
  method recorder (line 139) | fn recorder(&self, capture_cursor: bool) -> Result<Box<dyn Recorder>, Bo...
  method drop (line 154) | fn drop(&mut self) {
  type XDisplay (line 161) | struct XDisplay {
    method new (line 166) | pub fn new() -> Option<Self> {
    method lock (line 174) | pub fn lock(&self) {
    method unlock (line 178) | pub fn unlock(&self) {
  method drop (line 184) | fn drop(&mut self) {
  type X11Context (line 191) | pub struct X11Context {
    method new (line 196) | pub fn new() -> Option<Self> {
    method capturables (line 203) | pub fn capturables(&mut self) -> Result<Vec<X11Capturable>, CError> {
    method map_input_device_to_entire_screen (line 239) | pub fn map_input_device_to_entire_screen(&mut self, device_name: &str,...
  type CImage (line 260) | struct CImage {
    method new (line 267) | pub fn new() -> Self {
    method size (line 275) | pub fn size(&self) -> usize {
    method data (line 279) | pub fn data(&self) -> &[u8] {
  type RecorderX11 (line 284) | pub struct RecorderX11 {
    method new (line 294) | pub fn new(mut capturable: X11Capturable, capture_cursor: bool) -> Res...
  method drop (line 313) | fn drop(&mut self) {
  method capture (line 324) | fn capture(&mut self) -> Result<PixelProvider<'_>, Box<dyn Error>> {

FILE: src/cerror.rs
  type CError (line 8) | pub struct CError {
    method new (line 20) | pub fn new() -> Self {
    method is_err (line 27) | pub fn is_err(&self) -> bool {
    method code (line 31) | pub fn code(&self) -> i32 {
    method to_enum (line 35) | pub fn to_enum(&self) -> CErrorCode {
    method fmt (line 45) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    method fmt (line 53) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type CErrorCode (line 13) | pub enum CErrorCode {

FILE: src/config.rs
  type ThemeType (line 9) | pub enum ThemeType {
    method apply (line 38) | pub fn apply(&self) {
    method name (line 53) | pub fn name(&self) -> String {
    method to_index (line 57) | pub fn to_index(&self) -> i32 {
    method from_index (line 61) | pub fn from_index(i: i32) -> Self {
    method themes (line 66) | pub fn themes() -> &'static [ThemeType] {
  constant THEME_LIST (line 20) | const THEME_LIST: [ThemeType; 8] = [
  method default (line 32) | fn default() -> Self {
  type Config (line 73) | pub struct Config {
  function read_config (line 151) | pub fn read_config() -> Option<Config> {
  function write_config (line 178) | pub fn write_config(conf: &Config) {
  function get_config (line 202) | pub fn get_config() -> Config {

FILE: src/gui.rs
  function run (line 32) | pub fn run(config: &Config, log_receiver: mpsc::Receiver<String>) {
  constant BORDER (line 387) | const BORDER: i32 = 30;
  type InputAreaWindowContext (line 390) | struct InputAreaWindowContext {
  function get_input_area (line 398) | pub fn get_input_area(
  function create_custom_input_area_window (line 444) | fn create_custom_input_area_window() -> InputAreaWindowContext {
  function custom_input_area_window_handle_events (line 511) | fn custom_input_area_window_handle_events(
  function show_overlay_window (line 717) | fn show_overlay_window(winctx: &mut InputAreaWindowContext) {
  function get_full_workspace_rect (line 773) | pub fn get_full_workspace_rect() -> Rect {

FILE: src/input/autopilot_device.rs
  type AutoPilotDevice (line 13) | pub struct AutoPilotDevice {
    method new (line 18) | pub fn new(capturable: Box<dyn Capturable>) -> Self {
  method send_wheel_event (line 24) | fn send_wheel_event(&mut self, event: &WheelEvent) {
  method send_pointer_event (line 32) | fn send_pointer_event(&mut self, event: &PointerEvent) {
  method send_keyboard_event (line 78) | fn send_keyboard_event(&mut self, event: &KeyboardEvent) {
  method set_capturable (line 160) | fn set_capturable(&mut self, capturable: Box<dyn Capturable>) {
  method device_type (line 164) | fn device_type(&self) -> InputDeviceType {

FILE: src/input/autopilot_device_win.rs
  type WindowsInput (line 15) | pub struct WindowsInput {
    method new (line 24) | pub fn new(capturable: Box<dyn Capturable>) -> Self {
  method send_wheel_event (line 39) | fn send_wheel_event(&mut self, event: &WheelEvent) {
  method send_pointer_event (line 43) | fn send_pointer_event(&mut self, event: &PointerEvent) {
  method send_keyboard_event (line 216) | fn send_keyboard_event(&mut self, event: &KeyboardEvent) {
  method set_capturable (line 220) | fn set_capturable(&mut self, capturable: Box<dyn Capturable>) {
  method device_type (line 224) | fn device_type(&self) -> InputDeviceType {

FILE: src/input/device.rs
  type InputDeviceType (line 5) | pub enum InputDeviceType {
  type InputDevice (line 12) | pub trait InputDevice {
    method send_wheel_event (line 13) | fn send_wheel_event(&mut self, event: &WheelEvent);
    method send_pointer_event (line 14) | fn send_pointer_event(&mut self, event: &PointerEvent);
    method send_keyboard_event (line 15) | fn send_keyboard_event(&mut self, event: &KeyboardEvent);
    method set_capturable (line 16) | fn set_capturable(&mut self, capturable: Box<dyn Capturable>);
    method device_type (line 17) | fn device_type(&self) -> InputDeviceType;

FILE: src/input/uinput_device.rs
  function init_uinput_keyboard (line 19) | fn init_uinput_keyboard(name: *const c_char, err: *mut CError) -> c_int;
  function init_uinput_stylus (line 20) | fn init_uinput_stylus(name: *const c_char, err: *mut CError) -> c_int;
  function init_uinput_mouse (line 21) | fn init_uinput_mouse(name: *const c_char, err: *mut CError) -> c_int;
  function init_uinput_touch (line 22) | fn init_uinput_touch(name: *const c_char, err: *mut CError) -> c_int;
  function destroy_uinput_device (line 23) | fn destroy_uinput_device(fd: c_int);
  function send_uinput_event (line 24) | fn send_uinput_event(device: c_int, typ: c_int, code: c_int, value: c_in...
  type MultiTouch (line 27) | struct MultiTouch {
  type UInputDevice (line 31) | pub struct UInputDevice {
    method new (line 52) | pub fn new(capturable: Box<dyn Capturable>, id: &Option<String>) -> Re...
    method transform_x (line 117) | fn transform_x(&self, x: f64) -> i32 {
    method transform_y (line 122) | fn transform_y(&self, y: f64) -> i32 {
    method transform_pressure (line 127) | fn transform_pressure(&self, p: f64) -> i32 {
    method transform_touch_size (line 131) | fn transform_touch_size(&self, s: f64) -> i32 {
    method find_slot (line 135) | fn find_slot(&self, id: i64) -> Option<usize> {
    method send (line 151) | fn send(&self, fd: c_int, typ: c_int, code: c_int, value: c_int) {
  method drop (line 163) | fn drop(&mut self) {
  constant ET_SYNC (line 174) | const ET_SYNC: c_int = 0x00;
  constant ET_KEY (line 175) | const ET_KEY: c_int = 0x01;
  constant ET_RELATIVE (line 176) | const ET_RELATIVE: c_int = 0x02;
  constant ET_ABSOLUTE (line 177) | const ET_ABSOLUTE: c_int = 0x03;
  constant ET_MSC (line 178) | const ET_MSC: c_int = 0x04;
  constant EC_SYNC_REPORT (line 181) | const EC_SYNC_REPORT: c_int = 0;
  constant EC_KEY_MOUSE_LEFT (line 183) | const EC_KEY_MOUSE_LEFT: c_int = 0x110;
  constant EC_KEY_MOUSE_RIGHT (line 184) | const EC_KEY_MOUSE_RIGHT: c_int = 0x111;
  constant EC_KEY_MOUSE_MIDDLE (line 185) | const EC_KEY_MOUSE_MIDDLE: c_int = 0x112;
  constant EC_KEY_TOOL_PEN (line 186) | const EC_KEY_TOOL_PEN: c_int = 0x140;
  constant EC_KEY_TOOL_RUBBER (line 187) | const EC_KEY_TOOL_RUBBER: c_int = 0x141;
  constant EC_KEY_TOUCH (line 188) | const EC_KEY_TOUCH: c_int = 0x14a;
  constant EC_KEY_TOOL_FINGER (line 189) | const EC_KEY_TOOL_FINGER: c_int = 0x145;
  constant EC_KEY_TOOL_DOUBLETAP (line 190) | const EC_KEY_TOOL_DOUBLETAP: c_int = 0x14d;
  constant EC_KEY_TOOL_TRIPLETAP (line 191) | const EC_KEY_TOOL_TRIPLETAP: c_int = 0x14e;
  constant EC_KEY_TOOL_QUADTAP (line 192) | const EC_KEY_TOOL_QUADTAP: c_int = 0x14f;
  constant EC_KEY_TOOL_QUINTTAP (line 193) | const EC_KEY_TOOL_QUINTTAP: c_int = 0x148;
  constant EC_REL_HWHEEL (line 197) | const EC_REL_HWHEEL: c_int = 0x06;
  constant EC_REL_WHEEL (line 198) | const EC_REL_WHEEL: c_int = 0x08;
  constant EC_REL_WHEEL_HI_RES (line 199) | const EC_REL_WHEEL_HI_RES: c_int = 0x0b;
  constant EC_REL_HWHEEL_HI_RES (line 200) | const EC_REL_HWHEEL_HI_RES: c_int = 0x0c;
  constant EC_ABSOLUTE_X (line 202) | const EC_ABSOLUTE_X: c_int = 0x00;
  constant EC_ABSOLUTE_Y (line 203) | const EC_ABSOLUTE_Y: c_int = 0x01;
  constant EC_ABSOLUTE_PRESSURE (line 204) | const EC_ABSOLUTE_PRESSURE: c_int = 0x18;
  constant EC_ABSOLUTE_TILT_X (line 205) | const EC_ABSOLUTE_TILT_X: c_int = 0x1a;
  constant EC_ABSOLUTE_TILT_Y (line 206) | const EC_ABSOLUTE_TILT_Y: c_int = 0x1b;
  constant EC_ABS_MT_SLOT (line 207) | const EC_ABS_MT_SLOT: c_int = 0x2f;
  constant EC_ABS_MT_TOUCH_MAJOR (line 208) | const EC_ABS_MT_TOUCH_MAJOR: c_int = 0x30;
  constant EC_ABS_MT_TOUCH_MINOR (line 209) | const EC_ABS_MT_TOUCH_MINOR: c_int = 0x31;
  constant EC_ABS_MT_ORIENTATION (line 210) | const EC_ABS_MT_ORIENTATION: c_int = 0x34;
  constant EC_ABS_MT_POSITION_X (line 211) | const EC_ABS_MT_POSITION_X: c_int = 0x35;
  constant EC_ABS_MT_POSITION_Y (line 212) | const EC_ABS_MT_POSITION_Y: c_int = 0x36;
  constant EC_ABS_MT_TRACKING_ID (line 213) | const EC_ABS_MT_TRACKING_ID: c_int = 0x39;
  constant EC_ABS_MT_PRESSURE (line 214) | const EC_ABS_MT_PRESSURE: c_int = 0x3a;
  constant EC_MSC_TIMESTAMP (line 216) | const EC_MSC_TIMESTAMP: c_int = 0x05;
  constant ABS_MAX (line 221) | const ABS_MAX: f64 = 65535.0;
  constant MAX_SCREEN_MAPPING_TRIES (line 237) | const MAX_SCREEN_MAPPING_TRIES: usize = 100;
  method send_wheel_event (line 240) | fn send_wheel_event(&mut self, event: &WheelEvent) {
  method send_pointer_event (line 278) | fn send_pointer_event(&mut self, event: &PointerEvent) {
  method send_keyboard_event (line 643) | fn send_keyboard_event(&mut self, event: &KeyboardEvent) {
  method set_capturable (line 909) | fn set_capturable(&mut self, capturable: Box<dyn Capturable>) {
  method device_type (line 913) | fn device_type(&self) -> InputDeviceType {

FILE: src/input/uinput_keys.rs
  constant KEY_ESC (line 3) | pub const KEY_ESC: c_int = 1;
  constant KEY_1 (line 4) | pub const KEY_1: c_int = 2;
  constant KEY_2 (line 5) | pub const KEY_2: c_int = 3;
  constant KEY_3 (line 6) | pub const KEY_3: c_int = 4;
  constant KEY_4 (line 7) | pub const KEY_4: c_int = 5;
  constant KEY_5 (line 8) | pub const KEY_5: c_int = 6;
  constant KEY_6 (line 9) | pub const KEY_6: c_int = 7;
  constant KEY_7 (line 10) | pub const KEY_7: c_int = 8;
  constant KEY_8 (line 11) | pub const KEY_8: c_int = 9;
  constant KEY_9 (line 12) | pub const KEY_9: c_int = 10;
  constant KEY_0 (line 13) | pub const KEY_0: c_int = 11;
  constant KEY_MINUS (line 14) | pub const KEY_MINUS: c_int = 12;
  constant KEY_EQUAL (line 15) | pub const KEY_EQUAL: c_int = 13;
  constant KEY_BACKSPACE (line 16) | pub const KEY_BACKSPACE: c_int = 14;
  constant KEY_TAB (line 17) | pub const KEY_TAB: c_int = 15;
  constant KEY_Q (line 18) | pub const KEY_Q: c_int = 16;
  constant KEY_W (line 19) | pub const KEY_W: c_int = 17;
  constant KEY_E (line 20) | pub const KEY_E: c_int = 18;
  constant KEY_R (line 21) | pub const KEY_R: c_int = 19;
  constant KEY_T (line 22) | pub const KEY_T: c_int = 20;
  constant KEY_Y (line 23) | pub const KEY_Y: c_int = 21;
  constant KEY_U (line 24) | pub const KEY_U: c_int = 22;
  constant KEY_I (line 25) | pub const KEY_I: c_int = 23;
  constant KEY_O (line 26) | pub const KEY_O: c_int = 24;
  constant KEY_P (line 27) | pub const KEY_P: c_int = 25;
  constant KEY_LEFTBRACE (line 28) | pub const KEY_LEFTBRACE: c_int = 26;
  constant KEY_RIGHTBRACE (line 29) | pub const KEY_RIGHTBRACE: c_int = 27;
  constant KEY_ENTER (line 30) | pub const KEY_ENTER: c_int = 28;
  constant KEY_LEFTCTRL (line 31) | pub const KEY_LEFTCTRL: c_int = 29;
  constant KEY_A (line 32) | pub const KEY_A: c_int = 30;
  constant KEY_S (line 33) | pub const KEY_S: c_int = 31;
  constant KEY_D (line 34) | pub const KEY_D: c_int = 32;
  constant KEY_F (line 35) | pub const KEY_F: c_int = 33;
  constant KEY_G (line 36) | pub const KEY_G: c_int = 34;
  constant KEY_H (line 37) | pub const KEY_H: c_int = 35;
  constant KEY_J (line 38) | pub const KEY_J: c_int = 36;
  constant KEY_K (line 39) | pub const KEY_K: c_int = 37;
  constant KEY_L (line 40) | pub const KEY_L: c_int = 38;
  constant KEY_SEMICOLON (line 41) | pub const KEY_SEMICOLON: c_int = 39;
  constant KEY_APOSTROPHE (line 42) | pub const KEY_APOSTROPHE: c_int = 40;
  constant KEY_GRAVE (line 43) | pub const KEY_GRAVE: c_int = 41;
  constant KEY_LEFTSHIFT (line 44) | pub const KEY_LEFTSHIFT: c_int = 42;
  constant KEY_BACKSLASH (line 45) | pub const KEY_BACKSLASH: c_int = 43;
  constant KEY_Z (line 46) | pub const KEY_Z: c_int = 44;
  constant KEY_X (line 47) | pub const KEY_X: c_int = 45;
  constant KEY_C (line 48) | pub const KEY_C: c_int = 46;
  constant KEY_V (line 49) | pub const KEY_V: c_int = 47;
  constant KEY_B (line 50) | pub const KEY_B: c_int = 48;
  constant KEY_N (line 51) | pub const KEY_N: c_int = 49;
  constant KEY_M (line 52) | pub const KEY_M: c_int = 50;
  constant KEY_COMMA (line 53) | pub const KEY_COMMA: c_int = 51;
  constant KEY_DOT (line 54) | pub const KEY_DOT: c_int = 52;
  constant KEY_SLASH (line 55) | pub const KEY_SLASH: c_int = 53;
  constant KEY_RIGHTSHIFT (line 56) | pub const KEY_RIGHTSHIFT: c_int = 54;
  constant KEY_KPASTERISK (line 57) | pub const KEY_KPASTERISK: c_int = 55;
  constant KEY_LEFTALT (line 58) | pub const KEY_LEFTALT: c_int = 56;
  constant KEY_SPACE (line 59) | pub const KEY_SPACE: c_int = 57;
  constant KEY_CAPSLOCK (line 60) | pub const KEY_CAPSLOCK: c_int = 58;
  constant KEY_F1 (line 61) | pub const KEY_F1: c_int = 59;
  constant KEY_F2 (line 62) | pub const KEY_F2: c_int = 60;
  constant KEY_F3 (line 63) | pub const KEY_F3: c_int = 61;
  constant KEY_F4 (line 64) | pub const KEY_F4: c_int = 62;
  constant KEY_F5 (line 65) | pub const KEY_F5: c_int = 63;
  constant KEY_F6 (line 66) | pub const KEY_F6: c_int = 64;
  constant KEY_F7 (line 67) | pub const KEY_F7: c_int = 65;
  constant KEY_F8 (line 68) | pub const KEY_F8: c_int = 66;
  constant KEY_F9 (line 69) | pub const KEY_F9: c_int = 67;
  constant KEY_F10 (line 70) | pub const KEY_F10: c_int = 68;
  constant KEY_NUMLOCK (line 71) | pub const KEY_NUMLOCK: c_int = 69;
  constant KEY_SCROLLLOCK (line 72) | pub const KEY_SCROLLLOCK: c_int = 70;
  constant KEY_KP7 (line 73) | pub const KEY_KP7: c_int = 71;
  constant KEY_KP8 (line 74) | pub const KEY_KP8: c_int = 72;
  constant KEY_KP9 (line 75) | pub const KEY_KP9: c_int = 73;
  constant KEY_KPMINUS (line 76) | pub const KEY_KPMINUS: c_int = 74;
  constant KEY_KP4 (line 77) | pub const KEY_KP4: c_int = 75;
  constant KEY_KP5 (line 78) | pub const KEY_KP5: c_int = 76;
  constant KEY_KP6 (line 79) | pub const KEY_KP6: c_int = 77;
  constant KEY_KPPLUS (line 80) | pub const KEY_KPPLUS: c_int = 78;
  constant KEY_KP1 (line 81) | pub const KEY_KP1: c_int = 79;
  constant KEY_KP2 (line 82) | pub const KEY_KP2: c_int = 80;
  constant KEY_KP3 (line 83) | pub const KEY_KP3: c_int = 81;
  constant KEY_KP0 (line 84) | pub const KEY_KP0: c_int = 82;
  constant KEY_KPDOT (line 85) | pub const KEY_KPDOT: c_int = 83;
  constant KEY_ZENKAKUHANKAKU (line 87) | pub const KEY_ZENKAKUHANKAKU: c_int = 85;
  constant KEY_102ND (line 88) | pub const KEY_102ND: c_int = 86;
  constant KEY_F11 (line 89) | pub const KEY_F11: c_int = 87;
  constant KEY_F12 (line 90) | pub const KEY_F12: c_int = 88;
  constant KEY_RO (line 91) | pub const KEY_RO: c_int = 89;
  constant KEY_KATAKANA (line 92) | pub const KEY_KATAKANA: c_int = 90;
  constant KEY_HIRAGANA (line 93) | pub const KEY_HIRAGANA: c_int = 91;
  constant KEY_HENKAN (line 94) | pub const KEY_HENKAN: c_int = 92;
  constant KEY_KATAKANAHIRAGANA (line 95) | pub const KEY_KATAKANAHIRAGANA: c_int = 93;
  constant KEY_MUHENKAN (line 96) | pub const KEY_MUHENKAN: c_int = 94;
  constant KEY_KPJPCOMMA (line 97) | pub const KEY_KPJPCOMMA: c_int = 95;
  constant KEY_KPENTER (line 98) | pub const KEY_KPENTER: c_int = 96;
  constant KEY_RIGHTCTRL (line 99) | pub const KEY_RIGHTCTRL: c_int = 97;
  constant KEY_KPSLASH (line 100) | pub const KEY_KPSLASH: c_int = 98;
  constant KEY_SYSRQ (line 101) | pub const KEY_SYSRQ: c_int = 99;
  constant KEY_RIGHTALT (line 102) | pub const KEY_RIGHTALT: c_int = 100;
  constant KEY_LINEFEED (line 103) | pub const KEY_LINEFEED: c_int = 101;
  constant KEY_HOME (line 104) | pub const KEY_HOME: c_int = 102;
  constant KEY_UP (line 105) | pub const KEY_UP: c_int = 103;
  constant KEY_PAGEUP (line 106) | pub const KEY_PAGEUP: c_int = 104;
  constant KEY_LEFT (line 107) | pub const KEY_LEFT: c_int = 105;
  constant KEY_RIGHT (line 108) | pub const KEY_RIGHT: c_int = 106;
  constant KEY_END (line 109) | pub const KEY_END: c_int = 107;
  constant KEY_DOWN (line 110) | pub const KEY_DOWN: c_int = 108;
  constant KEY_PAGEDOWN (line 111) | pub const KEY_PAGEDOWN: c_int = 109;
  constant KEY_INSERT (line 112) | pub const KEY_INSERT: c_int = 110;
  constant KEY_DELETE (line 113) | pub const KEY_DELETE: c_int = 111;
  constant KEY_MACRO (line 114) | pub const KEY_MACRO: c_int = 112;
  constant KEY_MUTE (line 115) | pub const KEY_MUTE: c_int = 113;
  constant KEY_VOLUMEDOWN (line 116) | pub const KEY_VOLUMEDOWN: c_int = 114;
  constant KEY_VOLUMEUP (line 117) | pub const KEY_VOLUMEUP: c_int = 115;
  constant KEY_POWER (line 118) | pub const KEY_POWER: c_int = 116;
  constant KEY_KPEQUAL (line 119) | pub const KEY_KPEQUAL: c_int = 117;
  constant KEY_KPPLUSMINUS (line 120) | pub const KEY_KPPLUSMINUS: c_int = 118;
  constant KEY_PAUSE (line 121) | pub const KEY_PAUSE: c_int = 119;
  constant KEY_SCALE (line 122) | pub const KEY_SCALE: c_int = 120;
  constant KEY_KPCOMMA (line 124) | pub const KEY_KPCOMMA: c_int = 121;
  constant KEY_HANGEUL (line 125) | pub const KEY_HANGEUL: c_int = 122;
  constant KEY_HANGUEL (line 126) | pub const KEY_HANGUEL: c_int = KEY_HANGEUL;
  constant KEY_HANJA (line 127) | pub const KEY_HANJA: c_int = 123;
  constant KEY_YEN (line 128) | pub const KEY_YEN: c_int = 124;
  constant KEY_LEFTMETA (line 129) | pub const KEY_LEFTMETA: c_int = 125;
  constant KEY_RIGHTMETA (line 130) | pub const KEY_RIGHTMETA: c_int = 126;
  constant KEY_COMPOSE (line 131) | pub const KEY_COMPOSE: c_int = 127;
  constant KEY_STOP (line 133) | pub const KEY_STOP: c_int = 128;
  constant KEY_AGAIN (line 134) | pub const KEY_AGAIN: c_int = 129;
  constant KEY_PROPS (line 135) | pub const KEY_PROPS: c_int = 130;
  constant KEY_UNDO (line 136) | pub const KEY_UNDO: c_int = 131;
  constant KEY_FRONT (line 137) | pub const KEY_FRONT: c_int = 132;
  constant KEY_COPY (line 138) | pub const KEY_COPY: c_int = 133;
  constant KEY_OPEN (line 139) | pub const KEY_OPEN: c_int = 134;
  constant KEY_PASTE (line 140) | pub const KEY_PASTE: c_int = 135;
  constant KEY_FIND (line 141) | pub const KEY_FIND: c_int = 136;
  constant KEY_CUT (line 142) | pub const KEY_CUT: c_int = 137;
  constant KEY_HELP (line 143) | pub const KEY_HELP: c_int = 138;
  constant KEY_MENU (line 144) | pub const KEY_MENU: c_int = 139;
  constant KEY_CALC (line 145) | pub const KEY_CALC: c_int = 140;
  constant KEY_SETUP (line 146) | pub const KEY_SETUP: c_int = 141;
  constant KEY_SLEEP (line 147) | pub const KEY_SLEEP: c_int = 142;
  constant KEY_WAKEUP (line 148) | pub const KEY_WAKEUP: c_int = 143;
  constant KEY_FILE (line 149) | pub const KEY_FILE: c_int = 144;
  constant KEY_SENDFILE (line 150) | pub const KEY_SENDFILE: c_int = 145;
  constant KEY_DELETEFILE (line 151) | pub const KEY_DELETEFILE: c_int = 146;
  constant KEY_XFER (line 152) | pub const KEY_XFER: c_int = 147;
  constant KEY_PROG1 (line 153) | pub const KEY_PROG1: c_int = 148;
  constant KEY_PROG2 (line 154) | pub const KEY_PROG2: c_int = 149;
  constant KEY_WWW (line 155) | pub const KEY_WWW: c_int = 150;
  constant KEY_MSDOS (line 156) | pub const KEY_MSDOS: c_int = 151;
  constant KEY_COFFEE (line 157) | pub const KEY_COFFEE: c_int = 152;
  constant KEY_SCREENLOCK (line 158) | pub const KEY_SCREENLOCK: c_int = KEY_COFFEE;
  constant KEY_ROTATE_DISPLAY (line 159) | pub const KEY_ROTATE_DISPLAY: c_int = 153;
  constant KEY_DIRECTION (line 160) | pub const KEY_DIRECTION: c_int = KEY_ROTATE_DISPLAY;
  constant KEY_CYCLEWINDOWS (line 161) | pub const KEY_CYCLEWINDOWS: c_int = 154;
  constant KEY_MAIL (line 162) | pub const KEY_MAIL: c_int = 155;
  constant KEY_BOOKMARKS (line 163) | pub const KEY_BOOKMARKS: c_int = 156;
  constant KEY_COMPUTER (line 164) | pub const KEY_COMPUTER: c_int = 157;
  constant KEY_BACK (line 165) | pub const KEY_BACK: c_int = 158;
  constant KEY_FORWARD (line 166) | pub const KEY_FORWARD: c_int = 159;
  constant KEY_CLOSECD (line 167) | pub const KEY_CLOSECD: c_int = 160;
  constant KEY_EJECTCD (line 168) | pub const KEY_EJECTCD: c_int = 161;
  constant KEY_EJECTCLOSECD (line 169) | pub const KEY_EJECTCLOSECD: c_int = 162;
  constant KEY_NEXTSONG (line 170) | pub const KEY_NEXTSONG: c_int = 163;
  constant KEY_PLAYPAUSE (line 171) | pub const KEY_PLAYPAUSE: c_int = 164;
  constant KEY_PREVIOUSSONG (line 172) | pub const KEY_PREVIOUSSONG: c_int = 165;
  constant KEY_STOPCD (line 173) | pub const KEY_STOPCD: c_int = 166;
  constant KEY_RECORD (line 174) | pub const KEY_RECORD: c_int = 167;
  constant KEY_REWIND (line 175) | pub const KEY_REWIND: c_int = 168;
  constant KEY_PHONE (line 176) | pub const KEY_PHONE: c_int = 169;
  constant KEY_ISO (line 177) | pub const KEY_ISO: c_int = 170;
  constant KEY_CONFIG (line 178) | pub const KEY_CONFIG: c_int = 171;
  constant KEY_HOMEPAGE (line 179) | pub const KEY_HOMEPAGE: c_int = 172;
  constant KEY_REFRESH (line 180) | pub const KEY_REFRESH: c_int = 173;
  constant KEY_EXIT (line 181) | pub const KEY_EXIT: c_int = 174;
  constant KEY_MOVE (line 182) | pub const KEY_MOVE: c_int = 175;
  constant KEY_EDIT (line 183) | pub const KEY_EDIT: c_int = 176;
  constant KEY_SCROLLUP (line 184) | pub const KEY_SCROLLUP: c_int = 177;
  constant KEY_SCROLLDOWN (line 185) | pub const KEY_SCROLLDOWN: c_int = 178;
  constant KEY_KPLEFTPAREN (line 186) | pub const KEY_KPLEFTPAREN: c_int = 179;
  constant KEY_KPRIGHTPAREN (line 187) | pub const KEY_KPRIGHTPAREN: c_int = 180;
  constant KEY_NEW (line 188) | pub const KEY_NEW: c_int = 181;
  constant KEY_REDO (line 189) | pub const KEY_REDO: c_int = 182;
  constant KEY_F13 (line 191) | pub const KEY_F13: c_int = 183;
  constant KEY_F14 (line 192) | pub const KEY_F14: c_int = 184;
  constant KEY_F15 (line 193) | pub const KEY_F15: c_int = 185;
  constant KEY_F16 (line 194) | pub const KEY_F16: c_int = 186;
  constant KEY_F17 (line 195) | pub const KEY_F17: c_int = 187;
  constant KEY_F18 (line 196) | pub const KEY_F18: c_int = 188;
  constant KEY_F19 (line 197) | pub const KEY_F19: c_int = 189;
  constant KEY_F20 (line 198) | pub const KEY_F20: c_int = 190;
  constant KEY_F21 (line 199) | pub const KEY_F21: c_int = 191;
  constant KEY_F22 (line 200) | pub const KEY_F22: c_int = 192;
  constant KEY_F23 (line 201) | pub const KEY_F23: c_int = 193;
  constant KEY_F24 (line 202) | pub const KEY_F24: c_int = 194;
  constant KEY_PLAYCD (line 204) | pub const KEY_PLAYCD: c_int = 200;
  constant KEY_PAUSECD (line 205) | pub const KEY_PAUSECD: c_int = 201;
  constant KEY_PROG3 (line 206) | pub const KEY_PROG3: c_int = 202;
  constant KEY_PROG4 (line 207) | pub const KEY_PROG4: c_int = 203;
  constant KEY_DASHBOARD (line 208) | pub const KEY_DASHBOARD: c_int = 204;
  constant KEY_SUSPEND (line 209) | pub const KEY_SUSPEND: c_int = 205;
  constant KEY_CLOSE (line 210) | pub const KEY_CLOSE: c_int = 206;
  constant KEY_PLAY (line 211) | pub const KEY_PLAY: c_int = 207;
  constant KEY_FASTFORWARD (line 212) | pub const KEY_FASTFORWARD: c_int = 208;
  constant KEY_BASSBOOST (line 213) | pub const KEY_BASSBOOST: c_int = 209;
  constant KEY_PRINT (line 214) | pub const KEY_PRINT: c_int = 210;
  constant KEY_HP (line 215) | pub const KEY_HP: c_int = 211;
  constant KEY_CAMERA (line 216) | pub const KEY_CAMERA: c_int = 212;
  constant KEY_SOUND (line 217) | pub const KEY_SOUND: c_int = 213;
  constant KEY_QUESTION (line 218) | pub const KEY_QUESTION: c_int = 214;
  constant KEY_EMAIL (line 219) | pub const KEY_EMAIL: c_int = 215;
  constant KEY_CHAT (line 220) | pub const KEY_CHAT: c_int = 216;
  constant KEY_SEARCH (line 221) | pub const KEY_SEARCH: c_int = 217;
  constant KEY_CONNECT (line 222) | pub const KEY_CONNECT: c_int = 218;
  constant KEY_FINANCE (line 223) | pub const KEY_FINANCE: c_int = 219;
  constant KEY_SPORT (line 224) | pub const KEY_SPORT: c_int = 220;
  constant KEY_SHOP (line 225) | pub const KEY_SHOP: c_int = 221;
  constant KEY_ALTERASE (line 226) | pub const KEY_ALTERASE: c_int = 222;
  constant KEY_CANCEL (line 227) | pub const KEY_CANCEL: c_int = 223;
  constant KEY_BRIGHTNESSDOWN (line 228) | pub const KEY_BRIGHTNESSDOWN: c_int = 224;
  constant KEY_BRIGHTNESSUP (line 229) | pub const KEY_BRIGHTNESSUP: c_int = 225;
  constant KEY_MEDIA (line 230) | pub const KEY_MEDIA: c_int = 226;
  constant KEY_SWITCHVIDEOMODE (line 232) | pub const KEY_SWITCHVIDEOMODE: c_int = 227;
  constant KEY_KBDILLUMTOGGLE (line 233) | pub const KEY_KBDILLUMTOGGLE: c_int = 228;
  constant KEY_KBDILLUMDOWN (line 234) | pub const KEY_KBDILLUMDOWN: c_int = 229;
  constant KEY_KBDILLUMUP (line 235) | pub const KEY_KBDILLUMUP: c_int = 230;
  constant KEY_SEND (line 237) | pub const KEY_SEND: c_int = 231;
  constant KEY_REPLY (line 238) | pub const KEY_REPLY: c_int = 232;
  constant KEY_FORWARDMAIL (line 239) | pub const KEY_FORWARDMAIL: c_int = 233;
  constant KEY_SAVE (line 240) | pub const KEY_SAVE: c_int = 234;
  constant KEY_DOCUMENTS (line 241) | pub const KEY_DOCUMENTS: c_int = 235;
  constant KEY_BATTERY (line 243) | pub const KEY_BATTERY: c_int = 236;
  constant KEY_BLUETOOTH (line 245) | pub const KEY_BLUETOOTH: c_int = 237;
  constant KEY_WLAN (line 246) | pub const KEY_WLAN: c_int = 238;
  constant KEY_UWB (line 247) | pub const KEY_UWB: c_int = 239;
  constant KEY_UNKNOWN (line 249) | pub const KEY_UNKNOWN: c_int = 240;
  constant KEY_VIDEO_NEXT (line 251) | pub const KEY_VIDEO_NEXT: c_int = 241;
  constant KEY_VIDEO_PREV (line 252) | pub const KEY_VIDEO_PREV: c_int = 242;
  constant KEY_BRIGHTNESS_CYCLE (line 253) | pub const KEY_BRIGHTNESS_CYCLE: c_int = 243;
  constant KEY_BRIGHTNESS_AUTO (line 254) | pub const KEY_BRIGHTNESS_AUTO: c_int = 244;
  constant KEY_BRIGHTNESS_ZERO (line 255) | pub const KEY_BRIGHTNESS_ZERO: c_int = KEY_BRIGHTNESS_AUTO;
  constant KEY_DISPLAY_OFF (line 256) | pub const KEY_DISPLAY_OFF: c_int = 245;
  constant KEY_WWAN (line 258) | pub const KEY_WWAN: c_int = 246;
  constant KEY_WIMAX (line 259) | pub const KEY_WIMAX: c_int = KEY_WWAN;
  constant KEY_RFKILL (line 260) | pub const KEY_RFKILL: c_int = 247;
  constant KEY_MICMUTE (line 262) | pub const KEY_MICMUTE: c_int = 248;

FILE: src/log.rs
  function init_ffmpeg_logger (line 9) | fn init_ffmpeg_logger();
  type GuiTracingWriter (line 12) | struct GuiTracingWriter {
  method write (line 17) | fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
  method flush (line 23) | fn flush(&mut self) -> std::io::Result<()> {
  type GuiTracingWriterFactory (line 28) | struct GuiTracingWriterFactory {
    type Writer (line 33) | type Writer = GuiTracingWriter;
    method make_writer (line 34) | fn make_writer(&'a self) -> Self::Writer {
  function get_log_level (line 41) | pub fn get_log_level() -> tracing::Level {
  function setup_logging (line 57) | pub fn setup_logging(sender: mpsc::SyncSender<String>) {
  function log_error_rust (line 94) | fn log_error_rust(msg: *const c_char) {
  function log_debug_rust (line 100) | fn log_debug_rust(msg: *const c_char) {
  function log_info_rust (line 106) | fn log_info_rust(msg: *const c_char) {
  function log_trace_rust (line 112) | fn log_trace_rust(msg: *const c_char) {
  function log_warn_rust (line 118) | fn log_warn_rust(msg: *const c_char) {

FILE: src/main.rs
  function main (line 32) | fn main() {
  function bench_capture_x11 (line 129) | fn bench_capture_x11(b: &mut Bencher) {
  function bench_video_x11 (line 140) | fn bench_video_x11(b: &mut Bencher) {
  function bench_capture_wayland (line 159) | fn bench_capture_wayland(b: &mut Bencher) {
  function bench_video_wayland (line 173) | fn bench_video_wayland(b: &mut Bencher) {
  function bench_video_vaapi (line 194) | fn bench_video_vaapi(b: &mut Bencher) {
  function bench_video_x264 (line 223) | fn bench_video_x264(b: &mut Bencher) {
  function bench_video_nvenc (line 252) | fn bench_video_nvenc(b: &mut Bencher) {

FILE: src/protocol.rs
  type ClientConfiguration (line 4) | pub struct ClientConfiguration {
  type MessageInbound (line 16) | pub enum MessageInbound {
  type MessageOutbound (line 29) | pub enum MessageOutbound {
  type Rect (line 39) | pub struct Rect {
  method default (line 47) | fn default() -> Self {
  type CustomInputAreas (line 58) | pub struct CustomInputAreas {
  type PointerType (line 65) | pub enum PointerType {
  type PointerEventType (line 77) | pub enum PointerEventType {
  type KeyboardEventType (line 97) | pub enum KeyboardEventType {
  type KeyboardLocation (line 107) | pub enum KeyboardLocation {
  function location_from (line 114) | fn location_from<'de, D: Deserializer<'de>>(deserializer: D) -> Result<K...
  function button_from (line 140) | fn button_from<'de, D: Deserializer<'de>>(deserializer: D) -> Result<But...
  type KeyboardEvent (line 149) | pub struct KeyboardEvent {
  type PointerEvent (line 162) | pub struct PointerEvent {
  type WheelEvent (line 185) | pub struct WheelEvent {
  type WeylusSender (line 191) | pub trait WeylusSender {
    method send_message (line 193) | fn send_message(&mut self, message: MessageOutbound) -> Result<(), Sel...
    method send_video (line 194) | fn send_video(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
  type WeylusReceiver (line 197) | pub trait WeylusReceiver: Iterator<Item = Result<MessageInbound, Self::E...

FILE: src/video.rs
  function init_video_encoder (line 9) | fn init_video_encoder(
  function open_video (line 20) | fn open_video(handle: *mut c_void, err: *mut CError);
  function destroy_video_encoder (line 21) | fn destroy_video_encoder(handle: *mut c_void);
  function encode_video_frame (line 22) | fn encode_video_frame(handle: *mut c_void, micros: c_int, err: *mut CErr...
  function fill_rgb (line 24) | fn fill_rgb(ctx: *mut c_void, data: *const u8, err: *mut CError);
  function fill_rgb0 (line 25) | fn fill_rgb0(ctx: *mut c_void, data: *const u8, err: *mut CError);
  function fill_bgr0 (line 26) | fn fill_bgr0(ctx: *mut c_void, data: *const u8, stride: c_int, err: *mut...
  function write_video_packet (line 31) | fn write_video_packet(video_encoder: *mut c_void, buf: *const c_uchar, b...
  type PixelProvider (line 39) | pub enum PixelProvider<'a> {
  function size (line 49) | pub fn size(&self) -> (usize, usize) {
  type EncoderOptions (line 60) | pub struct EncoderOptions {
  type VideoEncoder (line 67) | pub struct VideoEncoder {
    method new (line 78) | pub fn new(
    method encode (line 118) | pub fn encode(&mut self, pixel_provider: PixelProvider) {
    method check_size (line 151) | pub fn check_size(
  method drop (line 166) | fn drop(&mut self) {

FILE: src/web.rs
  type WebStartUpMessage (line 26) | pub enum WebStartUpMessage {
  type Web2UiMessage (line 31) | pub enum Web2UiMessage {
  constant INDEX_HTML (line 35) | pub const INDEX_HTML: &str = std::include_str!("../www/templates/index.h...
  constant ACCESS_HTML (line 36) | pub const ACCESS_HTML: &str = std::include_str!("../www/static/access_co...
  constant STYLE_CSS (line 37) | pub const STYLE_CSS: &str = std::include_str!("../www/static/style.css");
  constant LIB_JS (line 38) | pub const LIB_JS: &str = std::include_str!("../www/static/lib.js");
  type IndexTemplateContext (line 41) | struct IndexTemplateContext {
  function response_from_str (line 49) | fn response_from_str(s: &str, content_type: &str) -> Response<Full<Bytes...
  function response_not_found (line 57) | fn response_not_found() -> Response<Full<Bytes>> {
  function response_from_path_or_default (line 65) | async fn response_from_path_or_default(
  function serve (line 82) | async fn serve(
  type WebServerConfig (line 221) | pub struct WebServerConfig {
  type Context (line 231) | struct Context<'a> {
  function run (line 237) | pub fn run(
  function run_server (line 258) | async fn run_server(

FILE: src/websocket.rs
  type VideoConfig (line 22) | struct VideoConfig {
  type VideoCommands (line 30) | enum VideoCommands {
  function send_message (line 37) | fn send_message<S>(sender: &mut S, message: MessageOutbound)
  type WeylusClientHandler (line 46) | pub struct WeylusClientHandler<S, R, FnUInput> {
  type WeylusClientConfig (line 61) | pub struct WeylusClientConfig {
  function new (line 69) | pub fn new(
  function run (line 102) | pub fn run(mut self)
  function send_message (line 157) | fn send_message(&mut self, message: MessageOutbound)
  function process_wheel_event (line 164) | fn process_wheel_event(&mut self, event: &WheelEvent) {
  function process_pointer_event (line 171) | fn process_pointer_event(&mut self, event: &PointerEvent) {
  function process_keyboard_event (line 182) | fn process_keyboard_event(&mut self, event: &KeyboardEvent) {
  function send_capturable_list (line 193) | fn send_capturable_list(&mut self)
  function update_config (line 210) | fn update_config(&mut self, config: ClientConfiguration)
  function handle_video (line 303) | fn handle_video<S: WeylusSender + Clone + 'static>(
  type WsWeylusReceiver (line 443) | pub struct WsWeylusReceiver {
  type Item (line 448) | type Item = Result<MessageInbound, Infallible>;
  method next (line 450) | fn next(&mut self) -> Option<Self::Item> {
  type Error (line 456) | type Error = Infallible;
  type WsMessage (line 459) | pub enum WsMessage {
  type WsWeylusSender (line 468) | pub struct WsWeylusSender {
  type Error (line 473) | type Error = tokio::sync::mpsc::error::SendError<WsMessage>;
  method send_message (line 475) | fn send_message(&mut self, message: MessageOutbound) -> Result<(), Self:...
  method send_video (line 480) | fn send_video(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
  function weylus_websocket_channel (line 485) | pub fn weylus_websocket_channel(

FILE: src/weylus.rs
  type Weylus (line 10) | pub struct Weylus {
    method new (line 16) | pub fn new() -> Self {
    method start (line 23) | pub fn start(
    method stop (line 102) | pub fn stop(&mut self) {
    method wait (line 107) | fn wait(&mut self) {
  method drop (line 117) | fn drop(&mut self) {

FILE: ts/lib.ts
  type Window (line 1) | interface Window {
  type LogLevel (line 5) | enum LogLevel {
  function run (line 23) | function run(level: string) {
  function log (line 50) | function log(level: LogLevel, msg: string) {
  function frame_rate_scale (line 72) | function frame_rate_scale(x: number) {
  function frame_rate_scale_inv (line 76) | function frame_rate_scale_inv(x: number) {
  function calc_max_video_resolution (line 81) | function calc_max_video_resolution(scale: number) {
  function fresh_canvas (line 88) | function fresh_canvas() {
  class Rect (line 97) | class Rect {
  class CustomInputAreas (line 104) | class CustomInputAreas {
  class Settings (line 110) | class Settings {
    method constructor (line 125) | constructor(webSocket: WebSocket) {
    method send_server_config (line 241) | send_server_config() {
    method save_settings (line 257) | save_settings() {
    method load_settings (line 269) | load_settings() {
    method stretched_video (line 338) | stretched_video() {
    method pointer_types (line 342) | pointer_types() {
    method toggle (line 353) | toggle() {
    method onCapturableList (line 358) | onCapturableList(window_names: string[]) {
    method toggle_energysaving (line 379) | toggle_energysaving(energysaving: boolean) {
    method video_enabled (line 397) | video_enabled(): boolean {
  class PEvent (line 406) | class PEvent {
    method constructor (line 425) | constructor(eventType: string, event: PointerEvent, targetRect: DOMRec...
  class WEvent (line 474) | class WEvent {
    method constructor (line 479) | constructor(event: WheelEvent) {
  class Painter (line 524) | class Painter {
    method constructor (line 547) | constructor(canvas: HTMLCanvasElement) {
    method loadShader (line 559) | loadShader(type, source): WebGLShader {
    method setupWebGL (line 572) | setupWebGL() {
    method render (line 602) | render() {
    method appendEventToLine (line 630) | appendEventToLine(event: PointerEvent) {
    method onstart (line 666) | onstart(event: PointerEvent) {
    method onmove (line 670) | onmove(event: PointerEvent) {
    method onstop (line 679) | onstop(event: PointerEvent) {
  class PointerHandler (line 689) | class PointerHandler {
    method constructor (line 693) | constructor(webSocket: WebSocket) {
    method onEvent (line 745) | onEvent(event: PointerEvent, event_type: string) {
  class KEvent (line 828) | class KEvent {
    method constructor (line 838) | constructor(event_type: string, event: KeyboardEvent) {
  class KeyboardHandler (line 850) | class KeyboardHandler {
    method constructor (line 853) | constructor(webSocket: WebSocket) {
    method onEvent (line 889) | onEvent(event: KeyboardEvent, event_type: string) {
  function frame_rate_stats (line 897) | function frame_rate_stats() {
  function handle_messages (line 906) | function handle_messages(
  function check_apis (line 1002) | function check_apis() {
  function init (line 1025) | function init() {
  function stretch_video (line 1120) | function stretch_video() {
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (447K chars).
[
  {
    "path": ".clang-format",
    "chars": 295,
    "preview": "Language: Cpp\nStandard: Latest\nBasedOnStyle: LLVM\n\nColumnLimit: 100\nIndentWidth: 4\nTabWidth: 4\nUseTab: ForContinuationAn"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 29,
    "preview": "github: H-M-H\nliberapay: HMH\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 4529,
    "preview": "name: Build\n\non:\n  push:\n    branches: [ '*' ]\n    tags:\n      - v*\n  pull_request:\n    branches: [ master ]\n\njobs:\n  bu"
  },
  {
    "path": ".gitignore",
    "chars": 147,
    "preview": "/.vscode\n/target\n/deps/ffmpeg\n/deps/x264\n/deps/dist*\n/deps/nv-codec-headers\n/deps/libva\nc_helper/target\nc_helper/Cargo.l"
  },
  {
    "path": "CONTRIBUTORS",
    "chars": 1808,
    "preview": "If you want to contribute to Weylus you have to agree to license your\ncontributions under the 3-Clause BSD License. To d"
  },
  {
    "path": "Cargo.toml",
    "chars": 2420,
    "preview": "[package]\nname = \"weylus\"\nversion = \"0.11.4\"\nauthors = [\"HMH <henry@freedesk.net>\"]\nlicense = \"AGPL-3.0-or-later\"\neditio"
  },
  {
    "path": "LICENSE",
    "chars": 34738,
    "preview": "Weylus is licensed under the GNU Affero General Public License, version\n3 or later. Contributed work is licensed under t"
  },
  {
    "path": "Readme.md",
    "chars": 20008,
    "preview": "# Weylus\n![Build](https://github.com/H-M-H/Weylus/workflows/Build/badge.svg)\n\nWeylus turns your tablet or smart phone in"
  },
  {
    "path": "build.rs",
    "chars": 6707,
    "preview": "use std::env;\nuse std::path::Path;\nuse std::process::Command;\n\nfn build_ffmpeg(dist_dir: &Path, enable_libnpp: bool) {\n "
  },
  {
    "path": "build_in_local_container.sh",
    "chars": 696,
    "preview": "#!/usr/bin/env sh\n\nset -ex\n\nrm -f docker/archive.tar.gz\ngit ls-files | tar Tczf - docker/archive.tar.gz\n\npodman run --re"
  },
  {
    "path": "compile_flags.txt",
    "chars": 84,
    "preview": "-lX11\n-lXext\n-Wall\n-Wextra\n-Ideps/dist/include\n-DHAS_NVENC\n-DHAS_LIBNPP\n-DHAS_VAAPI\n"
  },
  {
    "path": "deps/awk.patch",
    "chars": 890,
    "preview": "diff --git a/configure b/configure\nindex 8569a60bf8..928b19db69 100755\n--- a/configure\n+++ b/configure\n@@ -4690,7 +4690,"
  },
  {
    "path": "deps/build.sh",
    "chars": 2310,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\nexport TARGET_OS=\"$CARGO_CFG_TARGET_OS\"\n\nif [ \"$OSTYPE\" == \"linux-gnu\" ]; then\n    export "
  },
  {
    "path": "deps/clean.sh",
    "chars": 167,
    "preview": "#!/usr/bin/env bash\n\nset -x\nfor d in ffmpeg x264 nv-codec-headers libva; do\n    test -d \"$d\" || continue\n    (cd \"$d\" &&"
  },
  {
    "path": "deps/clean_all.sh",
    "chars": 77,
    "preview": "#!/usr/bin/env bash\n\nset -ex\nrm -rf ffmpeg x264 nv-codec-headers libva dist*\n"
  },
  {
    "path": "deps/command_limit.patch",
    "chars": 439,
    "preview": "diff --git a/ffbuild/library.mak b/ffbuild/library.mak\nindex ad09f20..e63196d 100644\n--- a/ffbuild/library.mak\n+++ b/ffb"
  },
  {
    "path": "deps/download.sh",
    "chars": 881,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nsource \"$SCRIPT_DIR/refs.sh\"\nso"
  },
  {
    "path": "deps/ffmpeg.sh",
    "chars": 416,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\ncd ffmpeg\nPKG_CONFIG_PATH=\"$DIST/lib/pkgconfig\" ./configure \\\n\t--prefix=\"$DIST\" \\\n\t--disab"
  },
  {
    "path": "deps/hashes.sh",
    "chars": 412,
    "preview": "#!/usr/bin/env bash\n\n# Generated by update_hashes.sh — do not edit manually.\n# Run ./update_hashes.sh to regenerate.\n\nX2"
  },
  {
    "path": "deps/libva.sh",
    "chars": 553,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\ncd libva\n\n# required to make ffmpeg's configure work\nsed -i -e \"s/-lva$/-lva -ldrm -ldl/\" "
  },
  {
    "path": "deps/nv-codec-headers.sh",
    "chars": 98,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\ncd nv-codec-headers\nmake PREFIX=\"$DIST\"\nmake install PREFIX=\"$DIST\"\n"
  },
  {
    "path": "deps/refs.sh",
    "chars": 470,
    "preview": "#!/usr/bin/env bash\n\n# Dependency URLs and the branch/tag/ref to pin.\n# After editing, run ./update_hashes.sh to regener"
  },
  {
    "path": "deps/update_hashes.sh",
    "chars": 873,
    "preview": "#!/usr/bin/env bash\n\n# Resolves each ref in refs.sh to a commit hash and writes hashes.sh.\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$("
  },
  {
    "path": "deps/x264.sh",
    "chars": 234,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\ncd x264\n./configure \\\n\t--prefix=\"$DIST\" \\\n\t--exec-prefix=\"$DIST\" \\\n\t--enable-static \\\n\t--e"
  },
  {
    "path": "docker/Dockerfile",
    "chars": 1552,
    "preview": "FROM debian:bullseye\nENV RUSTUP_HOME=\"/usr/local/rustup\" CARGO_HOME=\"/usr/local/cargo\" PATH=\"/usr/local/cargo/bin:$PATH\""
  },
  {
    "path": "docker/Dockerfile_alpine",
    "chars": 842,
    "preview": "FROM alpine:latest\nENV RUSTUP_HOME=\"/usr/local/rustup\" CARGO_HOME=\"/usr/local/cargo\" PATH=\"/usr/local/cargo/bin:$PATH\"\n\n"
  },
  {
    "path": "docker_build.sh",
    "chars": 810,
    "preview": "#!/usr/bin/env sh\n\nset -ex\n\n# cross compile windows version\ncargo build --target x86_64-pc-windows-gnu --release\n\n# clea"
  },
  {
    "path": "lib/encode_video.c",
    "chars": 21722,
    "preview": "#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <libavcodec/avcodec.h>\n#include <libavfilter/buffe"
  },
  {
    "path": "lib/error.c",
    "chars": 224,
    "preview": "#include \"error.h\"\n\nvoid fill_error(Error* err, int code, const char* fmt, ...)\n{\n\tif (!err)\n\t\treturn;\n\terr->code = code"
  },
  {
    "path": "lib/error.h",
    "chars": 1078,
    "preview": "#pragma once\n\n#include <stdarg.h>\n#include <stdio.h>\n\nstruct Error\n{\n\tint code;\n\tchar error_str[1024];\n};\n\ntypedef struc"
  },
  {
    "path": "lib/linux/uinput.c",
    "chars": 9364,
    "preview": "#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <linux/input-event-codes.h>\n#include <linux/input.h>\n"
  },
  {
    "path": "lib/linux/uinput_info.md",
    "chars": 463,
    "preview": "# Some References on how to develop things using uinput\n\n- uinput: https://www.kernel.org/doc/html/latest/input/uinput.h"
  },
  {
    "path": "lib/linux/xcapture.c",
    "chars": 7723,
    "preview": "#include <X11/X.h>\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n\n#include <X11/extensions/XShm.h>\n#include <X11/extensio"
  },
  {
    "path": "lib/linux/xhelper.c",
    "chars": 12379,
    "preview": "#include <X11/X.h>\n#include <X11/Xlib.h>\n#include <X11/extensions/XInput.h>\n#include <X11/extensions/XInput2.h>\n#include"
  },
  {
    "path": "lib/linux/xhelper.h",
    "chars": 961,
    "preview": "#pragma once\n\n#include <X11/X.h>\n#include <X11/Xatom.h>\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n\n#include <iconv.h>"
  },
  {
    "path": "lib/log.c",
    "chars": 1012,
    "preview": "#include \"log.h\"\n\n// rust functions living in log.rs\nvoid log_error_rust(const char*);\nvoid log_debug_rust(const char*);"
  },
  {
    "path": "lib/log.h",
    "chars": 722,
    "preview": "#pragma once\n\n#include <stdarg.h>\n#include <stdio.h>\n\n#if defined(__clang__) || defined(__GNUC__)\n__attribute__((__forma"
  },
  {
    "path": "src/capturable/captrs_capture.rs",
    "chars": 2317,
    "preview": "use crate::capturable::{Capturable, Recorder};\nuse captrs::Capturer;\nuse std::boxed::Box;\nuse std::error::Error;\nuse win"
  },
  {
    "path": "src/capturable/core_graphics.rs",
    "chars": 9864,
    "preview": "use std::boxed::Box;\nuse std::error::Error;\nuse std::ffi::c_void;\nuse std::time::{Duration, Instant};\n\nuse core_foundati"
  },
  {
    "path": "src/capturable/mod.rs",
    "chars": 5201,
    "preview": "use std::boxed::Box;\nuse std::error::Error;\nuse tracing::warn;\n\n#[cfg(target_os = \"macos\")]\npub mod core_graphics;\n#[cfg"
  },
  {
    "path": "src/capturable/pipewire.rs",
    "chars": 20367,
    "preview": "use std::collections::HashMap;\nuse std::error::Error;\nuse std::os::unix::io::AsRawFd;\nuse std::sync::{Arc, Mutex};\nuse s"
  },
  {
    "path": "src/capturable/remote_desktop_dbus.rs",
    "chars": 13344,
    "preview": "// This code was autogenerated with `dbus-codegen-rust -c blocking -m None`, see https://github.com/diwic/dbus-rs\n// XML"
  },
  {
    "path": "src/capturable/testsrc.rs",
    "chars": 3957,
    "preview": "use crate::capturable::{Capturable, Geometry, Recorder};\nuse crate::video::PixelProvider;\nuse std::error::Error;\n\n#[deri"
  },
  {
    "path": "src/capturable/win_ctx.rs",
    "chars": 2723,
    "preview": "use std::mem::zeroed;\nuse std::{mem, ptr};\nuse winapi::shared::dxgi::{\n    CreateDXGIFactory1, IDXGIAdapter1, IDXGIFacto"
  },
  {
    "path": "src/capturable/x11.rs",
    "chars": 9042,
    "preview": "use crate::capturable::{Capturable, Geometry, Recorder};\nuse crate::cerror::CError;\nuse crate::video::PixelProvider;\nuse"
  },
  {
    "path": "src/cerror.rs",
    "chars": 1149,
    "preview": "use std::error::Error;\nuse std::ffi::CStr;\nuse std::fmt;\n\nuse std::os::raw::{c_char, c_int};\n\n#[repr(C)]\npub struct CErr"
  },
  {
    "path": "src/config.rs",
    "chars": 6438,
    "preview": "use std::net::IpAddr;\nuse std::{fs, path::PathBuf};\n\nuse clap::Parser;\nuse serde::{Deserialize, Serialize};\nuse tracing:"
  },
  {
    "path": "src/gui.rs",
    "chars": 30198,
    "preview": "use std::cmp::min;\nuse std::io::Cursor;\nuse std::iter::Iterator;\nuse std::net::{IpAddr, SocketAddr};\nuse std::sync::atom"
  },
  {
    "path": "src/input/autopilot_device.rs",
    "chars": 6175,
    "preview": "use autopilot::geometry::Size;\nuse autopilot::mouse;\nuse autopilot::mouse::ScrollDirection;\nuse autopilot::screen::size "
  },
  {
    "path": "src/input/autopilot_device_win.rs",
    "chars": 9851,
    "preview": "use winapi::shared::minwindef::DWORD;\nuse winapi::shared::windef::{HWND, POINT};\nuse winapi::um::winuser::*;\n\nuse tracin"
  },
  {
    "path": "src/input/device.rs",
    "chars": 563,
    "preview": "use crate::capturable::Capturable;\nuse crate::protocol::{KeyboardEvent, PointerEvent, WheelEvent};\n\n#[derive(PartialEq, "
  },
  {
    "path": "src/input/mod.rs",
    "chars": 223,
    "preview": "pub mod autopilot_device;\npub mod device;\n\n#[cfg(target_os = \"windows\")]\npub mod autopilot_device_win;\n#[cfg(target_os ="
  },
  {
    "path": "src/input/uinput_device.rs",
    "chars": 40474,
    "preview": "use std::cmp::Ordering;\nuse std::ffi::CString;\nuse std::os::raw::{c_char, c_int};\nuse std::time::{Duration, Instant};\n\nu"
  },
  {
    "path": "src/input/uinput_keys.rs",
    "chars": 9672,
    "preview": "use std::os::raw::c_int;\n\npub const KEY_ESC: c_int = 1;\npub const KEY_1: c_int = 2;\npub const KEY_2: c_int = 3;\npub cons"
  },
  {
    "path": "src/log.rs",
    "chars": 3336,
    "preview": "use std::ffi::CStr;\nuse std::io::Write;\nuse std::os::raw::c_char;\nuse std::sync::mpsc;\nuse tracing::{debug, error, info,"
  },
  {
    "path": "src/main.rs",
    "chars": 7993,
    "preview": "#![cfg_attr(feature = \"bench\", feature(test))]\n#[cfg(feature = \"bench\")]\nextern crate test;\n\n#[macro_use]\nextern crate b"
  },
  {
    "path": "src/protocol.rs",
    "chars": 4856,
    "preview": "use serde::{Deserialize, Deserializer, Serialize};\n\n#[derive(Serialize, Deserialize, Debug)]\npub struct ClientConfigurat"
  },
  {
    "path": "src/strings/uinput_error.txt",
    "chars": 906,
    "preview": "Weylus uses the uinput interface to simulate input events on Linux. To enable stylus and multi-touch support /dev/uinput"
  },
  {
    "path": "src/video.rs",
    "chars": 5145,
    "preview": "use std::os::raw::{c_int, c_uchar, c_void};\nuse std::time::Instant;\n\nuse tracing::warn;\n\nuse crate::cerror::CError;\n\next"
  },
  {
    "path": "src/web.rs",
    "chars": 12928,
    "preview": "use bytes::Bytes;\nuse fastwebsockets::upgrade;\nuse handlebars::Handlebars;\nuse http_body_util::combinators::BoxBody;\nuse"
  },
  {
    "path": "src/websocket.rs",
    "chars": 21562,
    "preview": "use fastwebsockets::{FragmentCollectorRead, Frame, OpCode, WebSocket, WebSocketError};\nuse hyper::upgrade::Upgraded;\nuse"
  },
  {
    "path": "src/weylus.rs",
    "chars": 3858,
    "preview": "use std::net::SocketAddr;\nuse std::sync::Arc;\nuse tracing::error;\n\nuse crate::config::Config;\nuse crate::video::EncoderO"
  },
  {
    "path": "ts/lib.ts",
    "chars": 42513,
    "preview": "interface Window {\n    ManagedMediaSource: any;\n}\n\nenum LogLevel {\n    ERROR = 0,\n    WARN,\n    INFO,\n    DEBUG,\n    TRA"
  },
  {
    "path": "tsconfig.json",
    "chars": 206,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\", \"es6\"],\n    \"module\": \"commonjs\",\n    \"out"
  },
  {
    "path": "weylus.desktop",
    "chars": 251,
    "preview": "[Desktop Entry]\nType=Application\nName=Weylus\nComment=Use your tablet as graphic tablet/touch screen on your computer.\nTr"
  },
  {
    "path": "weylus_tls.sh",
    "chars": 1691,
    "preview": "#!/usr/bin/env sh\n\nfunction die {\n    # cleanup to ensure restarting this script doesn't fail because\n    # of ports tha"
  },
  {
    "path": "www/static/access_code.html",
    "chars": 553,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n\t\t<meta name=\"apple-mobile-w"
  },
  {
    "path": "www/static/style.css",
    "chars": 4079,
    "preview": "@media (prefers-color-scheme: dark) {\n    :root {\n        --color: #ddd;\n        --background-color-1: #303030;\n        "
  },
  {
    "path": "www/templates/index.html",
    "chars": 4512,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1"
  }
]

About this extraction

This page contains the full source code of the H-M-H/Weylus GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (414.1 KB), approximately 106.5k tokens, and a symbol index with 760 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!