Merge branch 'master' of https://github.com/loki-47-6F-64/sunshine into loki-47-6F-64-master

This commit is contained in:
Elia Zammuto 2021-05-29 22:06:28 +02:00
commit 5761b05f3b
55 changed files with 4731 additions and 2456 deletions

67
.clang-format Normal file
View File

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: true
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 2
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: Never
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 2
Cpp11BracedListStyle: false
UseTab: Never

1
.gitignore vendored
View File

@ -6,4 +6,5 @@ cmake-build*
*.swp
*.kdev4
.cache
.idea

View File

@ -5,6 +5,14 @@ project(Sunshine)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(Simple-Web-Server)
if(WIN32)
# Ugly hack to compile with #include <qos2.h>
add_compile_definitions(
QOS_FLOWID=UINT32
PQOS_FLOWID=UINT32*
QOS_NON_ADAPTIVE_FLOW=2)
endif()
add_subdirectory(moonlight-common-c/enet)
find_package(Threads REQUIRED)
@ -71,6 +79,7 @@ if(WIN32)
libstdc++.a
libwinpthread.a
libssp.a
Qwave
winmm
ksuser
wsock32
@ -90,6 +99,7 @@ else()
find_package(FFmpeg REQUIRED)
set(PLATFORM_TARGET_FILES
sunshine/platform/linux/display.cpp
sunshine/platform/linux/audio.cpp
sunshine/platform/linux/input.cpp)
set(PLATFORM_LIBRARIES

View File

@ -1,6 +1,9 @@
# Introduction
Sunshine is a Gamestream host for Moonlight
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/cgrtw2g3fq9b0b70/branch/master?svg=true)](https://ci.appveyor.com/project/loki-47-6F-64/sunshine/branch/master)
[![Downloads](https://img.shields.io/github/downloads/Loki-47-6F-64/sunshine/total)](https://github.com/Loki-47-6F-64/sunshine/releases)
- [Building](README.md#building)
- [Credits](README.md#credits)
@ -12,39 +15,51 @@ Sunshine is a Gamestream host for Moonlight
### Requirements:
Ubuntu 20.04:
sudo apt install cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
Install the following
```
sudo apt install cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
```
### Compilation:
- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules`
- `cd sunshine && mkdir build && cd build`
- `cmake ..`
- `make`: It is suggested to use the `-j C#` flags with this command, `C#` being the number of cores your PC has
- `make -j ${nproc}`
### Setup:
sunshine needs access to uinput to create mouse and gamepad events:
- Add user to group 'input': "usermod -a -G input username
- Create a file: "/etc/udev/rules.d/85-sunshine-input.rules"
- The contents of the file is as follows:
KERNEL=="uinput", GROUP="input", mode="0660"
- assets/sunshine.conf is an example configuration file. Modify it as you see fit and use it by running: "sunshine path/to/sunshine.conf"
- path/to/build/dir/sunshine.service is used to start sunshine in the background:
- `cp sunshine.service $HOME/.config/systemd/user/`
- Modify $HOME/.config/systemd/user/sunshine.conf to point to the sunshine executable
- `systemctl --user start sunshine`
- Add user to group 'input':
`usermod -a -G input $USER`
- Create udev rules:
- Run the following command:
`nano /etc/udev/rules.d/85-sunshine-input.rules`
- Input the following contents:
`KERNEL=="uinput", GROUP="input", mode="0660"`
- Save the file and exit
1. `CTRL+X` to start exit
2. `Y` to save modifications
- `assets/sunshine.conf` is an example configuration file. Modify it as you see fit, then use it by running:
`sunshine path/to/sunshine.conf`
- Configure autostart service
`path/to/build/dir/sunshine.service` is used to start sunshine in the background. To use it, do the following:
1. Copy it to the users systemd, `cp sunshine.service ~/.config/systemd/user/`
2. Starting
- Onetime:
`systemctl --user start sunshine`
- Always on boot:
`systemctl --user enable sunshine`
- assets/apps.json is an [example](README.md#application-list) of a list of applications that are started just before running a stream
- `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream
### Trouleshooting:
* If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input":
* groups
* If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
* pacmd list-sources | grep "name:"
* Copy the name to the configuration option "audio_sink"
* restart sunshine
- If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input":
- `groups $USER`
- If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
1. pacmd list-sources | grep "name:"
2. Copy the name to the configuration option "audio_sink"
3. restart sunshine
## Windows 10
@ -90,6 +105,7 @@ sunshine needs access to uinput to create mouse and gamepad events:
- [Simple-Web-Server](https://gitlab.com/eidheim/Simple-Web-Server)
- [Moonlight](https://github.com/moonlight-stream)
- [Looking-Glass](https://github.com/gnif/LookingGlass) (For showing me how to properly capture frames on Windows, saving me a lot of time :)
- [Eretik](http://eretik.omegahg.com/) (For creating PolicyConfig.h, allowing me to change the default audio device on Windows programmatically)
## Application List:
- You can use Environment variables in place of values
@ -105,15 +121,20 @@ sunshine needs access to uinput to create mouse and gamepad events:
"cmd":"command to open app",
"prep-cmd":[
{
"do":"somecommand",
"undo":"undothatcommand"
"do":"some-command",
"undo":"undo-that-command"
}
],
"detached":[
"some-command",
"another-command"
]
}
```
- name: Self explanatory
- output <optional>: The file where the output of the command is stored
- If it is not specified, the output is ignored
- detached: A list of commands to be run and forgotten about
- prep-cmd: A list of commands to be run before/after the application
- If any of the prep-commands fail, starting the application is aborted
- do: Run before the application

View File

@ -9,7 +9,7 @@ environment:
install:
- sh: sudo apt update --ignore-missing
- sh: sudo apt install -y build-essential cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
- sh: sudo apt install -y build-essential fakeroot gcc-10 g++-10 cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
before_build:
@ -20,7 +20,7 @@ before_build:
build_script:
- cmd: set OLDPATH=%PATH%
- cmd: set PATH=C:\msys64\mingw64\bin
- sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
- sh: cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" ..
- sh: make -j$(nproc)
- cmd: mingw32-make -j2

View File

@ -13,7 +13,7 @@
"name":"Steam BigPicture",
"output":"steam.txt",
"cmd":"steam -bigpicture"
"detached":["setsid steam steam://open/bigpicture"]
}
]
}

View File

@ -7,9 +7,7 @@
"name":"Steam BigPicture",
"output":"steam.txt",
"prep-cmd":[
{"do":"steam \"steam://open/bigpicture\""}
]
"detached":["steam steam://open/bigpicture"]
}
]
}

View File

@ -37,6 +37,26 @@
# The file where current state of Sunshine is stored
# file_state = sunshine_state.json
# The display modes advertised by Sunshine
#
# Some versions of Moonlight, such as Moonlight-nx (Switch),
# rely on this list to ensure that the requested resolutions and fps
# are supported.
#
# fps = [10, 30, 60, 90, 120]
# resolutions = [
# 352x240,
# 480x360,
# 858x480,
# 1280x720,
# 1920x1080,
# 2560x1080,
# 3440x1440,
# 1920x1200,
# 3860x2160,
# 3840x1600,
# ]
# How long to wait in milliseconds for data from moonlight before shutting down the stream
# ping_timeout = 2000
@ -79,12 +99,16 @@
#
# You can find the name of the audio sink using the following command:
# !! Linux only !!
# pacmd list-sources | grep "name:"
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor
# pacmd list-sinks | grep "name:"
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo
#
# !! Windows only !!
# tools\audio-info.exe
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
#
# The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows Sunshine
# to stream audio, while muting the speakers.
# virtual_sink = {0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}
# !! Windows only !!
# You can select the video card you want to stream:
@ -94,8 +118,8 @@
# output_name = \\.\DISPLAY1
# !! Linux only !!
# Set the display number to stream. I have no idea how they are numbered. They start from 1, usually.
# output_name = 1
# Set the display number to stream. I have no idea how they are numbered. They start from 0, usually.
# output_name = 0
###############################################
# FFmpeg software encoding parameters
@ -206,4 +230,4 @@
# To set the initial state of flags -0 and -1 to on, set the following flags:
# flags = 01
#
# See: sunshine --help for all options under the header: flags
# See: sunshine --help for all options under the header: flags

View File

@ -35,7 +35,7 @@ Package: sunshine
Architecture: amd64
Maintainer: @loki
Priority: optional
Version: 0.2.1
Version: 0.4.1
Depends: libssl1.1, libavdevice58, libboost-thread1.71.0, libboost-filesystem1.71.0, libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0
Description: Gamestream host for Moonlight
EOF

@ -1 +1 @@
Subproject commit cfeb0ffd90992ee0fa4b6b4a179652e3e2786873
Subproject commit 5d09d43b0866d9fa30cc1741f9e10de5420deaad

View File

@ -2,12 +2,7 @@
Description=Sunshine Gamestream Server for Moonlight
[Service]
WorkingDirectory=/home/%u
Environment="DISPLAY=:0"
Type=simple
# wait for Xorg
ExecStartPre=/bin/sh -c 'while ! pgrep Xorg; do sleep 2; done'
ExecStart=@SUNSHINE_EXECUTABLE_PATH@
[Install]
WantedBy=default.target
WantedBy=graphical-session.target

View File

@ -4,55 +4,78 @@
#include "platform/common.h"
#include "utility.h"
#include "thread_safe.h"
#include "audio.h"
#include "config.h"
#include "main.h"
#include "thread_safe.h"
#include "utility.h"
namespace audio {
using namespace std::literals;
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
struct audio_ctx_t {
// We want to change the sink for the first stream only
std::unique_ptr<std::atomic_bool> sink_flag;
std::unique_ptr<platf::audio_control_t> control;
bool restore_sink;
platf::sink_t sink;
};
constexpr std::uint8_t map_stereo[] { 0, 1 };
constexpr std::uint8_t map_surround51[] {0, 4, 1, 5, 2, 3};
constexpr std::uint8_t map_high_surround51[] {0, 1, 2, 3, 4, 5};
static int start_audio_control(audio_ctx_t &ctx);
static void stop_audio_control(audio_ctx_t &);
int map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000;
static opus_stream_config_t stereo = {
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{
SAMPLE_RATE,
2,
1,
1,
map_stereo
};
static opus_stream_config_t Surround51 = {
platf::speaker::map_stereo,
},
{
SAMPLE_RATE,
6,
4,
2,
map_surround51
};
static opus_stream_config_t HighSurround51 = {
platf::speaker::map_surround51,
},
{
SAMPLE_RATE,
6,
6,
0,
map_high_surround51
platf::speaker::map_surround51,
},
{
SAMPLE_RATE,
8,
5,
3,
platf::speaker::map_surround71,
},
{
SAMPLE_RATE,
8,
8,
0,
platf::speaker::map_surround71,
},
};
auto control_shared = safe::make_shared<audio_ctx_t>(start_audio_control, stop_audio_control);
void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t config, void *channel_data) {
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stereo;
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
opus_t opus { opus_multistream_encoder_create(
stream->sampleRate,
stream->channelCount,
@ -60,12 +83,11 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
stream->coupledStreams,
stream->mapping,
OPUS_APPLICATION_AUDIO,
nullptr)
};
nullptr) };
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
while(auto sample = samples->pop()) {
packet_t packet { 16*1024 }; // 16KB
packet_t packet { 16 * 1024 }; // 16KB
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
if(bytes < 0) {
@ -81,6 +103,48 @@ void encodeThread(packet_queue_t packets, sample_queue_t samples, config_t confi
}
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data) {
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
auto ref = control_shared.ref();
if(!ref) {
return;
}
auto &control = ref->control;
if(!control) {
BOOST_LOG(error) << "Couldn't create audio control"sv;
return;
}
std::string *sink =
config::audio.sink.empty() ? &ref->sink.host : &config::audio.sink;
if(ref->sink.null) {
auto &null = *ref->sink.null;
switch(stream->channelCount) {
case 2:
sink = &null.stereo;
break;
case 6:
sink = &null.surround51;
break;
case 8:
sink = &null.surround71;
break;
}
}
// Only the first to start a session may change the default sink
if(!ref->sink_flag->exchange(true, std::memory_order_acquire)) {
ref->restore_sink = !config.flags[config_t::HOST_AUDIO];
// If the client requests audio on the host, don't change the default sink
if(!config.flags[config_t::HOST_AUDIO] && control->set_sink(*sink)) {
return;
}
}
auto samples = std::make_shared<sample_queue_t::element_type>(30);
std::thread thread { encodeThread, packets, samples, config, channel_data };
@ -91,43 +155,84 @@ void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t co
shutdown_event->view();
});
//FIXME: Pick correct opus_stream_config_t based on config.channels
auto stream = &stereo;
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
auto mic = platf::microphone(stream->sampleRate);
auto mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
BOOST_LOG(error) << "Couldn't create audio input"sv ;
BOOST_LOG(error) << "Couldn't create audio input"sv;
return;
}
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
int samples_per_frame = frame_size * stream->channelCount;
while(!shutdown_event->peek()) {
std::vector<std::int16_t> sample_buffer;
sample_buffer.resize(samples_per_frame);
auto status = mic->sample(sample_buffer);
switch(status) {
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
mic = platf::microphone(stream->sampleRate);
if(!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv ;
case platf::capture_e::ok:
break;
case platf::capture_e::timeout:
continue;
case platf::capture_e::reinit:
mic.reset();
mic = control->microphone(stream->mapping, stream->channelCount, stream->sampleRate, frame_size);
if(!mic) {
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv;
return;
}
return;
default:
return;
}
return;
default:
return;
}
samples->raise(std::move(sample_buffer));
}
}
int map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch(channels) {
case 2:
return STEREO;
case 6:
return SURROUND51 + shift;
case 8:
return SURROUND71 + shift;
}
return STEREO;
}
int start_audio_control(audio_ctx_t &ctx) {
ctx.sink_flag = std::make_unique<std::atomic_bool>(false);
if(!(ctx.control = platf::audio_control())) {
return -1;
}
auto sink = ctx.control->sink_info();
if(!sink) {
return -1;
}
// The default sink has not been replaced yet.
ctx.restore_sink = false;
ctx.sink = std::move(*sink);
return 0;
}
void stop_audio_control(audio_ctx_t &ctx) {
// restore audio-sink if applicable
if(!ctx.restore_sink) {
return;
}
const std::string &sink = config::audio.sink.empty() ? ctx.sink.host : config::audio.sink;
if(!sink.empty()) {
// Best effort, it's allowed to fail
ctx.control->set_sink(sink);
}
}
} // namespace audio

View File

@ -1,18 +1,45 @@
#ifndef SUNSHINE_AUDIO_H
#define SUNSHINE_AUDIO_H
#include "utility.h"
#include "thread_safe.h"
#include "utility.h"
namespace audio {
enum stream_config_e : int {
STEREO,
SURROUND51,
HIGH_SURROUND51,
SURROUND71,
HIGH_SURROUND71,
MAX_STREAM_CONFIG
};
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
};
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
struct config_t {
enum flags_e : int {
HIGH_QUALITY,
HOST_AUDIO,
MAX_FLAGS
};
int packetDuration;
int channels;
int mask;
std::bitset<MAX_FLAGS> flags;
};
using packet_t = util::buffer_t<std::uint8_t>;
using packet_queue_t = std::shared_ptr<safe::queue_t<std::pair<void*, packet_t>>>;
using packet_t = util::buffer_t<std::uint8_t>;
using packet_queue_t = std::shared_ptr<safe::queue_t<std::pair<void *, packet_t>>>;
void capture(safe::signal_t *shutdown_event, packet_queue_t packets, config_t config, void *channel_data);
}
} // namespace audio
#endif

View File

@ -1,11 +1,12 @@
#include <algorithm>
#include <fstream>
#include <iostream>
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include "utility.h"
#include "config.h"
#include "utility.h"
#define CA_DIR "credentials"
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
@ -32,12 +33,12 @@ enum preset_e : int {
};
enum rc_e : int {
constqp = 0x0, /**< Constant QP mode */
vbr = 0x1, /**< Variable bitrate mode */
cbr = 0x2, /**< Constant bitrate mode */
cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */
cbr_hq = 0x10, /**< CBR, high quality (slower) */
vbr_hq = 0x20 /**< VBR, high quality (slower) */
constqp = 0x0, /**< Constant QP mode */
vbr = 0x1, /**< Variable bitrate mode */
cbr = 0x2, /**< Constant bitrate mode */
cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */
cbr_hq = 0x10, /**< CBR, high quality (slower) */
vbr_hq = 0x20 /**< VBR, high quality (slower) */
};
enum coder_e : int {
@ -47,7 +48,8 @@ enum coder_e : int {
};
std::optional<preset_e> preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) if(preset == #x##sv) return x
#define _CONVERT_(x) \
if(preset == #x##sv) return x
_CONVERT_(slow);
_CONVERT_(medium);
_CONVERT_(fast);
@ -64,7 +66,8 @@ std::optional<preset_e> preset_from_view(const std::string_view &preset) {
}
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) if(rc == #x##sv) return x
#define _CONVERT_(x) \
if(rc == #x##sv) return x
_CONVERT_(constqp);
_CONVERT_(vbr);
_CONVERT_(cbr);
@ -77,12 +80,12 @@ std::optional<rc_e> rc_from_view(const std::string_view &rc) {
int coder_from_view(const std::string_view &coder) {
if(coder == "auto"sv) return _auto;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
return -1;
}
}
} // namespace nv
namespace amd {
enum quality_e : int {
@ -106,7 +109,8 @@ enum coder_e : int {
};
std::optional<quality_e> quality_from_view(const std::string_view &quality) {
#define _CONVERT_(x) if(quality == #x##sv) return x
#define _CONVERT_(x) \
if(quality == #x##sv) return x
_CONVERT_(speed);
_CONVERT_(balanced);
//_CONVERT_(quality2);
@ -116,7 +120,8 @@ std::optional<quality_e> quality_from_view(const std::string_view &quality) {
}
std::optional<rc_e> rc_from_view(const std::string_view &rc) {
#define _CONVERT_(x) if(rc == #x##sv) return x
#define _CONVERT_(x) \
if(rc == #x##sv) return x
_CONVERT_(constqp);
_CONVERT_(vbr_latency);
_CONVERT_(vbr_peak);
@ -127,40 +132,38 @@ std::optional<rc_e> rc_from_view(const std::string_view &rc) {
int coder_from_view(const std::string_view &coder) {
if(coder == "auto"sv) return _auto;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
if(coder == "cabac"sv || coder == "ac"sv) return cabac;
if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc;
return -1;
}
}
} // namespace amd
video_t video {
0, // crf
0, // crf
28, // qp
0, // hevc_mode
1, // min_threads
{
"superfast"s, // preset
"superfast"s, // preset
"zerolatency"s, // tune
}, // software
}, // software
{
nv::llhq,
std::nullopt,
-1
}, // nv
-1 }, // nv
{
amd::balanced,
std::nullopt,
-1
}, // amd
-1 }, // amd
{}, // encoder
{}, // adapter_name
{}, // output_name
{}, // output_name
};
audio_t audio {};
@ -171,7 +174,7 @@ stream_t stream {
APPS_JSON_PATH,
10, // fecPercentage
1 // channels
1 // channels
};
nvhttp_t nvhttp {
@ -180,42 +183,113 @@ nvhttp_t nvhttp {
CERTIFICATE_FILE,
boost::asio::ip::host_name(), // sunshine_name,
"sunshine_state.json"s // file_state
"sunshine_state.json"s, // file_state
{}, // external_ip
{
"352x240"s,
"480x360"s,
"858x480"s,
"1280x720"s,
"1920x1080"s,
"2560x1080"s,
"3440x1440"s
"1920x1200"s,
"3860x2160"s,
"3840x1600"s,
}, // supported resolutions
{ 10, 30, 60, 90, 120 }, // supported fps
};
input_t input {
2s, // back_button_timeout
500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 } // key_repeat_period
2s, // back_button_timeout
500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 } // key_repeat_period
};
sunshine_t sunshine {
2, // min_log_level
0 // flags
0 // flags
};
bool whitespace(char ch) {
bool endline(char ch) {
return ch == '\r' || ch == '\n';
}
bool space_tab(char ch) {
return ch == ' ' || ch == '\t';
}
std::string to_string(const char *begin, const char *end) {
return { begin, (std::size_t)(end - begin) };
bool whitespace(char ch) {
return space_tab(ch) || endline(ch);
}
std::optional<std::pair<std::string, std::string>> parse_line(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
begin = std::find_if(begin, end, std::not_fn(whitespace));
end = std::find(begin, end, '#');
end = std::find_if(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
std::string to_string(const char *begin, const char *end) {
std::string result;
auto eq = std::find(begin, end, '=');
if(eq == end || eq == begin) {
return std::nullopt;
KITTY_WHILE_LOOP(auto pos = begin, pos != end, {
auto comment = std::find(pos, end, '#');
auto endl = std::find_if(comment, end, endline);
result.append(pos, comment);
pos = endl;
})
return result;
}
template<class It>
It skip_list(It skipper, It end) {
int stack = 1;
while(skipper != end && stack) {
if(*skipper == '[') {
++stack;
}
if(*skipper == ']') {
--stack;
}
++skipper;
}
auto end_name = std::find_if(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
auto begin_val = std::find_if(eq + 1, end, std::not_fn(whitespace));
return skipper;
}
return std::pair { to_string(begin, end_name), to_string(begin_val, end) };
std::pair<
std::string_view::const_iterator,
std::optional<std::pair<std::string, std::string>>>
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
begin = std::find_if_not(begin, end, whitespace);
auto endl = std::find_if(begin, end, endline);
auto endc = std::find(begin, endl, '#');
endc = std::find_if(std::make_reverse_iterator(endc), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
auto eq = std::find(begin, endc, '=');
if(eq == endc || eq == begin) {
return std::make_pair(endl, std::nullopt);
}
auto end_name = std::find_if_not(std::make_reverse_iterator(eq), std::make_reverse_iterator(begin), space_tab).base();
auto begin_val = std::find_if_not(eq + 1, endc, space_tab);
if(begin_val == endl) {
return std::make_pair(endl, std::nullopt);
}
// Lists might contain newlines
if(*begin_val == '[') {
endl = skip_list(begin_val + 1, end);
if(endl == end) {
std::cout << "Warning: Config option ["sv << to_string(begin, end_name) << "] Missing ']'"sv;
return std::make_pair(endl, std::nullopt);
}
}
return std::make_pair(
endl,
std::make_pair(to_string(begin, end_name), to_string(begin_val, endl)));
}
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content) {
@ -225,10 +299,14 @@ std::unordered_map<std::string, std::string> parse_config(std::string_view file_
auto end = std::end(file_content);
while(pos < end) {
auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; });
auto var = parse_line(pos, newline);
// auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; });
TUPLE_2D(endl, var, parse_option(pos, end));
pos = endl;
if(pos != end) {
pos += (*pos == '\r') ? 2 : 1;
}
pos = (*newline == '\r') ? newline + 2 : newline + 1;
if(!var) {
continue;
}
@ -270,7 +348,7 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
}
auto &val = it->second;
input = util::from_chars(&val[0], &val[0] + val.size());
input = util::from_chars(&val[0], &val[0] + val.size());
vars.erase(it);
}
@ -283,7 +361,7 @@ void int_f(std::unordered_map<std::string, std::string> &vars, const std::string
}
auto &val = it->second;
input = util::from_chars(&val[0], &val[0] + val.size());
input = util::from_chars(&val[0], &val[0] + val.size());
vars.erase(it);
}
@ -318,15 +396,14 @@ void int_between_f(std::unordered_map<std::string, std::string> &vars, const std
}
bool to_bool(std::string &boolean) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); });
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); });
return
boolean == "true"sv ||
boolean == "yes"sv ||
boolean == "enable"sv ||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
return boolean == "true"sv ||
boolean == "yes"sv ||
boolean == "enable"sv ||
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
}
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
std::string tmp;
string_f(vars, name, tmp);
@ -337,7 +414,7 @@ void bool_f(std::unordered_map<std::string, std::string> &vars, const std::strin
input = to_bool(tmp) ? 1 : 0;
}
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp;
string_f(vars, name, tmp);
@ -366,33 +443,86 @@ void double_between_f(std::unordered_map<std::string, std::string> &vars, const
}
}
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
std::string string;
string_f(vars, name, string);
if(string.empty()) {
return;
}
input.clear();
auto begin = std::cbegin(string);
if(*begin == '[') {
++begin;
}
begin = std::find_if_not(begin, std::cend(string), whitespace);
if(begin == std::cend(string)) {
return;
}
auto pos = begin;
while(pos < std::cend(string)) {
if(*pos == '[') {
pos = skip_list(pos + 1, std::cend(string)) + 1;
}
else if(*pos == ']') {
break;
}
else if(*pos == ',') {
input.emplace_back(begin, pos);
pos = begin = std::find_if_not(pos + 1, std::cend(string), whitespace);
}
else {
++pos;
}
}
if(pos != begin) {
input.emplace_back(begin, pos);
}
}
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list;
list_string_f(vars, name, list);
for(auto &el : list) {
input.emplace_back(util::from_view(el));
}
}
void print_help(const char *name) {
std::cout <<
"Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl <<
" Any configurable option can be overwritten with: \"name=value\""sv << std::endl << std::endl <<
" --help | print help"sv << std::endl << std::endl <<
" flags"sv << std::endl <<
" -0 | Read PIN from stdin"sv << std::endl <<
" -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl <<
" | Effectively starting as if for the first time without overwriting any pairings with your devices"sv;
std::cout
<< "Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl
<< " Any configurable option can be overwritten with: \"name=value\""sv << std::endl
<< std::endl
<< " --help | print help"sv << std::endl
<< std::endl
<< " flags"sv << std::endl
<< " -0 | Read PIN from stdin"sv << std::endl
<< " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl
<< " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv;
}
int apply_flags(const char *line) {
int ret = 0;
while(*line != '\0') {
switch(*line) {
case '0':
config::sunshine.flags[config::flag::PIN_STDIN].flip();
break;
case '1':
config::sunshine.flags[config::flag::FRESH_STATE].flip();
break;
case 'p':
config::sunshine.flags[config::flag::CONST_PIN].flip();
break;
default:
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
ret = -1;
case '0':
config::sunshine.flags[config::flag::PIN_STDIN].flip();
break;
case '1':
config::sunshine.flags[config::flag::FRESH_STATE].flip();
break;
case 'p':
config::sunshine.flags[config::flag::CONST_PIN].flip();
break;
default:
std::cout << "Warning: Unrecognized flag: ["sv << *line << ']' << std::endl;
ret = -1;
}
++line;
@ -409,9 +539,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
int_f(vars, "crf", video.crf);
int_f(vars, "qp", video.qp);
int_f(vars, "min_threads", video.min_threads);
int_between_f(vars, "hevc_mode", video.hevc_mode, {
0, 3
});
int_between_f(vars, "hevc_mode", video.hevc_mode, { 0, 3 });
string_f(vars, "sw_preset", video.sw.preset);
string_f(vars, "sw_tune", video.sw.tune);
int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view);
@ -431,29 +559,24 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
string_f(vars, "sunshine_name", nvhttp.sunshine_name);
string_f(vars, "file_state", nvhttp.file_state);
string_f(vars, "external_ip", nvhttp.external_ip);
list_string_f(vars, "resolutions"s, nvhttp.resolutions);
list_int_f(vars, "fps"s, nvhttp.fps);
string_f(vars, "audio_sink", audio.sink);
string_f(vars, "virtual_sink", audio.virtual_sink);
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, {
"pc"sv, "lan"sv, "wan"sv
});
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, { "pc"sv, "lan"sv, "wan"sv });
int to = -1;
int_between_f(vars, "ping_timeout", to, {
-1, std::numeric_limits<int>::max()
});
int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits<int>::max() });
if(to != -1) {
stream.ping_timeout = std::chrono::milliseconds(to);
}
int_between_f(vars, "channels", stream.channels, {
1, std::numeric_limits<int>::max()
});
int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits<int>::max() });
string_f(vars, "file_apps", stream.file_apps);
int_between_f(vars, "fec_percentage", stream.fec_percentage, {
1, 100
});
int_between_f(vars, "fec_percentage", stream.fec_percentage, { 1, 100 });
to = std::numeric_limits<int>::min();
int_f(vars, "back_button_timeout", to);
@ -463,12 +586,10 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
}
double repeat_frequency { 0 };
double_between_f(vars, "key_repeat_frequency", repeat_frequency, {
0, std::numeric_limits<double>::max()
});
double_between_f(vars, "key_repeat_frequency", repeat_frequency, { 0, std::numeric_limits<double>::max() });
if(repeat_frequency > 0) {
config::input.key_repeat_period = std::chrono::duration<double> {1 / repeat_frequency };
config::input.key_repeat_period = std::chrono::duration<double> { 1 / repeat_frequency };
}
to = -1;
@ -478,9 +599,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
}
std::string log_level_string;
string_restricted_f(vars, "min_log_level", log_level_string, {
"verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv
});
string_restricted_f(vars, "min_log_level", log_level_string, { "verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv });
if(!log_level_string.empty()) {
if(log_level_string == "verbose"sv) {
@ -514,7 +633,7 @@ void apply_config(std::unordered_map<std::string, std::string> &&vars) {
}
if(sunshine.min_log_level <= 3) {
for(auto &[var,_] : vars) {
for(auto &[var, _] : vars) {
std::cout << "Warning: Unrecognized configurable option ["sv << var << ']' << std::endl;
}
}
@ -525,7 +644,7 @@ int parse(int argc, char *argv[]) {
std::unordered_map<std::string, std::string> cmd_vars;
for(auto x = argc -1; x > 0; --x) {
for(auto x = argc - 1; x > 0; --x) {
auto line = argv[x];
if(line == "--help"sv) {
@ -546,7 +665,7 @@ int parse(int argc, char *argv[]) {
config_file = line;
}
else {
auto var = parse_line(line, line_end);
TUPLE_EL(var, 1, parse_option(line, line_end));
if(!var) {
print_help(*argv);
return -1;
@ -568,10 +687,9 @@ int parse(int argc, char *argv[]) {
auto vars = parse_config(std::string {
// Quick and dirty
std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>()
});
std::istreambuf_iterator<char>() });
for(auto &[name,value] : cmd_vars) {
for(auto &[name, value] : cmd_vars) {
vars.insert_or_assign(std::move(name), std::move(value));
}
@ -579,4 +697,4 @@ int parse(int argc, char *argv[]) {
return 0;
}
}
} // namespace config

View File

@ -1,17 +1,18 @@
#ifndef SUNSHINE_CONFIG_H
#define SUNSHINE_CONFIG_H
#include <chrono>
#include <string>
#include <bitset>
#include <chrono>
#include <optional>
#include <unordered_map>
#include <string>
#include <vector>
namespace config {
struct video_t {
// ffmpeg params
int crf; // higher == more compression and less quality
int qp; // higher == more compression and less quality, ignored if crf != 0
int qp; // higher == more compression and less quality, ignored if crf != 0
int hevc_mode;
@ -40,6 +41,7 @@ struct video_t {
struct audio_t {
std::string sink;
std::string virtual_sink;
};
struct stream_t {
@ -66,6 +68,8 @@ struct nvhttp_t {
std::string file_state;
std::string external_ip;
std::vector<std::string> resolutions;
std::vector<int> fps;
};
struct input_t {
@ -77,9 +81,9 @@ struct input_t {
namespace flag {
enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
FLAG_SIZE,
CONST_PIN= 4 // Use "universal" pin
FRESH_STATE, // Do not load or save state
CONST_PIN = 4, // Use "universal" pin
FLAG_SIZE
};
}
@ -98,6 +102,4 @@ extern sunshine_t sunshine;
int parse(int argc, char *argv[]);
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content);
}
#endif

View File

@ -2,14 +2,14 @@
// Created by loki on 5/31/19.
//
#include <openssl/pem.h>
#include "crypto.h"
#include <openssl/pem.h>
namespace crypto {
using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
//using rsa_t = util::safe_ptr<RSA, RSA_free>;
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx {X509_STORE_CTX_new() } {}
cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx { X509_STORE_CTX_new() } {}
void cert_chain_t::add(x509_t &&cert) {
x509_store_t x509_store { X509_STORE_new() };
@ -26,7 +26,7 @@ void cert_chain_t::add(x509_t &&cert) {
*/
const char *cert_chain_t::verify(x509_t::element_type *cert) {
int err_code = 0;
for(auto &[_,x509_store] : _certs) {
for(auto &[_, x509_store] : _certs) {
auto fg = util::fail_guard([this]() {
X509_STORE_CTX_cleanup(_cert_ctx.get());
});
@ -36,7 +36,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
auto err = X509_verify_cert(_cert_ctx.get());
if (err == 1) {
if(err == 1) {
return nullptr;
}
@ -46,7 +46,7 @@ const char *cert_chain_t::verify(x509_t::element_type *cert) {
if(err_code == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
return nullptr;
}
if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
if(err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
return X509_verify_cert_error_string(err_code);
}
}
@ -63,7 +63,7 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t>
});
// Gen 7 servers use 128-bit AES ECB
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
@ -72,11 +72,11 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t>
plaintext.resize((cipher.size() + 15) / 16 * 16);
auto size = (int)plaintext.size();
// Encrypt into the caller's buffer, leaving room for the auth tag to be prepended
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
return -1;
}
@ -85,7 +85,7 @@ int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t>
}
int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
std::vector<std::uint8_t> &plaintext) {
std::vector<std::uint8_t> &plaintext) {
auto cipher = tagged_cipher.substr(16);
auto tag = tagged_cipher.substr(0, 16);
@ -93,15 +93,15 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
EVP_CIPHER_CTX_reset(ctx.get());
});
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
if(EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
return -1;
}
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) {
if(EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) {
return -1;
}
@ -109,16 +109,16 @@ int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
plaintext.resize((cipher.size() + 15) / 16 * 16);
int size;
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
if(EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t *)cipher.data(), cipher.size()) != 1) {
return -1;
}
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char*>(tag.data())) != 1) {
if(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.data())) != 1) {
return -1;
}
int len = size;
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
if(EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
return -1;
}
@ -134,7 +134,7 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_
});
// Gen 7 servers use 128-bit AES ECB
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
if(EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
return -1;
}
@ -143,11 +143,11 @@ int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_
cipher.resize((plaintext.size() + 15) / 16 * 16);
auto size = (int)cipher.size();
// Encrypt into the caller's buffer
if (EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t*)plaintext.data(), plaintext.size()) != 1) {
if(EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t *)plaintext.data(), plaintext.size()) != 1) {
return -1;
}
if (EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
if(EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
return -1;
}
@ -230,14 +230,14 @@ std::string_view signature(const x509_t &x) {
const ASN1_BIT_STRING *asn1 = nullptr;
X509_get0_signature(&asn1, nullptr, x.get());
return { (const char*)asn1->data, (std::size_t)asn1->length };
return { (const char *)asn1->data, (std::size_t)asn1->length };
}
std::string rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
RAND_bytes((uint8_t*)r.data(), r.size());
RAND_bytes((uint8_t *)r.data(), r.size());
return r;
}
@ -297,8 +297,8 @@ creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
X509_set_pubkey(x509.get(), pkey.get());
auto name = X509_get_subject_name(x509.get());
X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC,
(const std::uint8_t*)cn.data(), cn.size(),
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(const std::uint8_t *)cn.data(), cn.size(),
-1, 0);
X509_set_issuer_name(x509.get(), name);
@ -324,7 +324,7 @@ bool verify(const x509_t &x509, const std::string_view &data, const std::string_
return false;
}
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t*)signature.data(), signature.size()) != 1) {
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t *)signature.data(), signature.size()) != 1) {
return false;
}
@ -338,4 +338,4 @@ bool verify256(const x509_t &x509, const std::string_view &data, const std::stri
void md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx);
}
}
} // namespace crypto

View File

@ -5,12 +5,12 @@
#ifndef SUNSHINE_CRYPTO_H
#define SUNSHINE_CRYPTO_H
#include <cassert>
#include <array>
#include <cassert>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
#include <openssl/rand.h>
#include "utility.h"
@ -25,14 +25,14 @@ void md_ctx_destroy(EVP_MD_CTX *);
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
using aes_t = std::array<std::uint8_t, 16>;
using x509_t = util::safe_ptr<X509, X509_free>;
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
using aes_t = std::array<std::uint8_t, 16>;
using x509_t = util::safe_ptr<X509, X509_free>;
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
sha256_t hash(const std::string_view &plaintext);
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
@ -58,6 +58,7 @@ public:
void add(x509_t &&cert);
const char *verify(x509_t::element_type *cert);
private:
std::vector<std::pair<x509_t, x509_store_t>> _certs;
x509_store_ctx_t _cert_ctx;
@ -66,13 +67,14 @@ private:
class cipher_t {
public:
cipher_t(const aes_t &key);
cipher_t(cipher_t&&) noexcept = default;
cipher_t &operator=(cipher_t&&) noexcept = default;
cipher_t(cipher_t &&) noexcept = default;
cipher_t &operator=(cipher_t &&) noexcept = default;
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
int decrypt_gcm(aes_t &iv, const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
private:
cipher_ctx_t ctx;
aes_t key;
@ -80,6 +82,6 @@ private:
public:
bool padding;
};
}
} // namespace crypto
#endif //SUNSHINE_CRYPTO_H

View File

@ -2,21 +2,27 @@
// Created by loki on 6/20/19.
//
// define uint32_t for <moonlight-common-c/src/Input.h>
#include <cstdint>
extern "C" {
#include <moonlight-common-c/src/Input.h>
}
#include <bitset>
#include "main.h"
#include "config.h"
#include "utility.h"
#include "input.h"
#include "main.h"
#include "platform/common.h"
#include "thread_pool.h"
#include "utility.h"
namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t)*8);
constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
#define DISABLE_LEFT_BUTTON_DELAY ((util::ThreadPool::task_id_t)0x01)
#define ENABLE_LEFT_BUTTON_DELAY nullptr
enum class button_state_e {
NONE,
DOWN,
@ -40,6 +46,11 @@ void free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false;
}
touch_port_event_t touch_port_event;
platf::touch_port_t touch_port {
0, 0, 0, 0
};
static util::TaskPool::task_id_t task_id {};
static std::unordered_map<short, bool> key_press {};
static std::array<std::uint8_t, 5> mouse_press {};
@ -48,7 +59,7 @@ static platf::input_t platf_input;
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
void free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad(platf_input, id, platf::gamepad_state_t{});
platf::gamepad(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id);
free_id(gamepadMask, id);
@ -57,7 +68,7 @@ struct gamepad_t {
gamepad_t() : gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {}
~gamepad_t() {
if(id >= 0) {
task_pool.push([id=this->id]() {
task_pool.push([id = this->id]() {
free_gamepad(platf_input, id);
});
}
@ -78,20 +89,32 @@ struct gamepad_t {
};
struct input_t {
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS) { }
input_t() : active_gamepad_state {}, gamepads(MAX_GAMEPADS), mouse_left_button_timeout {} {}
std::uint16_t active_gamepad_state;
std::vector<gamepad_t> gamepads;
util::ThreadPool::task_id_t mouse_left_button_timeout;
};
using namespace std::literals;
void print(PNV_MOUSE_MOVE_PACKET packet) {
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse move packet--"sv << std::endl
<< "--begin relative mouse move packet--"sv << std::endl
<< "deltaX ["sv << util::endian::big(packet->deltaX) << ']' << std::endl
<< "deltaY ["sv << util::endian::big(packet->deltaY) << ']' << std::endl
<< "--end mouse move packet--"sv;
<< "--end relative mouse move packet--"sv;
}
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl
<< "x ["sv << util::endian::big(packet->x) << ']' << std::endl
<< "y ["sv << util::endian::big(packet->y) << ']' << std::endl
<< "width ["sv << util::endian::big(packet->width) << ']' << std::endl
<< "height ["sv << util::endian::big(packet->height) << ']' << std::endl
<< "--end absolute mouse move packet--"sv;
}
void print(PNV_MOUSE_BUTTON_PACKET packet) {
@ -135,50 +158,137 @@ void print(PNV_MULTI_CONTROLLER_PACKET packet) {
constexpr int PACKET_TYPE_SCROLL_OR_KEYBOARD = PACKET_TYPE_SCROLL;
void print(void *input) {
int input_type = util::endian::big(*(int*)input);
int input_type = util::endian::big(*(int *)input);
switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE:
print((PNV_MOUSE_MOVE_PACKET)input);
break;
case PACKET_TYPE_MOUSE_BUTTON:
print((PNV_MOUSE_BUTTON_PACKET)input);
break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
{
char *tmp_input = (char*)input + 4;
if(tmp_input[0] == 0x0A) {
print((PNV_SCROLL_PACKET)input);
}
else {
print((PNV_KEYBOARD_PACKET)input);
}
break;
case PACKET_TYPE_REL_MOUSE_MOVE:
print((PNV_REL_MOUSE_MOVE_PACKET)input);
break;
case PACKET_TYPE_ABS_MOUSE_MOVE:
print((PNV_ABS_MOUSE_MOVE_PACKET)input);
break;
case PACKET_TYPE_MOUSE_BUTTON:
print((PNV_MOUSE_BUTTON_PACKET)input);
break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
char *tmp_input = (char *)input + 4;
if(tmp_input[0] == 0x0A) {
print((PNV_SCROLL_PACKET)input);
}
case PACKET_TYPE_MULTI_CONTROLLER:
print((PNV_MULTI_CONTROLLER_PACKET)input);
break;
else {
print((PNV_KEYBOARD_PACKET)input);
}
break;
}
case PACKET_TYPE_MULTI_CONTROLLER:
print((PNV_MULTI_CONTROLLER_PACKET)input);
break;
}
}
void passthrough(platf::input_t &input, PNV_MOUSE_MOVE_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
platf::move_mouse(input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
}
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
if(input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
}
if(touch_port_event->peek()) {
touch_port = *touch_port_event->pop();
}
float x = util::endian::big(packet->x);
float y = util::endian::big(packet->y);
// Prevent divide by zero
// Don't expect it to happen, but just in case
if(!packet->width || !packet->height) {
BOOST_LOG(warning) << "Moonlight passed invalid dimensions"sv;
return;
}
float width = util::endian::big(packet->width);
float height = util::endian::big(packet->height);
auto scale_x = (float)touch_port.width / width;
auto scale_y = (float)touch_port.height / height;
platf::abs_mouse(platf_input, touch_port, x * scale_x, y * scale_y);
}
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
auto constexpr BUTTON_RELEASED = 0x09;
auto constexpr BUTTON_LEFT = 0x01;
auto constexpr BUTTON_RIGHT = 0x03;
display_cursor = true;
auto release = packet->action == BUTTON_RELEASED;
auto button = util::endian::big(packet->button);
if(button > 0 && button < mouse_press.size()) {
mouse_press[button] = packet->action != BUTTON_RELEASED;
}
if(mouse_press[button] != release) {
// button state is already what we want
return;
}
platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED);
mouse_press[button] = !release;
}
///////////////////////////////////
/*/
* When Moonlight sends mouse input through absolute coordinates,
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
* As a result, Sunshine will left click on hyperlinks in the browser before right clicking
*
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
* absolute mouse coordinates have been send.
*
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
*
* input->mouse_left_button_timeout can only be nullptr
* when the last mouse coordinates were absolute
/*/
if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) {
auto f = [=]() {
auto left_released = mouse_press[BUTTON_LEFT];
if(left_released) {
// Already released left button
return;
}
platf::button_mouse(platf_input, BUTTON_LEFT, release);
mouse_press[BUTTON_LEFT] = false;
input->mouse_left_button_timeout = nullptr;
};
input->mouse_left_button_timeout = task_pool.pushDelayed(std::move(f), 10ms).task_id;
return;
}
if(
button == BUTTON_RIGHT && !release &&
input->mouse_left_button_timeout > DISABLE_LEFT_BUTTON_DELAY) {
platf::button_mouse(platf_input, BUTTON_RIGHT, false);
platf::button_mouse(platf_input, BUTTON_RIGHT, true);
mouse_press[BUTTON_RIGHT] = false;
return;
}
///////////////////////////////////
platf::button_mouse(platf_input, button, release);
}
void repeat_key(short key_code) {
@ -223,15 +333,15 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
platf::keyboard(platf_input, packet->keyCode & 0x00FF, release);
}
void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) {
void passthrough(PNV_SCROLL_PACKET packet) {
display_cursor = true;
platf::scroll(input, util::endian::big(packet->scrollAmt1));
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
}
int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state) {
auto xorGamepadMask = old_state ^ new_state;
if (!xorGamepadMask) {
if(!xorGamepadMask) {
return 0;
}
@ -240,7 +350,7 @@ int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std
auto &gamepad = gamepads[x];
if((old_state >> x) & 1) {
if (gamepad.id < 0) {
if(gamepad.id < 0) {
return -1;
}
@ -300,7 +410,7 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
display_cursor = false;
std::uint16_t bf = packet->buttonFlags;
platf::gamepad_state_t gamepad_state{
platf::gamepad_state_t gamepad_state {
bf,
packet->leftTrigger,
packet->rightTrigger,
@ -312,30 +422,30 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
auto bf_new = gamepad_state.buttonFlags;
switch(gamepad.back_button_state) {
case button_state_e::UP:
if(!(platf::BACK & bf_new)) {
gamepad.back_button_state = button_state_e::NONE;
}
gamepad_state.buttonFlags &= ~platf::BACK;
break;
case button_state_e::DOWN:
if(platf::BACK & bf_new) {
gamepad.back_button_state = button_state_e::NONE;
}
gamepad_state.buttonFlags |= platf::BACK;
break;
case button_state_e::NONE:
break;
case button_state_e::UP:
if(!(platf::BACK & bf_new)) {
gamepad.back_button_state = button_state_e::NONE;
}
gamepad_state.buttonFlags &= ~platf::BACK;
break;
case button_state_e::DOWN:
if(platf::BACK & bf_new) {
gamepad.back_button_state = button_state_e::NONE;
}
gamepad_state.buttonFlags |= platf::BACK;
break;
case button_state_e::NONE:
break;
}
bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags;
bf = gamepad_state.buttonFlags ^ gamepad.gamepad_state.buttonFlags;
bf_new = gamepad_state.buttonFlags;
if (platf::BACK & bf) {
if (platf::BACK & bf_new) {
if(platf::BACK & bf) {
if(platf::BACK & bf_new) {
// Don't emulate home button if timeout < 0
if(config::input.back_button_timeout >= 0ms) {
gamepad.back_timeout_id = task_pool.pushDelayed([input, controller=packet->controllerNumber]() {
auto f = [input, controller = packet->controllerNumber]() {
auto &gamepad = input->gamepads[controller];
auto &state = gamepad.gamepad_state;
@ -354,10 +464,12 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
platf::gamepad(platf_input, gamepad.id, state);
gamepad.back_timeout_id = nullptr;
}, config::input.back_button_timeout).task_id;
};
gamepad.back_timeout_id = task_pool.pushDelayed(std::move(f), config::input.back_button_timeout).task_id;
}
}
else if (gamepad.back_timeout_id) {
else if(gamepad.back_timeout_id) {
task_pool.cancel(gamepad.back_timeout_id);
gamepad.back_timeout_id = nullptr;
}
@ -371,30 +483,32 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET pa
void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) {
void *payload = input_data.data();
int input_type = util::endian::big(*(int*)payload);
int input_type = util::endian::big(*(int *)payload);
switch(input_type) {
case PACKET_TYPE_MOUSE_MOVE:
passthrough(platf_input, (PNV_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_MOUSE_BUTTON:
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD:
{
char *tmp_input = (char*)payload + 4;
if(tmp_input[0] == 0x0A) {
passthrough(platf_input, (PNV_SCROLL_PACKET)payload);
}
else {
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
}
break;
case PACKET_TYPE_REL_MOUSE_MOVE:
passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_ABS_MOUSE_MOVE:
passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_MOUSE_BUTTON:
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
break;
case PACKET_TYPE_SCROLL_OR_KEYBOARD: {
char *tmp_input = (char *)payload + 4;
if(tmp_input[0] == 0x0A) {
passthrough((PNV_SCROLL_PACKET)payload);
}
case PACKET_TYPE_MULTI_CONTROLLER:
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload);
break;
else {
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
}
break;
}
case PACKET_TYPE_MULTI_CONTROLLER:
passthrough(input, (PNV_MULTI_CONTROLLER_PACKET)payload);
break;
}
}
@ -402,14 +516,20 @@ void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&in
task_pool.push(passthrough_helper, input, util::cmove(input_data));
}
void reset() {
if(task_id) {
task_pool.cancel(task_id);
}
void reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(task_id);
task_pool.cancel(input->mouse_left_button_timeout);
// Ensure input is synchronous
// Ensure input is synchronous, by using the task_pool
task_pool.push([]() {
for(auto& kp : key_press) {
for(int x = 0; x < mouse_press.size(); ++x) {
if(mouse_press[x]) {
platf::button_mouse(platf_input, x, true);
mouse_press[x] = false;
}
}
for(auto &kp : key_press) {
platf::keyboard(platf_input, kp.first & 0x00FF, true);
key_press[kp.first] = false;
}
@ -417,7 +537,8 @@ void reset() {
}
void init() {
platf_input = platf::input();
touch_port_event = std::make_unique<touch_port_event_t::element_type>();
platf_input = platf::input();
}
std::shared_ptr<input_t> alloc() {
@ -427,8 +548,9 @@ std::shared_ptr<input_t> alloc() {
task_pool.pushDelayed([]() {
platf::move_mouse(platf_input, 1, 1);
platf::move_mouse(platf_input, -1, -1);
}, 100ms);
},
100ms);
return input;
}
}
} // namespace input

View File

@ -5,18 +5,23 @@
#ifndef SUNSHINE_INPUT_H
#define SUNSHINE_INPUT_H
#include "platform/common.h"
#include "thread_safe.h"
namespace input {
struct input_t;
void print(void *input);
void reset();
void reset(std::shared_ptr<input_t> &input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
void init();
std::shared_ptr<input_t> alloc();
}
using touch_port_event_t = std::unique_ptr<safe::event_t<platf::touch_port_t>>;
extern touch_port_event_t touch_port_event;
} // namespace input
#endif //SUNSHINE_INPUT_H

View File

@ -4,29 +4,28 @@
#include "process.h"
#include <thread>
#include <iostream>
#include <csignal>
#include <iostream>
#include <thread>
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/attributes/clock.hpp>
#include <boost/log/common.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include "video.h"
#include "input.h"
#include "config.h"
#include "nvhttp.h"
#include "httpcommon.h"
#include "confighttp.h"
#include "rtsp.h"
#include "config.h"
#include "thread_pool.h"
#include "video.h"
#include "platform/common.h"
extern "C" {
#include <rs.h>
#include <libavutil/log.h>
#include <rs.h>
}
using namespace std::literals;
@ -82,31 +81,31 @@ int main(int argc, char *argv[]) {
sink->locked_backend()->add_stream(stream);
sink->set_filter(severity >= config::sunshine.min_log_level);
sink->set_formatter([message="Message"s, severity="Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
constexpr int DATE_BUFFER_SIZE = 21 +2 +1; // Full string plus ": \0"
sink->set_formatter([message = "Message"s, severity = "Severity"s](const bl::record_view &view, bl::formatting_ostream &os) {
constexpr int DATE_BUFFER_SIZE = 21 + 2 + 1; // Full string plus ": \0"
auto log_level = view.attribute_values()[severity].extract<int>().get();
std::string_view log_type;
switch(log_level) {
case 0:
log_type = "Verbose: "sv;
break;
case 1:
log_type = "Debug: "sv;
break;
case 2:
log_type = "Info: "sv;
break;
case 3:
log_type = "Warning: "sv;
break;
case 4:
log_type = "Error: "sv;
break;
case 5:
log_type = "Fatal: "sv;
break;
case 0:
log_type = "Verbose: "sv;
break;
case 1:
log_type = "Debug: "sv;
break;
case 2:
log_type = "Info: "sv;
break;
case 3:
log_type = "Warning: "sv;
break;
case 4:
log_type = "Error: "sv;
break;
case 5:
log_type = "Fatal: "sv;
break;
};
char _date[DATE_BUFFER_SIZE];
@ -133,7 +132,6 @@ int main(int argc, char *argv[]) {
return 4;
}
input::init();
reed_solomon_init();
if(video::init()) {
return 2;

View File

@ -5,8 +5,8 @@
#ifndef SUNSHINE_MAIN_H
#define SUNSHINE_MAIN_H
#include <boost/log/common.hpp>
#include "thread_pool.h"
#include <boost/log/common.hpp>
extern util::ThreadPool task_pool;
extern bool display_cursor;

View File

@ -11,23 +11,24 @@ template<class T>
class MoveByCopy {
public:
typedef T move_type;
private:
move_type _to_move;
public:
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) { }
public:
explicit MoveByCopy(move_type &&to_move) : _to_move(std::move(to_move)) {}
MoveByCopy(MoveByCopy &&other) = default;
MoveByCopy(const MoveByCopy &other) {
*this = other;
}
MoveByCopy& operator=(MoveByCopy &&other) = default;
MoveByCopy& operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy&>(other)._to_move);
MoveByCopy &operator=(MoveByCopy &&other) = default;
MoveByCopy &operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this;
}
@ -44,7 +45,7 @@ MoveByCopy<T> cmove(T &movable) {
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
template<class T>
MoveByCopy<T> const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T&>(movable)));
}
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
}
} // namespace util
#endif

View File

@ -2,9 +2,9 @@
// Created by loki on 12/27/19.
//
#include <algorithm>
#include "network.h"
#include "utility.h"
#include <algorithm>
namespace net {
using namespace std::literals;
@ -21,17 +21,17 @@ std::vector<std::tuple<std::uint32_t, std::uint32_t>> lan_ips {
};
std::uint32_t ip(const std::string_view &ip_str) {
auto begin = std::begin(ip_str);
auto end = std::end(ip_str);
auto begin = std::begin(ip_str);
auto end = std::end(ip_str);
auto temp_end = std::find(begin, end, '.');
std::uint32_t ip = 0;
auto shift = 24;
auto shift = 24;
while(temp_end != end) {
ip += (util::from_chars(begin, temp_end) << shift);
shift -= 8;
begin = temp_end + 1;
begin = temp_end + 1;
temp_end = std::find(begin, end, '.');
}
@ -43,7 +43,7 @@ std::uint32_t ip(const std::string_view &ip_str) {
// In the format "xxx.xxx.xxx.xxx/x"
std::pair<std::uint32_t, std::uint32_t> ip_block(const std::string_view &ip_str) {
auto begin = std::begin(ip_str);
auto end = std::find(begin, std::end(ip_str), '/');
auto end = std::find(begin, std::end(ip_str), '/');
auto addr = ip({ begin, (std::size_t)(end - begin) });
@ -82,12 +82,12 @@ net_e from_address(const std::string_view &view) {
std::string_view to_enum_string(net_e net) {
switch(net) {
case PC:
return "pc"sv;
case LAN:
return "lan"sv;
case WAN:
return "wan"sv;
case PC:
return "pc"sv;
case LAN:
return "lan"sv;
case WAN:
return "wan"sv;
}
// avoid warning
@ -112,4 +112,4 @@ void free_host(ENetHost *host) {
enet_host_destroy(host);
}
}
} // namespace net

View File

@ -14,8 +14,8 @@
namespace net {
void free_host(ENetHost *host);
using host_t = util::safe_ptr<ENetHost, free_host>;
using peer_t = ENetPeer*;
using host_t = util::safe_ptr<ENetHost, free_host>;
using peer_t = ENetPeer *;
using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>;
enum net_e : int {
@ -30,6 +30,6 @@ std::string_view to_enum_string(net_e net);
net_e from_address(const std::string_view &view);
host_t host_create(ENetAddress &addr, std::size_t peers, std::uint16_t port);
}
} // namespace net
#endif //SUNSHINE_NETWORK_H

View File

@ -2,13 +2,15 @@
// Created by loki on 6/3/19.
//
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h"
#include <filesystem>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/asio/ssl/context.hpp>
@ -17,17 +19,19 @@
#include <boost/asio/ssl/context_base.hpp>
#include "config.h"
#include "utility.h"
#include "rtsp.h"
#include "crypto.h"
#include "main.h"
#include "network.h"
#include "nvhttp.h"
#include "platform/common.h"
#include "network.h"
#include "rtsp.h"
#include "utility.h"
#include "uuid.h"
#include "main.h"
#include "httpcommon.h"
namespace nvhttp {
using namespace std::literals;
constexpr auto PORT_HTTP = 47989;
@ -67,8 +71,8 @@ struct pair_session_t {
struct {
util::Either<
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>,
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>
> response;
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>>
response;
std::string salt;
} async_insert_pin;
};
@ -77,11 +81,11 @@ struct pair_session_t {
std::unordered_map<std::string, pair_session_t> map_id_sess;
std::unordered_map<std::string, client_t> map_id_client;
using args_t = SimpleWeb::CaseInsensitiveMultimap;
using args_t = SimpleWeb::CaseInsensitiveMultimap;
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>;
using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Request>;
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>;
using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Request>;
enum class op_e {
ADD,
@ -93,7 +97,7 @@ void save_state() {
root.put("root.uniqueid", http::unique_id);
auto &nodes = root.add_child("root.devices", pt::ptree {});
for(auto &[_,client] : map_id_client) {
for(auto &[_, client] : map_id_client) {
pt::ptree node;
node.put("uniqueid"s, client.uniqueID);
@ -114,7 +118,6 @@ void save_state() {
void load_state() {
auto file_state = fs::current_path() / config::nvhttp.file_state;
if(!fs::exists(file_state)) {
http::unique_id = util::uuid_t::generate().string();
return;
@ -123,7 +126,8 @@ void load_state() {
pt::ptree root;
try {
pt::read_json(config::nvhttp.file_state, root);
} catch (std::exception &e) {
}
catch(std::exception &e) {
BOOST_LOG(warning) << e.what();
return;
@ -132,8 +136,8 @@ void load_state() {
http::unique_id = root.get<std::string>("root.uniqueid");
auto device_nodes = root.get_child("root.devices");
for(auto &[_,device_node] : device_nodes) {
auto uniqID = device_node.get<std::string>("uniqueid");
for(auto &[_, device_node] : device_nodes) {
auto uniqID = device_node.get<std::string>("uniqueid");
auto &client = map_id_client.emplace(uniqID, client_t {}).first->second;
client.uniqueID = uniqID;
@ -146,16 +150,14 @@ void load_state() {
void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) {
switch(op) {
case op_e::ADD:
{
auto &client = map_id_client[uniqueID];
client.certs.emplace_back(std::move(cert));
client.uniqueID = uniqueID;
}
break;
case op_e::REMOVE:
map_id_client.erase(uniqueID);
break;
case op_e::ADD: {
auto &client = map_id_client[uniqueID];
client.certs.emplace_back(std::move(cert));
client.uniqueID = uniqueID;
} break;
case op_e::REMOVE:
map_id_client.erase(uniqueID);
break;
}
if(!config::sunshine.flags[config::flag::FRESH_STATE]) {
@ -163,6 +165,20 @@ void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op)
}
}
stream::launch_session_t make_launch_session(bool host_audio, const args_t &args) {
stream::launch_session_t launch_session;
launch_session.host_audio = host_audio;
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
auto prepend_iv_p = (uint8_t *)&prepend_iv;
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv));
std::fill(next, std::end(launch_session.iv), 0);
return launch_session;
}
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if(sess.async_insert_pin.salt.size() < 32) {
tree.put("root.paired", 0);
@ -171,10 +187,10 @@ void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin
}
std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 };
auto salt = util::from_hex<std::array<uint8_t, 16>>(salt_view, true);
auto key = crypto::gen_aes_key(*salt, pin);
auto key = crypto::gen_aes_key(*salt, pin);
sess.cipher_key = std::make_unique<crypto::aes_t>(key);
tree.put("root.paired", 1);
@ -193,7 +209,7 @@ void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &ar
sess.clienthash = std::move(decrypted);
auto serversecret = sess.serversecret;
auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret);
auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret);
serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign));
@ -211,14 +227,14 @@ void clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args)
std::vector<uint8_t> decrypted;
cipher.decrypt(challenge, decrypted);
auto x509 = crypto::x509(conf_intern.servercert);
auto sign = crypto::signature(x509);
auto x509 = crypto::x509(conf_intern.servercert);
auto sign = crypto::signature(x509);
auto serversecret = crypto::rand(16);
decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign));
decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret));
auto hash = crypto::hash({ (char*)decrypted.data(), decrypted.size() });
auto hash = crypto::hash({ (char *)decrypted.data(), decrypted.size() });
auto serverchallenge = crypto::rand(16);
std::string plaintext;
@ -248,7 +264,7 @@ void clientpairingsecret(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cer
assert((secret.size() + sign.size()) == pairingsecret.size());
auto x509 = crypto::x509(client.cert);
auto x509 = crypto::x509(client.cert);
auto x509_sign = crypto::signature(x509);
std::string data;
@ -302,8 +318,8 @@ template<class T>
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
BOOST_LOG(debug) << "DESTINATION :: "sv << request->path;
for(auto &[name, val] : request->header) {
BOOST_LOG(debug) << name << " -- " << val;
@ -330,26 +346,33 @@ void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> resp
pt::write_xml(data, tree);
response->write(data.str());
*response << "HTTP/1.1 404 NOT FOUND\r\n" << data.str();
*response << "HTTP/1.1 404 NOT FOUND\r\n"
<< data.str();
}
template<class T>
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
pt::ptree tree;
auto args = request->parse_query_string();
if(args.find("uniqueid"s) == std::end(args)) {
tree.put("root.<xmlattr>.status_code", 400);
return;
}
auto uniqID { std::move(args.at("uniqueid"s)) };
auto sess_it = map_id_sess.find(uniqID);
pt::ptree tree;
args_t::const_iterator it;
if(it = args.find("phrase"); it != std::end(args)) {
if(it->second == "getservercert"sv) {
pair_session_t sess;
sess.client.uniqueID = std::move(uniqID);
sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true);
sess.client.cert = util::from_hex_vec(args.at("clientcert"s), true);
BOOST_LOG(debug) << sess.client.cert;
auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first;
@ -431,7 +454,7 @@ void pin(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response,
if(async_response.has_left() && async_response.left()) {
async_response.left()->write(data.str());
}
else if(async_response.has_right() && async_response.right()){
else if(async_response.has_right() && async_response.right()) {
async_response.right()->write(data.str());
}
else {
@ -451,13 +474,13 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
print_req<T>(request);
int pair_status = 0;
if constexpr (std::is_same_v<SimpleWeb::HTTPS, T>) {
auto args = request->parse_query_string();
if constexpr(std::is_same_v<SimpleWeb::HTTPS, T>) {
auto args = request->parse_query_string();
auto clientID = args.find("uniqueid"s);
if(clientID != std::end(args)) {
if (auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) {
if(auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) {
pair_status = 1;
}
}
@ -489,10 +512,35 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
tree.put("root.ExternalIP", config::nvhttp.external_ip);
}
pt::ptree display_nodes;
for(auto &resolution : config::nvhttp.resolutions) {
auto pred = [](auto ch) { return ch == ' ' || ch == '\t' || ch == 'x'; };
auto middle = std::find_if(std::begin(resolution), std::end(resolution), pred);
if(middle == std::end(resolution)) {
BOOST_LOG(warning) << resolution << " is not in the proper format for a resolution: WIDTHxHEIGHT"sv;
continue;
}
auto width = util::from_chars(&*std::begin(resolution), &*middle);
auto height = util::from_chars(&*(middle + 1), &*std::end(resolution));
for(auto fps : config::nvhttp.fps) {
pt::ptree display_node;
display_node.put("Width", width);
display_node.put("Height", height);
display_node.put("RefreshRate", fps);
display_nodes.add_child("DisplayMode", display_node);
}
}
if(!config::nvhttp.resolutions.empty()) {
tree.add_child("root.SupportedDisplayMode", display_nodes);
}
auto current_appid = proc::proc.running();
tree.put("root.PairStatus", pair_status);
tree.put("root.currentgame", current_appid >= 0 ? current_appid + 1 : 0);
tree.put("root.state", current_appid >= 0 ? "_SERVER_BUSY" : "_SERVER_FREE");
tree.put("root.state", current_appid >= 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE");
std::ostringstream data;
@ -503,9 +551,6 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
void applist(resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(request);
auto args = request->parse_query_string();
auto clientID = args.at("uniqueid"s);
pt::ptree tree;
auto g = util::fail_guard([&]() {
@ -515,6 +560,15 @@ void applist(resp_https_t response, req_https_t request) {
response->write(data.str());
});
auto args = request->parse_query_string();
if(args.find("uniqueid"s) == std::end(args)) {
tree.put("root.<xmlattr>.status_code", 400);
return;
}
auto clientID = args.at("uniqueid"s);
auto client = map_id_client.find(clientID);
if(client == std::end(map_id_client)) {
tree.put("root.<xmlattr>.status_code", 501);
@ -538,7 +592,7 @@ void applist(resp_https_t response, req_https_t request) {
}
}
void launch(resp_https_t response, req_https_t request) {
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(request);
pt::ptree tree;
@ -557,7 +611,19 @@ void launch(resp_https_t response, req_https_t request) {
}
auto args = request->parse_query_string();
auto appid = util::from_view(args.at("appid")) -1;
if(
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args) ||
args.find("localAudioPlayMode"s) == std::end(args) ||
args.find("appid"s) == std::end(args)) {
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
return;
}
auto appid = util::from_view(args.at("appid")) - 1;
auto current_appid = proc::proc.running();
if(current_appid != -1) {
@ -577,23 +643,14 @@ void launch(resp_https_t response, req_https_t request) {
}
}
stream::launch_session_t launch_session;
auto clientID = args.at("uniqueid"s);
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
auto prepend_iv_p = (uint8_t*)&prepend_iv;
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv));
std::fill(next, std::end(launch_session.iv), 0);
stream::launch_session_raise(launch_session);
host_audio = util::from_view(args.at("localAudioPlayMode"));
stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.gamesession", 1);
}
void resume(resp_https_t response, req_https_t request) {
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SimpleWeb::HTTPS>(request);
pt::ptree tree;
@ -621,18 +678,18 @@ void resume(resp_https_t response, req_https_t request) {
return;
}
stream::launch_session_t launch_session;
auto args = request->parse_query_string();
auto clientID = args.at("uniqueid"s);
launch_session.gcm_key = *util::from_hex<crypto::aes_t>(args.at("rikey"s), true);
uint32_t prepend_iv = util::endian::big<uint32_t>(util::from_view(args.at("rikeyid"s)));
auto prepend_iv_p = (uint8_t*)&prepend_iv;
if(
args.find("rikey"s) == std::end(args) ||
args.find("rikeyid"s) == std::end(args)) {
auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv));
std::fill(next, std::end(launch_session.iv), 0);
tree.put("root.resume", 0);
tree.put("root.<xmlattr>.status_code", 400);
stream::launch_session_raise(launch_session);
return;
}
stream::launch_session_raise(make_launch_session(host_audio, args));
tree.put("root.<xmlattr>.status_code", 200);
tree.put("root.resume", 1);
@ -689,7 +746,7 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem);
crypto::cert_chain_t cert_chain;
for(auto &[_,client] : map_id_client) {
for(auto &[_, client] : map_id_client) {
for(auto &cert : client.certs) {
cert_chain.add(crypto::x509(cert));
}
@ -733,37 +790,41 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
return 1;
});
// /resume doesn't get the parameter "localAudioPlayMode"
// /launch will store it in host_audio
bool host_audio {};
https_server_t https_server { ctx, boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once };
http_server_t http_server;
https_server.default_resource = not_found<SimpleWeb::HTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTPS>;
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTPS>(add_cert, resp, req); };
https_server.resource["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset;
https_server.resource["^/launch$"]["GET"] = launch;
https_server.default_resource = not_found<SimpleWeb::HTTPS>;
https_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTPS>;
https_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTPS>(add_cert, resp, req); };
https_server.resource["^/applist$"]["GET"] = applist;
https_server.resource["^/appasset$"]["GET"] = appasset;
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
https_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTPS>;
https_server.resource["^/resume$"]["GET"] = resume;
https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
https_server.resource["^/cancel$"]["GET"] = cancel;
https_server.config.reuse_address = true;
https_server.config.address = "0.0.0.0"s;
https_server.config.port = PORT_HTTPS;
https_server.config.address = "0.0.0.0"s;
https_server.config.port = PORT_HTTPS;
http_server.default_resource = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
http_server.default_resource = not_found<SimpleWeb::HTTP>;
http_server.resource["^/serverinfo$"]["GET"] = serverinfo<SimpleWeb::HTTP>;
http_server.resource["^/pair$"]["GET"] = [&add_cert](auto resp, auto req) { pair<SimpleWeb::HTTP>(add_cert, resp, req); };
http_server.resource["^/pin/([0-9]+)$"]["GET"] = pin<SimpleWeb::HTTP>;
http_server.config.reuse_address = true;
http_server.config.address = "0.0.0.0"s;
http_server.config.port = PORT_HTTP;
http_server.config.address = "0.0.0.0"s;
http_server.config.port = PORT_HTTP;
try {
https_server.bind();
http_server.bind();
} catch(boost::system::system_error &err) {
}
catch(boost::system::system_error &err) {
BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what();
shutdown_event->raise(true);
@ -773,7 +834,8 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
auto accept_and_run = [&](auto *http_server) {
try {
http_server->accept_and_run();
} catch(boost::system::system_error &err) {
}
catch(boost::system::system_error &err) {
// It's possible the exception gets thrown after calling http_server->stop() from a different thread
if(shutdown_event->peek()) {
return;
@ -795,5 +857,4 @@ void start(std::shared_ptr<safe::signal_t> shutdown_event) {
ssl.join();
tcp.join();
}
}
}

View File

@ -12,8 +12,8 @@
#include "thread_safe.h"
#define CA_DIR SUNSHINE_ASSETS_DIR "/demoCA"
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
namespace nvhttp {
void start(std::shared_ptr<safe::signal_t> shutdown_event);

View File

@ -5,9 +5,10 @@
#ifndef SUNSHINE_COMMON_H
#define SUNSHINE_COMMON_H
#include <string>
#include <mutex>
#include "sunshine/utility.h"
#include <bitset>
#include <mutex>
#include <string>
struct sockaddr;
namespace platf {
@ -29,6 +30,43 @@ constexpr std::uint16_t B = 0x2000;
constexpr std::uint16_t X = 0x4000;
constexpr std::uint16_t Y = 0x8000;
namespace speaker {
enum speaker_e {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
MAX_SPEAKERS,
};
constexpr std::uint8_t map_stereo[] {
FRONT_LEFT, FRONT_RIGHT
};
constexpr std::uint8_t map_surround51[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
};
constexpr std::uint8_t map_surround71[] {
FRONT_LEFT,
FRONT_RIGHT,
FRONT_CENTER,
LOW_FREQUENCY,
LOW_FREQUENCY,
BACK_LEFT,
BACK_RIGHT,
SIDE_LEFT,
SIDE_RIGHT,
};
} // namespace speaker
enum class dev_type_e {
none,
dxgi,
@ -44,8 +82,10 @@ enum class pix_fmt_e {
};
inline std::string_view from_pix_fmt(pix_fmt_e pix_fmt) {
using namespace std::literals;
#define _CONVERT(x) case pix_fmt_e:: x : return #x ## sv
using namespace std::literals;
#define _CONVERT(x) \
case pix_fmt_e::x: \
return #x##sv
switch(pix_fmt) {
_CONVERT(yuv420p);
_CONVERT(yuv420p10);
@ -58,6 +98,17 @@ using namespace std::literals;
return "unknown"sv;
}
// Dimensions for touchscreen input
struct touch_port_t {
std::uint32_t offset_x, offset_y;
std::uint32_t width, height;
constexpr touch_port_t(
std::uint32_t offset_x, std::uint32_t offset_y,
std::uint32_t width, std::uint32_t height) noexcept : offset_x { offset_x }, offset_y { offset_y },
width { width }, height { height } {};
};
struct gamepad_state_t {
std::uint16_t buttonFlags;
std::uint8_t lt;
@ -75,19 +126,33 @@ public:
struct img_t {
public:
std::uint8_t *data {};
std::int32_t width {};
std::uint8_t *data {};
std::int32_t width {};
std::int32_t height {};
std::int32_t pixel_pitch {};
std::int32_t row_pitch {};
img_t() = default;
img_t(const img_t&) = delete;
img_t(img_t&&) = delete;
img_t() = default;
img_t(const img_t &) = delete;
img_t(img_t &&) = delete;
virtual ~img_t() = default;
};
struct sink_t {
// Play on host PC
std::string host;
// On Windows, it is not possible to create a virtual sink
// Therefore, it is optional
struct null_t {
std::string stereo;
std::string surround51;
std::string surround71;
};
std::optional<null_t> null;
};
struct hwdevice_t {
void *data {};
platf::img_t *img {};
@ -110,8 +175,9 @@ enum class capture_e : int {
class display_t {
public:
display_t() noexcept : offset_x { 0 }, offset_y { 0 } {}
virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) = 0;
virtual std::shared_ptr<img_t> alloc_img() = 0;
virtual std::shared_ptr<img_t> alloc_img() = 0;
virtual int dummy_img(img_t *img) = 0;
@ -121,6 +187,9 @@ public:
virtual ~display_t() = default;
// Offsets for when streaming a specific monitor. By default, they are 0.
int offset_x, offset_y;
int width, height;
};
@ -131,8 +200,18 @@ public:
virtual ~mic_t() = default;
};
class audio_control_t {
public:
virtual int set_sink(const std::string &sink) = 0;
void freeInput(void*);
virtual std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) = 0;
virtual std::optional<sink_t> sink_info() = 0;
virtual ~audio_control_t() = default;
};
void freeInput(void *);
using input_t = util::safe_ptr<void, freeInput>;
@ -141,11 +220,12 @@ std::string get_mac_address(const std::string_view &address);
std::string from_sockaddr(const sockaddr *const);
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const);
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate);
std::unique_ptr<audio_control_t> audio_control();
std::shared_ptr<display_t> display(dev_type_e hwdevice_type);
input_t input();
void move_mouse(input_t &input, int deltaX, int deltaY);
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
void button_mouse(input_t &input, int button, bool release);
void scroll(input_t &input, int distance);
void keyboard(input_t &input, uint16_t modcode, bool release);
@ -155,6 +235,6 @@ int alloc_gamepad(input_t &input, int nr);
void free_gamepad(input_t &input, int nr);
[[nodiscard]] std::unique_ptr<deinit_t> init();
}
} // namespace platf
#endif //SUNSHINE_COMMON_H

View File

@ -0,0 +1,441 @@
//
// Created by loki on 5/16/21.
//
#include <bitset>
#include <boost/regex.hpp>
#include <pulse/error.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include "sunshine/platform/common.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/thread_safe.h"
namespace platf {
using namespace std::literals;
constexpr pa_channel_position_t position_mapping[] {
PA_CHANNEL_POSITION_FRONT_LEFT,
PA_CHANNEL_POSITION_FRONT_RIGHT,
PA_CHANNEL_POSITION_FRONT_CENTER,
PA_CHANNEL_POSITION_LFE,
PA_CHANNEL_POSITION_REAR_LEFT,
PA_CHANNEL_POSITION_REAR_RIGHT,
PA_CHANNEL_POSITION_SIDE_LEFT,
PA_CHANNEL_POSITION_SIDE_RIGHT,
};
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss;
ss << "rate=48000 sink_name="sv << name << " format=s16le channels="sv << channels << " channel_map="sv;
std::for_each_n(mapping, channels - 1, [&ss](std::uint8_t pos) {
ss << pa_channel_position_to_string(position_mapping[pos]) << ',';
});
ss << pa_channel_position_to_string(position_mapping[mapping[channels - 1]]);
ss << " sink_properties=device.description="sv << name;
auto result = ss.str();
BOOST_LOG(debug) << "null-sink args: "sv << result;
return result;
}
struct mic_attr_t : public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic;
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if(pa_simple_read(mic.get(), buf, sample_size * 2, &status)) {
BOOST_LOG(error) << "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate) {
auto mic = std::make_unique<mic_attr_t>();
pa_sample_spec ss { PA_SAMPLE_S16LE, sample_rate, (std::uint8_t)channels };
pa_channel_map pa_map;
pa_map.channels = channels;
std::for_each_n(pa_map.map, pa_map.channels, [mapping](auto &channel) mutable {
channel = position_mapping[*mapping++];
});
int status;
const char *audio_sink = "@DEFAULT_MONITOR@";
if(!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
}
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
"sunshine-record", &ss, &pa_map, nullptr, &status));
if(!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
log_flush();
std::abort();
}
return mic;
}
namespace pa {
template<bool B, class T>
struct add_const_helper;
template<class T>
struct add_const_helper<true, T> {
using type = const std::remove_pointer_t<T> *;
};
template<class T>
struct add_const_helper<false, T> {
using type = const T *;
};
template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
template<class T>
void pa_free(T *p) {
pa_xfree(p);
}
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
using string_t = util::safe_ptr<char, pa_free<char>>;
template<class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *)userdata;
// For some reason, pulseaudio calls this callback after disconnecting
if(i && eol) {
return;
}
f(ctx, i, eol);
}
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *)userdata;
alarm->ring(i);
}
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *)userdata;
f(ctx);
}
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *)userdata;
alarm->ring(status ? 0 : 1);
}
class server_t : public audio_control_t {
enum ctx_event_e : int {
ready,
terminated,
failed
};
public:
loop_t loop;
ctx_t ctx;
struct {
std::uint32_t stereo = PA_INVALID_INDEX;
std::uint32_t surround51 = PA_INVALID_INDEX;
std::uint32_t surround71 = PA_INVALID_INDEX;
} index;
std::unique_ptr<safe::event_t<ctx_event_e>> events;
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::thread worker;
int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new());
ctx.reset(pa_context_new(pa_mainloop_get_api(loop.get()), "sunshine"));
events_cb = std::make_unique<std::function<void(ctx_t::pointer)>>([this](ctx_t::pointer ctx) {
switch(pa_context_get_state(ctx)) {
case PA_CONTEXT_READY:
events->raise(ready);
break;
case PA_CONTEXT_TERMINATED:
BOOST_LOG(debug) << "Pulseadio context terminated"sv;
events->raise(terminated);
break;
case PA_CONTEXT_FAILED:
BOOST_LOG(debug) << "Pulseadio context failed"sv;
events->raise(failed);
break;
case PA_CONTEXT_CONNECTING:
BOOST_LOG(debug) << "Connecting to pulseaudio"sv;
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
break;
}
});
pa_context_set_state_callback(ctx.get(), ctx_state_cb, events_cb.get());
auto status = pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr);
if(status) {
BOOST_LOG(error) << "Couldn't connect to pulseaudio: "sv << pa_strerror(status);
return -1;
}
worker = std::thread {
[](loop_t::pointer loop) {
int retval;
auto status = pa_mainloop_run(loop, &retval);
if(status < 0) {
BOOST_LOG(fatal) << "Couldn't run pulseaudio main loop"sv;
log_flush();
std::abort();
}
},
loop.get()
};
auto event = events->pop();
if(event == failed) {
return -1;
}
return 0;
}
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_load_module(
ctx.get(),
"module-null-sink",
to_string(name, channel_mapping, channels).c_str(),
cb_i,
alarm.get()),
};
alarm->wait();
return *alarm->status();
}
int unload_null(std::uint32_t i) {
if(i == PA_INVALID_INDEX) {
return 0;
}
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_unload_module(ctx.get(), i, success_cb, alarm.get())
};
alarm->wait();
if(*alarm->status()) {
BOOST_LOG(error) << "Couldn't unload null-sink with index ["sv << i << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
return 0;
}
std::optional<sink_t> sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51";
constexpr auto surround71 = "sink-sunshine-surround71";
auto alarm = safe::make_alarm<int>();
sink_t sink;
// If hardware sink with more channels found, set that as host
int channels = 0;
// Count of all virtual sinks that are created by us
int nullcount = 0;
cb_t<pa_sink_info *> f = [&](ctx_t::pointer ctx, const pa_sink_info *sink_info, int eol) {
if(!sink_info) {
if(!eol) {
BOOST_LOG(error) << "Couldn't get pulseaudio sink info: "sv << pa_strerror(pa_context_errno(ctx));
alarm->ring(-1);
}
alarm->ring(0);
return;
}
if(sink_info->flags & PA_SINK_HARDWARE &&
sink_info->channel_map.channels > channels) {
sink.host = sink_info->name;
channels = sink_info->channel_map.channels;
}
// Ensure Sunshine won't create a sink that already exists.
if(!std::strcmp(sink_info->name, stereo)) {
index.stereo = sink_info->owner_module;
++nullcount;
}
else if(!std::strcmp(sink_info->name, surround51)) {
index.surround51 = sink_info->owner_module;
++nullcount;
}
else if(!std::strcmp(sink_info->name, surround71)) {
index.surround71 = sink_info->owner_module;
++nullcount;
}
};
op_t op { pa_context_get_sink_info_list(ctx.get(), cb<pa_sink_info *>, &f) };
if(!op) {
BOOST_LOG(error) << "Couldn't create card info operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return std::nullopt;
}
alarm->wait();
if(*alarm->status()) {
return std::nullopt;
}
if(!channels) {
BOOST_LOG(warning) << "Couldn't find hardware sink"sv;
}
if(index.stereo == PA_INVALID_INDEX) {
index.stereo = load_null(stereo, speaker::map_stereo, sizeof(speaker::map_stereo));
if(index.stereo == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for stereo: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if(index.surround51 == PA_INVALID_INDEX) {
index.surround51 = load_null(surround51, speaker::map_surround51, sizeof(speaker::map_surround51));
if(index.surround51 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-51: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if(index.surround71 == PA_INVALID_INDEX) {
index.surround71 = load_null(surround71, speaker::map_surround71, sizeof(speaker::map_surround71));
if(index.surround71 == PA_INVALID_INDEX) {
BOOST_LOG(warning) << "Couldn't create virtual sink for surround-71: "sv << pa_strerror(pa_context_errno(ctx.get()));
}
else {
++nullcount;
}
}
if(nullcount == 3) {
sink.null = std::make_optional(sink_t::null_t { stereo, surround51, surround71 });
}
return std::make_optional(std::move(sink));
}
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size) override {
return ::platf::microphone(mapping, channels, sample_rate);
}
int set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>();
op_t op {
pa_context_set_default_sink(
ctx.get(), sink.c_str(), success_cb, alarm.get()),
};
if(!op) {
BOOST_LOG(error) << "Couldn't create set default-sink operation: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
alarm->wait();
if(*alarm->status()) {
BOOST_LOG(error) << "Couldn't set default-sink ["sv << sink << "]: "sv << pa_strerror(pa_context_errno(ctx.get()));
return -1;
}
return 0;
}
~server_t() override {
unload_null(index.stereo);
unload_null(index.surround51);
unload_null(index.surround71);
if(worker.joinable()) {
pa_context_disconnect(ctx.get());
KITTY_WHILE_LOOP(auto event = events->pop(), event != terminated && event != failed, {
event = events->pop();
})
pa_mainloop_quit(loop.get(), 0);
worker.join();
}
}
};
} // namespace pa
std::unique_ptr<audio_control_t> audio_control() {
auto audio = std::make_unique<pa::server_t>();
if(audio->init()) {
return nullptr;
}
return audio;
}
std::unique_ptr<deinit_t> init() {
return std::make_unique<deinit_t>();
}
} // namespace platf

View File

@ -5,7 +5,6 @@
#include "sunshine/platform/common.h"
#include <fstream>
#include <bitset>
#include <arpa/inet.h>
#include <ifaddrs.h>
@ -15,33 +14,32 @@
#include <X11/Xutil.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include "sunshine/task_pool.h"
#include "sunshine/config.h"
#include "sunshine/main.h"
#include "sunshine/task_pool.h"
namespace platf
{
namespace platf {
using namespace std::literals;
void freeImage(XImage*);
void freeX(XFixesCursorImage*);
void freeImage(XImage *);
void freeX(XFixesCursorImage *);
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
using xcb_connect_t = util::safe_ptr<xcb_connection_t, xcb_disconnect>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
using xcb_cursor_img = util::c_ptr<xcb_xfixes_get_cursor_image_reply_t>;
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
using xdisplay_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
using ximg_t = util::safe_ptr<XImage, freeImage>;
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
using ximg_t = util::safe_ptr<XImage, freeImage>;
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
using crtc_info_t = util::safe_ptr<_XRRCrtcInfo, XRRFreeCrtcInfo>;
using output_info_t = util::safe_ptr<_XRROutputInfo, XRRFreeOutputInfo>;
using screen_res_t = util::safe_ptr<_XRRScreenResources, XRRFreeScreenResources>;
class shm_id_t {
public:
@ -52,7 +50,7 @@ public:
}
~shm_id_t() {
if (id != -1) {
if(id != -1) {
shmctl(id, IPC_RMID, nullptr);
id = -1;
}
@ -62,37 +60,28 @@ public:
class shm_data_t {
public:
shm_data_t() : data { (void*) -1 }
{
}
shm_data_t(void *data) : data { data }
{
shm_data_t() : data { (void *)-1 } {}
shm_data_t(void *data) : data { data } {}
shm_data_t(shm_data_t &&other) noexcept : data(other.data) {
other.data = (void *)-1;
}
shm_data_t(shm_data_t &&other) noexcept : data(other.data)
{
other.data = (void*) -1;
}
~shm_data_t()
{
if ((std::uintptr_t) data != -1)
{
~shm_data_t() {
if((std::uintptr_t)data != -1) {
shmdt(data);
data = (void*) -1;
}
}
void *data;
};
struct x11_img_t: public img_t {
struct x11_img_t : public img_t {
ximg_t img;
};
struct shm_img_t: public img_t {
~shm_img_t() override
{
struct shm_img_t : public img_t {
~shm_img_t() override {
delete[] data;
data = nullptr;
}
@ -101,9 +90,8 @@ struct shm_img_t: public img_t {
void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
xcursor_t overlay { XFixesGetCursorImage(display) };
if (!overlay) {
BOOST_LOG(error)
<< "Couldn't get cursor from XFixesGetCursorImage"sv;
if(!overlay) {
BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
return;
}
@ -113,66 +101,60 @@ void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
overlay->x -= offsetX;
overlay->y -= offsetY;
overlay->x = std::max((short) 0, overlay->x);
overlay->y = std::max((short) 0, overlay->y);
overlay->x = std::max((short)0, overlay->x);
overlay->y = std::max((short)0, overlay->y);
auto pixels = (int*) img.data;
auto pixels = (int *)img.data;
auto screen_height = img.height;
auto screen_width = img.width;
auto screen_width = img.width;
auto delta_height = std::min<uint16_t>(overlay->height,std::max(0, screen_height - overlay->y));
auto delta_width = std::min<uint16_t>(overlay->width,std::max(0, screen_width - overlay->x));
for (auto y = 0; y < delta_height; ++y) {
auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y));
auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x));
for(auto y = 0; y < delta_height; ++y) {
auto overlay_begin = &overlay->pixels[y * overlay->width];
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];
auto pixels_begin = &pixels[(y + overlay->y)* (img.row_pitch / img.pixel_pitch) + overlay->x];
auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x];
std::for_each(overlay_begin, overlay_end,[&](long pixel) {
int *pixel_p = (int*) &pixel;
std::for_each(overlay_begin, overlay_end, [&](long pixel) {
int *pixel_p = (int *)&pixel;
auto colors_in = (uint8_t*) pixels_begin;
auto colors_in = (uint8_t *)pixels_begin;
auto alpha = (*(uint*) pixel_p) >> 24u;
if (alpha == 255) {
auto alpha = (*(uint *)pixel_p) >> 24u;
if(alpha == 255) {
*pixels_begin = *pixel_p;
}
else {
auto colors_out = (uint8_t*) pixel_p;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) +255 /2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
auto colors_out = (uint8_t *)pixel_p;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
}
++pixels_begin;
});
}
}
struct x11_attr_t: public display_t
{
struct x11_attr_t : public display_t {
xdisplay_t xdisplay;
Window xwindow;
XWindowAttributes xattr;
Display* displayDisplay;
/*
* Remember last X (NOT the streamed monitor!) size. This way we can trigger reinitialization if the dimensions changed while streaming
* Last X (NOT the streamed monitor!) size.
* This way we can trigger reinitialization if the dimensions changed while streaming
*/
int lastWidth,lastHeight;
int lastWidth, lastHeight;
/*
* Offsets for when streaming a specific monitor. By default, they are 0.
*/
int displayOffsetX,displayOffsetY;
x11_attr_t() : xdisplay { displayDisplay = XOpenDisplay(nullptr) }, xwindow { }, xattr { }
{
x11_attr_t() : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {} {
XInitThreads();
if (!xdisplay) {
BOOST_LOG(fatal) << "Could not open x11 display"sv;
log_flush();
std::abort();
}
int init() {
if(!xdisplay) {
BOOST_LOG(error) << "Could not open X11 display"sv;
return -1;
}
xwindow = DefaultRootWindow(xdisplay.get());
@ -184,53 +166,46 @@ struct x11_attr_t: public display_t
streamedMonitor = (int)util::from_view(config::video.output_name);
}
//If the value has been set at all
if (streamedMonitor != -1) {
BOOST_LOG(info) << "Configuring selected monitor ("<< streamedMonitor<<") to stream. If it fails here, you may need Xrandr >= 1.5"sv;
XRRScreenResources *screenr = XRRGetScreenResources(displayDisplay, xwindow);
// This is the key right here. Use XRRScreenResources::noutput
if(streamedMonitor != -1) {
BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv;
screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) };
int output = screenr->noutput;
if (streamedMonitor >= output) {
BOOST_LOG(error)<< "Could not stream selected display number because there aren't so many."sv;
if(streamedMonitor >= output) {
BOOST_LOG(error) << "Could not stream display number ["sv << streamedMonitor << "], there are only ["sv << output << "] displays."sv;
return -1;
}
else {
XRROutputInfo* out_info = XRRGetOutputInfo(displayDisplay, screenr, screenr->outputs[streamedMonitor]);
if (NULL == out_info || out_info->connection != RR_Connected)
{
BOOST_LOG(error)<< "Could not stream selected display because it doesn't seem to be connected"sv;
}
else
{
XRRCrtcInfo* crt_info = XRRGetCrtcInfo(displayDisplay, screenr, out_info->crtc);
BOOST_LOG(info)<<"Streaming display: "<<out_info->name<<" with res "<<crt_info->width<<" x "<<crt_info->height<<+" offset by "<<crt_info->x<<" x "<<crt_info->y<<"."sv;
width = crt_info -> width;
height = crt_info -> height;
displayOffsetX = crt_info -> x;
displayOffsetY = crt_info -> y;
XRRFreeCrtcInfo(crt_info);
}
XRRFreeOutputInfo(out_info);
output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[streamedMonitor]) };
if(!out_info || out_info->connection != RR_Connected) {
BOOST_LOG(error) << "Could not stream selected display because it doesn't seem to be connected"sv;
return -1;
}
XRRFreeScreenResources(screenr);
crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), out_info->crtc) };
BOOST_LOG(info)
<< "Streaming display: "sv << out_info->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;
width = crt_info->width;
height = crt_info->height;
offset_x = crt_info->x;
offset_y = crt_info->y;
}
else {
width = xattr.width;
width = xattr.width;
height = xattr.height;
}
lastWidth = xattr.width;
lastWidth = xattr.width;
lastHeight = xattr.height;
return 0;
}
/**
* Called when the display attributes should change.
*/
void refresh()
{
void refresh() {
XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's
}
@ -238,22 +213,22 @@ struct x11_attr_t: public display_t
refresh();
//The whole X server changed, so we gotta reinit everything
if (xattr.width != lastWidth || xattr.height != lastHeight) {
BOOST_LOG(warning)<< "X dimensions changed in non-SHM mode, request reinit"sv;
if(xattr.width != lastWidth || xattr.height != lastHeight) {
BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
return capture_e::reinit;
}
XImage *img { XGetImage(xdisplay.get(), xwindow, displayOffsetX, displayOffsetY, width,height, AllPlanes, ZPixmap) };
XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) };
auto img_out = (x11_img_t*) img_out_base;
img_out->width = img->width;
img_out->height = img->height;
img_out->data = (uint8_t*) img->data;
img_out->row_pitch = img->bytes_per_line;
auto img_out = (x11_img_t *)img_out_base;
img_out->width = img->width;
img_out->height = img->height;
img_out->data = (uint8_t *)img->data;
img_out->row_pitch = img->bytes_per_line;
img_out->pixel_pitch = img->bits_per_pixel / 8;
img_out->img.reset(img);
if (cursor) {
blend_cursor(xdisplay.get(), *img_out_base,displayOffsetX,displayOffsetY);
if(cursor) {
blend_cursor(xdisplay.get(), *img_out_base, offset_x, offset_y);
}
return capture_e::ok;
@ -269,7 +244,7 @@ struct x11_attr_t: public display_t
}
};
struct shm_attr_t: public x11_attr_t {
struct shm_attr_t : public x11_attr_t {
xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
xcb_connect_t xcb;
xcb_screen_t *display;
@ -284,37 +259,37 @@ struct shm_attr_t: public x11_attr_t {
void delayed_refresh() {
refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id;
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
shm_attr_t() : x11_attr_t(), shm_xdisplay { XOpenDisplay(nullptr) } {
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh,2s, this).task_id;
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
~shm_attr_t() override {
while (!task_pool.cancel(refresh_task_id));
while(!task_pool.cancel(refresh_task_id))
;
}
capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override {
//The whole X server changed, so we gotta reinit everything
if (xattr.width != lastWidth || xattr.height != lastHeight) {
BOOST_LOG(warning)<< "X dimensions changed in SHM mode, request reinit"sv;
if(xattr.width != lastWidth || xattr.height != lastHeight) {
BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
return capture_e::reinit;
}
else {
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root,displayOffsetX, displayOffsetY, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie,nullptr) };
if (!img_reply) {
BOOST_LOG(error)
<< "Could not get image reply"sv;
xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) };
if(!img_reply) {
BOOST_LOG(error) << "Could not get image reply"sv;
return capture_e::reinit;
}
std::copy_n((std::uint8_t*) data.data, frame_size(), img->data);
std::copy_n((std::uint8_t *)data.data, frame_size(), img->data);
if (cursor) {
blend_cursor(shm_xdisplay.get(), *img,displayOffsetX,displayOffsetY);
if(cursor) {
blend_cursor(shm_xdisplay.get(), *img, offset_x, offset_y);
}
return capture_e::ok;
@ -322,12 +297,12 @@ struct shm_attr_t: public x11_attr_t {
}
std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<shm_img_t>();
img->width = width;
img->height = height;
auto img = std::make_shared<shm_img_t>();
img->width = width;
img->height = height;
img->pixel_pitch = 4;
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
img->row_pitch = img->pixel_pitch * width;
img->data = new std::uint8_t[height * img->row_pitch];
return img;
}
@ -337,50 +312,41 @@ struct shm_attr_t: public x11_attr_t {
}
int init() {
if(x11_attr_t::init()) {
return 1;
}
shm_xdisplay.reset(XOpenDisplay(nullptr));
xcb.reset(xcb_connect(nullptr, nullptr));
if (xcb_connection_has_error(xcb.get()))
{
if(xcb_connection_has_error(xcb.get())) {
return -1;
}
if (!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present)
{
BOOST_LOG(error)
<< "Missing SHM extension"sv;
if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) {
BOOST_LOG(error) << "Missing SHM extension"sv;
return -1;
}
auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get()));
display = iter.data;
seg = xcb_generate_id(xcb.get());
display = iter.data;
seg = xcb_generate_id(xcb.get());
shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
if (shm_id.id == -1)
{
BOOST_LOG(error)
<< "shmget failed"sv;
if(shm_id.id == -1) {
BOOST_LOG(error) << "shmget failed"sv;
return -1;
}
xcb_shm_attach(xcb.get(), seg, shm_id.id, false);
data.data = shmat(shm_id.id, nullptr, 0);
if ((uintptr_t) data.data == -1)
{
BOOST_LOG(error)
<< "shmat failed"sv;
if((uintptr_t)data.data == -1) {
BOOST_LOG(error) << "shmat failed"sv;
return -1;
}
/*
* Commented out resetting of the sizes when intializing X in SHM mode. It might be wrong. Expect issues. This is the default mode, so poisoning those variables is not desired
*/
// width = display->width_in_pixels;
// height = display->height_in_pixels;
return 0;
}
@ -389,79 +355,32 @@ struct shm_attr_t: public x11_attr_t {
}
};
struct mic_attr_t: public mic_t {
pa_sample_spec ss;
util::safe_ptr<pa_simple, pa_simple_free> mic;
explicit mic_attr_t(pa_sample_format format, std::uint32_t sample_rate,
std::uint8_t channels) : ss { format, sample_rate, channels }, mic { } { }
capture_e sample(std::vector<std::int16_t> &sample_buf) override {
auto sample_size = sample_buf.size();
auto buf = sample_buf.data();
int status;
if (pa_simple_read(mic.get(), buf, sample_size * 2, &status))
{
BOOST_LOG(error)
<< "pa_simple_read() failed: "sv << pa_strerror(status);
return capture_e::error;
}
return capture_e::ok;
}
};
std::shared_ptr<display_t> shm_display() {
auto shm = std::make_shared<shm_attr_t>();
if (shm->init()) {
return nullptr;
}
return shm;
}
std::shared_ptr<display_t> display(platf::dev_type_e hwdevice_type) {
if (hwdevice_type != platf::dev_type_e::none) {
BOOST_LOG(error)<< "Could not initialize display with the given hw device type."sv;
if(hwdevice_type != platf::dev_type_e::none) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr;
}
auto shm_disp = shm_display(); //Attempt to use shared memory X11 to avoid copying the frame
// Attempt to use shared memory X11 to avoid copying the frame
auto shm_disp = std::make_shared<shm_attr_t>();
if (!shm_disp) {
return std::make_shared<x11_attr_t>(); //Fallback to standard way if else
auto status = shm_disp->init();
if(status > 0) {
// x11_attr_t::init() failed, don't bother trying again.
return nullptr;
}
return shm_disp;
}
std::unique_ptr<mic_t> microphone(std::uint32_t sample_rate) {
auto mic = std::make_unique<mic_attr_t>(PA_SAMPLE_S16LE, sample_rate, 2);
int status;
const char *audio_sink = "@DEFAULT_MONITOR@";
if (!config::audio.sink.empty()) {
audio_sink = config::audio.sink.c_str();
if(status == 0) {
return shm_disp;
}
mic->mic.reset(
pa_simple_new(nullptr, "sunshine",
pa_stream_direction_t::PA_STREAM_RECORD, audio_sink,
"sunshine-record", &mic->ss, nullptr, nullptr, &status));
if (!mic->mic) {
auto err_str = pa_strerror(status);
BOOST_LOG(error) << "pa_simple_new() failed: "sv << err_str;
log_flush();
std::abort();
// Fallback
auto x11_disp = std::make_shared<x11_attr_t>();
if(x11_disp->init()) {
return nullptr;
}
return mic;
return x11_disp;
}
ifaddr_t get_ifaddrs() {
@ -476,14 +395,14 @@ std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN];
auto family = ip_addr->sa_family;
if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
}
if (family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
}
return std::string { data };
@ -494,36 +413,35 @@ std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_
auto family = ip_addr->sa_family;
std::uint16_t port;
if (family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*) ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
port = ((sockaddr_in6*) ip_addr)->sin6_port;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data,
INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *)ip_addr)->sin6_port;
}
if (family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*) ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in*) ip_addr)->sin_port;
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data,
INET_ADDRSTRLEN);
port = ((sockaddr_in *)ip_addr)->sin_port;
}
return
{ port, std::string {data}};
return { port, std::string { data } };
}
std::string get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
for (auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if (pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
for(auto pos = ifaddrs.get(); pos != nullptr; pos = pos->ifa_next) {
if(pos->ifa_addr && address == from_sockaddr(pos->ifa_addr)) {
std::ifstream mac_file("/sys/class/net/"s + pos->ifa_name + "/address");
if (mac_file.good()) {
if(mac_file.good()) {
std::string mac_address;
std::getline(mac_file, mac_address);
return mac_address;
}
}
}
BOOST_LOG(warning)
<< "Unable to find MAC address for "sv << address;
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
return "00:00:00:00:00:00"s;
}
@ -533,4 +451,4 @@ void freeImage(XImage *p) {
void freeX(XFixesCursorImage *p) {
XFree(p);
}
}
} // namespace platf

View File

@ -1,16 +1,17 @@
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <libevdev/libevdev.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <cmath>
#include <cstring>
#include <filesystem>
#include "sunshine/platform/common.h"
#include "sunshine/main.h"
#include "sunshine/platform/common.h"
#include "sunshine/utility.h"
// Support older versions
@ -24,13 +25,27 @@
namespace platf {
using namespace std::literals;
using evdev_t = util::safe_ptr<libevdev, libevdev_free>;
using evdev_t = util::safe_ptr<libevdev, libevdev_free>;
using uinput_t = util::safe_ptr<libevdev_uinput, libevdev_uinput_destroy>;
using keyboard_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
constexpr touch_port_t target_touch_port {
0, 0,
19200, 12000
};
struct input_raw_t {
public:
void clear_touchscreen() {
std::filesystem::path touch_path { "sunshine_touchscreen"sv };
if(std::filesystem::is_symlink(touch_path)) {
std::filesystem::remove(touch_path);
}
touch_input.reset();
}
void clear_mouse() {
std::filesystem::path mouse_path { "sunshine_mouse"sv };
@ -51,13 +66,11 @@ public:
std::filesystem::remove(gamepad_path);
}
gamepads[nr] = std::make_pair(uinput_t{}, gamepad_state_t {});
gamepads[nr] = std::make_pair(uinput_t {}, gamepad_state_t {});
}
int create_mouse() {
libevdev_uinput *buf {};
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
mouse_input.reset(buf);
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err);
@ -69,13 +82,24 @@ public:
return 0;
}
int create_touchscreen() {
int err = libevdev_uinput_create_from_device(touch_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &touch_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Touchscreen: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(touch_input.get()), "sunshine_touchscreen"sv);
return 0;
}
int alloc_gamepad(int nr) {
TUPLE_2D_REF(input, gamepad_state, gamepads[nr]);
libevdev_uinput *buf;
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input);
input.reset(buf);
gamepad_state = gamepad_state_t {};
if(err) {
@ -96,6 +120,7 @@ public:
}
void clear() {
clear_touchscreen();
clear_mouse();
for(int x = 0; x < gamepads.size(); ++x) {
clear_gamepad(x);
@ -106,18 +131,33 @@ public:
clear();
}
evdev_t gamepad_dev;
std::vector<std::pair<uinput_t, gamepad_state_t>> gamepads;
evdev_t mouse_dev;
uinput_t mouse_input;
uinput_t touch_input;
evdev_t gamepad_dev;
evdev_t touch_dev;
evdev_t mouse_dev;
keyboard_t keyboard;
};
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto touchscreen = ((input_raw_t *)input.get())->touch_input.get();
auto scaled_x = (int)std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = (int)std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, scaled_x);
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_y);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchscreen, EV_SYN, SYN_REPORT, 0);
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto mouse = ((input_raw_t*)input.get())->mouse_input.get();
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
if(deltaX) {
libevdev_uinput_write_event(mouse, EV_REL, REL_X, deltaX);
@ -136,26 +176,26 @@ void button_mouse(input_t &input, int button, bool release) {
if(button == 1) {
btn_type = BTN_LEFT;
scan = 90001;
scan = 90001;
}
else if(button == 2) {
btn_type = BTN_MIDDLE;
scan = 90003;
scan = 90003;
}
else if(button == 3) {
btn_type = BTN_RIGHT;
scan = 90002;
scan = 90002;
}
else if(button == 4) {
btn_type = BTN_SIDE;
scan = 90004;
scan = 90004;
}
else {
btn_type = BTN_EXTRA;
scan = 90005;
scan = 90005;
}
auto mouse = ((input_raw_t*)input.get())->mouse_input.get();
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
libevdev_uinput_write_event(mouse, EV_MSC, MSC_SCAN, scan);
libevdev_uinput_write_event(mouse, EV_KEY, btn_type, release ? 0 : 1);
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
@ -164,7 +204,7 @@ void button_mouse(input_t &input, int button, bool release) {
void scroll(input_t &input, int high_res_distance) {
int distance = high_res_distance / 120;
auto mouse = ((input_raw_t*)input.get())->mouse_input.get();
auto mouse = ((input_raw_t *)input.get())->mouse_input.get();
libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL, distance);
libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL_HI_RES, high_res_distance);
libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0);
@ -172,7 +212,7 @@ void scroll(input_t &input, int high_res_distance) {
uint16_t keysym(uint16_t modcode) {
constexpr auto VK_NUMPAD = 0x60;
constexpr auto VK_F1 = 0x70;
constexpr auto VK_F1 = 0x70;
if(modcode >= VK_NUMPAD && modcode < VK_NUMPAD + 10) {
return XK_KP_0 + (modcode - VK_NUMPAD);
@ -184,108 +224,108 @@ uint16_t keysym(uint16_t modcode) {
switch(modcode) {
case 0x08:
return XK_BackSpace;
case 0x09:
return XK_Tab;
case 0x0D:
return XK_Return;
case 0x13:
return XK_Pause;
case 0x14:
return XK_Caps_Lock;
case 0x1B:
return XK_Escape;
case 0x21:
return XK_Page_Up;
case 0x22:
return XK_Page_Down;
case 0x23:
return XK_End;
case 0x24:
return XK_Home;
case 0x25:
return XK_Left;
case 0x26:
return XK_Up;
case 0x27:
return XK_Right;
case 0x28:
return XK_Down;
case 0x29:
return XK_Select;
case 0x2B:
return XK_Execute;
case 0x2C:
return XK_Print;
case 0x2D:
return XK_Insert;
case 0x2E:
return XK_Delete;
case 0x2F:
return XK_Help;
case 0x6A:
return XK_KP_Multiply;
case 0x6B:
return XK_KP_Add;
case 0x6C:
return XK_KP_Separator;
case 0x6D:
return XK_KP_Subtract;
case 0x6E:
return XK_KP_Decimal;
case 0x6F:
return XK_KP_Divide;
case 0x90:
return XK_Num_Lock;
case 0x91:
return XK_Scroll_Lock;
case 0xA0:
return XK_Shift_L;
case 0xA1:
return XK_Shift_R;
case 0xA2:
return XK_Control_L;
case 0xA3:
return XK_Control_R;
case 0xA4:
return XK_Alt_L;
case 0xA5: /* return XK_Alt_R; */
return XK_Super_L;
case 0x5B:
return XK_Super_L;
case 0x5C:
return XK_Super_R;
case 0xBA:
return XK_semicolon;
case 0xBB:
return XK_equal;
case 0xBC:
return XK_comma;
case 0xBD:
return XK_minus;
case 0xBE:
return XK_period;
case 0xBF:
return XK_slash;
case 0xC0:
return XK_grave;
case 0xDB:
return XK_bracketleft;
case 0xDC:
return XK_backslash;
case 0xDD:
return XK_bracketright;
case 0xDE:
return XK_apostrophe;
case 0x08:
return XK_BackSpace;
case 0x09:
return XK_Tab;
case 0x0D:
return XK_Return;
case 0x13:
return XK_Pause;
case 0x14:
return XK_Caps_Lock;
case 0x1B:
return XK_Escape;
case 0x21:
return XK_Page_Up;
case 0x22:
return XK_Page_Down;
case 0x23:
return XK_End;
case 0x24:
return XK_Home;
case 0x25:
return XK_Left;
case 0x26:
return XK_Up;
case 0x27:
return XK_Right;
case 0x28:
return XK_Down;
case 0x29:
return XK_Select;
case 0x2B:
return XK_Execute;
case 0x2C:
return XK_Print;
case 0x2D:
return XK_Insert;
case 0x2E:
return XK_Delete;
case 0x2F:
return XK_Help;
case 0x6A:
return XK_KP_Multiply;
case 0x6B:
return XK_KP_Add;
case 0x6C:
return XK_KP_Separator;
case 0x6D:
return XK_KP_Subtract;
case 0x6E:
return XK_KP_Decimal;
case 0x6F:
return XK_KP_Divide;
case 0x90:
return XK_Num_Lock;
case 0x91:
return XK_Scroll_Lock;
case 0xA0:
return XK_Shift_L;
case 0xA1:
return XK_Shift_R;
case 0xA2:
return XK_Control_L;
case 0xA3:
return XK_Control_R;
case 0xA4:
return XK_Alt_L;
case 0xA5: /* return XK_Alt_R; */
return XK_Super_L;
case 0x5B:
return XK_Super_L;
case 0x5C:
return XK_Super_R;
case 0xBA:
return XK_semicolon;
case 0xBB:
return XK_equal;
case 0xBC:
return XK_comma;
case 0xBD:
return XK_minus;
case 0xBE:
return XK_period;
case 0xBF:
return XK_slash;
case 0xC0:
return XK_grave;
case 0xDB:
return XK_bracketleft;
case 0xDC:
return XK_backslash;
case 0xDD:
return XK_bracketright;
case 0xDE:
return XK_apostrophe;
}
return modcode;
}
void keyboard(input_t &input, uint16_t modcode, bool release) {
auto &keyboard = ((input_raw_t*)input.get())->keyboard;
KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode));
auto &keyboard = ((input_raw_t *)input.get())->keyboard;
KeyCode kc = XKeysymToKeycode(keyboard.get(), keysym(modcode));
if(!kc) {
return;
@ -298,18 +338,18 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
}
int alloc_gamepad(input_t &input, int nr) {
return ((input_raw_t*)input.get())->alloc_gamepad(nr);
return ((input_raw_t *)input.get())->alloc_gamepad(nr);
}
void free_gamepad(input_t &input, int nr) {
((input_raw_t*)input.get())->clear_gamepad(nr);
((input_raw_t *)input.get())->clear_gamepad(nr);
}
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t*)input.get())->gamepads[nr]);
TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t *)input.get())->gamepads[nr]);
auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags;
auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags;
auto bf_new = gamepad_state.buttonFlags;
if(bf) {
@ -326,17 +366,17 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0X, button_state);
}
if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0);
if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);
if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0);
if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0);
if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0);
if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0);
if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0);
if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0);
if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0);
if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0);
if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0);
if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0);
if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0);
}
if(gamepad_state_old.lt != gamepad_state.lt) {
@ -368,7 +408,7 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
}
evdev_t mouse() {
evdev_t dev { libevdev_new() };
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Mouse");
libevdev_set_id_product(dev.get(), 0x4038);
@ -409,6 +449,48 @@ evdev_t mouse() {
return dev;
}
evdev_t touchscreen() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Touch");
libevdev_set_id_product(dev.get(), 0xDEAD);
libevdev_set_id_vendor(dev.get(), 0xBEEF);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Touchscreen passthrough");
libevdev_enable_property(dev.get(), INPUT_PROP_DIRECT);
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_PEN, nullptr); // Needed to be enabled for BTN_TOOL_FINGER to work.
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_FINGER, nullptr);
input_absinfo absx {
0,
0,
target_touch_port.width,
1,
0,
28
};
input_absinfo absy {
0,
0,
target_touch_port.height,
1,
0,
28
};
libevdev_enable_event_type(dev.get(), EV_ABS);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &absx);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &absy);
return dev;
}
evdev_t x360() {
evdev_t dev { libevdev_new() };
@ -471,7 +553,7 @@ evdev_t x360() {
input_t input() {
input_t result { new input_raw_t() };
auto &gp = *(input_raw_t*)result.get();
auto &gp = *(input_raw_t *)result.get();
gp.keyboard.reset(XOpenDisplay(nullptr));
@ -486,10 +568,11 @@ input_t input() {
// Ensure starting from clean slate
gp.clear();
gp.mouse_dev = mouse();
gp.touch_dev = touchscreen();
gp.mouse_dev = mouse();
gp.gamepad_dev = x360();
if(gp.create_mouse()) {
if(gp.create_mouse() || gp.create_touchscreen()) {
log_flush();
std::abort();
}
@ -498,9 +581,7 @@ input_t input() {
}
void freeInput(void *p) {
auto *input = (input_raw_t*)p;
auto *input = (input_raw_t *)p;
delete input;
}
std::unique_ptr<deinit_t> init() { return std::make_unique<deinit_t>(); }
}
} // namespace platf

View File

@ -0,0 +1,164 @@
// ----------------------------------------------------------------------------
// PolicyConfig.h
// Undocumented COM-interface IPolicyConfig.
// Use for set default audio render endpoint
// @author EreTIk
// http://eretik.omegahg.com/
// ----------------------------------------------------------------------------
#pragma once
#ifdef __MINGW32__
#undef DEFINE_GUID
#ifdef __cplusplus
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#else
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#endif
DEFINE_GUID(IID_IPolicyConfig, 0xf8679f50, 0x850a, 0x41cf, 0x9c, 0x72, 0x43, 0x0f, 0x29, 0x02, 0x90, 0xc8);
DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9);
#endif
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigClient
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
//
// interface IPolicyConfig
// {f8679f50-850a-41cf-9c72-430f290290c8}
//
// Query interface:
// CComPtr<IPolicyConfig> PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
//
// @compatible: Windows 7 and Later
// ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown {
public:
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64);
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64);
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *);
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT);
};
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista;
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigVistaClient
// {294935CE-F637-4E7C-A41B-AB255460B862}
//
// interface IPolicyConfigVista
// {568b9108-44bf-40b4-9006-86afe5b5a620}
//
// Query interface:
// CComPtr<IPolicyConfigVista> PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
//
// @compatible: Windows Vista and Later
// ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown {
public:
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT); // not available on Windows 7, use method from IPolicyConfig
};
// ----------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,14 @@
#ifndef SUNSHINE_DISPLAY_H
#define SUNSHINE_DISPLAY_H
#include <dxgi.h>
#include <d3d11.h>
#include <d3d11_4.h>
#include <d3dcommon.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include "sunshine/utility.h"
#include "sunshine/platform/common.h"
#include "sunshine/utility.h"
namespace platf::dxgi {
extern const char *format_str[];
@ -43,7 +43,7 @@ using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11Vide
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
}
} // namespace video
class hwdevice_t;
struct cursor_t {
@ -86,16 +86,14 @@ public:
DXGI_FORMAT format;
D3D_FEATURE_LEVEL feature_level;
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
{
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
}
D3DKMT_SCHEDULINGPRIORITYCLASS;
} D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
};
@ -123,8 +121,8 @@ public:
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height, pix_fmt_e pix_fmt) override;
gpu_cursor_t cursor;
std::vector<hwdevice_t*> hwdevices;
std::vector<hwdevice_t *> hwdevices;
};
}
} // namespace platf::dxgi
#endif

View File

@ -23,18 +23,18 @@ capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::ch
auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p);
switch(status) {
case S_OK:
has_frame = true;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
case S_OK:
has_frame = true;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
}
}
@ -52,20 +52,20 @@ capture_e duplication_t::release_frame() {
}
auto status = dup->ReleaseFrame();
switch (status) {
case S_OK:
has_frame = false;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
has_frame = false;
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
switch(status) {
case S_OK:
has_frame = false;
return capture_e::ok;
case DXGI_ERROR_WAIT_TIMEOUT:
return capture_e::timeout;
case WAIT_ABANDONED:
case DXGI_ERROR_ACCESS_LOST:
case DXGI_ERROR_ACCESS_DENIED:
has_frame = false;
return capture_e::reinit;
default:
BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view();
return capture_e::error;
}
}
@ -74,7 +74,7 @@ duplication_t::~duplication_t() {
}
int display_base_t::init() {
/* Uncomment when use of IDXGIOutput5 is implemented
/* Uncomment when use of IDXGIOutput5 is implemented
std::call_once(windows_cpp_once_flag, []() {
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4);
@ -93,7 +93,7 @@ int display_base_t::init() {
HRESULT status;
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory);
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
@ -102,7 +102,7 @@ int display_base_t::init() {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
auto adapter_name = converter.from_bytes(config::video.adapter_name);
auto output_name = converter.from_bytes(config::video.output_name);
auto output_name = converter.from_bytes(config::video.output_name);
adapter_t::pointer adapter_p;
for(int x = 0; factory->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) {
@ -129,8 +129,10 @@ int display_base_t::init() {
if(desc.AttachedToDesktop) {
output = std::move(output_tmp);
width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
offset_x = desc.DesktopCoordinates.left;
offset_y = desc.DesktopCoordinates.top;
width = desc.DesktopCoordinates.right - offset_x;
height = desc.DesktopCoordinates.bottom - offset_y;
}
}
@ -155,7 +157,7 @@ int display_base_t::init() {
D3D_FEATURE_LEVEL_9_1
};
status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p);
status = adapter->QueryInterface(IID_IDXGIAdapter, (void **)&adapter_p);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv;
return -1;
@ -193,7 +195,7 @@ int display_base_t::init() {
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl
<< "Capture size : "sv << width << 'x' << height;
<< "Capture size : "sv << width << 'x' << height;
// Bump up thread priority
{
@ -202,13 +204,13 @@ int display_base_t::init() {
HANDLE token;
LUID val;
if (OpenProcessToken(GetCurrentProcess(), flags, &token) &&
if(OpenProcessToken(GetCurrentProcess(), flags, &token) &&
!!LookupPrivilegeValue(NULL, SE_INC_BASE_PRIORITY_NAME, &val)) {
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = val;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = val;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) {
if(!AdjustTokenPrivileges(token, false, &tp, sizeof(tp), NULL, NULL)) {
BOOST_LOG(warning) << "Could not set privilege to increase GPU priority";
}
}
@ -216,19 +218,19 @@ int display_base_t::init() {
CloseHandle(token);
HMODULE gdi32 = GetModuleHandleA("GDI32");
if (gdi32) {
if(gdi32) {
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if (fn) {
if(fn) {
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to set realtime GPU priority. Please run application as administrator for optimal performance.";
}
}
}
dxgi::dxgi_t dxgi;
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi);
status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi);
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
@ -240,7 +242,7 @@ int display_base_t::init() {
// Try to reduce latency
{
dxgi::dxgi1_t dxgi {};
status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi);
status = device->QueryInterface(IID_IDXGIDevice, (void **)&dxgi);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
@ -256,7 +258,7 @@ int display_base_t::init() {
//TODO: Use IDXGIOutput5 for improved performance
{
dxgi::output1_t output1 {};
status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1);
status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv;
return -1;
@ -264,7 +266,7 @@ int display_base_t::init() {
// We try this twice, in case we still get an error on reinitialization
for(int x = 0; x < 2; ++x) {
status = output1->DuplicateOutput((IUnknown*)device.get(), &dup.dup);
status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup);
if(SUCCEEDED(status)) {
break;
}
@ -412,7 +414,7 @@ const char *format_str[] = {
"DXGI_FORMAT_V408"
};
}
} // namespace platf::dxgi
namespace platf {
std::shared_ptr<display_t> display(dev_type_e hwdevice_type) {
@ -433,4 +435,4 @@ std::shared_ptr<display_t> display(dev_type_e hwdevice_type) {
return nullptr;
}
}
} // namespace platf

View File

@ -1,12 +1,12 @@
#include "sunshine/main.h"
#include "display.h"
#include "sunshine/main.h"
namespace platf {
using namespace std::literals;
}
namespace platf::dxgi {
struct img_t : public ::platf::img_t {
struct img_t : public ::platf::img_t {
~img_t() override {
delete[] data;
data = nullptr;
@ -22,29 +22,29 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if(cursor_height > height || cursor_width > width) {
return;
}
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch;
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto pixels_per_byte = width / pitch;
auto bytes_per_row = delta_width / pixels_per_byte;
auto bytes_per_row = delta_width / pixels_per_byte;
auto img_data = (int*)img.data;
auto img_data = (int *)img.data;
for(int i = 0; i < delta_height; ++i) {
auto and_mask = &cursor_img_data[i * pitch];
auto xor_mask = &cursor_img_data[(i + height) * pitch];
@ -76,8 +76,8 @@ void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
}
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
auto colors_out = (std::uint8_t*)&cursor_pixel;
auto colors_in = (std::uint8_t*)img_pixel_p;
auto colors_out = (std::uint8_t *)&cursor_pixel;
auto colors_in = (std::uint8_t *)img_pixel_p;
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = colors_out[3];
@ -85,15 +85,15 @@ void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
*img_pixel_p = cursor_pixel;
}
else {
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255;
colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
}
}
void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
//TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = ((std::uint8_t*)&cursor_pixel)[3];
auto alpha = ((std::uint8_t *)&cursor_pixel)[3];
if(alpha == 0xFF) {
*img_pixel_p ^= cursor_pixel;
}
@ -111,30 +111,30 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
auto cursor_skip_y = -std::min(0, cursor.y);
auto cursor_skip_x = -std::min(0, cursor.x);
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
// img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data
auto cursor_truncate_y = std::max(0, cursor.y - img.height);
auto cursor_truncate_x = std::max(0, cursor.x - img.width);
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto img_skip_y = std::max(0, cursor.y);
auto img_skip_x = std::max(0, cursor.x);
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_width = width - cursor_skip_x - cursor_truncate_x;
auto cursor_height = height - cursor_skip_y - cursor_truncate_y;
if(cursor_height > height || cursor_width > width) {
return;
}
auto cursor_img_data = (int*)&cursor.img_data[cursor_skip_y * pitch];
auto cursor_img_data = (int *)&cursor.img_data[cursor_skip_y * pitch];
int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x));
auto img_data = (int*)img.data;
auto img_data = (int *)img.data;
for(int i = 0; i < delta_height; ++i) {
auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x];
auto cursor_end = &cursor_begin[delta_width];
auto cursor_end = &cursor_begin[delta_width];
auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x];
std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) {
@ -151,22 +151,22 @@ void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
void blend_cursor(const cursor_t &cursor, img_t &img) {
switch(cursor.shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
blend_cursor_color(cursor, img, false);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
blend_cursor_monochrome(cursor, img);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
blend_cursor_color(cursor, img, true);
break;
default:
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
blend_cursor_color(cursor, img, false);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:
blend_cursor_monochrome(cursor, img);
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
blend_cursor_color(cursor, img, true);
break;
default:
BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']';
}
}
capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
auto img = (img_t*)img_base;
auto img = (img_t *)img_base;
HRESULT status;
@ -174,9 +174,9 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
resource_t::pointer res_p {};
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
resource_t res{res_p};
resource_t res { res_p };
if (capture_status != capture_e::ok) {
if(capture_status != capture_e::ok) {
return capture_status;
}
@ -187,7 +187,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
UINT dummy;
status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
@ -195,18 +195,18 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
}
if(frame_info.LastMouseUpdateTime.QuadPart) {
cursor.x = frame_info.PointerPosition.Position.x;
cursor.y = frame_info.PointerPosition.Position.y;
cursor.x = frame_info.PointerPosition.Position.x;
cursor.y = frame_info.PointerPosition.Position.y;
cursor.visible = frame_info.PointerPosition.Visible;
}
// If frame has been updated
if (frame_info.LastPresentTime.QuadPart != 0) {
if(frame_info.LastPresentTime.QuadPart != 0) {
{
texture2d_t src {};
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
@ -221,14 +221,14 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
}
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}
const bool mouse_update =
const bool mouse_update =
(frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) &&
(cursor_visible && cursor.visible);
@ -238,7 +238,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
return capture_e::timeout;
}
std::copy_n((std::uint8_t*)img_info.pData, height * img_info.RowPitch, (std::uint8_t*)img->data);
std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data);
if(cursor_visible && cursor.visible) {
blend_cursor(cursor, *img);
@ -250,11 +250,11 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
std::shared_ptr<platf::img_t> display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>();
img->pixel_pitch = 4;
img->row_pitch = img_info.RowPitch;
img->width = width;
img->height = height;
img->data = new std::uint8_t[img->row_pitch * height];
img->pixel_pitch = 4;
img->row_pitch = img_info.RowPitch;
img->width = width;
img->height = height;
img->data = new std::uint8_t[img->row_pitch * height];
return img;
}
@ -269,14 +269,14 @@ int display_ram_t::init() {
}
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_STAGING;
t.Format = format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
t.Usage = D3D11_USAGE_STAGING;
t.Format = format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
auto status = device->CreateTexture2D(&t, nullptr, &texture);
@ -294,4 +294,4 @@ int display_ram_t::init() {
return 0;
}
}
} // namespace platf::dxgi

View File

@ -1,10 +1,12 @@
#include <cmath>
#include <codecvt>
#include <d3dcompiler.h>
#include <directxmath.h>
#include "sunshine/main.h"
#include "display.h"
#include "sunshine/main.h"
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders"
namespace platf {
@ -12,8 +14,6 @@ using namespace std::literals;
}
namespace platf::dxgi {
constexpr float aquamarine[] { 0.498039246f, 1.000000000f, 0.831372619f, 1.000000000f };
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
@ -30,7 +30,7 @@ using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3
using float4 = DirectX::XMFLOAT4;
using float3 = DirectX::XMFLOAT3;
using float2 = DirectX::XMFLOAT2;
struct __attribute__ ((__aligned__ (16))) color_t {
struct __attribute__((__aligned__(16))) color_t {
float4 color_vec_y;
float4 color_vec_u;
float4 color_vec_v;
@ -44,10 +44,10 @@ color_t make_color_matrix(float Cr, float Cb, float U_max, float V_max, float ad
float Cr_i = 1.0f - Cr;
float Cb_i = 1.0f - Cb;
float shift_y = range_Y.x / 256.0f;
float shift_y = range_Y.x / 256.0f;
float shift_uv = range_UV.x / 256.0f;
float scale_y = (range_Y.y - range_Y.x) / 256.0f;
float scale_y = (range_Y.y - range_Y.x) / 256.0f;
float scale_uv = (range_UV.y - range_UV.x) / 256.0f;
return {
{ Cr, Cg, Cb, add_Y },
@ -66,7 +66,7 @@ color_t colors[] {
};
template<class T>
buf_t make_buffer(device_t::pointer device, const T& t) {
buf_t make_buffer(device_t::pointer device, const T &t) {
static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment");
D3D11_BUFFER_DESC buffer_desc {
@ -91,18 +91,18 @@ buf_t make_buffer(device_t::pointer device, const T& t) {
blend_t make_blend(device_t::pointer device, bool enable) {
D3D11_BLEND_DESC bdesc {};
auto &rt = bdesc.RenderTarget[0];
rt.BlendEnable = enable;
auto &rt = bdesc.RenderTarget[0];
rt.BlendEnable = enable;
rt.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
if(enable) {
rt.BlendOp = D3D11_BLEND_OP_ADD;
rt.BlendOp = D3D11_BLEND_OP_ADD;
rt.BlendOpAlpha = D3D11_BLEND_OP_ADD;
rt.SrcBlend = D3D11_BLEND_SRC_ALPHA;
rt.SrcBlend = D3D11_BLEND_SRC_ALPHA;
rt.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
rt.SrcBlendAlpha = D3D11_BLEND_ZERO;
rt.SrcBlendAlpha = D3D11_BLEND_ZERO;
rt.DestBlendAlpha = D3D11_BLEND_ZERO;
}
@ -130,79 +130,79 @@ struct img_d3d_t : public platf::img_t {
~img_d3d_t() override = default;
};
util::buffer_t<std::uint8_t> make_cursor_image(util::buffer_t<std::uint8_t> &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t black = 0xFF000000;
constexpr std::uint32_t white = 0xFFFFFFFF;
util::buffer_t<std::uint8_t> make_cursor_image(util::buffer_t<std::uint8_t> &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t black = 0xFF000000;
constexpr std::uint32_t white = 0xFFFFFFFF;
constexpr std::uint32_t transparent = 0;
switch(shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
std::for_each((std::uint32_t*)std::begin(img_data), (std::uint32_t*)std::end(img_data), [](auto &pixel) {
if(pixel & 0xFF000000) {
pixel = transparent;
}
});
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
return std::move(img_data);
default:
break;
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:
std::for_each((std::uint32_t *)std::begin(img_data), (std::uint32_t *)std::end(img_data), [](auto &pixel) {
if(pixel & 0xFF000000) {
pixel = transparent;
}
});
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
return std::move(img_data);
default:
break;
}
shape_info.Height /= 2;
util::buffer_t<std::uint8_t> cursor_img { shape_info.Width * shape_info.Height * 4 };
auto bytes = shape_info.Pitch * shape_info.Height;
auto pixel_begin = (std::uint32_t*)std::begin(cursor_img);
auto pixel_data = pixel_begin;
auto and_mask = std::begin(img_data);
auto xor_mask = std::begin(img_data) + bytes;
auto bytes = shape_info.Pitch * shape_info.Height;
auto pixel_begin = (std::uint32_t *)std::begin(cursor_img);
auto pixel_data = pixel_begin;
auto and_mask = std::begin(img_data);
auto xor_mask = std::begin(img_data) + bytes;
for(auto x = 0; x < bytes; ++x) {
for(auto x = 0; x < bytes; ++x) {
for(auto c = 7; c >= 0; --c) {
auto bit = 1 << c;
auto bit = 1 << c;
auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0);
switch(color_type) {
case 0: //black
*pixel_data = black;
break;
case 2: //white
*pixel_data = white;
break;
case 1: //transparent
{
*pixel_data = transparent;
case 0: //black
*pixel_data = black;
break;
case 2: //white
*pixel_data = white;
break;
case 1: //transparent
{
*pixel_data = transparent;
break;
break;
}
case 3: //inverse
{
auto top_p = pixel_data - shape_info.Width;
auto left_p = pixel_data - 1;
auto right_p = pixel_data + 1;
auto bottom_p = pixel_data + shape_info.Width;
// Get the x coordinate of the pixel
auto column = (pixel_data - pixel_begin) % shape_info.Width != 0;
if(top_p >= pixel_begin && *top_p == transparent) {
*top_p = black;
}
case 3: //inverse
{
auto top_p = pixel_data - shape_info.Width;
auto left_p = pixel_data - 1;
auto right_p = pixel_data + 1;
auto bottom_p = pixel_data + shape_info.Width;
// Get the x coordinate of the pixel
auto column = (pixel_data - pixel_begin) % shape_info.Width != 0;
if(top_p >= pixel_begin && *top_p == transparent) {
*top_p = black;
}
if(column != 0 && left_p >= pixel_begin && *left_p == transparent) {
*left_p = black;
}
if(bottom_p < (std::uint32_t*)std::end(cursor_img)) {
*bottom_p = black;
}
if(column != shape_info.Width -1) {
*right_p = black;
}
*pixel_data = white;
if(column != 0 && left_p >= pixel_begin && *left_p == transparent) {
*left_p = black;
}
if(bottom_p < (std::uint32_t *)std::end(cursor_img)) {
*bottom_p = black;
}
if(column != shape_info.Width - 1) {
*right_p = black;
}
*pixel_data = white;
}
}
++pixel_data;
@ -225,7 +225,7 @@ blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) {
#endif
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
auto wFile = converter.from_bytes(file);
auto wFile = converter.from_bytes(file);
auto status = D3DCompileFromFile(wFile.c_str(), nullptr, nullptr, entrypoint, shader_model, flags, 0, &compiled_p, &msg_p);
if(msg_p) {
@ -251,7 +251,7 @@ blob_t compile_vertex_shader(LPCSTR file) {
class hwdevice_t : public platf::hwdevice_t {
public:
hwdevice_t(std::vector<hwdevice_t*> *hwdevices_p) : hwdevices_p { hwdevices_p } {}
hwdevice_t(std::vector<hwdevice_t *> *hwdevices_p) : hwdevices_p { hwdevices_p } {}
hwdevice_t() = delete;
void set_cursor_pos(LONG rel_x, LONG rel_y, bool visible) {
@ -271,7 +271,7 @@ public:
int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) {
auto device = (device_t::pointer)data;
cursor_view.Width = width;
cursor_view.Width = width;
cursor_view.Height = height;
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
@ -290,7 +290,7 @@ public:
}
int convert(platf::img_t &img_base) override {
auto &img = (img_d3d_t&)img_base;
auto &img = (img_d3d_t &)img_base;
if(!img.input_res) {
auto device = (device_t::pointer)data;
@ -329,35 +329,48 @@ public:
input_res_p = scene_sr.get();
}
_init_view_port(out_width, out_height);
_init_view_port(this->img.width, this->img.height);
device_ctx_p->OMSetRenderTargets(1, &nv12_Y_rt, nullptr);
device_ctx_p->VSSetShader(scene_vs.get(), nullptr, 0);
device_ctx_p->PSSetShader(convert_Y_ps.get(), nullptr, 0);
device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res);
device_ctx_p->Draw(3, 0);
device_ctx_p->RSSetViewports(1, &outY_view);
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
device_ctx_p->Draw(3, 0);
_init_view_port(out_width / 2, out_height / 2);
// Artifacts start appearing on the rendered image if Sunshine doesn't flush
// before rendering on the UV part of the image.
device_ctx_p->Flush();
_init_view_port(this->img.width / 2, this->img.height / 2);
device_ctx_p->OMSetRenderTargets(1, &nv12_UV_rt, nullptr);
device_ctx_p->VSSetShader(convert_UV_vs.get(), nullptr, 0);
device_ctx_p->PSSetShader(convert_UV_ps.get(), nullptr, 0);
device_ctx_p->PSSetShaderResources(0, 1, &back_img.input_res);
device_ctx_p->Draw(3, 0);
device_ctx_p->RSSetViewports(1, &outUV_view);
device_ctx_p->PSSetShaderResources(0, 1, &input_res_p);
device_ctx_p->Draw(3, 0);
device_ctx_p->Flush();
return 0;
}
void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override {
switch (colorspace) {
case 5: // SWS_CS_SMPTE170M
color_p = &colors[0];
break;
case 1: // SWS_CS_ITU709
color_p = &colors[2];
break;
case 9: // SWS_CS_BT2020
default:
BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv;
color_p = &colors[0];
switch(colorspace) {
case 5: // SWS_CS_SMPTE170M
color_p = &colors[0];
break;
case 1: // SWS_CS_ITU709
color_p = &colors[2];
break;
case 9: // SWS_CS_BT2020
default:
BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv;
color_p = &colors[0];
};
if(color_range > 1) {
@ -377,9 +390,8 @@ public:
int init(
std::shared_ptr<platf::display_t> display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p,
int in_width, int in_height, int out_width, int out_height,
pix_fmt_e pix_fmt
) {
int out_width, int out_height,
pix_fmt_e pix_fmt) {
HRESULT status;
device_p->AddRef();
@ -387,14 +399,26 @@ public:
this->device_ctx_p = device_ctx_p;
cursor_visible = false;
cursor_visible = false;
cursor_view.MinDepth = 0.0f;
cursor_view.MaxDepth = 1.0f;
platf::hwdevice_t::img = &img;
this->out_width = out_width;
this->out_height = out_height;
float in_width = display->width;
float in_height = display->height;
// // Ensure aspect ratio is maintained
auto scalar = std::fminf(out_width / in_width, out_height / in_height);
auto out_width_f = in_width * scalar;
auto out_height_f = in_height * scalar;
// result is always positive
auto offsetX = (out_width - out_width_f) / 2;
auto offsetY = (out_height - out_height_f) / 2;
outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f };
outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f };
status = device_p->CreateVertexShader(scene_vs_hlsl->GetBufferPointer(), scene_vs_hlsl->GetBufferSize(), nullptr, &scene_vs);
if(status) {
@ -427,7 +451,7 @@ public:
}
blend_disable = make_blend(device_p, false);
blend_enable = make_blend(device_p, true);
blend_enable = make_blend(device_p, true);
if(!blend_disable || !blend_enable) {
return -1;
@ -460,14 +484,14 @@ public:
&input_layout);
D3D11_TEXTURE2D_DESC t {};
t.Width = out_width;
t.Height = out_height;
t.MipLevels = 1;
t.ArraySize = 1;
t.Width = out_width;
t.Height = out_height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010;
t.BindFlags = D3D11_BIND_RENDER_TARGET;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010;
t.BindFlags = D3D11_BIND_RENDER_TARGET;
status = device_p->CreateTexture2D(&t, nullptr, &img.texture);
if(FAILED(status)) {
@ -475,11 +499,11 @@ public:
return -1;
}
img.display = std::move(display);
img.width = out_width;
img.height = out_height;
img.data = (std::uint8_t*)img.texture.get();
img.row_pitch = out_width;
img.display = std::move(display);
img.width = out_width;
img.height = out_height;
img.data = (std::uint8_t *)img.texture.get();
img.row_pitch = out_width;
img.pixel_pitch = 1;
D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc {
@ -494,20 +518,20 @@ public:
}
nv12_rt_desc.Format = DXGI_FORMAT_R8G8_UNORM;
status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt);
status = device_p->CreateRenderTargetView(img.texture.get(), &nv12_rt_desc, &nv12_UV_rt);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
D3D11_SAMPLER_DESC sampler_desc {};
sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampler_desc.MinLOD = 0;
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
sampler_desc.MinLOD = 0;
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
status = device_p->CreateSamplerState(&sampler_desc, &sampler_linear);
if(FAILED(status)) {
@ -515,6 +539,25 @@ public:
return -1;
}
// Color the background black, so that the padding for keeping the aspect ratio
// is black
if(img.display->dummy_img(&back_img)) {
BOOST_LOG(warning) << "Couldn't create an image to set background color to black"sv;
return -1;
}
D3D11_SHADER_RESOURCE_VIEW_DESC desc {
DXGI_FORMAT_B8G8R8A8_UNORM,
D3D11_SRV_DIMENSION_TEXTURE2D
};
desc.Texture2D.MipLevels = 1;
status = device_p->CreateShaderResourceView(back_img.texture.get(), &desc, &back_img.input_res);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create input shader resource view [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
}
device_ctx_p->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu);
device_ctx_p->PSSetSamplers(0, 1, &sampler_linear);
device_ctx_p->PSSetConstantBuffers(0, 1, &color_matrix);
@ -527,7 +570,7 @@ public:
~hwdevice_t() override {
if(data) {
((ID3D11Device*)data)->Release();
((ID3D11Device *)data)->Release();
}
auto it = std::find(std::begin(*hwdevices_p), std::end(*hwdevices_p), this);
@ -535,6 +578,7 @@ public:
hwdevices_p->erase(it);
}
}
private:
void _init_view_port(float x, float y, float width, float height) {
D3D11_VIEWPORT view {
@ -619,25 +663,29 @@ public:
img_d3d_t img;
// Clear nv12 render target to black
img_d3d_t back_img;
vs_t convert_UV_vs;
ps_t convert_UV_ps;
ps_t convert_Y_ps;
ps_t scene_ps;
vs_t scene_vs;
D3D11_VIEWPORT outY_view;
D3D11_VIEWPORT outUV_view;
D3D11_VIEWPORT cursor_view;
bool cursor_visible;
float out_width, out_height;
device_ctx_t::pointer device_ctx_p;
// The destructor will remove itself from the list of hardware devices, this is done synchronously
std::vector<hwdevice_t*> *hwdevices_p;
std::vector<hwdevice_t *> *hwdevices_p;
};
capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) {
auto img = (img_d3d_t*)img_base;
auto img = (img_d3d_t *)img_base;
HRESULT status;
@ -645,7 +693,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
resource_t::pointer res_p {};
auto capture_status = dup.next_frame(frame_info, timeout, &res_p);
resource_t res{ res_p };
resource_t res { res_p };
if(capture_status != capture_e::ok) {
return capture_status;
@ -653,7 +701,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0;
const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0;
const bool update_flag = mouse_update_flag || frame_update_flag;
const bool update_flag = mouse_update_flag || frame_update_flag;
if(!update_flag) {
return capture_e::timeout;
@ -666,7 +714,7 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
UINT dummy;
status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info);
if (FAILED(status)) {
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
@ -682,14 +730,14 @@ capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::millisec
// Create texture for cursor
D3D11_TEXTURE2D_DESC t {};
t.Width = shape_info.Width;
t.Height = cursor_img.size() / data.SysMemPitch;
t.MipLevels = 1;
t.ArraySize = 1;
t.Width = shape_info.Width;
t.Height = cursor_img.size() / data.SysMemPitch;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
texture2d_t texture;
auto status = device->CreateTexture2D(&t, &data, &texture);
@ -734,14 +782,14 @@ std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
auto img = std::make_shared<img_d3d_t>();
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = format;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = format;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
auto status = device->CreateTexture2D(&t, nullptr, &img->texture);
if(FAILED(status)) {
@ -749,7 +797,7 @@ std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
return nullptr;
}
img->data = (std::uint8_t*)img->texture.get();
img->data = (std::uint8_t *)img->texture.get();
img->row_pitch = 0;
img->pixel_pitch = 4;
img->width = 0;
@ -760,24 +808,25 @@ std::shared_ptr<platf::img_t> display_vram_t::alloc_img() {
}
int display_vram_t::dummy_img(platf::img_t *img_base) {
auto img = (img_d3d_t*)img_base;
auto img = (img_d3d_t *)img_base;
img->row_pitch = width * 4;
img->row_pitch = width * 4;
auto dummy_data = std::make_unique<int[]>(width * height);
D3D11_SUBRESOURCE_DATA data {
dummy_data.get(),
(UINT)img->row_pitch
};
std::fill_n(dummy_data.get(), width * height, 0);
D3D11_TEXTURE2D_DESC t {};
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.Width = width;
t.Height = height;
t.MipLevels = 1;
t.ArraySize = 1;
t.SampleDesc.Count = 1;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = format;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
t.Usage = D3D11_USAGE_DEFAULT;
t.Format = format;
t.BindFlags = D3D11_BIND_SHADER_RESOURCE;
dxgi::texture2d_t tex;
auto status = device->CreateTexture2D(&t, &data, &tex);
@ -787,7 +836,7 @@ int display_vram_t::dummy_img(platf::img_t *img_base) {
}
img->texture = std::move(tex);
img->data = (std::uint8_t*)img->texture.get();
img->data = (std::uint8_t *)img->texture.get();
img->height = height;
img->width = width;
img->pixel_pitch = 4;
@ -808,7 +857,6 @@ std::shared_ptr<platf::hwdevice_t> display_vram_t::make_hwdevice(int width, int
shared_from_this(),
device.get(),
device_ctx.get(),
this->width, this->height,
width, height,
pix_fmt);
@ -864,4 +912,4 @@ int init() {
return 0;
}
}
} // namespace platf::dxgi

View File

@ -1,11 +1,15 @@
#include <sstream>
#include <cmath>
#include <iomanip>
#include <sstream>
#include <ws2tcpip.h>
// prevent clang format from "optimizing" the header include order
// clang-format off
#include <winsock2.h>
#include <iphlpapi.h>
#include <windows.h>
#include <winuser.h>
#include <iphlpapi.h>
#include <ws2tcpip.h>
// clang-format on
#include <ViGEm/Client.h>
@ -17,7 +21,12 @@ using namespace std::literals;
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
volatile HDESK _lastKnownInputDesktop = NULL;
volatile HDESK _lastKnownInputDesktop = NULL;
constexpr touch_port_t target_touch_port {
0, 0,
65535, 65535
};
HDESK pairInputDesktop();
class vigem_t {
@ -94,11 +103,11 @@ std::string from_sockaddr(const sockaddr *const socket_address) {
auto family = socket_address->sa_family;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &((sockaddr_in6 *)socket_address)->sin6_addr, data, INET6_ADDRSTRLEN);
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)socket_address)->sin_addr, data, INET_ADDRSTRLEN);
inet_ntop(AF_INET, &((sockaddr_in *)socket_address)->sin_addr, data, INET_ADDRSTRLEN);
}
return std::string { data };
@ -110,13 +119,13 @@ std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_
auto family = ip_addr->sa_family;
std::uint16_t port;
if(family == AF_INET6) {
inet_ntop(AF_INET6, &((sockaddr_in6*)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
port = ((sockaddr_in6*)ip_addr)->sin6_port;
inet_ntop(AF_INET6, &((sockaddr_in6 *)ip_addr)->sin6_addr, data, INET6_ADDRSTRLEN);
port = ((sockaddr_in6 *)ip_addr)->sin6_port;
}
if(family == AF_INET) {
inet_ntop(AF_INET, &((sockaddr_in*)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
port = ((sockaddr_in*)ip_addr)->sin_port;
inet_ntop(AF_INET, &((sockaddr_in *)ip_addr)->sin_addr, data, INET_ADDRSTRLEN);
port = ((sockaddr_in *)ip_addr)->sin_port;
}
return { port, std::string { data } };
@ -157,7 +166,7 @@ std::string get_mac_address(const std::string_view &address) {
input_t input() {
input_t result { new vigem_t {} };
auto vigem = (vigem_t*)result.get();
auto vigem = (vigem_t *)result.get();
if(vigem->init()) {
return nullptr;
}
@ -165,61 +174,86 @@ input_t input() {
return result;
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
INPUT i {};
i.type = INPUT_MOUSE;
auto &mi = i.mi;
mi.dwFlags = MOUSEEVENTF_MOVE;
mi.dx = deltaX;
mi.dy = deltaY;
void send_input(INPUT &i) {
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
if (_lastKnownInputDesktop != hDesk) {
if(_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send mouse movement input"sv;
BOOST_LOG(warning) << "Couldn't send input"sv;
}
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
INPUT i {};
i.type = INPUT_MOUSE;
auto &mi = i.mi;
mi.dwFlags =
MOUSEEVENTF_MOVE |
MOUSEEVENTF_ABSOLUTE |
// MOUSEEVENTF_VIRTUALDESK maps to the entirety of the desktop rather than the primary desktop
MOUSEEVENTF_VIRTUALDESK;
auto scaled_x = std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width));
auto scaled_y = std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height));
mi.dx = scaled_x;
mi.dy = scaled_y;
send_input(i);
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
INPUT i {};
i.type = INPUT_MOUSE;
auto &mi = i.mi;
mi.dwFlags = MOUSEEVENTF_MOVE;
mi.dx = deltaX;
mi.dy = deltaY;
send_input(i);
}
void button_mouse(input_t &input, int button, bool release) {
constexpr auto KEY_STATE_DOWN = (SHORT)0x8000;
INPUT i {};
i.type = INPUT_MOUSE;
i.type = INPUT_MOUSE;
auto &mi = i.mi;
int mouse_button;
if(button == 1) {
mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN;
mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN;
mouse_button = VK_LBUTTON;
}
else if(button == 2) {
mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN;
mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN;
mouse_button = VK_MBUTTON;
}
else if(button == 3) {
mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN;
mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN;
mouse_button = VK_RBUTTON;
}
else if(button == 4) {
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.mouseData = XBUTTON1;
mouse_button = VK_XBUTTON1;
}
else {
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN;
mi.mouseData = XBUTTON2;
mouse_button = VK_XBUTTON2;
}
auto key_state = GetAsyncKeyState(mouse_button);
auto key_state = GetAsyncKeyState(mouse_button);
bool key_state_down = (key_state & KEY_STATE_DOWN) != 0;
if(key_state_down != release) {
BOOST_LOG(warning) << "Button state of mouse_button ["sv << button << "] does not match the desired state"sv;
@ -227,37 +261,19 @@ void button_mouse(input_t &input, int button, bool release) {
return;
}
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
if (_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send mouse button input"sv;
}
send_input(i);
}
void scroll(input_t &input, int distance) {
INPUT i {};
i.type = INPUT_MOUSE;
i.type = INPUT_MOUSE;
auto &mi = i.mi;
mi.dwFlags = MOUSEEVENTF_WHEEL;
mi.dwFlags = MOUSEEVENTF_WHEEL;
mi.mouseData = distance;
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
if (_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send mouse scroll input"sv;
}
send_input(i);
}
void keyboard(input_t &input, uint16_t modcode, bool release) {
@ -266,12 +282,12 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
}
INPUT i {};
i.type = INPUT_KEYBOARD;
i.type = INPUT_KEYBOARD;
auto &ki = i.ki;
// For some reason, MapVirtualKey(VK_LWIN, MAPVK_VK_TO_VSC) doesn't seem to work :/
if(modcode != VK_LWIN && modcode != VK_RWIN && modcode != VK_PAUSE) {
ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC);
ki.wScan = MapVirtualKey(modcode, MAPVK_VK_TO_VSC);
ki.dwFlags = KEYEVENTF_SCANCODE;
}
else {
@ -280,39 +296,30 @@ void keyboard(input_t &input, uint16_t modcode, bool release) {
// https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags
switch(modcode) {
case VK_RMENU:
case VK_RCONTROL:
case VK_INSERT:
case VK_DELETE:
case VK_HOME:
case VK_END:
case VK_PRIOR:
case VK_NEXT:
case VK_UP:
case VK_DOWN:
case VK_LEFT:
case VK_RIGHT:
case VK_DIVIDE:
ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
break;
default:
break;
case VK_RMENU:
case VK_RCONTROL:
case VK_INSERT:
case VK_DELETE:
case VK_HOME:
case VK_END:
case VK_PRIOR:
case VK_NEXT:
case VK_UP:
case VK_DOWN:
case VK_LEFT:
case VK_RIGHT:
case VK_DIVIDE:
ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
break;
default:
break;
}
if(release) {
ki.dwFlags |= KEYEVENTF_KEYUP;
}
retry:
auto send = SendInput(1, &i, sizeof(INPUT));
if(send != 1) {
auto hDesk = pairInputDesktop();
if (_lastKnownInputDesktop != hDesk) {
_lastKnownInputDesktop = hDesk;
goto retry;
}
BOOST_LOG(warning) << "Couldn't send keyboard input"sv;
}
send_input(i);
}
int alloc_gamepad(input_t &input, int nr) {
@ -320,7 +327,7 @@ int alloc_gamepad(input_t &input, int nr) {
return 0;
}
return ((vigem_t*)input.get())->alloc_x360(nr);
return ((vigem_t *)input.get())->alloc_x360(nr);
}
void free_gamepad(input_t &input, int nr) {
@ -328,7 +335,7 @@ void free_gamepad(input_t &input, int nr) {
return;
}
((vigem_t*)input.get())->free_target(nr);
((vigem_t *)input.get())->free_target(nr);
}
void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
// If there is no gamepad support
@ -336,7 +343,7 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
return;
}
auto vigem = (vigem_t*)input.get();
auto vigem = (vigem_t *)input.get();
auto &xusb = *(PXUSB_REPORT)&gamepad_state;
auto &x360 = vigem->x360s[nr];
@ -350,19 +357,20 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) {
}
}
int thread_priority() {
int thread_priority() {
return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1;
}
HDESK pairInputDesktop() {
auto hDesk = OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, FALSE, GENERIC_ALL);
if (NULL == hDesk) {
if(NULL == hDesk) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to OpenInputDesktop [0x"sv << util::hex(err).to_string_view() << ']';
}
else {
BOOST_LOG(info) << std::endl << "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']';
if (!SetThreadDesktop(hDesk) ) {
BOOST_LOG(info) << std::endl
<< "Opened desktop [0x"sv << util::hex(hDesk).to_string_view() << ']';
if(!SetThreadDesktop(hDesk)) {
auto err = GetLastError();
BOOST_LOG(error) << "Failed to SetThreadDesktop [0x"sv << util::hex(err).to_string_view() << ']';
}
@ -373,8 +381,8 @@ HDESK pairInputDesktop() {
}
void freeInput(void *p) {
auto vigem = (vigem_t*)p;
auto vigem = (vigem_t *)p;
delete vigem;
}
}
} // namespace platf

View File

@ -2,16 +2,18 @@
// Created by loki on 12/14/19.
//
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include "process.h"
#include <vector>
#include <string>
#include <vector>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include "utility.h"
#include "main.h"
#include "utility.h"
namespace proc {
using namespace std::literals;
@ -57,11 +59,11 @@ int proc_t::execute(int app_id) {
// Ensure starting from a clean slate
terminate();
_app_id = app_id;
_app_id = app_id;
auto &proc = _apps[app_id];
_undo_begin = std::begin(proc.prep_cmds);
_undo_it = _undo_begin;
_undo_it = _undo_begin;
if(!proc.output.empty() && proc.output != "null"sv) {
_pipe.reset(fopen(proc.output.c_str(), "a"));
@ -80,17 +82,31 @@ int proc_t::execute(int app_id) {
auto ret = exe(cmd, _env, _pipe, ec);
if(ec) {
BOOST_LOG(error) << "System: "sv << ec.message();
BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
return -1;
}
if(ret != 0) {
BOOST_LOG(error) << "Return code ["sv << ret << ']';
BOOST_LOG(error) << '[' << cmd << "] failed with code ["sv << ret << ']';
return -1;
}
}
BOOST_LOG(debug) << "Starting ["sv << proc.cmd << ']';
for(auto &cmd : proc.detached) {
BOOST_LOG(info) << "Spawning ["sv << cmd << ']';
if(proc.output.empty()) {
bp::spawn(cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
}
else {
bp::spawn(cmd, _env, bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
}
if(ec) {
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
}
}
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
if(proc.cmd.empty()) {
placebo = true;
}
@ -102,7 +118,7 @@ int proc_t::execute(int app_id) {
}
if(ec) {
BOOST_LOG(info) << "System: "sv << ec.message();
BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
return -1;
}
@ -133,7 +149,7 @@ void proc_t::terminate() {
std::abort();
}
for(;_undo_it != _undo_begin; --_undo_it) {
for(; _undo_it != _undo_begin; --_undo_it) {
auto &cmd = (_undo_it - 1)->undo_cmd;
if(cmd.empty()) {
@ -178,9 +194,11 @@ std::string_view::iterator find_match(std::string_view::iterator begin, std::str
do {
++begin;
switch(*begin) {
case '(': ++stack;
case '(':
++stack;
break;
case ')': --stack;
case ')':
--stack;
}
} while(begin != end && stack != 0);
@ -191,7 +209,7 @@ std::string_view::iterator find_match(std::string_view::iterator begin, std::str
}
std::string parse_env_val(bp::native_environment &env, const std::string_view &val_raw) {
auto pos = std::begin(val_raw);
auto pos = std::begin(val_raw);
auto dollar = std::find(pos, std::end(val_raw), '$');
std::stringstream ss;
@ -200,23 +218,23 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v
auto next = dollar + 1;
if(next != std::end(val_raw)) {
switch(*next) {
case '(': {
ss.write(pos, (dollar - pos));
auto var_begin = next + 1;
auto var_end = find_match(next, std::end(val_raw));
case '(': {
ss.write(pos, (dollar - pos));
auto var_begin = next + 1;
auto var_end = find_match(next, std::end(val_raw));
ss << env[std::string { var_begin, var_end }].to_string();
ss << env[std::string { var_begin, var_end }].to_string();
pos = var_end + 1;
next = var_end;
pos = var_end + 1;
next = var_end;
break;
}
case '$':
ss.write(pos, (next - pos));
pos = next + 1;
++next;
break;
break;
}
case '$':
ss.write(pos, (next - pos));
pos = next + 1;
++next;
break;
}
dollar = std::find(next, std::end(val_raw), '$');
@ -231,7 +249,7 @@ std::string parse_env_val(bp::native_environment &env, const std::string_view &v
return ss.str();
}
std::optional<proc::proc_t> parse(const std::string& file_name) {
std::optional<proc::proc_t> parse(const std::string &file_name) {
pt::ptree tree;
try {
@ -242,27 +260,27 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
auto this_env = boost::this_process::environment();
for(auto &[name,val] : env_vars) {
for(auto &[name, val] : env_vars) {
this_env[name] = parse_env_val(this_env, val.get_value<std::string>());
}
std::vector<proc::ctx_t> apps;
for(auto &[_,app_node] : apps_node) {
for(auto &[_, app_node] : apps_node) {
proc::ctx_t ctx;
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
auto output = app_node.get_optional<std::string>("output"s);
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
auto cmd = app_node.get_optional<std::string>("cmd"s);
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
auto output = app_node.get_optional<std::string>("output"s);
auto name = parse_env_val(this_env, app_node.get<std::string>("name"s));
auto cmd = app_node.get_optional<std::string>("cmd"s);
std::vector<proc::cmd_t> prep_cmds;
if(prep_nodes_opt) {
auto &prep_nodes = *prep_nodes_opt;
prep_cmds.reserve(prep_nodes.size());
for(auto &[_, prep_node] : prep_nodes) {
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto do_cmd = parse_env_val(this_env, prep_node.get<std::string>("do"s));
auto undo_cmd = prep_node.get_optional<std::string>("undo"s);
if(undo_cmd) {
@ -274,6 +292,16 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
}
}
std::vector<std::string> detached;
if(detached_nodes_opt) {
auto &detached_nodes = *detached_nodes_opt;
detached.reserve(detached_nodes.size());
for(auto &[_, detached_val] : detached_nodes) {
detached.emplace_back(parse_env_val(this_env, detached_val.get_value<std::string>()));
}
}
if(output) {
ctx.output = parse_env_val(this_env, *output);
}
@ -282,8 +310,9 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
ctx.cmd = parse_env_val(this_env, *cmd);
}
ctx.name = std::move(name);
ctx.name = std::move(name);
ctx.prep_cmds = std::move(prep_cmds);
ctx.detached = std::move(detached);
apps.emplace_back(std::move(ctx));
}
@ -291,7 +320,8 @@ std::optional<proc::proc_t> parse(const std::string& file_name) {
return proc::proc_t {
std::move(this_env), std::move(apps)
};
} catch (std::exception &e) {
}
catch(std::exception &e) {
BOOST_LOG(error) << e.what();
}
@ -310,4 +340,4 @@ void refresh(const std::string &file_name) {
proc = std::move(*proc_opt);
}
}
}
} // namespace proc

View File

@ -9,8 +9,8 @@
#define __kernel_entry
#endif
#include <unordered_map>
#include <optional>
#include <unordered_map>
#include <boost/process.hpp>
@ -30,6 +30,7 @@ struct cmd_t {
};
/*
* pre_cmds -- guaranteed to be executed unless any of the commands fail.
* detached -- commands detached from Sunshine
* cmd -- Runs indefinitely until:
* No session is running and a different set of commands it to be executed
* Command exits
@ -41,6 +42,14 @@ struct cmd_t {
struct ctx_t {
std::vector<cmd_t> prep_cmds;
/**
* Some applications, such as Steam,
* either exit quickly, or keep running indefinitely.
* Steam.exe is one such application.
* That is why some applications need be run and forgotten about
*/
std::vector<std::string> detached;
std::string name;
std::string cmd;
std::string output;
@ -52,10 +61,9 @@ public:
proc_t(
boost::process::environment &&env,
std::vector<ctx_t> &&apps) :
_app_id(-1),
_env(std::move(env)),
_apps(std::move(apps)) {}
std::vector<ctx_t> &&apps) : _app_id(-1),
_env(std::move(env)),
_apps(std::move(apps)) {}
int execute(int app_id);
@ -68,7 +76,7 @@ public:
const std::vector<ctx_t> &get_apps() const;
std::vector<ctx_t> &get_apps();
void terminate();
private:
@ -89,8 +97,8 @@ private:
};
void refresh(const std::string &file_name);
std::optional<proc::proc_t> parse(const std::string& file_name);
std::optional<proc::proc_t> parse(const std::string &file_name);
extern proc_t proc;
}
} // namespace proc
#endif //SUNSHINE_PROCESS_H

View File

@ -10,12 +10,12 @@ public:
typedef T iterator;
typedef typename std::iterator<std::random_access_iterator_tag, V>::value_type class_t;
typedef class_t& reference;
typedef class_t* pointer;
typedef class_t &reference;
typedef class_t *pointer;
typedef std::ptrdiff_t diff_t;
iterator operator += (diff_t step) {
iterator operator+=(diff_t step) {
while(step-- > 0) {
++_this();
}
@ -23,7 +23,7 @@ public:
return _this();
}
iterator operator -= (diff_t step) {
iterator operator-=(diff_t step) {
while(step-- > 0) {
--_this();
}
@ -31,19 +31,19 @@ public:
return _this();
}
iterator operator +(diff_t step) {
iterator operator+(diff_t step) {
iterator new_ = _this();
return new_ += step;
}
iterator operator -(diff_t step) {
iterator operator-(diff_t step) {
iterator new_ = _this();
return new_ -= step;
}
diff_t operator -(iterator first) {
diff_t operator-(iterator first) {
diff_t step = 0;
while(first != _this()) {
++step;
@ -53,8 +53,14 @@ public:
return step;
}
iterator operator++() { _this().inc(); return _this(); }
iterator operator--() { _this().dec(); return _this(); }
iterator operator++() {
_this().inc();
return _this();
}
iterator operator--() {
_this().dec();
return _this();
}
iterator operator++(int) {
iterator new_ = _this();
@ -78,35 +84,35 @@ public:
pointer operator->() { return &*_this(); }
const pointer operator->() const { return &*_this(); }
bool operator != (const iterator &other) const {
bool operator!=(const iterator &other) const {
return !(_this() == other);
}
bool operator < (const iterator &other) const {
bool operator<(const iterator &other) const {
return !(_this() >= other);
}
bool operator >= (const iterator &other) const {
bool operator>=(const iterator &other) const {
return _this() == other || _this() > other;
}
bool operator <= (const iterator &other) const {
bool operator<=(const iterator &other) const {
return _this() == other || _this() < other;
}
bool operator == (const iterator &other) const { return _this().eq(other); };
bool operator > (const iterator &other) const { return _this().gt(other); }
private:
bool operator==(const iterator &other) const { return _this().eq(other); };
bool operator>(const iterator &other) const { return _this().gt(other); }
iterator &_this() { return *static_cast<iterator*>(this); }
const iterator &_this() const { return *static_cast<const iterator*>(this); }
private:
iterator &_this() { return *static_cast<iterator *>(this); }
const iterator &_this() const { return *static_cast<const iterator *>(this); }
};
template<class V, class It>
class round_robin_t : public it_wrap_t<V, round_robin_t<V, It>> {
public:
using iterator = It;
using pointer = V*;
using pointer = V *;
round_robin_t(iterator begin, iterator end) : _begin(begin), _end(end), _pos(begin) {}
@ -119,10 +125,10 @@ public:
}
void dec() {
if(_pos == _begin) {
if(_pos == _begin) {
_pos = _end;
}
--_pos;
}
@ -133,6 +139,7 @@ public:
pointer get() const {
return &*_pos;
}
private:
It _begin;
It _end;
@ -144,6 +151,6 @@ template<class V, class It>
round_robin_t<V, It> make_round_robin(It begin, It end) {
return round_robin_t<V, It>(begin, end);
}
}
} // namespace util
#endif

View File

@ -7,10 +7,10 @@ extern "C" {
}
#include "config.h"
#include "input.h"
#include "main.h"
#include "network.h"
#include "rtsp.h"
#include "input.h"
#include "stream.h"
#include "sync.h"
@ -25,7 +25,7 @@ namespace stream {
//FIXME: Quick and dirty workaround for bug in MinGW 9.3 causing a linker error when using std::to_string
template<class T>
std::string to_string(T && t) {
std::string to_string(T &&t) {
std::stringstream ss;
ss << std::forward<T>(t);
return ss.str();
@ -41,11 +41,11 @@ void free_msg(PRTSP_MESSAGE msg) {
class rtsp_server_t;
using msg_t = util::safe_ptr<RTSP_MESSAGE, free_msg>;
using cmd_func_t = std::function<void(rtsp_server_t*, net::peer_t, msg_t&&)>;
using msg_t = util::safe_ptr<RTSP_MESSAGE, free_msg>;
using cmd_func_t = std::function<void(rtsp_server_t *, net::peer_t, msg_t &&)>;
void print_msg(PRTSP_MESSAGE msg);
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t&& req);
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t &&req);
class rtsp_server_t {
public:
@ -69,6 +69,7 @@ public:
}
void session_raise(launch_session_t launch_session) {
//FIXME: If client abandons us at this stage, _slot_count won't be raised again.
--_slot_count;
launch_event.raise(launch_session);
}
@ -82,61 +83,61 @@ public:
ENetEvent event;
auto res = enet_host_service(_host.get(), &event, std::chrono::floor<std::chrono::milliseconds>(timeout).count());
if (res > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE: {
net::packet_t packet{event.packet};
net::peer_t peer{event.peer};
if(res > 0) {
switch(event.type) {
case ENET_EVENT_TYPE_RECEIVE: {
net::packet_t packet { event.packet };
net::peer_t peer { event.peer };
msg_t req { new msg_t::element_type };
msg_t req { new msg_t::element_type };
//TODO: compare addresses of the peers
if (_queue_packet.second == nullptr) {
parseRtspMessage(req.get(), (char *) packet->data, packet->dataLength);
for (auto option = req->options; option != nullptr; option = option->next) {
if ("Content-length"sv == option->option) {
_queue_packet = std::make_pair(peer, std::move(packet));
return;
}
//TODO: compare addresses of the peers
if(_queue_packet.second == nullptr) {
parseRtspMessage(req.get(), (char *)packet->data, packet->dataLength);
for(auto option = req->options; option != nullptr; option = option->next) {
if("Content-length"sv == option->option) {
_queue_packet = std::make_pair(peer, std::move(packet));
return;
}
}
else {
std::vector<char> full_payload;
auto old_msg = std::move(_queue_packet);
auto &old_packet = old_msg.second;
std::string_view new_payload{(char *) packet->data, packet->dataLength};
std::string_view old_payload{(char *) old_packet->data, old_packet->dataLength};
full_payload.resize(new_payload.size() + old_payload.size());
std::copy(std::begin(old_payload), std::end(old_payload), std::begin(full_payload));
std::copy(std::begin(new_payload), std::end(new_payload), std::begin(full_payload) + old_payload.size());
parseRtspMessage(req.get(), full_payload.data(), full_payload.size());
}
print_msg(req.get());
msg_t resp;
auto func = _map_cmd_cb.find(req->message.request.command);
if (func != std::end(_map_cmd_cb)) {
func->second(this, peer, std::move(req));
}
else {
cmd_not_found(host(), peer, std::move(req));
}
return;
}
case ENET_EVENT_TYPE_CONNECT:
BOOST_LOG(info) << "CLIENT CONNECTED TO RTSP"sv;
break;
case ENET_EVENT_TYPE_DISCONNECT:
BOOST_LOG(info) << "CLIENT DISCONNECTED FROM RTSP"sv;
break;
case ENET_EVENT_TYPE_NONE:
break;
else {
std::vector<char> full_payload;
auto old_msg = std::move(_queue_packet);
auto &old_packet = old_msg.second;
std::string_view new_payload { (char *)packet->data, packet->dataLength };
std::string_view old_payload { (char *)old_packet->data, old_packet->dataLength };
full_payload.resize(new_payload.size() + old_payload.size());
std::copy(std::begin(old_payload), std::end(old_payload), std::begin(full_payload));
std::copy(std::begin(new_payload), std::end(new_payload), std::begin(full_payload) + old_payload.size());
parseRtspMessage(req.get(), full_payload.data(), full_payload.size());
}
print_msg(req.get());
msg_t resp;
auto func = _map_cmd_cb.find(req->message.request.command);
if(func != std::end(_map_cmd_cb)) {
func->second(this, peer, std::move(req));
}
else {
cmd_not_found(host(), peer, std::move(req));
}
return;
}
case ENET_EVENT_TYPE_CONNECT:
BOOST_LOG(info) << "CLIENT CONNECTED TO RTSP"sv;
break;
case ENET_EVENT_TYPE_DISCONNECT:
BOOST_LOG(info) << "CLIENT DISCONNECTED FROM RTSP"sv;
break;
case ENET_EVENT_TYPE_NONE:
break;
}
}
}
@ -149,7 +150,7 @@ public:
auto lg = _session_slots.lock();
for(auto &slot : *_session_slots) {
if (slot && (all || session::state(*slot) == session::state_e::STOPPING)) {
if(slot && (all || session::state(*slot) == session::state_e::STOPPING)) {
session::stop(*slot);
session::join(*slot);
@ -194,7 +195,6 @@ public:
safe::event_t<launch_session_t> launch_event;
private:
// named _queue_packet because I want to make it an actual queue
// It's like this for convenience sake
std::pair<net::peer_t, net::packet_t> _queue_packet;
@ -225,11 +225,11 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) {
auto payload = std::make_pair(resp->payload, resp->payloadLength);
auto lg = util::fail_guard([&]() {
resp->payload = payload.first;
resp->payload = payload.first;
resp->payloadLength = payload.second;
});
resp->payload = nullptr;
resp->payload = nullptr;
resp->payloadLength = 0;
int serialized_len;
@ -264,45 +264,69 @@ void respond(net::host_t::pointer host, net::peer_t peer, msg_t &resp) {
void respond(net::host_t::pointer host, net::peer_t peer, POPTION_ITEM options, int statuscode, const char *status_msg, int seqn, const std::string_view &payload) {
msg_t resp { new msg_t::element_type };
createRtspResponse(resp.get(), nullptr, 0, const_cast<char*>("RTSP/1.0"), statuscode, const_cast<char*>(status_msg), seqn, options, const_cast<char*>(payload.data()), (int)payload.size());
createRtspResponse(resp.get(), nullptr, 0, const_cast<char *>("RTSP/1.0"), statuscode, const_cast<char *>(status_msg), seqn, options, const_cast<char *>(payload.data()), (int)payload.size());
respond(host, peer, resp);
}
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t&& req) {
void cmd_not_found(net::host_t::pointer host, net::peer_t peer, msg_t &&req) {
respond(host, peer, nullptr, 404, "NOT FOUND", req->sequenceNumber, {});
}
void cmd_option(rtsp_server_t *server, net::peer_t peer, msg_t&& req) {
void cmd_option(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char*>("CSeq");
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str());
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {});
}
void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t&& req) {
void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char*>("CSeq");
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str());
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
std::string_view payload;
if(config::video.hevc_mode == 1) {
payload = "surround-params=NONE"sv;
}
else {
payload = "sprop-parameter-sets=AAAAAU;surround-params=NONE"sv;
std::stringstream ss;
if(config::video.hevc_mode != 1) {
ss << "sprop-parameter-sets=AAAAAU"sv << std::endl;
}
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, payload);
for(int x = 0; x < audio::MAX_STREAM_CONFIG; ++x) {
auto &stream_config = audio::stream_configs[x];
std::uint8_t mapping[platf::speaker::MAX_SPEAKERS];
auto mapping_p = stream_config.mapping;
/**
* GFE advertises incorrect mapping for normal quality configurations,
* as a result, Moonlight rotates all channels from index '3' to the right
* To work around this, rotate channels to the left from index '3'
*/
if(x == audio::SURROUND51 || x == audio::SURROUND71) {
std::copy_n(mapping_p, stream_config.channelCount, mapping);
std::rotate(mapping + 3, mapping + 4, mapping + audio::MAX_STREAM_CONFIG);
mapping_p = mapping;
}
ss << "a=fmtp:97 surround-params="sv << stream_config.channelCount << stream_config.streams << stream_config.coupledStreams;
std::for_each_n(mapping_p, stream_config.channelCount, [&ss](std::uint8_t digit) {
ss << (char)(digit + '0');
});
ss << std::endl;
}
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, ss.str());
}
void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
@ -311,10 +335,10 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
auto &seqn = options[0];
auto &session_option = options[1];
seqn.option = const_cast<char*>("CSeq");
seqn.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
seqn.content = const_cast<char*>(seqn_str.c_str());
seqn.content = const_cast<char *>(seqn_str.c_str());
std::string_view target { req->message.request.target };
auto begin = std::find(std::begin(target), std::end(target), '=') + 1;
@ -324,8 +348,8 @@ void cmd_setup(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
if(type == "audio"sv) {
seqn.next = &session_option;
session_option.option = const_cast<char*>("Session");
session_option.content = const_cast<char*>("DEADBEEFCAFE;timeout = 90");
session_option.option = const_cast<char *>("Session");
session_option.content = const_cast<char *>("DEADBEEFCAFE;timeout = 90");
}
else if(type != "video"sv && type != "control"sv) {
cmd_not_found(server->host(), peer, std::move(req));
@ -340,10 +364,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char*>("CSeq");
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str());
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
if(!server->launch_event.peek()) {
// /launch has not been used
@ -362,10 +386,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
};
{
auto pos = std::begin(payload);
auto pos = std::begin(payload);
auto begin = pos;
while (pos != std::end(payload)) {
if (whitespace(*pos++)) {
while(pos != std::end(payload)) {
if(whitespace(*pos++)) {
lines.emplace_back(begin, pos - begin - 1);
while(pos != std::end(payload) && whitespace(*pos)) { ++pos; }
@ -386,10 +410,10 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
auto pos = line.find(':');
auto name = line.substr(2, pos - 2);
auto val = line.substr(pos + 1);
auto val = line.substr(pos + 1);
if(val[val.size() -1] == ' ') {
val = val.substr(0, val.size() -1);
if(val[val.size() - 1] == ' ') {
val = val.substr(0, val.size() - 1);
}
args.emplace(name, val);
}
@ -402,11 +426,16 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
args.try_emplace("x-nv-aqos.packetDuration"sv, "5"sv);
config_t config;
config.audio.flags[audio::config_t::HOST_AUDIO] = launch_session->host_audio;
try {
config.audio.channels = util::from_view(args.at("x-nv-audio.surround.numChannels"sv));
config.audio.mask = util::from_view(args.at("x-nv-audio.surround.channelMask"sv));
config.audio.packetDuration = util::from_view(args.at("x-nv-aqos.packetDuration"sv));
config.audio.flags[audio::config_t::HIGH_QUALITY] =
util::from_view(args.at("x-nv-audio.surround.AudioQuality"sv));
config.packetsize = util::from_view(args.at("x-nv-video[0].packetSize"sv));
config.monitor.height = util::from_view(args.at("x-nv-video[0].clientViewportHt"sv));
@ -418,8 +447,8 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
config.monitor.encoderCscMode = util::from_view(args.at("x-nv-video[0].encoderCscMode"sv));
config.monitor.videoFormat = util::from_view(args.at("x-nv-vqos[0].bitStreamFormat"sv));
config.monitor.dynamicRange = util::from_view(args.at("x-nv-video[0].dynamicRangeMode"sv));
} catch(std::out_of_range &) {
}
catch(std::out_of_range &) {
respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {});
return;
@ -442,7 +471,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
return;
}
if(session::start(*session, platf::from_sockaddr((sockaddr*)&peer->address.address))) {
if(session::start(*session, platf::from_sockaddr((sockaddr *)&peer->address.address))) {
BOOST_LOG(error) << "Failed to start a streaming session"sv;
server->clear(slot);
@ -457,10 +486,10 @@ void cmd_play(rtsp_server_t *server, net::peer_t peer, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
option.option = const_cast<char*>("CSeq");
option.option = const_cast<char *>("CSeq");
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char*>(seqn_str.c_str());
auto seqn_str = to_string(req->sequenceNumber);
option.content = const_cast<char *>(seqn_str.c_str());
respond(server->host(), peer, &option, 200, "OK", req->sequenceNumber, {});
}
@ -518,7 +547,7 @@ void print_msg(PRTSP_MESSAGE msg) {
BOOST_LOG(debug) << "status :: "sv << status;
}
else {
auto& req = msg->message.request;
auto &req = msg->message.request;
std::string_view command { req.command };
std::string_view target { req.target };
@ -534,6 +563,8 @@ void print_msg(PRTSP_MESSAGE msg) {
BOOST_LOG(debug) << name << " :: "sv << content;
}
BOOST_LOG(debug) << "---Begin MessageBuffer---"sv << std::endl << messageBuffer << std::endl << "---End MessageBuffer---"sv << std::endl;
}
BOOST_LOG(debug) << "---Begin MessageBuffer---"sv << std::endl
<< messageBuffer << std::endl
<< "---End MessageBuffer---"sv << std::endl;
}
} // namespace stream

View File

@ -14,6 +14,8 @@ namespace stream {
struct launch_session_t {
crypto::aes_t gcm_key;
crypto::aes_t iv;
bool host_audio;
};
void launch_session_raise(launch_session_t launch_session);
@ -21,6 +23,6 @@ int session_count();
void rtpThread(std::shared_ptr<safe::signal_t> shutdown_event);
}
} // namespace stream
#endif //SUNSHINE_RTSP_H

View File

@ -4,8 +4,8 @@
#include "process.h"
#include <queue>
#include <future>
#include <queue>
#include <fstream>
#include <openssl/err.h>
@ -15,14 +15,14 @@ extern "C" {
#include <rs.h>
}
#include "network.h"
#include "config.h"
#include "utility.h"
#include "stream.h"
#include "thread_safe.h"
#include "sync.h"
#include "input.h"
#include "main.h"
#include "network.h"
#include "stream.h"
#include "sync.h"
#include "thread_safe.h"
#include "utility.h"
#define IDX_START_A 0
#define IDX_REQUEST_IDR_FRAME 0
@ -45,7 +45,7 @@ static const short packetTypes[] = {
};
constexpr auto VIDEO_STREAM_PORT = 47998;
constexpr auto CONTROL_PORT = 47999;
constexpr auto CONTROL_PORT = 47999;
constexpr auto AUDIO_STREAM_PORT = 48000;
namespace asio = boost::asio;
@ -89,7 +89,7 @@ using rh_t = util::safe_ptr<reed_solomon, reed_solomon_release>;
using video_packet_t = util::c_ptr<video_packet_raw_t>;
using audio_packet_t = util::c_ptr<audio_packet_raw_t>;
using message_queue_t = std::shared_ptr<safe::queue_t<std::pair<std::uint16_t, std::string>>>;
using message_queue_t = std::shared_ptr<safe::queue_t<std::pair<std::uint16_t, std::string>>>;
using message_queue_queue_t = std::shared_ptr<safe::queue_t<std::tuple<socket_e, asio::ip::address, message_queue_t>>>;
static inline void while_starting_do_nothing(std::atomic<session::state_e> &state) {
@ -124,7 +124,7 @@ public:
// Therefore, iterate is implemented further down the source file
void iterate(std::chrono::milliseconds timeout);
void map(uint16_t type, std::function<void(session_t *, const std::string_view&)> cb) {
void map(uint16_t type, std::function<void(session_t *, const std::string_view &)> cb) {
_map_type_cb.emplace(type, std::move(cb));
}
@ -140,10 +140,10 @@ public:
}
// Callbacks
std::unordered_map<std::uint16_t, std::function<void(session_t *, const std::string_view&)>> _map_type_cb;
std::unordered_map<std::uint16_t, std::function<void(session_t *, const std::string_view &)>> _map_type_cb;
// Mapping ip:port to session
util::sync_t<std::unordered_multimap<std::string, std::pair<std::uint16_t, session_t*>>> _map_addr_session;
util::sync_t<std::unordered_multimap<std::string, std::pair<std::uint16_t, session_t *>>> _map_addr_session;
ENetAddress _addr;
net::host_t _host;
@ -210,7 +210,7 @@ static auto broadcast = safe::make_shared<broadcast_ctx_t>(start_broadcast, end_
safe::signal_t broadcast_shutdown_event;
session_t *control_server_t::get_session(const net::peer_t peer) {
TUPLE_2D(port, addr_string, platf::from_sockaddr_ex((sockaddr*)&peer->address.address));
TUPLE_2D(port, addr_string, platf::from_sockaddr_ex((sockaddr *)&peer->address.address));
auto lg = _map_addr_session.lock();
TUPLE_2D(begin, end, _map_addr_session->equal_range(addr_string));
@ -231,7 +231,7 @@ session_t *control_server_t::get_session(const net::peer_t peer) {
TUPLE_2D_REF(session_port, session_p, it->second);
session_p->control.peer = peer;
session_port = port;
session_port = port;
return session_p;
}
@ -246,7 +246,7 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) {
if(res > 0) {
auto session = get_session(event.peer);
if(!session) {
BOOST_LOG(warning) << "Rejected connection from ["sv << platf::from_sockaddr((sockaddr*)&event.peer->address.address) << "]: it's not properly set up"sv;
BOOST_LOG(warning) << "Rejected connection from ["sv << platf::from_sockaddr((sockaddr *)&event.peer->address.address) << "]: it's not properly set up"sv;
enet_peer_disconnect_now(event.peer, 0);
return;
@ -255,37 +255,37 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) {
session->pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
switch(event.type) {
case ENET_EVENT_TYPE_RECEIVE:
{
net::packet_t packet { event.packet };
case ENET_EVENT_TYPE_RECEIVE: {
net::packet_t packet { event.packet };
auto type = (std::uint16_t *)packet->data;
std::string_view payload { (char*)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) };
auto type = (std::uint16_t *)packet->data;
std::string_view payload { (char *)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) };
auto cb = _map_type_cb.find(*type);
if(cb == std::end(_map_type_cb)) {
BOOST_LOG(warning)
<< "type [Unknown] { "sv << util::hex(*type).to_string_view() << " }"sv << std::endl
<< "---data---"sv << std::endl << util::hex_vec(payload) << std::endl << "---end data---"sv;
}
else {
cb->second(session, payload);
}
auto cb = _map_type_cb.find(*type);
if(cb == std::end(_map_type_cb)) {
BOOST_LOG(warning)
<< "type [Unknown] { "sv << util::hex(*type).to_string_view() << " }"sv << std::endl
<< "---data---"sv << std::endl
<< util::hex_vec(payload) << std::endl
<< "---end data---"sv;
}
break;
case ENET_EVENT_TYPE_CONNECT:
BOOST_LOG(info) << "CLIENT CONNECTED"sv;
break;
case ENET_EVENT_TYPE_DISCONNECT:
BOOST_LOG(info) << "CLIENT DISCONNECTED"sv;
// No more clients to send video data to ^_^
if(session->state == session::state_e::RUNNING) {
session::stop(*session);
}
break;
case ENET_EVENT_TYPE_NONE:
break;
else {
cb->second(session, payload);
}
} break;
case ENET_EVENT_TYPE_CONNECT:
BOOST_LOG(info) << "CLIENT CONNECTED"sv;
break;
case ENET_EVENT_TYPE_DISCONNECT:
BOOST_LOG(info) << "CLIENT DISCONNECTED"sv;
// No more clients to send video data to ^_^
if(session->state == session::state_e::RUNNING) {
session::stop(*session);
}
break;
case ENET_EVENT_TYPE_NONE:
break;
}
}
}
@ -302,11 +302,11 @@ struct fec_t {
util::buffer_t<char> shards;
char *data(size_t el) {
return &shards[el*blocksize];
return &shards[el * blocksize];
}
std::string_view operator[](size_t el) const {
return { &shards[el*blocksize], blocksize };
return { &shards[el * blocksize], blocksize };
}
size_t size() const {
@ -314,38 +314,42 @@ struct fec_t {
}
};
fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage) {
static fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercentage) {
auto payload_size = payload.size();
auto pad = payload_size % blocksize != 0;
auto data_shards = payload_size / blocksize + (pad ? 1 : 0);
auto parity_shards = (data_shards * fecpercentage + 99) / 100;
auto nr_shards = data_shards + parity_shards;
auto nr_shards = data_shards + parity_shards;
if(nr_shards > DATA_SHARDS_MAX) {
BOOST_LOG(error)
BOOST_LOG(warning)
<< "Number of fragments for reed solomon exceeds DATA_SHARDS_MAX"sv << std::endl
<< nr_shards << " > "sv << DATA_SHARDS_MAX;
<< nr_shards << " > "sv << DATA_SHARDS_MAX
<< ", skipping error correction"sv;
return { 0 };
nr_shards = data_shards;
fecpercentage = 0;
}
util::buffer_t<char> shards { nr_shards * blocksize };
util::buffer_t<uint8_t*> shards_p { nr_shards };
util::buffer_t<uint8_t *> shards_p { nr_shards };
// copy payload + padding
auto next = std::copy(std::begin(payload), std::end(payload), std::begin(shards));
std::fill(next, std::end(shards), 0); // padding with zero
for(auto x = 0; x < nr_shards; ++x) {
shards_p[x] = (uint8_t*)&shards[x * blocksize];
shards_p[x] = (uint8_t *)&shards[x * blocksize];
}
// packets = parity_shards + data_shards
rs_t rs { reed_solomon_new(data_shards, parity_shards) };
if(data_shards + parity_shards <= DATA_SHARDS_MAX) {
// packets = parity_shards + data_shards
rs_t rs { reed_solomon_new(data_shards, parity_shards) };
reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize);
reed_solomon_encode(rs.get(), shards_p.begin(), nr_shards, blocksize);
}
return {
data_shards,
@ -355,11 +359,11 @@ fec_t encode(const std::string_view &payload, size_t blocksize, size_t fecpercen
std::move(shards)
};
}
}
} // namespace fec
template<class F>
std::vector<uint8_t> insert(uint64_t insert_size, uint64_t slice_size, const std::string_view &data, F &&f) {
auto pad = data.size() % slice_size != 0;
auto pad = data.size() % slice_size != 0;
auto elements = data.size() / slice_size + (pad ? 1 : 0);
std::vector<uint8_t> result;
@ -367,20 +371,20 @@ std::vector<uint8_t> insert(uint64_t insert_size, uint64_t slice_size, const std
auto next = std::begin(data);
for(auto x = 0; x < elements - 1; ++x) {
void *p = &result[x*(insert_size + slice_size)];
void *p = &result[x * (insert_size + slice_size)];
f(p, x, elements);
std::copy(next, next + slice_size, (char*)p + insert_size);
std::copy(next, next + slice_size, (char *)p + insert_size);
next += slice_size;
}
auto x = elements - 1;
void *p = &result[x*(insert_size + slice_size)];
auto x = elements - 1;
void *p = &result[x * (insert_size + slice_size)];
f(p, x, elements);
std::copy(next, std::end(data), (char*)p + insert_size);
std::copy(next, std::end(data), (char *)p + insert_size);
return result;
}
@ -389,7 +393,7 @@ std::vector<uint8_t> replace(const std::string_view &original, const std::string
std::vector<uint8_t> replaced;
auto begin = std::begin(original);
auto next = std::search(begin, std::end(original), std::begin(old), std::end(old));
auto next = std::search(begin, std::end(original), std::begin(old), std::end(old));
std::copy(begin, next, std::back_inserter(replaced));
std::copy(std::begin(_new), std::end(_new), std::back_inserter(replaced));
@ -408,8 +412,8 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
});
server->map(packetTypes[IDX_LOSS_STATS], [&](session_t *session, const std::string_view &payload) {
int32_t *stats = (int32_t*)payload.data();
auto count = stats[0];
int32_t *stats = (int32_t *)payload.data();
auto count = stats[0];
std::chrono::milliseconds t { stats[1] };
auto lastGoodFrame = stats[3];
@ -424,9 +428,9 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
});
server->map(packetTypes[IDX_INVALIDATE_REF_FRAMES], [&](session_t *session, const std::string_view &payload) {
auto frames = (std::int64_t *)payload.data();
auto frames = (std::int64_t *)payload.data();
auto firstFrame = frames[0];
auto lastFrame = frames[1];
auto lastFrame = frames[1];
BOOST_LOG(debug)
<< "type [IDX_INVALIDATE_REF_FRAMES]"sv << std::endl
@ -439,7 +443,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
server->map(packetTypes[IDX_INPUT_DATA], [&](session_t *session, const std::string_view &payload) {
BOOST_LOG(debug) << "type [IDX_INPUT_DATA]"sv;
int32_t tagged_cipher_length = util::endian::big(*(int32_t*)payload.data());
int32_t tagged_cipher_length = util::endian::big(*(int32_t *)payload.data());
std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length };
crypto::cipher_t cipher { session->gcm_key };
@ -498,7 +502,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se
payload[0] = packetTypes[IDX_TERMINATION];
payload[1] = reason;
server->send(std::string_view {(char*)payload.data(), payload.size()});
server->send(std::string_view { (char *)payload.data(), payload.size() });
auto lg = server->_map_addr_session.lock();
for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) {
@ -519,7 +523,7 @@ void recvThread(broadcast_ctx_t &ctx) {
auto &audio_sock = ctx.audio_sock;
auto &message_queue_queue = ctx.message_queue_queue;
auto &io = ctx.io;
auto &io = ctx.io;
udp::endpoint peer;
@ -532,28 +536,28 @@ void recvThread(broadcast_ctx_t &ctx) {
TUPLE_3D_REF(socket_type, addr, message_queue, *message_queue_opt);
switch(socket_type) {
case socket_e::video:
if(message_queue) {
peer_to_video_session.emplace(addr, message_queue);
}
else {
peer_to_video_session.erase(addr);
}
break;
case socket_e::audio:
if(message_queue) {
peer_to_audio_session.emplace(addr, message_queue);
}
else {
peer_to_audio_session.erase(addr);
}
break;
case socket_e::video:
if(message_queue) {
peer_to_video_session.emplace(addr, message_queue);
}
else {
peer_to_video_session.erase(addr);
}
break;
case socket_e::audio:
if(message_queue) {
peer_to_audio_session.emplace(addr, message_queue);
}
else {
peer_to_audio_session.erase(addr);
}
break;
}
}
};
auto recv_func_init = [&](udp::socket &sock, int buf_elem, std::map<asio::ip::address, message_queue_t> &peer_to_session) {
recv_func[buf_elem] = [&,buf_elem](const boost::system::error_code &ec, size_t bytes) {
recv_func[buf_elem] = [&, buf_elem](const boost::system::error_code &ec, size_t bytes) {
auto fg = util::fail_guard([&]() {
sock.async_receive_from(asio::buffer(buf[buf_elem]), peer, 0, recv_func[buf_elem]);
});
@ -601,20 +605,20 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
break;
}
auto session = (session_t*)packet->channel_data;
auto lowseq = session->video.lowseq;
auto session = (session_t *)packet->channel_data;
auto lowseq = session->video.lowseq;
std::string_view payload{(char *) packet->data, (size_t) packet->size};
std::string_view payload { (char *)packet->data, (size_t)packet->size };
std::vector<uint8_t> payload_new;
auto nv_packet_header = "\0017charss"sv;
std::copy(std::begin(nv_packet_header), std::end(nv_packet_header), std::back_inserter(payload_new));
std::copy(std::begin(payload), std::end(payload), std::back_inserter(payload_new));
payload = {(char *) payload_new.data(), payload_new.size()};
payload = { (char *)payload_new.data(), payload_new.size() };
// make sure moonlight recognizes the nalu code for IDR frames
if (packet->flags & AV_PKT_FLAG_KEY) {
if(packet->flags & AV_PKT_FLAG_KEY) {
// TODO: Not all encoders encode their IDR frames with the 4 byte NALU prefix
std::string_view frame_old = "\000\000\001e"sv;
std::string_view frame_new = "\000\000\000\001e"sv;
@ -624,27 +628,25 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
}
payload_new = replace(payload, frame_old, frame_new);
payload = {(char *) payload_new.data(), payload_new.size()};
payload = { (char *)payload_new.data(), payload_new.size() };
}
// insert packet headers
auto blocksize = session->config.packetsize + MAX_RTP_HEADER_SIZE;
auto blocksize = session->config.packetsize + MAX_RTP_HEADER_SIZE;
auto payload_blocksize = blocksize - sizeof(video_packet_raw_t);
auto fecPercentage = config::stream.fec_percentage;
payload_new = insert(sizeof(video_packet_raw_t), payload_blocksize,
payload, [&](void *p, int fecIndex, int end) {
payload, [&](void *p, int fecIndex, int end) {
video_packet_raw_t *video_packet = (video_packet_raw_t *)p;
video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA;
video_packet->packet.frameIndex = packet->pts;
video_packet->packet.flags = FLAG_CONTAINS_PIC_DATA;
video_packet->packet.frameIndex = packet->pts;
video_packet->packet.streamPacketIndex = ((uint32_t)lowseq + fecIndex) << 8;
video_packet->packet.fecInfo = (
fecIndex << 12 |
end << 22 |
fecPercentage << 4
);
video_packet->packet.fecInfo = (fecIndex << 12 |
end << 22 |
fecPercentage << 4);
if(fecIndex == 0) {
video_packet->packet.flags |= FLAG_SOF;
@ -654,11 +656,11 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
video_packet->packet.flags |= FLAG_EOF;
}
video_packet->rtp.header = FLAG_EXTENSION;
video_packet->rtp.header = FLAG_EXTENSION;
video_packet->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + fecIndex);
});
payload = {(char *) payload_new.data(), payload_new.size()};
payload = { (char *)payload_new.data(), payload_new.size() };
auto shards = fec::encode(payload, blocksize, fecPercentage);
if(shards.data_shards == 0) {
@ -666,17 +668,15 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
continue;
}
for (auto x = shards.data_shards; x < shards.size(); ++x) {
for(auto x = shards.data_shards; x < shards.size(); ++x) {
auto *inspect = (video_packet_raw_t *)shards.data(x);
inspect->packet.frameIndex = packet->pts;
inspect->packet.fecInfo = (
x << 12 |
shards.data_shards << 22 |
fecPercentage << 4
);
inspect->packet.fecInfo = (x << 12 |
shards.data_shards << 22 |
shards.percentage << 4);
inspect->rtp.header = FLAG_EXTENSION;
inspect->rtp.header = FLAG_EXTENSION;
inspect->rtp.sequenceNumber = util::endian::big<uint16_t>(lowseq + x);
}
@ -698,27 +698,27 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid
}
void audioBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, audio::packet_queue_t packets) {
while (auto packet = packets->pop()) {
while(auto packet = packets->pop()) {
if(shutdown_event->peek()) {
break;
}
TUPLE_2D_REF(channel_data, packet_data, *packet);
auto session = (session_t*)channel_data;
auto session = (session_t *)channel_data;
auto frame = session->audio.frame++;
audio_packet_t audio_packet { (audio_packet_raw_t*)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) };
audio_packet_t audio_packet { (audio_packet_raw_t *)malloc(sizeof(audio_packet_raw_t) + packet_data.size()) };
audio_packet->rtp.header = 0;
audio_packet->rtp.packetType = 97;
audio_packet->rtp.header = 0;
audio_packet->rtp.packetType = 97;
audio_packet->rtp.sequenceNumber = util::endian::big(frame);
audio_packet->rtp.timestamp = 0;
audio_packet->rtp.ssrc = 0;
audio_packet->rtp.timestamp = 0;
audio_packet->rtp.ssrc = 0;
std::copy(std::begin(packet_data), std::end(packet_data), audio_packet->payload());
sock.send_to(asio::buffer((char*)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer);
sock.send_to(asio::buffer((char *)audio_packet.get(), sizeof(audio_packet_raw_t) + packet_data.size()), session->audio.peer);
BOOST_LOG(verbose) << "Audio ["sv << frame << "] :: send..."sv;
}
@ -761,12 +761,12 @@ int start_broadcast(broadcast_ctx_t &ctx) {
return -1;
}
ctx.video_packets = std::make_shared<video::packet_queue_t::element_type>(30);
ctx.audio_packets = std::make_shared<audio::packet_queue_t::element_type>(30);
ctx.video_packets = std::make_shared<video::packet_queue_t::element_type>(30);
ctx.audio_packets = std::make_shared<audio::packet_queue_t::element_type>(30);
ctx.message_queue_queue = std::make_shared<message_queue_queue_t::element_type>(30);
ctx.video_thread = std::thread { videoBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.video_sock), ctx.video_packets };
ctx.audio_thread = std::thread { audioBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.audio_sock), ctx.audio_packets };
ctx.video_thread = std::thread { videoBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.video_sock), ctx.video_packets };
ctx.audio_thread = std::thread { audioBroadcastThread, &broadcast_shutdown_event, std::ref(ctx.audio_sock), ctx.audio_packets };
ctx.control_thread = std::thread { controlBroadcastThread, &broadcast_shutdown_event, &ctx.control_server };
ctx.recv_thread = std::thread { recvThread, std::ref(ctx) };
@ -842,7 +842,7 @@ void videoThread(session_t *session, std::string addr_str) {
while_starting_do_nothing(session->state);
auto addr = asio::ip::make_address(addr_str);
auto ref = broadcast.ref();
auto ref = broadcast.ref();
auto port = recv_ping(ref, socket_e::video, addr, config::stream.ping_timeout);
if(port < 0) {
return;
@ -864,7 +864,7 @@ void audioThread(session_t *session, std::string addr_str) {
auto addr = asio::ip::make_address(addr_str);
auto ref = broadcast.ref();
auto ref = broadcast.ref();
auto port = recv_ping(ref, socket_e::audio, addr, config::stream.ping_timeout);
if(port < 0) {
return;
@ -884,7 +884,7 @@ state_e state(session_t &session) {
void stop(session_t &session) {
while_starting_do_nothing(session.state);
auto expected = state_e::RUNNING;
auto expected = state_e::RUNNING;
auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING);
if(already_stopping) {
return;
@ -902,7 +902,7 @@ void join(session_t &session) {
session.controlEnd.view();
//Reset input on session stop to avoid stuck repeated keys
BOOST_LOG(debug) << "Resetting Input..."sv;
input::reset();
input::reset(session.input);
BOOST_LOG(debug) << "Session ended"sv;
}
@ -918,8 +918,8 @@ int start(session_t &session, const std::string &addr_string) {
session.pingTimeout = std::chrono::steady_clock::now() + config::stream.ping_timeout;
session.audioThread = std::thread {audioThread, &session, addr_string};
session.videoThread = std::thread {videoThread, &session, addr_string};
session.audioThread = std::thread { audioThread, &session, addr_string };
session.videoThread = std::thread { videoThread, &session, addr_string };
session.state.store(state_e::RUNNING, std::memory_order_relaxed);
@ -929,12 +929,12 @@ int start(session_t &session, const std::string &addr_string) {
std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypto::aes_t &iv) {
auto session = std::make_shared<session_t>();
session->config = config;
session->config = config;
session->gcm_key = gcm_key;
session->iv = iv;
session->iv = iv;
session->video.idr_events = std::make_shared<video::idr_event_t::element_type>();
session->video.lowseq = 0;
session->video.lowseq = 0;
session->audio.frame = 1;
@ -943,5 +943,5 @@ std::shared_ptr<session_t> alloc(config_t &config, crypto::aes_t &gcm_key, crypt
return session;
}
}
}
} // namespace session
} // namespace stream

View File

@ -7,9 +7,9 @@
#include <boost/asio.hpp>
#include "video.h"
#include "audio.h"
#include "crypto.h"
#include "video.h"
namespace stream {
struct session_t;
@ -18,7 +18,6 @@ struct config_t {
video::config_t monitor;
int packetsize;
bool sops;
std::optional<int> gcmap;
};
@ -35,9 +34,9 @@ int start(session_t &session, const std::string &addr_string);
void stop(session_t &session);
void join(session_t &session);
state_e state(session_t &session);
}
} // namespace session
extern safe::signal_t broadcast_shutdown_event;
}
} // namespace stream
#endif //SUNSHINE_STREAM_H

View File

@ -5,9 +5,9 @@
#ifndef SUNSHINE_SYNC_H
#define SUNSHINE_SYNC_H
#include <utility>
#include <mutex>
#include <array>
#include <mutex>
#include <utility>
namespace util {
@ -21,8 +21,8 @@ public:
return std::lock_guard { _lock };
}
template<class ...Args>
sync_t(Args&&... args) : raw {std::forward<Args>(args)... } {}
template<class... Args>
sync_t(Args &&...args) : raw { std::forward<Args>(args)... } {}
sync_t &operator=(sync_t &&other) noexcept {
std::lock(_lock, other._lock);
@ -84,11 +84,12 @@ public:
}
value_t raw;
private:
mutex_t _lock;
};
}
} // namespace util
#endif //T_MAN_SYNC_H

View File

@ -1,18 +1,18 @@
#ifndef KITTY_TASK_POOL_H
#define KITTY_TASK_POOL_H
#include <deque>
#include <vector>
#include <future>
#include <chrono>
#include <utility>
#include <deque>
#include <functional>
#include <future>
#include <mutex>
#include <type_traits>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
#include "utility.h"
#include "move_by_copy.h"
#include "utility.h"
namespace util {
class _ImplBase {
@ -29,8 +29,7 @@ class _Impl : public _ImplBase {
Function _func;
public:
_Impl(Function&& f) : _func(std::forward<Function>(f)) { }
_Impl(Function &&f) : _func(std::forward<Function>(f)) {}
void run() override {
_func();
@ -40,7 +39,7 @@ public:
class TaskPool {
public:
typedef std::unique_ptr<_ImplBase> __task;
typedef _ImplBase* task_id_t;
typedef _ImplBase *task_id_t;
typedef std::chrono::steady_clock::time_point __time_point;
@ -53,9 +52,10 @@ public:
timer_task_t(task_id_t task_id, std::future<R> &future) : task_id { task_id }, future { std::move(future) } {}
};
protected:
std::deque<__task> _tasks;
std::vector<std::pair<__time_point, __task>> _timer_tasks;
std::vector<std::pair<__time_point, __task>> _timer_tasks;
std::mutex _task_mutex;
public:
@ -70,8 +70,8 @@ public:
}
template<class Function, class... Args>
auto push(Function && newTask, Args &&... args) {
static_assert(std::is_invocable_v<Function, Args&&...>, "arguments don't match the function");
auto push(Function &&newTask, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>;
@ -81,12 +81,12 @@ public:
};
task_t task(std::move(bind));
auto future = task.get_future();
std::lock_guard<std::mutex> lg(_task_mutex);
_tasks.emplace_back(toRunnable(std::move(task)));
return future;
}
@ -107,14 +107,14 @@ public:
* @return an id to potentially delay the task
*/
template<class Function, class X, class Y, class... Args>
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&... args) {
static_assert(std::is_invocable_v<Function, Args&&...>, "arguments don't match the function");
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
static_assert(std::is_invocable_v<Function, Args &&...>, "arguments don't match the function");
using __return = std::invoke_result_t<Function, Args &&...>;
using task_t = std::packaged_task<__return()>;
__time_point time_point;
if constexpr (std::is_floating_point_v<X>) {
if constexpr(std::is_floating_point_v<X>) {
time_point = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
}
else {
@ -127,7 +127,7 @@ public:
task_t task(std::move(bind));
auto future = task.get_future();
auto future = task.get_future();
auto runnable = toRunnable(std::move(task));
task_id_t task_id = &*runnable;
@ -160,13 +160,14 @@ public:
}
// smaller time goes to the back
auto prev = it -1;
auto prev = it - 1;
while(it > _timer_tasks.cbegin()) {
if(std::get<0>(*it) > std::get<0>(*prev)) {
std::swap(*it, *prev);
}
--prev; --it;
--prev;
--it;
}
}
@ -201,20 +202,20 @@ public:
std::optional<__task> pop() {
std::lock_guard lg(_task_mutex);
if(!_tasks.empty()) {
__task task = std::move(_tasks.front());
_tasks.pop_front();
return std::move(task);
}
if(!_timer_tasks.empty() && std::get<0>(_timer_tasks.back()) <= std::chrono::steady_clock::now()) {
__task task = std::move(std::get<1>(_timer_tasks.back()));
_timer_tasks.pop_back();
return std::move(task);
}
return std::nullopt;
}
@ -233,12 +234,12 @@ public:
return std::get<0>(_timer_tasks.back());
}
private:
private:
template<class Function>
std::unique_ptr<_ImplBase> toRunnable(Function &&f) {
return std::make_unique<_Impl<Function>>(std::forward<Function&&>(f));
return std::make_unique<_Impl<Function>>(std::forward<Function &&>(f));
}
};
}
} // namespace util
#endif

View File

@ -1,8 +1,8 @@
#ifndef KITTY_THREAD_POOL_H
#define KITTY_THREAD_POOL_H
#include <thread>
#include "task_pool.h"
#include <thread>
namespace util {
/*
@ -12,32 +12,33 @@ namespace util {
class ThreadPool : public TaskPool {
public:
typedef TaskPool::__task __task;
private:
std::vector<std::thread> _thread;
std::condition_variable _cv;
std::mutex _lock;
bool _continue;
public:
ThreadPool() : _continue { false } {}
explicit ThreadPool(int threads) : _thread(threads), _continue { true } {
for (auto &t : _thread) {
for(auto &t : _thread) {
t = std::thread(&ThreadPool::_main, this);
}
}
~ThreadPool() noexcept {
if (!_continue) return;
if(!_continue) return;
stop();
join();
}
template<class Function, class... Args>
auto push(Function && newTask, Args &&... args) {
auto push(Function &&newTask, Args &&...args) {
std::lock_guard lg(_lock);
auto future = TaskPool::push(std::forward<Function>(newTask), std::forward<Args>(args)...);
@ -52,7 +53,7 @@ public:
}
template<class Function, class X, class Y, class... Args>
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&... args) {
auto pushDelayed(Function &&newTask, std::chrono::duration<X, Y> duration, Args &&...args) {
std::lock_guard lg(_lock);
auto future = TaskPool::pushDelayed(std::forward<Function>(newTask), duration, std::forward<Args>(args)...);
@ -79,15 +80,14 @@ public:
}
void join() {
for (auto & t : _thread) {
for(auto &t : _thread) {
t.join();
}
}
public:
void _main() {
while (_continue) {
while(_continue) {
if(auto task = this->pop()) {
(*task)->run();
}
@ -117,5 +117,5 @@ public:
}
}
};
}
} // namespace util
#endif

View File

@ -5,28 +5,28 @@
#ifndef SUNSHINE_THREAD_SAFE_H
#define SUNSHINE_THREAD_SAFE_H
#include <vector>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <vector>
#include "utility.h"
namespace safe {
template<class T>
class event_t {
public:
using status_t = util::optional_t<T>;
public:
template<class...Args>
template<class... Args>
void raise(Args &&...args) {
std::lock_guard lg { _lock };
if(!_continue) {
return;
}
if constexpr (std::is_same_v<std::optional<T>, status_t>) {
if constexpr(std::is_same_v<std::optional<T>, status_t>) {
_status = std::make_optional<T>(std::forward<Args>(args)...);
}
else {
@ -38,42 +38,42 @@ public:
// pop and view shoud not be used interchangebly
status_t pop() {
std::unique_lock ul{ _lock };
std::unique_lock ul { _lock };
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
while (!_status) {
while(!_status) {
_cv.wait(ul);
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
}
auto val = std::move(_status);
_status = util::false_v<status_t>;
_status = util::false_v<status_t>;
return val;
}
// pop and view shoud not be used interchangebly
template<class Rep, class Period>
status_t pop(std::chrono::duration<Rep, Period> delay) {
std::unique_lock ul{ _lock };
std::unique_lock ul { _lock };
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
while (!_status) {
if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
while(!_status) {
if(!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
return util::false_v<status_t>;
}
}
auto val = std::move(_status);
_status = util::false_v<status_t>;
_status = util::false_v<status_t>;
return val;
}
@ -81,14 +81,14 @@ public:
const status_t &view() {
std::unique_lock ul { _lock };
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
while (!_status) {
while(!_status) {
_cv.wait(ul);
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
}
@ -103,7 +103,7 @@ public:
}
void stop() {
std::lock_guard lg{ _lock };
std::lock_guard lg { _lock };
_continue = false;
@ -111,7 +111,7 @@ public:
}
void reset() {
std::lock_guard lg{ _lock };
std::lock_guard lg { _lock };
_continue = true;
@ -121,8 +121,8 @@ public:
[[nodiscard]] bool running() const {
return _continue;
}
private:
private:
bool _continue { true };
status_t _status { util::false_v<status_t> };
@ -131,14 +131,101 @@ private:
};
template<class T>
class queue_t {
class alarm_raw_t {
public:
using status_t = util::optional_t<T>;
alarm_raw_t() : _status { util::false_v<status_t> } {}
void ring(const status_t &status) {
std::lock_guard lg(_lock);
_status = status;
_cv.notify_one();
}
void ring(status_t &&status) {
std::lock_guard lg(_lock);
_status = std::move(status);
_cv.notify_one();
}
template<class Rep, class Period>
auto wait_for(const std::chrono::duration<Rep, Period> &rel_time) {
std::unique_lock ul(_lock);
return _cv.wait_for(ul, rel_time, [this]() { return (bool)status(); });
}
template<class Rep, class Period, class Pred>
auto wait_for(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
std::unique_lock ul(_lock);
return _cv.wait_for(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); });
}
template<class Rep, class Period>
auto wait_until(const std::chrono::duration<Rep, Period> &rel_time) {
std::unique_lock ul(_lock);
return _cv.wait_until(ul, rel_time, [this]() { return (bool)status(); });
}
template<class Rep, class Period, class Pred>
auto wait_until(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
std::unique_lock ul(_lock);
return _cv.wait_until(ul, rel_time, [this, &pred]() { return (bool)status() || pred(); });
}
auto wait() {
std::unique_lock ul(_lock);
_cv.wait(ul, [this]() { return (bool)status(); });
}
template<class Pred>
auto wait(Pred &&pred) {
std::unique_lock ul(_lock);
_cv.wait(ul, [this, &pred]() { return (bool)status() || pred(); });
}
const status_t &status() const {
return _status;
}
status_t &status() {
return _status;
}
void reset() {
_status = status_t {};
}
private:
std::mutex _lock;
std::condition_variable _cv;
status_t _status;
};
template<class T>
using alarm_t = std::shared_ptr<alarm_raw_t<T>>;
template<class T>
alarm_t<T> make_alarm() {
return std::make_shared<alarm_raw_t<T>>();
}
template<class T>
class queue_t {
public:
using status_t = util::optional_t<T>;
queue_t(std::uint32_t max_elements) : _max_elements { max_elements } {}
template<class ...Args>
void raise(Args &&... args) {
template<class... Args>
void raise(Args &&...args) {
std::lock_guard ul { _lock };
if(!_continue) {
@ -164,12 +251,12 @@ public:
status_t pop(std::chrono::duration<Rep, Period> delay) {
std::unique_lock ul { _lock };
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
while (_queue.empty()) {
if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
while(_queue.empty()) {
if(!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
return util::false_v<status_t>;
}
}
@ -183,14 +270,14 @@ public:
status_t pop() {
std::unique_lock ul { _lock };
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
while (_queue.empty()) {
while(_queue.empty()) {
_cv.wait(ul);
if (!_continue) {
if(!_continue) {
return util::false_v<status_t>;
}
}
@ -202,7 +289,6 @@ public:
}
std::vector<T> &unsafe() {
std::lock_guard { _lock };
return _queue;
}
@ -219,7 +305,6 @@ public:
}
private:
bool _continue { true };
std::uint32_t _max_elements;
@ -235,7 +320,7 @@ public:
using element_type = T;
using construct_f = std::function<int(element_type &)>;
using destruct_f = std::function<void(element_type &)>;
using destruct_f = std::function<void(element_type &)>;
struct ptr_t {
shared_t *owner;
@ -252,7 +337,7 @@ public:
return;
}
auto tmp = ptr.owner->ref();
auto tmp = ptr.owner->ref();
tmp.owner = nullptr;
}
@ -282,7 +367,7 @@ public:
}
}
operator bool () const {
operator bool() const {
return owner != nullptr;
}
@ -298,22 +383,22 @@ public:
}
element_type *get() const {
return reinterpret_cast<element_type*>(owner->_object_buf.data());
return reinterpret_cast<element_type *>(owner->_object_buf.data());
}
element_type *operator->() {
return reinterpret_cast<element_type*>(owner->_object_buf.data());
return reinterpret_cast<element_type *>(owner->_object_buf.data());
}
};
template<class FC, class FD>
shared_t(FC && fc, FD &&fd) : _construct { std::forward<FC>(fc) }, _destruct { std::forward<FD>(fd) } {}
shared_t(FC &&fc, FD &&fd) : _construct { std::forward<FC>(fc) }, _destruct { std::forward<FD>(fd) } {}
[[nodiscard]] ptr_t ref() {
std::lock_guard lg { _lock };
if(!_count) {
new(_object_buf.data()) element_type;
if(_construct(*reinterpret_cast<element_type*>(_object_buf.data()))) {
if(_construct(*reinterpret_cast<element_type *>(_object_buf.data()))) {
return ptr_t { nullptr };
}
}
@ -322,6 +407,7 @@ public:
return ptr_t { this };
}
private:
construct_f _construct;
destruct_f _destruct;
@ -340,6 +426,6 @@ auto make_shared(F_Construct &&fc, F_Destruct &&fd) {
}
using signal_t = event_t<bool>;
}
} // namespace safe
#endif //SUNSHINE_THREAD_SAFE_H

View File

@ -1,63 +1,71 @@
#ifndef UTILITY_H
#define UTILITY_H
#include <algorithm>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <optional>
#include <string_view>
#include <type_traits>
#include <variant>
#include <vector>
#include <memory>
#include <type_traits>
#include <algorithm>
#include <optional>
#include <mutex>
#include <condition_variable>
#include <string_view>
#define KITTY_WHILE_LOOP(x, y, z) { x;while(y) z }
#define KITTY_DECL_CONSTR(x)\
x(x&&) noexcept = default;\
x&operator=(x&&) noexcept = default;\
#define KITTY_WHILE_LOOP(x, y, z) \
{ \
x; \
while(y) z \
}
#define KITTY_DECL_CONSTR(x) \
x(x &&) noexcept = default; \
x &operator=(x &&) noexcept = default; \
x();
#define KITTY_DEFAULT_CONSTR(x)\
x(x&&) noexcept = default;\
x&operator=(x&&) noexcept = default;\
x() = default;
#define KITTY_DEFAULT_CONSTR(x) \
x(x &&) noexcept = default; \
x &operator=(x &&) noexcept = default; \
x() = default;
#define KITTY_DEFAULT_CONSTR_THROW(x)\
x(x&&) = default;\
x&operator=(x&&) = default;\
x() = default;
#define KITTY_DEFAULT_CONSTR_THROW(x) \
x(x &&) = default; \
x &operator=(x &&) = default; \
x() = default;
#define TUPLE_2D(a,b, expr)\
decltype(expr) a##_##b = expr;\
auto &a = std::get<0>(a##_##b);\
auto &b = std::get<1>(a##_##b)
#define TUPLE_2D(a, b, expr) \
decltype(expr) a##_##b = expr; \
auto &a = std::get<0>(a##_##b); \
auto &b = std::get<1>(a##_##b)
#define TUPLE_2D_REF(a,b, expr)\
auto &a##_##b = expr;\
auto &a = std::get<0>(a##_##b);\
auto &b = std::get<1>(a##_##b)
#define TUPLE_2D_REF(a, b, expr) \
auto &a##_##b = expr; \
auto &a = std::get<0>(a##_##b); \
auto &b = std::get<1>(a##_##b)
#define TUPLE_3D(a,b,c, expr)\
decltype(expr) a##_##b##_##c = expr;\
auto &a = std::get<0>(a##_##b##_##c);\
auto &b = std::get<1>(a##_##b##_##c);\
auto &c = std::get<2>(a##_##b##_##c)
#define TUPLE_3D(a, b, c, expr) \
decltype(expr) a##_##b##_##c = expr; \
auto &a = std::get<0>(a##_##b##_##c); \
auto &b = std::get<1>(a##_##b##_##c); \
auto &c = std::get<2>(a##_##b##_##c)
#define TUPLE_3D_REF(a,b,c, expr)\
auto &a##_##b##_##c = expr;\
auto &a = std::get<0>(a##_##b##_##c);\
auto &b = std::get<1>(a##_##b##_##c);\
auto &c = std::get<2>(a##_##b##_##c)
#define TUPLE_3D_REF(a, b, c, expr) \
auto &a##_##b##_##c = expr; \
auto &a = std::get<0>(a##_##b##_##c); \
auto &b = std::get<1>(a##_##b##_##c); \
auto &c = std::get<2>(a##_##b##_##c)
#define TUPLE_EL(a, b, expr) \
decltype(expr) a##_ = expr; \
auto &a = std::get<b>(a##_)
namespace util {
template<template<typename...> class X, class...Y>
template<template<typename...> class X, class... Y>
struct __instantiation_of : public std::false_type {};
template<template<typename...> class X, class... Y>
struct __instantiation_of<X, X<Y...>> : public std::true_type {};
template<template<typename...> class X, class T, class...Y>
template<template<typename...> class X, class T, class... Y>
static constexpr auto instantiation_of_v = __instantiation_of<X, T, Y...>::value;
template<bool V, class X, class Y>
@ -76,14 +84,16 @@ struct __either<false, X, Y> {
template<bool V, class X, class Y>
using either_t = typename __either<V, X, Y>::type;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
template<class T>
class FailGuard {
public:
FailGuard() = delete;
FailGuard(T && f) noexcept : _func { std::forward<T>(f) } {}
FailGuard(T &&f) noexcept : _func { std::forward<T>(f) } {}
FailGuard(FailGuard &&other) noexcept : _func { std::move(other._func) } {
this->failure = other.failure;
@ -103,12 +113,13 @@ public:
void disable() { failure = false; }
bool failure { true };
private:
T _func;
};
template<class T>
[[nodiscard]] auto fail_guard(T && f) {
[[nodiscard]] auto fail_guard(T &&f) {
return FailGuard<T> { std::forward<T>(f) };
}
@ -118,9 +129,9 @@ void append_struct(std::vector<uint8_t> &buf, const T &_struct) {
buf.reserve(data_len);
auto *data = (uint8_t *) & _struct;
auto *data = (uint8_t *)&_struct;
for (size_t x = 0; x < data_len; ++x) {
for(size_t x = 0; x < data_len; ++x) {
buf.push_back(data[x]);
}
}
@ -129,24 +140,26 @@ template<class T>
class Hex {
public:
typedef T elem_type;
private:
const char _bits[16] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
char _hex[sizeof(elem_type) * 2];
public:
Hex(const elem_type &elem, bool rev) {
if(!rev) {
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem) + sizeof(elem_type) - 1;
for (auto it = begin(); it < cend();) {
for(auto it = begin(); it < cend();) {
*it++ = _bits[*data / 16];
*it++ = _bits[*data-- % 16];
}
}
else {
const uint8_t *data = reinterpret_cast<const uint8_t *>(&elem);
for (auto it = begin(); it < cend();) {
for(auto it = begin(); it < cend();) {
*it++ = _bits[*data / 16];
*it++ = _bits[*data++ % 16];
}
@ -178,7 +191,7 @@ Hex<T> hex(const T &elem, bool rev = false) {
template<class It>
std::string hex_vec(It begin, It end, bool rev = false) {
auto str_size = 2*std::distance(begin, end);
auto str_size = 2 * std::distance(begin, end);
std::string hex;
@ -189,14 +202,14 @@ std::string hex_vec(It begin, It end, bool rev = false) {
};
if(rev) {
for (auto it = std::begin(hex); it < std::end(hex);) {
for(auto it = std::begin(hex); it < std::end(hex);) {
*it++ = _bits[((uint8_t)*begin) / 16];
*it++ = _bits[((uint8_t)*begin++) % 16];
}
}
else {
--end;
for (auto it = std::begin(hex); it < std::end(hex);) {
for(auto it = std::begin(hex); it < std::end(hex);) {
*it++ = _bits[((uint8_t)*end) / 16];
*it++ = _bits[((uint8_t)*end--) % 16];
}
@ -207,7 +220,7 @@ std::string hex_vec(It begin, It end, bool rev = false) {
}
template<class C>
std::string hex_vec(C&& c, bool rev = false) {
std::string hex_vec(C &&c, bool rev = false) {
return hex_vec(std::begin(c), std::end(c), rev);
}
@ -216,7 +229,7 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
std::uint8_t buf[sizeof(T)];
static char constexpr shift_bit = 'a' - 'A';
auto is_convertable = [] (char ch) -> bool {
auto is_convertable = [](char ch) -> bool {
if(isdigit(ch)) {
return true;
}
@ -235,9 +248,9 @@ std::optional<T> from_hex(const std::string_view &hex, bool rev = false) {
return std::nullopt;
}
const char *data = hex.data() + hex.size() -1;
const char *data = hex.data() + hex.size() - 1;
auto convert = [] (char ch) -> std::uint8_t {
auto convert = [](char ch) -> std::uint8_t {
if(ch >= '0' && ch <= '9') {
return (std::uint8_t)ch - '0';
}
@ -266,7 +279,7 @@ inline std::string from_hex_vec(const std::string &hex, bool rev = false) {
std::string buf;
static char constexpr shift_bit = 'a' - 'A';
auto is_convertable = [] (char ch) -> bool {
auto is_convertable = [](char ch) -> bool {
if(isdigit(ch)) {
return true;
}
@ -283,9 +296,9 @@ inline std::string from_hex_vec(const std::string &hex, bool rev = false) {
auto buf_size = std::count_if(std::begin(hex), std::end(hex), is_convertable) / 2;
buf.resize(buf_size);
const char *data = hex.data() + hex.size() -1;
const char *data = hex.data() + hex.size() - 1;
auto convert = [] (char ch) -> std::uint8_t {
auto convert = [](char ch) -> std::uint8_t {
if(ch >= '0' && ch <= '9') {
return (std::uint8_t)ch - '0';
}
@ -317,18 +330,18 @@ public:
std::size_t operator()(const value_type &value) const {
const auto *p = reinterpret_cast<const char *>(&value);
return std::hash<std::string_view>{}(std::string_view { p, sizeof(value_type) });
return std::hash<std::string_view> {}(std::string_view { p, sizeof(value_type) });
}
};
template<class T>
auto enm(const T& val) -> const std::underlying_type_t<T>& {
return *reinterpret_cast<const std::underlying_type_t<T>*>(&val);
auto enm(const T &val) -> const std::underlying_type_t<T> & {
return *reinterpret_cast<const std::underlying_type_t<T> *>(&val);
}
template<class T>
auto enm(T& val) -> std::underlying_type_t<T>& {
return *reinterpret_cast<std::underlying_type_t<T>*>(&val);
auto enm(T &val) -> std::underlying_type_t<T> & {
return *reinterpret_cast<std::underlying_type_t<T> *>(&val);
}
inline std::int64_t from_chars(const char *begin, const char *end) {
@ -377,11 +390,11 @@ public:
};
// Compared to std::unique_ptr, it adds the ability to get the address of the pointer itself
template <typename T, typename D = std::default_delete<T>>
template<typename T, typename D = std::default_delete<T>>
class uniq_ptr {
public:
using element_type = T;
using pointer = element_type*;
using pointer = element_type *;
using deleter_type = D;
constexpr uniq_ptr() noexcept : _p { nullptr } {}
@ -436,7 +449,7 @@ public:
pointer release() {
auto tmp = _p;
_p = nullptr;
_p = nullptr;
return tmp;
}
@ -468,69 +481,70 @@ public:
return &_p;
}
deleter_type& get_deleter() {
deleter_type &get_deleter() {
return _deleter;
}
const deleter_type& get_deleter() const {
const deleter_type &get_deleter() const {
return _deleter;
}
explicit operator bool() const {
return _p != nullptr;
}
protected:
pointer _p;
deleter_type _deleter;
};
template<class T1, class D1, class T2, class D2>
bool operator==(const uniq_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
bool operator==(const uniq_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const uniq_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
bool operator!=(const uniq_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() != y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator==(const std::unique_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
bool operator==(const std::unique_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const std::unique_ptr<T1, D1>& x, const uniq_ptr<T2, D2>& y) {
bool operator!=(const std::unique_ptr<T1, D1> &x, const uniq_ptr<T2, D2> &y) {
return x.get() != y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator==(const uniq_ptr<T1, D1>& x, const std::unique_ptr<T1, D1>& y) {
bool operator==(const uniq_ptr<T1, D1> &x, const std::unique_ptr<T1, D1> &y) {
return x.get() == y.get();
}
template<class T1, class D1, class T2, class D2>
bool operator!=(const uniq_ptr<T1, D1>& x, const std::unique_ptr<T1, D1>& y) {
bool operator!=(const uniq_ptr<T1, D1> &x, const std::unique_ptr<T1, D1> &y) {
return x.get() != y.get();
}
template<class T, class D>
bool operator==(const uniq_ptr<T, D>& x, std::nullptr_t) {
bool operator==(const uniq_ptr<T, D> &x, std::nullptr_t) {
return !(bool)x;
}
template<class T, class D>
bool operator!=(const uniq_ptr<T, D>& x, std::nullptr_t) {
bool operator!=(const uniq_ptr<T, D> &x, std::nullptr_t) {
return (bool)x;
}
template<class T, class D>
bool operator==(std::nullptr_t, const uniq_ptr<T, D>& y) {
bool operator==(std::nullptr_t, const uniq_ptr<T, D> &y) {
return !(bool)y;
}
template<class T, class D>
bool operator!=(std::nullptr_t, const uniq_ptr<T, D>& y) {
bool operator!=(std::nullptr_t, const uniq_ptr<T, D> &y) {
return (bool)y;
}
@ -538,8 +552,8 @@ template<class T>
class wrap_ptr {
public:
using element_type = T;
using pointer = element_type*;
using reference = element_type&;
using pointer = element_type *;
using reference = element_type &;
wrap_ptr() : _own_ptr { false }, _p { nullptr } {}
wrap_ptr(pointer p) : _own_ptr { false }, _p { p } {}
@ -555,7 +569,7 @@ public:
_p = other._p;
_own_ptr = other._own_ptr;
_own_ptr = other._own_ptr;
other._own_ptr = false;
return *this;
@ -565,7 +579,7 @@ public:
wrap_ptr &operator=(std::unique_ptr<V> &&uniq_ptr) {
static_assert(std::is_base_of_v<element_type, V>, "element_type must be base class of V");
_own_ptr = true;
_p = uniq_ptr.release();
_p = uniq_ptr.release();
return *this;
}
@ -575,7 +589,7 @@ public:
delete _p;
}
_p = p;
_p = p;
_own_ptr = false;
return *this;
@ -617,13 +631,11 @@ struct __false_v<T, std::enable_if_t<instantiation_of_v<std::optional, T>>> {
template<class T>
struct __false_v<T, std::enable_if_t<
(
std::is_pointer_v<T> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T>
)
>> {
(
std::is_pointer_v<T> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T>)>> {
static constexpr std::nullptr_t value = nullptr;
};
@ -638,18 +650,18 @@ static constexpr auto false_v = __false_v<T>::value;
template<class T>
using optional_t = either_t<
(std::is_same_v<T, bool> ||
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T> ||
std::is_pointer_v<T>),
instantiation_of_v<std::unique_ptr, T> ||
instantiation_of_v<std::shared_ptr, T> ||
instantiation_of_v<uniq_ptr, T> ||
std::is_pointer_v<T>),
T, std::optional<T>>;
template<class T>
class buffer_t {
public:
buffer_t() : _els { 0 } {};
buffer_t(buffer_t&&) noexcept = default;
buffer_t &operator=(buffer_t&& other) noexcept = default;
buffer_t(buffer_t &&) noexcept = default;
buffer_t &operator=(buffer_t &&other) noexcept = default;
explicit buffer_t(size_t elements) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {}
explicit buffer_t(size_t elements, const T &t) : _els { elements }, _buf { std::make_unique<T[]>(elements) } {
@ -703,7 +715,7 @@ T either(std::optional<T> &&l, T &&r) {
return std::forward<T>(r);
}
template<class ReturnType, class ...Args>
template<class ReturnType, class... Args>
struct Function {
typedef ReturnType (*type)(Args...);
};
@ -711,18 +723,18 @@ struct Function {
template<class T, class ReturnType, typename Function<ReturnType, T>::type function>
struct Destroy {
typedef T pointer;
void operator()(pointer p) {
function(p);
}
};
template<class T, typename Function<void, T*>::type function>
using safe_ptr = uniq_ptr<T, Destroy<T*, void, function>>;
template<class T, typename Function<void, T *>::type function>
using safe_ptr = uniq_ptr<T, Destroy<T *, void, function>>;
// You cannot specialize an alias
template<class T, class ReturnType, typename Function<ReturnType, T*>::type function>
using safe_ptr_v2 = uniq_ptr<T, Destroy<T*, ReturnType, function>>;
template<class T, class ReturnType, typename Function<ReturnType, T *>::type function>
using safe_ptr_v2 = uniq_ptr<T, Destroy<T *, ReturnType, function>>;
template<class T>
void c_free(T *p) {
@ -737,22 +749,22 @@ template<class T = void>
struct endianness {
enum : bool {
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
defined(__BIG_ENDIAN__) || \
defined(__ARMEB__) || \
defined(__THUMBEB__) || \
defined(__AARCH64EB__) || \
defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
defined(__BIG_ENDIAN__) || \
defined(__ARMEB__) || \
defined(__THUMBEB__) || \
defined(__AARCH64EB__) || \
defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
// It's a big-endian target architecture
little = false,
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
defined(__LITTLE_ENDIAN__) || \
defined(__ARMEL__) || \
defined(__THUMBEL__) || \
defined(__AARCH64EL__) || \
defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \
defined(_WIN32)
defined(__LITTLE_ENDIAN__) || \
defined(__ARMEL__) || \
defined(__THUMBEL__) || \
defined(__AARCH64EL__) || \
defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \
defined(_WIN32)
// It's a little-endian target architecture
little = true,
little = true,
#else
#error "Unknown Endianness"
#endif
@ -761,15 +773,14 @@ struct endianness {
};
template<class T, class S = void>
struct endian_helper { };
struct endian_helper {};
template<class T>
struct endian_helper<T, std::enable_if_t<
!(instantiation_of_v<std::optional, T>)
>> {
!(instantiation_of_v<std::optional, T>)>> {
static inline T big(T x) {
if constexpr (endianness<T>::little) {
uint8_t *data = reinterpret_cast<uint8_t*>(&x);
if constexpr(endianness<T>::little) {
uint8_t *data = reinterpret_cast<uint8_t *>(&x);
std::reverse(data, data + sizeof(x));
}
@ -778,8 +789,8 @@ struct endian_helper<T, std::enable_if_t<
}
static inline T little(T x) {
if constexpr (endianness<T>::big) {
uint8_t *data = reinterpret_cast<uint8_t*>(&x);
if constexpr(endianness<T>::big) {
uint8_t *data = reinterpret_cast<uint8_t *>(&x);
std::reverse(data, data + sizeof(x));
}
@ -790,32 +801,31 @@ struct endian_helper<T, std::enable_if_t<
template<class T>
struct endian_helper<T, std::enable_if_t<
instantiation_of_v<std::optional, T>
>> {
static inline T little(T x) {
if(!x) return x;
instantiation_of_v<std::optional, T>>> {
static inline T little(T x) {
if(!x) return x;
if constexpr (endianness<T>::big) {
auto *data = reinterpret_cast<uint8_t*>(&*x);
if constexpr(endianness<T>::big) {
auto *data = reinterpret_cast<uint8_t *>(&*x);
std::reverse(data, data + sizeof(*x));
std::reverse(data, data + sizeof(*x));
}
return x;
}
return x;
}
static inline T big(T x) {
if(!x) return x;
static inline T big(T x) {
if(!x) return x;
if constexpr(endianness<T>::big) {
auto *data = reinterpret_cast<uint8_t *>(&*x);
if constexpr (endianness<T>::big) {
auto *data = reinterpret_cast<uint8_t*>(&*x);
std::reverse(data, data + sizeof(*x));
}
std::reverse(data, data + sizeof(*x));
return x;
}
return x;
}
};
template<class T>
@ -823,7 +833,6 @@ inline auto little(T x) { return endian_helper<T>::little(x); }
template<class T>
inline auto big(T x) { return endian_helper<T>::big(x); }
} /* endian */
} /* util */
} // namespace endian
} // namespace util
#endif

View File

@ -18,12 +18,12 @@ union uuid_t {
std::uniform_int_distribution<std::uint8_t> dist(0, std::numeric_limits<std::uint8_t>::max());
uuid_t buf;
for (auto &el : buf.b8) {
for(auto &el : buf.b8) {
el = dist(engine);
}
buf.b8[7] &= (std::uint8_t) 0b00101111;
buf.b8[9] &= (std::uint8_t) 0b10011111;
buf.b8[7] &= (std::uint8_t)0b00101111;
buf.b8[9] &= (std::uint8_t)0b10011111;
return buf;
}
@ -31,7 +31,7 @@ union uuid_t {
static uuid_t generate() {
std::random_device r;
std::default_random_engine engine{r()};
std::default_random_engine engine { r() };
return generate(engine);
}
@ -41,7 +41,7 @@ union uuid_t {
result.reserve(sizeof(uuid_t) * 2 + 4);
auto hex = util::hex(*this, true);
auto hex = util::hex(*this, true);
auto hex_view = hex.to_string_view();
std::string_view slices[] = {
@ -75,5 +75,5 @@ union uuid_t {
return (b64[0] > other.b64[0] || (b64[0] == other.b64[0] && b64[1] > other.b64[1]));
}
};
}
} // namespace util
#endif //T_MAN_UUID_H

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,9 @@
#ifndef SUNSHINE_VIDEO_H
#define SUNSHINE_VIDEO_H
#include "thread_safe.h"
#include "input.h"
#include "platform/common.h"
#include "thread_safe.h"
extern "C" {
#include <libavcodec/avcodec.h>
@ -14,16 +15,27 @@ extern "C" {
struct AVPacket;
namespace video {
void free_packet(AVPacket *packet);
struct packet_raw_t : public AVPacket {
template<class P>
explicit packet_raw_t(P *user_data) : channel_data { user_data } {
av_init_packet(this);
void init_packet() {
pts = AV_NOPTS_VALUE;
dts = AV_NOPTS_VALUE;
pos = -1;
duration = 0;
flags = 0;
stream_index = 0;
buf = nullptr;
side_data = nullptr;
side_data_elems = 0;
}
explicit packet_raw_t(std::nullptr_t null) : channel_data { nullptr } {
av_init_packet(this);
template<class P>
explicit packet_raw_t(P *user_data) : channel_data { user_data } {
init_packet();
}
explicit packet_raw_t(std::nullptr_t) : channel_data { nullptr } {
init_packet();
}
~packet_raw_t() {
@ -58,6 +70,6 @@ void capture(
void *channel_data);
int init();
}
} // namespace video
#endif //SUNSHINE_VIDEO_H

View File

@ -9,7 +9,7 @@ set_target_properties(dxgi-info PROPERTIES CXX_STANDARD 17)
target_link_libraries(dxgi-info
${CMAKE_THREAD_LIBS_INIT}
dxgi
${PLATFORM_LIBRARIES})
${PLATFORM_LIBRARIES})
target_compile_options(dxgi-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
add_executable(audio-info audio.cpp)
@ -17,5 +17,5 @@ set_target_properties(audio-info PROPERTIES CXX_STANDARD 17)
target_link_libraries(audio-info
${CMAKE_THREAD_LIBS_INIT}
ksuser
${PLATFORM_LIBRARIES})
${PLATFORM_LIBRARIES})
target_compile_options(audio-info PRIVATE ${SUNSHINE_COMPILE_OPTIONS})

View File

@ -2,9 +2,9 @@
// Created by loki on 1/24/20.
//
#include <roapi.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <roapi.h>
#include <synchapi.h>
@ -16,8 +16,8 @@
#include "sunshine/utility.h"
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
using namespace std::literals;
@ -26,7 +26,9 @@ const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
int device_state_filter = DEVICE_STATE_ACTIVE;
constexpr auto SAMPLE_RATE = 48000;
int device_state_filter = DEVICE_STATE_ACTIVE;
namespace audio {
template<class T>
void Release(T *p) {
@ -66,19 +68,118 @@ public:
const wchar_t *no_null(const wchar_t *str) {
return str ? str : L"Unknown";
}
void print_device(device_t &device) {
HRESULT status;
audio::wstring_t::pointer wstring_p {};
struct format_t {
std::string_view name;
int channels;
int channel_mask;
} formats[] {
{ "Mono"sv,
1,
SPEAKER_FRONT_CENTER },
{ "Stereo"sv,
2,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT },
{ "Surround 5.1"sv,
6,
SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT },
{ "Surround 7.1"sv,
8,
SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT }
};
void set_wave_format(audio::wave_format_t &wave_format, const format_t &format) {
wave_format->nChannels = format.channels;
wave_format->nBlockAlign = wave_format->nChannels * wave_format->wBitsPerSample / 8;
wave_format->nAvgBytesPerSec = wave_format->nSamplesPerSec * wave_format->nBlockAlign;
if(wave_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
((PWAVEFORMATEXTENSIBLE)wave_format.get())->dwChannelMask = format.channel_mask;
}
}
audio_client_t make_audio_client(device_t &device, const format_t &format) {
audio_client_t audio_client;
auto status = device->Activate(
IID_IAudioClient,
CLSCTX_ALL,
nullptr,
(void **)&audio_client);
if(FAILED(status)) {
std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return nullptr;
}
wave_format_t wave_format;
status = audio_client->GetMixFormat(&wave_format);
if(FAILED(status)) {
std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return nullptr;
}
wave_format->wBitsPerSample = 16;
wave_format->nSamplesPerSec = SAMPLE_RATE;
switch(wave_format->wFormatTag) {
case WAVE_FORMAT_PCM:
break;
case WAVE_FORMAT_IEEE_FLOAT:
break;
case WAVE_FORMAT_EXTENSIBLE: {
auto wave_ex = (PWAVEFORMATEXTENSIBLE)wave_format.get();
if(IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
wave_ex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
wave_ex->Samples.wValidBitsPerSample = 16;
break;
}
std::cout << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']' << std::endl;
}
default:
std::cout << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']' << std::endl;
return nullptr;
};
set_wave_format(wave_format, format);
status = audio_client->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
0, 0,
wave_format.get(),
nullptr);
if(status) {
return nullptr;
}
return audio_client;
}
void print_device(device_t &device) {
audio::wstring_t wstring;
DWORD device_state;
device->GetState(&device_state);
device->GetId(&wstring_p);
audio::wstring_t wstring { wstring_p };
device->GetId(&wstring);
audio::prop_t::pointer prop_p {};
device->OpenPropertyStore(STGM_READ, &prop_p);
audio::prop_t prop { prop_p };
audio::prop_t prop;
device->OpenPropertyStore(STGM_READ, &prop);
prop_var_t adapter_friendly_name;
prop_var_t device_friendly_name;
@ -94,18 +195,18 @@ void print_device(device_t &device) {
std::wstring device_state_string = L"Unknown"s;
switch(device_state) {
case DEVICE_STATE_ACTIVE:
device_state_string = L"Active"s;
break;
case DEVICE_STATE_DISABLED:
device_state_string = L"Disabled"s;
break;
case DEVICE_STATE_UNPLUGGED:
device_state_string = L"Unplugged"s;
break;
case DEVICE_STATE_NOTPRESENT:
device_state_string = L"Not present"s;
break;
case DEVICE_STATE_ACTIVE:
device_state_string = L"Active"s;
break;
case DEVICE_STATE_DISABLED:
device_state_string = L"Disabled"s;
break;
case DEVICE_STATE_UNPLUGGED:
device_state_string = L"Unplugged"s;
break;
case DEVICE_STATE_NOTPRESENT:
device_state_string = L"Not present"s;
break;
}
std::wcout
@ -114,55 +215,21 @@ void print_device(device_t &device) {
<< L"Device name : "sv << no_null((LPWSTR)device_friendly_name.prop.pszVal) << std::endl
<< L"Adapter name : "sv << no_null((LPWSTR)adapter_friendly_name.prop.pszVal) << std::endl
<< L"Device description : "sv << no_null((LPWSTR)device_desc.prop.pszVal) << std::endl
<< L"Device state : "sv << device_state_string << std::endl << std::endl;
<< L"Device state : "sv << device_state_string << std::endl
<< std::endl;
if(device_state != DEVICE_STATE_ACTIVE) {
return;
}
// Ensure WaveFromat is compatible
audio_client_t::pointer audio_client_p{};
status = device->Activate(
IID_IAudioClient,
CLSCTX_ALL,
nullptr,
(void **) &audio_client_p);
audio_client_t audio_client { audio_client_p };
for(const auto &format : formats) {
// Ensure WaveFromat is compatible
auto audio_client = make_audio_client(device, format);
if (FAILED(status)) {
std::cout << "Couldn't activate Device: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return;
std::cout << format.name << ": "sv << (!audio_client ? "unsupported"sv : "supported"sv) << std::endl;
}
wave_format_t::pointer wave_format_p{};
status = audio_client->GetMixFormat(&wave_format_p);
wave_format_t wave_format { wave_format_p };
if (FAILED(status)) {
std::cout << "Couldn't acquire Wave Format [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return;
}
switch(wave_format->wFormatTag) {
case WAVE_FORMAT_PCM:
break;
case WAVE_FORMAT_IEEE_FLOAT:
break;
case WAVE_FORMAT_EXTENSIBLE: {
auto wave_ex = (PWAVEFORMATEXTENSIBLE) wave_format.get();
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wave_ex->SubFormat)) {
break;
}
std::cout << "Unsupported Sub Format for WAVE_FORMAT_EXTENSIBLE: [0x"sv << util::hex(wave_ex->SubFormat).to_string_view() << ']' << std::endl;
}
default:
std::cout << "Unsupported Wave Format: [0x"sv << util::hex(wave_format->wFormatTag).to_string_view() << ']' << std::endl;
};
}
}
} // namespace audio
void print_help() {
std::cout
@ -213,26 +280,24 @@ int main(int argc, char *argv[]) {
HRESULT status;
audio::device_enum_t::pointer device_enum_p{};
audio::device_enum_t device_enum;
status = CoCreateInstance(
CLSID_MMDeviceEnumerator,
nullptr,
CLSCTX_ALL,
IID_IMMDeviceEnumerator,
(void **) &device_enum_p);
audio::device_enum_t device_enum { device_enum_p };
(void **)&device_enum);
if (FAILED(status)) {
if(FAILED(status)) {
std::cout << "Couldn't create Device Enumerator: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return -1;
}
audio::collection_t::pointer collection_p {};
status = device_enum->EnumAudioEndpoints(eRender, DEVICE_STATEMASK_ALL, &collection_p);
audio::collection_t collection { collection_p };
audio::collection_t collection;
status = device_enum->EnumAudioEndpoints(eRender, device_state_filter, &collection);
if (FAILED(status)) {
if(FAILED(status)) {
std::cout << "Couldn't enumerate: [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
return -1;
@ -241,11 +306,10 @@ int main(int argc, char *argv[]) {
UINT count;
collection->GetCount(&count);
std::cout << "====== Found "sv << count << " potential audio devices ======"sv << std::endl;
std::cout << "====== Found "sv << count << " audio devices ======"sv << std::endl;
for(auto x = 0; x < count; ++x) {
audio::device_t::pointer device_p {};
collection->Item(x, &device_p);
audio::device_t device { device_p };
audio::device_t device;
collection->Item(x, &device);
audio::print_device(device);
}

View File

@ -2,8 +2,8 @@
// Created by loki on 1/23/20.
//
#include <dxgi.h>
#include <d3dcommon.h>
#include <dxgi.h>
#include <iostream>
@ -16,17 +16,17 @@ void Release(T *dxgi) {
dxgi->Release();
}
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
}
} // namespace dxgi
int main(int argc, char *argv[]) {
HRESULT status;
dxgi::factory1_t::pointer factory_p {};
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p);
status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **)&factory_p);
dxgi::factory1_t factory { factory_p };
if(FAILED(status)) {
std::cout << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']' << std::endl;
@ -49,12 +49,13 @@ int main(int argc, char *argv[]) {
<< "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl
<< "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl
<< "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl << std::endl
<< "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl
<< std::endl
<< " ====== OUTPUT ======"sv << std::endl;
dxgi::output_t::pointer output_p {};
for(int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) {
dxgi::output_t output {output_p };
dxgi::output_t output { output_p };
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
@ -66,7 +67,8 @@ int main(int argc, char *argv[]) {
<< L" Output Name : "sv << desc.DeviceName << std::endl;
std::cout
<< " AttachedToDesktop : "sv << (desc.AttachedToDesktop ? "yes"sv : "no"sv) << std::endl
<< " Resolution : "sv << width << 'x' << height << std::endl << std::endl;
<< " Resolution : "sv << width << 'x' << height << std::endl
<< std::endl;
}
}