Merge remote-tracking branch 'origin/master'
This commit is contained in:
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -22,6 +22,10 @@
|
|||||||
path = third-party/inputtino
|
path = third-party/inputtino
|
||||||
url = https://github.com/games-on-whales/inputtino.git
|
url = https://github.com/games-on-whales/inputtino.git
|
||||||
branch = stable
|
branch = stable
|
||||||
|
[submodule "third-party/libdisplaydevice"]
|
||||||
|
path = third-party/libdisplaydevice
|
||||||
|
url = https://github.com/LizardByte/libdisplaydevice.git
|
||||||
|
branch = master
|
||||||
[submodule "third-party/nanors"]
|
[submodule "third-party/nanors"]
|
||||||
path = third-party/nanors
|
path = third-party/nanors
|
||||||
url = https://github.com/sleepybishop/nanors.git
|
url = https://github.com/sleepybishop/nanors.git
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ set(SUNSHINE_TARGET_FILES
|
|||||||
"${CMAKE_SOURCE_DIR}/src/uuid.h"
|
"${CMAKE_SOURCE_DIR}/src/uuid.h"
|
||||||
"${CMAKE_SOURCE_DIR}/src/config.h"
|
"${CMAKE_SOURCE_DIR}/src/config.h"
|
||||||
"${CMAKE_SOURCE_DIR}/src/config.cpp"
|
"${CMAKE_SOURCE_DIR}/src/config.cpp"
|
||||||
|
"${CMAKE_SOURCE_DIR}/src/display_device.h"
|
||||||
|
"${CMAKE_SOURCE_DIR}/src/display_device.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/src/entry_handler.cpp"
|
"${CMAKE_SOURCE_DIR}/src/entry_handler.cpp"
|
||||||
"${CMAKE_SOURCE_DIR}/src/entry_handler.h"
|
"${CMAKE_SOURCE_DIR}/src/entry_handler.h"
|
||||||
"${CMAKE_SOURCE_DIR}/src/file_handler.cpp"
|
"${CMAKE_SOURCE_DIR}/src/file_handler.cpp"
|
||||||
@@ -146,6 +148,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
|||||||
${MINIUPNP_LIBRARIES}
|
${MINIUPNP_LIBRARIES}
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
enet
|
enet
|
||||||
|
libdisplaydevice::display_device
|
||||||
opus
|
opus
|
||||||
${FFMPEG_LIBRARIES}
|
${FFMPEG_LIBRARIES}
|
||||||
${Boost_LIBRARIES}
|
${Boost_LIBRARIES}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet")
|
|||||||
# web server
|
# web server
|
||||||
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server")
|
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server")
|
||||||
|
|
||||||
|
# libdisplaydevice
|
||||||
|
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice")
|
||||||
|
|
||||||
# common dependencies
|
# common dependencies
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|||||||
@@ -865,10 +865,56 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
|||||||
<br>
|
<br>
|
||||||
**Windows:**
|
**Windows:**
|
||||||
<br>
|
<br>
|
||||||
Enter the following command in command prompt or PowerShell.
|
During Sunshine startup, you should see the list of detected displays:
|
||||||
@code{}
|
@code{}
|
||||||
%ProgramFiles%\Sunshine\tools\dxgi-info.exe
|
Info: Currently available display devices:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"device_id": "{64243705-4020-5895-b923-adc862c3457e}",
|
||||||
|
"display_name": "",
|
||||||
|
"friendly_name": "IDD HDR",
|
||||||
|
"info": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "{77f67f3e-754f-5d31-af64-ee037e18100a}",
|
||||||
|
"display_name": "",
|
||||||
|
"friendly_name": "SunshineHDR",
|
||||||
|
"info": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "{daeac860-f4db-5208-b1f5-cf59444fb768}",
|
||||||
|
"display_name": "\\\\.\\DISPLAY1",
|
||||||
|
"friendly_name": "ROG PG279Q",
|
||||||
|
"info": {
|
||||||
|
"hdr_state": null,
|
||||||
|
"origin_point": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"primary": true,
|
||||||
|
"refresh_rate": {
|
||||||
|
"type": "rational",
|
||||||
|
"value": {
|
||||||
|
"denominator": 1000,
|
||||||
|
"numerator": 119998
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
"height": 1440,
|
||||||
|
"width": 2560
|
||||||
|
},
|
||||||
|
"resolution_scale": {
|
||||||
|
"type": "rational",
|
||||||
|
"value": {
|
||||||
|
"denominator": 100,
|
||||||
|
"numerator": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@endcode
|
@endcode
|
||||||
|
You need to use the `device_id` value.
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -891,7 +937,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Example (Windows)</td>
|
<td>Example (Windows)</td>
|
||||||
<td colspan="2">@code{}
|
<td colspan="2">@code{}
|
||||||
output_name = \\.\DISPLAY1
|
output_name = {daeac860-f4db-5208-b1f5-cf59444fb768}
|
||||||
@endcode</td>
|
@endcode</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ If you forgot your credentials to the web UI, try this.
|
|||||||
@tip{Don't forget to replace `{new-username}` and `{new-password}` with your new credentials.
|
@tip{Don't forget to replace `{new-username}` and `{new-password}` with your new credentials.
|
||||||
Do not include the curly braces.}
|
Do not include the curly braces.}
|
||||||
|
|
||||||
|
### Unusual Mouse Behavior
|
||||||
|
If you experience unusual mouse behavior, try attaching a physical mouse to the Sunshine host.
|
||||||
|
|
||||||
### Web UI Access
|
### Web UI Access
|
||||||
Can't access the web UI?
|
Can't access the web UI?
|
||||||
|
|
||||||
@@ -190,6 +193,9 @@ has. You may get permission denied errors when attempting to launch a game or ap
|
|||||||
You will need to modify the security permissions on your disk. Ensure that user/principal SYSTEM has full
|
You will need to modify the security permissions on your disk. Ensure that user/principal SYSTEM has full
|
||||||
permissions on the disk.
|
permissions on the disk.
|
||||||
|
|
||||||
|
### Stuttering
|
||||||
|
If you experience stuttering using NVIDIA, try disabling `vsync:fast` in the NVIDIA Control Panel.
|
||||||
|
|
||||||
<div class="section_buttons">
|
<div class="section_buttons">
|
||||||
|
|
||||||
| Previous | Next |
|
| Previous | Next |
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ modules:
|
|||||||
# Test dependencies
|
# Test dependencies
|
||||||
- "modules/xvfb/xvfb.json"
|
- "modules/xvfb/xvfb.json"
|
||||||
|
|
||||||
|
# Build dependencies
|
||||||
|
- "modules/nlohmann_json.json"
|
||||||
|
|
||||||
# Runtime dependencies
|
# Runtime dependencies
|
||||||
- shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json
|
- shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json
|
||||||
- "modules/avahi.json"
|
- "modules/avahi.json"
|
||||||
|
|||||||
15
packaging/linux/flatpak/modules/nlohmann_json.json
Normal file
15
packaging/linux/flatpak/modules/nlohmann_json.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "nlohmann_json",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"config-opts": [
|
||||||
|
"-DJSON_MultipleHeaders=OFF",
|
||||||
|
"-DJSON_BuildTests=OFF"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz",
|
||||||
|
"sha256": "d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
85
src/display_device.cpp
Normal file
85
src/display_device.cpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* @file src/display_device.cpp
|
||||||
|
* @brief Definitions for display device handling.
|
||||||
|
*/
|
||||||
|
// header include
|
||||||
|
#include "display_device.h"
|
||||||
|
|
||||||
|
// lib includes
|
||||||
|
#include <display_device/json.h>
|
||||||
|
#include <display_device/retry_scheduler.h>
|
||||||
|
#include <display_device/settings_manager_interface.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
|
#include "platform/common.h"
|
||||||
|
|
||||||
|
// platform-specific includes
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <display_device/windows/settings_manager.h>
|
||||||
|
#include <display_device/windows/win_api_layer.h>
|
||||||
|
#include <display_device/windows/win_display_device.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace display_device {
|
||||||
|
namespace {
|
||||||
|
/**
|
||||||
|
* @brief A global for the settings manager interface whose lifetime is managed by `display_device::init()`.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> SM_INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a settings manager interface to manage display device settings.
|
||||||
|
* @return An interface or nullptr if the OS does not support the interface.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<SettingsManagerInterface>
|
||||||
|
make_settings_manager() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
// TODO: In the upcoming PR, add audio context capture and settings persistence
|
||||||
|
return std::make_unique<SettingsManager>(
|
||||||
|
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
|
||||||
|
nullptr,
|
||||||
|
std::make_unique<PersistentState>(nullptr),
|
||||||
|
WinWorkarounds {});
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<platf::deinit_t>
|
||||||
|
init() {
|
||||||
|
// We can support re-init without any issues, however we should make sure to cleanup first!
|
||||||
|
SM_INSTANCE = nullptr;
|
||||||
|
|
||||||
|
// If we fail to create settings manager, this means platform is not supported and
|
||||||
|
// we will need to provided error-free passtrough in other methods
|
||||||
|
if (auto settings_manager { make_settings_manager() }) {
|
||||||
|
SM_INSTANCE = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager));
|
||||||
|
|
||||||
|
const auto available_devices { SM_INSTANCE->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) };
|
||||||
|
BOOST_LOG(info) << "Currently available display devices:\n"
|
||||||
|
<< toJson(available_devices);
|
||||||
|
|
||||||
|
// TODO: In the upcoming PR, schedule recovery here
|
||||||
|
}
|
||||||
|
|
||||||
|
class deinit_t: public platf::deinit_t {
|
||||||
|
public:
|
||||||
|
~deinit_t() override {
|
||||||
|
// TODO: In the upcoming PR, execute recovery once here
|
||||||
|
SM_INSTANCE = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return std::make_unique<deinit_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
map_output_name(const std::string &output_name) {
|
||||||
|
if (!SM_INSTANCE) {
|
||||||
|
// Fallback to giving back the output name if the platform is not supported.
|
||||||
|
return output_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SM_INSTANCE->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); });
|
||||||
|
}
|
||||||
|
} // namespace display_device
|
||||||
39
src/display_device.h
Normal file
39
src/display_device.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @file src/display_device.h
|
||||||
|
* @brief Declarations for display device handling.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// lib includes
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
namespace platf {
|
||||||
|
class deinit_t;
|
||||||
|
} // namespace platf
|
||||||
|
|
||||||
|
namespace display_device {
|
||||||
|
/**
|
||||||
|
* @brief Initialize the implementation and perform the initial state recovery (if needed).
|
||||||
|
* @returns A deinit_t instance that performs cleanup when destroyed.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
* const auto init_guard { display_device::init() };
|
||||||
|
* @examples_end
|
||||||
|
*/
|
||||||
|
std::unique_ptr<platf::deinit_t>
|
||||||
|
init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Map the output name to a specific display.
|
||||||
|
* @param output_name The user-configurable output name.
|
||||||
|
* @returns Mapped display name or empty string if the output name could not be mapped.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
* const auto mapped_name_config { map_output_name(config::video.output_name) };
|
||||||
|
* const auto mapped_name_custom { map_output_name("{some-device-id}") };
|
||||||
|
* @examples_end
|
||||||
|
*/
|
||||||
|
std::string
|
||||||
|
map_output_name(const std::string &output_name);
|
||||||
|
} // namespace display_device
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <boost/log/expressions.hpp>
|
#include <boost/log/expressions.hpp>
|
||||||
#include <boost/log/sinks.hpp>
|
#include <boost/log/sinks.hpp>
|
||||||
#include <boost/log/sources/severity_logger.hpp>
|
#include <boost/log/sources/severity_logger.hpp>
|
||||||
|
#include <display_device/logging.h>
|
||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
@@ -106,6 +107,7 @@ namespace logging {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setup_av_logging(min_log_level);
|
setup_av_logging(min_log_level);
|
||||||
|
setup_libdisplaydevice_logging(min_log_level);
|
||||||
|
|
||||||
sink = boost::make_shared<text_sink>();
|
sink = boost::make_shared<text_sink>();
|
||||||
|
|
||||||
@@ -159,6 +161,37 @@ namespace logging {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
setup_libdisplaydevice_logging(int min_log_level) {
|
||||||
|
constexpr int min_level { static_cast<int>(display_device::Logger::LogLevel::verbose) };
|
||||||
|
constexpr int max_level { static_cast<int>(display_device::Logger::LogLevel::fatal) };
|
||||||
|
const auto log_level { static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level)) };
|
||||||
|
|
||||||
|
display_device::Logger::get().setLogLevel(log_level);
|
||||||
|
display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) {
|
||||||
|
switch (level) {
|
||||||
|
case display_device::Logger::LogLevel::verbose:
|
||||||
|
BOOST_LOG(verbose) << message;
|
||||||
|
break;
|
||||||
|
case display_device::Logger::LogLevel::debug:
|
||||||
|
BOOST_LOG(debug) << message;
|
||||||
|
break;
|
||||||
|
case display_device::Logger::LogLevel::info:
|
||||||
|
BOOST_LOG(info) << message;
|
||||||
|
break;
|
||||||
|
case display_device::Logger::LogLevel::warning:
|
||||||
|
BOOST_LOG(warning) << message;
|
||||||
|
break;
|
||||||
|
case display_device::Logger::LogLevel::error:
|
||||||
|
BOOST_LOG(error) << message;
|
||||||
|
break;
|
||||||
|
case display_device::Logger::LogLevel::fatal:
|
||||||
|
BOOST_LOG(fatal) << message;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
log_flush() {
|
log_flush() {
|
||||||
if (sink) {
|
if (sink) {
|
||||||
|
|||||||
@@ -66,6 +66,13 @@ namespace logging {
|
|||||||
void
|
void
|
||||||
setup_av_logging(int min_log_level);
|
setup_av_logging(int min_log_level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Setup logging for libdisplaydevice.
|
||||||
|
* @param min_log_level The log level.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
setup_libdisplaydevice_logging(int min_log_level);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Flush the log.
|
* @brief Flush the log.
|
||||||
* @examples
|
* @examples
|
||||||
|
|||||||
15
src/main.cpp
15
src/main.cpp
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
#include "confighttp.h"
|
#include "confighttp.h"
|
||||||
|
#include "display_device.h"
|
||||||
#include "entry_handler.h"
|
#include "entry_handler.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "httpcommon.h"
|
#include "httpcommon.h"
|
||||||
@@ -135,6 +136,14 @@ main(int argc, char *argv[]) {
|
|||||||
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
|
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adding guard here first as it also performs recovery after crash,
|
||||||
|
// otherwise people could theoretically end up without display output.
|
||||||
|
// It also should be destroyed before forced shutdown to expedite the cleanup.
|
||||||
|
auto display_device_deinit_guard = display_device::init();
|
||||||
|
if (!display_device_deinit_guard) {
|
||||||
|
BOOST_LOG(error) << "Display device session failed to initialize"sv;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
// Modify relevant NVIDIA control panel settings if the system has corresponding gpu
|
// Modify relevant NVIDIA control panel settings if the system has corresponding gpu
|
||||||
if (nvprefs_instance.load()) {
|
if (nvprefs_instance.load()) {
|
||||||
@@ -232,7 +241,7 @@ main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Create signal handler after logging has been initialized
|
// Create signal handler after logging has been initialized
|
||||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||||
on_signal(SIGINT, [&force_shutdown, shutdown_event]() {
|
on_signal(SIGINT, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() {
|
||||||
BOOST_LOG(info) << "Interrupt handler called"sv;
|
BOOST_LOG(info) << "Interrupt handler called"sv;
|
||||||
|
|
||||||
auto task = []() {
|
auto task = []() {
|
||||||
@@ -243,9 +252,10 @@ main(int argc, char *argv[]) {
|
|||||||
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
|
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
|
||||||
|
|
||||||
shutdown_event->raise(true);
|
shutdown_event->raise(true);
|
||||||
|
display_device_deinit_guard = nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
on_signal(SIGTERM, [&force_shutdown, shutdown_event]() {
|
on_signal(SIGTERM, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() {
|
||||||
BOOST_LOG(info) << "Terminate handler called"sv;
|
BOOST_LOG(info) << "Terminate handler called"sv;
|
||||||
|
|
||||||
auto task = []() {
|
auto task = []() {
|
||||||
@@ -256,6 +266,7 @@ main(int argc, char *argv[]) {
|
|||||||
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
|
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;
|
||||||
|
|
||||||
shutdown_event->raise(true);
|
shutdown_event->raise(true);
|
||||||
|
display_device_deinit_guard = nullptr;
|
||||||
});
|
});
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
|
|
||||||
|
#include "src/display_device.h"
|
||||||
#include "src/logging.h"
|
#include "src/logging.h"
|
||||||
#include "src/platform/common.h"
|
#include "src/platform/common.h"
|
||||||
#include "src/utility.h"
|
#include "src/utility.h"
|
||||||
@@ -541,7 +542,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
|||||||
// Default to main display
|
// Default to main display
|
||||||
macos_input->display = CGMainDisplayID();
|
macos_input->display = CGMainDisplayID();
|
||||||
|
|
||||||
auto output_name = config::video.output_name;
|
auto output_name = display_device::map_output_name(config::video.output_name);
|
||||||
// If output_name is set, try to find the display with that display id
|
// If output_name is set, try to find the display with that display id
|
||||||
if (!output_name.empty()) {
|
if (!output_name.empty()) {
|
||||||
uint32_t max_display = 32;
|
uint32_t max_display = 32;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ typedef long NTSTATUS;
|
|||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "src/config.h"
|
#include "src/config.h"
|
||||||
|
#include "src/display_device.h"
|
||||||
#include "src/logging.h"
|
#include "src/logging.h"
|
||||||
#include "src/platform/common.h"
|
#include "src/platform/common.h"
|
||||||
#include "src/video.h"
|
#include "src/video.h"
|
||||||
@@ -1101,7 +1102,8 @@ namespace platf {
|
|||||||
BOOST_LOG(debug) << "Detecting monitors..."sv;
|
BOOST_LOG(debug) << "Detecting monitors..."sv;
|
||||||
|
|
||||||
// We must set the GPU preference before calling any DXGI APIs!
|
// We must set the GPU preference before calling any DXGI APIs!
|
||||||
if (!dxgi::probe_for_gpu_preference(config::video.output_name)) {
|
const auto output_name { display_device::map_output_name(config::video.output_name) };
|
||||||
|
if (!dxgi::probe_for_gpu_preference(output_name)) {
|
||||||
BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv;
|
BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ extern "C" {
|
|||||||
#include "process.h"
|
#include "process.h"
|
||||||
#include "cbs.h"
|
#include "cbs.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "display_device.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
@@ -995,6 +996,8 @@ namespace video {
|
|||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
refresh_displays(platf::mem_type_e dev_type, std::vector<std::string> &display_names, int ¤t_display_index, std::string &preferred_display_name) {
|
refresh_displays(platf::mem_type_e dev_type, std::vector<std::string> &display_names, int ¤t_display_index, std::string &preferred_display_name) {
|
||||||
|
// It is possible that the output name may be empty even if it wasn't before (device disconnected) or vice-versa
|
||||||
|
const auto output_name { display_device::map_output_name(config::video.output_name) };
|
||||||
std::string current_display_name = preferred_display_name;
|
std::string current_display_name = preferred_display_name;
|
||||||
|
|
||||||
// If we have a current display index, let's start with that
|
// If we have a current display index, let's start with that
|
||||||
@@ -1013,7 +1016,7 @@ namespace video {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (display_names.empty()) {
|
else if (display_names.empty()) {
|
||||||
display_names.emplace_back(config::video.output_name);
|
display_names.emplace_back(output_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We now have a new display name list, so reset the index back to 0
|
// We now have a new display name list, so reset the index back to 0
|
||||||
@@ -1033,7 +1036,15 @@ namespace video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The old display was removed, so we'll start back at the first display again
|
// The old display was removed, so we'll start back at the first display again
|
||||||
BOOST_LOG(warning) << "Desired display ["sv << current_display_name << "] does not exist"sv;
|
BOOST_LOG(warning) << "Previous active display ["sv << current_display_name << "] is no longer present"sv;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int x = 0; x < display_names.size(); ++x) {
|
||||||
|
if (display_names[x] == output_name) {
|
||||||
|
current_display_index = x;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2359,6 +2370,7 @@ namespace video {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
validate_encoder(encoder_t &encoder, bool expect_failure) {
|
validate_encoder(encoder_t &encoder, bool expect_failure) {
|
||||||
|
const auto output_name { display_device::map_output_name(config::video.output_name) };
|
||||||
std::shared_ptr<platf::display_t> disp;
|
std::shared_ptr<platf::display_t> disp;
|
||||||
|
|
||||||
BOOST_LOG(info) << "Trying encoder ["sv << encoder.name << ']';
|
BOOST_LOG(info) << "Trying encoder ["sv << encoder.name << ']';
|
||||||
@@ -2378,7 +2390,7 @@ namespace video {
|
|||||||
config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0, 0 };
|
config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0, 0 };
|
||||||
|
|
||||||
// If the encoder isn't supported at all (not even H.264), bail early
|
// If the encoder isn't supported at all (not even H.264), bail early
|
||||||
reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config_autoselect);
|
reset_display(disp, encoder.platform_formats->dev_type, output_name, config_autoselect);
|
||||||
if (!disp) {
|
if (!disp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2486,7 +2498,7 @@ namespace video {
|
|||||||
const config_t generic_hdr_config = { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1, 0 };
|
const config_t generic_hdr_config = { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1, 0 };
|
||||||
|
|
||||||
// Reset the display since we're switching from SDR to HDR
|
// Reset the display since we're switching from SDR to HDR
|
||||||
reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, generic_hdr_config);
|
reset_display(disp, encoder.platform_formats->dev_type, output_name, generic_hdr_config);
|
||||||
if (!disp) {
|
if (!disp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2667,8 +2679,9 @@ namespace video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chosen_encoder == nullptr) {
|
if (chosen_encoder == nullptr) {
|
||||||
|
const auto output_name { display_device::map_output_name(config::video.output_name) };
|
||||||
BOOST_LOG(fatal) << "Unable to find display or encoder during startup."sv;
|
BOOST_LOG(fatal) << "Unable to find display or encoder during startup."sv;
|
||||||
if (!config::video.adapter_name.empty() || !config::video.output_name.empty()) {
|
if (!config::video.adapter_name.empty() || !output_name.empty()) {
|
||||||
BOOST_LOG(fatal) << "Please ensure your manually chosen GPU and monitor are connected and powered on."sv;
|
BOOST_LOG(fatal) << "Please ensure your manually chosen GPU and monitor are connected and powered on."sv;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import {ref, computed, inject} from 'vue'
|
|||||||
import {$tp} from '../../platform-i18n'
|
import {$tp} from '../../platform-i18n'
|
||||||
import PlatformLayout from '../../PlatformLayout.vue'
|
import PlatformLayout from '../../PlatformLayout.vue'
|
||||||
import AdapterNameSelector from './audiovideo/AdapterNameSelector.vue'
|
import AdapterNameSelector from './audiovideo/AdapterNameSelector.vue'
|
||||||
import LegacyDisplayOutputSelector from './audiovideo/LegacyDisplayOutputSelector.vue'
|
import DisplayOutputSelector from './audiovideo/DisplayOutputSelector.vue'
|
||||||
import NewDisplayOutputSelector from './audiovideo/NewDisplayOutputSelector.vue'
|
|
||||||
import DisplayDeviceOptions from "./audiovideo/DisplayDeviceOptions.vue";
|
import DisplayDeviceOptions from "./audiovideo/DisplayDeviceOptions.vue";
|
||||||
import DisplayModesSettings from "./audiovideo/DisplayModesSettings.vue";
|
import DisplayModesSettings from "./audiovideo/DisplayModesSettings.vue";
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ const validateFallbackMode = (event) => {
|
|||||||
:config="config"
|
:config="config"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LegacyDisplayOutputSelector
|
<DisplayOutputSelector
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
:config="config"
|
:config="config"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { $tp } from '../../../platform-i18n'
|
||||||
|
import PlatformLayout from '../../../PlatformLayout.vue'
|
||||||
|
|
||||||
|
const props = defineProps([
|
||||||
|
'platform',
|
||||||
|
'config'
|
||||||
|
])
|
||||||
|
|
||||||
|
const config = ref(props.config)
|
||||||
|
const outputNamePlaceholder = (props.platform === 'windows') ? '{de9bb7e2-186e-505b-9e93-f48793333810}' : '0'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="output_name" class="form-label">{{ $tp('config.output_name') }}</label>
|
||||||
|
<input type="text" class="form-control" id="output_name" :placeholder="outputNamePlaceholder"
|
||||||
|
v-model="config.output_name"/>
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $tp('config.output_name_desc') }}<br>
|
||||||
|
<PlatformLayout :platform="platform">
|
||||||
|
<template #windows>
|
||||||
|
<pre style="white-space: pre-line;">
|
||||||
|
<b> {</b>
|
||||||
|
<b> "device_id": "{de9bb7e2-186e-505b-9e93-f48793333810}"</b>
|
||||||
|
<b> "display_name": "\\\\.\\DISPLAY1"</b>
|
||||||
|
<b> "friendly_name": "ROG PG279Q"</b>
|
||||||
|
<b> ...</b>
|
||||||
|
<b> }</b>
|
||||||
|
</pre>
|
||||||
|
</template>
|
||||||
|
<template #linux>
|
||||||
|
<pre style="white-space: pre-line;">
|
||||||
|
Info: Detecting displays
|
||||||
|
Info: Detected display: DVI-D-0 (id: 0) connected: false
|
||||||
|
Info: Detected display: HDMI-0 (id: 1) connected: true
|
||||||
|
Info: Detected display: DP-0 (id: 2) connected: true
|
||||||
|
Info: Detected display: DP-1 (id: 3) connected: false
|
||||||
|
Info: Detected display: DVI-D-1 (id: 4) connected: false
|
||||||
|
</pre>
|
||||||
|
</template>
|
||||||
|
<template #macos>
|
||||||
|
<pre style="white-space: pre-line;">
|
||||||
|
Info: Detecting displays
|
||||||
|
Info: Detected display: Monitor-0 (id: 3) connected: true
|
||||||
|
Info: Detected display: Monitor-1 (id: 2) connected: true
|
||||||
|
</pre>
|
||||||
|
</template>
|
||||||
|
</PlatformLayout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { $tp } from '../../../platform-i18n'
|
|
||||||
import PlatformLayout from '../../../PlatformLayout.vue'
|
|
||||||
|
|
||||||
const props = defineProps([
|
|
||||||
'platform',
|
|
||||||
'config'
|
|
||||||
])
|
|
||||||
|
|
||||||
const config = ref(props.config)
|
|
||||||
const outputNamePlaceholder = (props.platform === 'windows') ? '\\\\.\\DISPLAY1' : '0'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="output_name" class="form-label">{{ $tp('config.output_name') }}</label>
|
|
||||||
<input type="text" class="form-control" id="output_name" :placeholder="outputNamePlaceholder"
|
|
||||||
v-model="config.output_name"/>
|
|
||||||
<div class="form-text">
|
|
||||||
{{ $tp('config.output_name_desc') }}<br>
|
|
||||||
<PlatformLayout :platform="platform">
|
|
||||||
<template #windows>
|
|
||||||
<pre>tools\dxgi-info.exe</pre>
|
|
||||||
</template>
|
|
||||||
<template #linux>
|
|
||||||
<pre style="white-space: pre-line;">
|
|
||||||
Info: Detecting displays
|
|
||||||
Info: Detected display: DVI-D-0 (id: 0) connected: false
|
|
||||||
Info: Detected display: HDMI-0 (id: 1) connected: true
|
|
||||||
Info: Detected display: DP-0 (id: 2) connected: true
|
|
||||||
Info: Detected display: DP-1 (id: 3) connected: false
|
|
||||||
Info: Detected display: DVI-D-1 (id: 4) connected: false
|
|
||||||
</pre>
|
|
||||||
</template>
|
|
||||||
<template #macos>
|
|
||||||
<pre style="white-space: pre-line;">
|
|
||||||
Info: Detecting displays
|
|
||||||
Info: Detected display: Monitor-0 (id: 3) connected: true
|
|
||||||
Info: Detected display: Monitor-1 (id: 2) connected: true
|
|
||||||
</pre>
|
|
||||||
</template>
|
|
||||||
</PlatformLayout>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { $tp } from '../../../platform-i18n'
|
|
||||||
import PlatformLayout from '../../../PlatformLayout.vue'
|
|
||||||
|
|
||||||
const props = defineProps([
|
|
||||||
'platform',
|
|
||||||
'config',
|
|
||||||
'displays'
|
|
||||||
])
|
|
||||||
|
|
||||||
const config = ref(props.config)
|
|
||||||
const outputNamePlaceholder = (props.platform === 'windows') ? '{de9bb7e2-186e-505b-9e93-f48793333810}' : '4531345'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="output_name" class="form-label">{{ $tp('config.output_name') }}</label>
|
|
||||||
<input type="text" class="form-control" id="output_name" :placeholder="outputNamePlaceholder"
|
|
||||||
v-model="config.output_name"/>
|
|
||||||
<div class="form-text">
|
|
||||||
<p style="white-space: pre-line">{{ $tp('config.output_name_desc') }}</p>
|
|
||||||
<PlatformLayout :platform="platform">
|
|
||||||
<template #windows>
|
|
||||||
<b> DEVICE ID: {de9bb7e2-186e-505b-9e93-f48793333810}</b><br>
|
|
||||||
<b> DISPLAY NAME: \\.\DISPLAY1</b><br>
|
|
||||||
<b> FRIENDLY NAME: ROG PG279Q</b><br>
|
|
||||||
<b> DEVICE STATE: PRIMARY</b><br>
|
|
||||||
<b> HDR STATE: UNKNOWN</b>
|
|
||||||
</template>
|
|
||||||
<template #linux>
|
|
||||||
</template>
|
|
||||||
<template #macos>
|
|
||||||
</template>
|
|
||||||
</PlatformLayout>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_common": {
|
"_common": {
|
||||||
"apply": "Anwenden",
|
"apply": "Übernehmen",
|
||||||
"auto": "Automatisch",
|
"auto": "Automatisch",
|
||||||
"autodetect": "AutoDetection (empfohlen)",
|
"autodetect": "AutoDetection (empfohlen)",
|
||||||
"beta": "(Beta)",
|
"beta": "(Beta)",
|
||||||
|
|||||||
@@ -291,9 +291,9 @@
|
|||||||
"origin_web_ui_allowed_pc": "Only localhost may access Web UI",
|
"origin_web_ui_allowed_pc": "Only localhost may access Web UI",
|
||||||
"origin_web_ui_allowed_wan": "Anyone may access Web UI",
|
"origin_web_ui_allowed_wan": "Anyone may access Web UI",
|
||||||
"output_name_desc_unix": "During Apollo startup, you should see the list of detected displays. Note: You need to use the id value inside the parenthesis. Below is an example; the actual output can be found in the Troubleshooting tab.",
|
"output_name_desc_unix": "During Apollo startup, you should see the list of detected displays. Note: You need to use the id value inside the parenthesis. Below is an example; the actual output can be found in the Troubleshooting tab.",
|
||||||
"output_name_desc_windows": "Manually specify a display to use for capture. If unset, the primary display is captured. Note: If you specified a GPU above, this display must be connected to that GPU. The appropriate values can be found using the following command:",
|
"output_name_desc_windows": "Manually specify a display device id to use for capture. If unset, the primary display is captured. Note: If you specified a GPU above, this display must be connected to that GPU. During Sunshine startup, you should see the list of detected displays. Below is an example; the actual output can be found in the Troubleshooting tab.",
|
||||||
"output_name_unix": "Display number",
|
"output_name_unix": "Display number",
|
||||||
"output_name_windows": "Output Name",
|
"output_name_windows": "Display Device Id",
|
||||||
"ping_timeout": "Ping Timeout",
|
"ping_timeout": "Ping Timeout",
|
||||||
"ping_timeout_desc": "How long to wait in milliseconds for data from moonlight before shutting down the stream",
|
"ping_timeout_desc": "How long to wait in milliseconds for data from moonlight before shutting down the stream",
|
||||||
"pkey": "Private Key",
|
"pkey": "Private Key",
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
"image": "Imagem:",
|
"image": "Imagem:",
|
||||||
"image_desc": "Caminho da aplicação icon/imagem/imagem que será enviado para o cliente. Imagem deve ser um arquivo PNG. Se não estiver definido, Apollo irá enviar a imagem da caixa padrão.",
|
"image_desc": "Caminho da aplicação icon/imagem/imagem que será enviado para o cliente. Imagem deve ser um arquivo PNG. Se não estiver definido, Apollo irá enviar a imagem da caixa padrão.",
|
||||||
"loading": "Carregandochar@@0",
|
"loading": "Carregandochar@@0",
|
||||||
"name": "Nome:",
|
"name": "Nome",
|
||||||
"output_desc": "O arquivo onde a saída do comando é armazenada, se não for especificado, a saída é ignorada",
|
"output_desc": "O arquivo onde a saída do comando é armazenada, se não for especificado, a saída é ignorada",
|
||||||
"output_name": "Saída",
|
"output_name": "Saída",
|
||||||
"run_as_desc": "Isto pode ser necessário para que alguns aplicativos que requerem permissões de administrador sejam executados corretamente.",
|
"run_as_desc": "Isto pode ser necessário para que alguns aplicativos que requerem permissões de administrador sejam executados corretamente.",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"amd_rc_cqp": "cqp -- modo qp constante",
|
"amd_rc_cqp": "cqp -- modo qp constante",
|
||||||
"amd_rc_desc": "Isto controla o método de controle da taxa para garantir que não estamos a exceder o alvo da taxa de bits do cliente. 'cqp' não é adequado para segmentação de taxa de bits e outras opções além de 'vbr_latency' dependem da aplicação HRD para ajudar a restringir os fluxos de taxa de bits.",
|
"amd_rc_desc": "Isto controla o método de controle da taxa para garantir que não estamos a exceder o alvo da taxa de bits do cliente. 'cqp' não é adequado para segmentação de taxa de bits e outras opções além de 'vbr_latency' dependem da aplicação HRD para ajudar a restringir os fluxos de taxa de bits.",
|
||||||
"amd_rc_group": "Configurações de controle de taxa AMF",
|
"amd_rc_group": "Configurações de controle de taxa AMF",
|
||||||
"amd_rc_vbr_latency": "latência vbr_ency -- taxa de bits com restrição de latência",
|
"amd_rc_vbr_latency": "vbr_latency -- bitrate variável limitado pela latência (recomendado se o HDR estiver desabilitado; padrão)",
|
||||||
"amd_rc_vbr_peak": "vbr_pico -- pico de taxa de bits variável restrita",
|
"amd_rc_vbr_peak": "vbr_pico -- pico de taxa de bits variável restrita",
|
||||||
"amd_usage": "Uso do AMF",
|
"amd_usage": "Uso do AMF",
|
||||||
"amd_usage_desc": "Isso define o perfil de codificação base. Todas as opções apresentadas abaixo substituirão um subconjunto do perfil de uso, mas há configurações ocultas adicionais aplicadas que não podem ser configuradas em outro lugar.",
|
"amd_usage_desc": "Isso define o perfil de codificação base. Todas as opções apresentadas abaixo substituirão um subconjunto do perfil de uso, mas há configurações ocultas adicionais aplicadas que não podem ser configuradas em outro lugar.",
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
},
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"device_name": "Nome do dispositivo",
|
"device_name": "Nome do dispositivo",
|
||||||
"pair_failure": "Falha no pareamento: Verifique se o PIN é digitado corretamente",
|
"pair_failure": "Pareamento Falhou: Verifique se o PIN foi digitado corretamente",
|
||||||
"pair_success": "Sucesso! Por favor, verifique a Lua Lunar para continuar",
|
"pair_success": "Sucesso! Por favor, verifique a Lua Lunar para continuar",
|
||||||
"pin_pairing": "PIN Pairing",
|
"pin_pairing": "PIN Pairing",
|
||||||
"send": "Mandar",
|
"send": "Mandar",
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
"amd_usage_ultralowlatency": "ultralowlatency -- 超低延迟(最快;默认)",
|
"amd_usage_ultralowlatency": "ultralowlatency -- 超低延迟(最快;默认)",
|
||||||
"amd_usage_webcam": "webcam -- 网络摄像头(慢)",
|
"amd_usage_webcam": "webcam -- 网络摄像头(慢)",
|
||||||
"amd_vbaq": "AMF 基于方差的自适应量化 (VBAQ)",
|
"amd_vbaq": "AMF 基于方差的自适应量化 (VBAQ)",
|
||||||
"amd_vbaq_desc": "人的视觉系统通常对高成形地区的艺术品不太敏感。 在 VBAQ 模式下,像素差异用于表示空间纹理的复杂性,使编码器能够将更多的比特分配给较平的区域。 启用此功能可提高主观视觉品质及一些内容。",
|
"amd_vbaq_desc": "人类的视觉系统通常对高度纹理化区域中的瑕疵不太敏感。在VBAQ模式下,像素方差被用来指示空间纹理的复杂性,这使得编码器可以将更多的比特分配给更平滑的区域。启用这个特性可以在某些内容上提升主观视觉质量。",
|
||||||
"apply_note": "点击“应用”重启 Apollo 并应用更改。这将终止任何正在运行的会话。",
|
"apply_note": "点击“应用”重启 Apollo 并应用更改。这将终止任何正在运行的会话。",
|
||||||
"audio_sink": "音频输出设备",
|
"audio_sink": "音频输出设备",
|
||||||
"audio_sink_desc_linux": "手动指定需要抓取的音频输出设备。如果您没有指定此变量,PulseAudio 将选择默认监测设备。 您可以使用以下任何命令找到音频输出设备的名称:",
|
"audio_sink_desc_linux": "手动指定需要抓取的音频输出设备。如果您没有指定此变量,PulseAudio 将选择默认监测设备。 您可以使用以下任何命令找到音频输出设备的名称:",
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
"follow_client_hdr_desc": "根据客户端请求自动设置串流屏幕 HDR 的开关。(不建议启用)",
|
"follow_client_hdr_desc": "根据客户端请求自动设置串流屏幕 HDR 的开关。(不建议启用)",
|
||||||
"gamepad": "模拟游戏手柄类型",
|
"gamepad": "模拟游戏手柄类型",
|
||||||
"gamepad_auto": "自动选择选项",
|
"gamepad_auto": "自动选择选项",
|
||||||
"gamepad_desc": "选择要在主机上模拟的游戏手表类型",
|
"gamepad_desc": "选择要在主机上模拟的游戏手柄类型",
|
||||||
"gamepad_ds4": "DS4 (PS4)",
|
"gamepad_ds4": "DS4 (PS4)",
|
||||||
"gamepad_ds5": "DS5 (PS5)",
|
"gamepad_ds5": "DS5 (PS5)",
|
||||||
"gamepad_switch": "Nintendo Pro (Switch)",
|
"gamepad_switch": "Nintendo Pro (Switch)",
|
||||||
|
|||||||
1
third-party/libdisplaydevice
vendored
Submodule
1
third-party/libdisplaydevice
vendored
Submodule
Submodule third-party/libdisplaydevice added at bbcd179c47
Reference in New Issue
Block a user