Add Windows clipboard support
This commit is contained in:
@@ -182,10 +182,7 @@ namespace confighttp {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
|
||||
*response << "HTTP/1.1 404 NOT FOUND\r\n"
|
||||
<< data.str();
|
||||
response->write(SimpleWeb::StatusCode::client_error_not_found, data.str());
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
115
src/nvhttp.cpp
115
src/nvhttp.cpp
@@ -592,12 +592,7 @@ namespace nvhttp {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
|
||||
*response
|
||||
<< "HTTP/1.1 404 NOT FOUND\r\n"
|
||||
<< data.str();
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_not_found, data.str());
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
@@ -1268,6 +1263,112 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
void
|
||||
getClipboard(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
auto named_cert_p = get_verified_cert(request);
|
||||
|
||||
if (
|
||||
!(named_cert_p->perm & PERM::_allow_view)
|
||||
|| !(named_cert_p->perm & PERM::clipboard_read)
|
||||
) {
|
||||
BOOST_LOG(debug) << "Permission Read Clipboard denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized);
|
||||
response->close_connection_after_response = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
auto clipboard_type = get_arg(args, "type");
|
||||
if (clipboard_type != "text"sv) {
|
||||
BOOST_LOG(debug) << "Clipboard type [" << clipboard_type << "] is not supported!";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request);
|
||||
response->close_connection_after_response = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids();
|
||||
|
||||
bool found = !connected_uuids.empty();
|
||||
|
||||
if (found) {
|
||||
found = (std::find(connected_uuids.begin(), connected_uuids.end(), named_cert_p->uuid) != connected_uuids.end());
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
BOOST_LOG(debug) << "Client ["<< named_cert_p->name << "] trying to get clipboard is not connected to a stream";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_forbidden);
|
||||
response->close_connection_after_response = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string content = platf::get_clipboard();
|
||||
response->write(content);
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
setClipboard(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
auto named_cert_p = get_verified_cert(request);
|
||||
|
||||
if (
|
||||
!(named_cert_p->perm & PERM::_allow_view)
|
||||
|| !(named_cert_p->perm & PERM::clipboard_set)
|
||||
) {
|
||||
BOOST_LOG(debug) << "Permission Write Clipboard denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized);
|
||||
response->close_connection_after_response = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
auto clipboard_type = get_arg(args, "type");
|
||||
if (clipboard_type != "text"sv) {
|
||||
BOOST_LOG(debug) << "Clipboard type [" << clipboard_type << "] is not supported!";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request);
|
||||
response->close_connection_after_response = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids();
|
||||
|
||||
bool found = !connected_uuids.empty();
|
||||
|
||||
if (found) {
|
||||
found = (std::find(connected_uuids.begin(), connected_uuids.end(), named_cert_p->uuid) != connected_uuids.end());
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
BOOST_LOG(debug) << "Client ["<< named_cert_p->name << "] trying to set clipboard is not connected to a stream";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::client_error_forbidden);
|
||||
response->close_connection_after_response = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string content = request->content.string();
|
||||
|
||||
bool success = platf::set_clipboard(content);
|
||||
|
||||
if (!success) {
|
||||
BOOST_LOG(debug) << "Setting clipboard failed!";
|
||||
|
||||
response->write(SimpleWeb::StatusCode::server_error_internal_server_error);
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
response->write();
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
start() {
|
||||
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
|
||||
@@ -1351,6 +1452,8 @@ namespace nvhttp {
|
||||
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
|
||||
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
|
||||
https_server.resource["^/cancel$"]["GET"] = cancel;
|
||||
https_server.resource["^/actions/clipboard$"]["GET"] = getClipboard;
|
||||
https_server.resource["^/actions/clipboard$"]["POST"] = setClipboard;
|
||||
|
||||
https_server.config.reuse_address = true;
|
||||
https_server.config.address = net::af_to_any_address_string(address_family);
|
||||
|
||||
@@ -895,4 +895,10 @@ namespace platf {
|
||||
std::unique_ptr<high_precision_timer>
|
||||
create_high_precision_timer();
|
||||
|
||||
std::string
|
||||
get_clipboard();
|
||||
|
||||
bool
|
||||
set_clipboard(const std::string& content);
|
||||
|
||||
} // namespace platf
|
||||
|
||||
@@ -1088,4 +1088,16 @@ get_local_ip_for_gateway() {
|
||||
create_high_precision_timer() {
|
||||
return std::make_unique<linux_high_precision_timer>();
|
||||
}
|
||||
|
||||
std::string
|
||||
get_clipboard() {
|
||||
// Placeholder
|
||||
return "";
|
||||
}
|
||||
|
||||
bool
|
||||
set_clipboard(const std::string& content) {
|
||||
// Placeholder
|
||||
return false;
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
@@ -546,6 +546,18 @@ namespace platf {
|
||||
create_high_precision_timer() {
|
||||
return std::make_unique<macos_high_precision_timer>();
|
||||
}
|
||||
|
||||
std::string
|
||||
get_clipboard() {
|
||||
// Placeholder
|
||||
return "";
|
||||
}
|
||||
|
||||
bool
|
||||
set_clipboard(const std::string& content) {
|
||||
// Placeholder
|
||||
return false;
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
namespace dyn {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <Shlwapi.h>
|
||||
|
||||
#include "misc.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "src/entry_handler.h"
|
||||
#include "src/globals.h"
|
||||
@@ -95,6 +96,10 @@ namespace {
|
||||
|
||||
namespace bp = boost::process;
|
||||
|
||||
static std::string ensureCrLf(const std::string& utf8Str);
|
||||
static std::wstring getClipboardData();
|
||||
static int setClipboardData(const std::string& acpStr);
|
||||
|
||||
using namespace std::literals;
|
||||
namespace platf {
|
||||
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
||||
@@ -1941,4 +1946,103 @@ namespace platf {
|
||||
create_high_precision_timer() {
|
||||
return std::make_unique<win32_high_precision_timer>();
|
||||
}
|
||||
|
||||
std::string
|
||||
get_clipboard() {
|
||||
std::string currentClipboard = to_utf8(getClipboardData());
|
||||
return currentClipboard;
|
||||
}
|
||||
|
||||
bool
|
||||
set_clipboard(const std::string& content) {
|
||||
std::string cpContent = convertUtf8ToCurrentCodepage(ensureCrLf(content));
|
||||
return !setClipboardData(cpContent);
|
||||
}
|
||||
} // namespace platf
|
||||
|
||||
static std::string ensureCrLf(const std::string& utf8Str) {
|
||||
std::string result;
|
||||
result.reserve(utf8Str.size() + utf8Str.length() / 2); // Reserve extra space
|
||||
|
||||
for (size_t i = 0; i < utf8Str.size(); ++i) {
|
||||
if (utf8Str[i] == '\n' && (i == 0 || utf8Str[i - 1] != '\r')) {
|
||||
result += '\r'; // Add \r before \n if not present
|
||||
}
|
||||
result += utf8Str[i]; // Always add the current character
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::wstring getClipboardData() {
|
||||
if (!OpenClipboard(nullptr)) {
|
||||
BOOST_LOG(warning) << "Failed to open clipboard.";
|
||||
return L"";
|
||||
}
|
||||
|
||||
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
|
||||
if (hData == nullptr) {
|
||||
BOOST_LOG(warning) << "No text data in clipboard or failed to get data.";
|
||||
CloseClipboard();
|
||||
return L"";
|
||||
}
|
||||
|
||||
wchar_t* pszText = static_cast<wchar_t*>(GlobalLock(hData));
|
||||
if (pszText == nullptr) {
|
||||
BOOST_LOG(warning) << "Failed to lock clipboard data.";
|
||||
CloseClipboard();
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring ret = pszText;
|
||||
|
||||
GlobalUnlock(hData);
|
||||
CloseClipboard();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int setClipboardData(const std::string& acpStr) {
|
||||
if (!OpenClipboard(nullptr)) {
|
||||
BOOST_LOG(warning) << "Failed to open clipboard.";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!EmptyClipboard()) {
|
||||
BOOST_LOG(warning) << "Failed to empty clipboard.";
|
||||
CloseClipboard();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Allocate global memory for the clipboard text
|
||||
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, acpStr.size() + 1);
|
||||
if (hGlobal == nullptr) {
|
||||
BOOST_LOG(warning) << "Failed to allocate global memory.";
|
||||
CloseClipboard();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Lock the global memory and copy the text
|
||||
char* pGlobal = static_cast<char*>(GlobalLock(hGlobal));
|
||||
if (pGlobal == nullptr) {
|
||||
BOOST_LOG(warning) << "Failed to lock global memory.";
|
||||
GlobalFree(hGlobal);
|
||||
CloseClipboard();
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(pGlobal, acpStr.c_str(), acpStr.size() + 1);
|
||||
GlobalUnlock(hGlobal);
|
||||
|
||||
// Set the clipboard data
|
||||
if (SetClipboardData(CF_TEXT, hGlobal) == nullptr) {
|
||||
BOOST_LOG(warning) << "Failed to set clipboard data.";
|
||||
GlobalFree(hGlobal);
|
||||
CloseClipboard();
|
||||
return 1;
|
||||
}
|
||||
|
||||
CloseClipboard();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,65 @@
|
||||
#include "utils.h"
|
||||
|
||||
std::wstring acpToUtf16(const std::string& origStr) {
|
||||
auto acp = GetACP();
|
||||
|
||||
int utf16Len = MultiByteToWideChar(acp, 0, origStr.c_str(), origStr.size(), NULL, 0);
|
||||
if (utf16Len == 0) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring utf16Str(utf16Len, L'\0');
|
||||
MultiByteToWideChar(acp, 0, origStr.c_str(), origStr.size(), &utf16Str[0], utf16Len);
|
||||
|
||||
return utf16Str;
|
||||
}
|
||||
|
||||
std::string utf16toAcp(const std::wstring& utf16Str) {
|
||||
auto acp = GetACP();
|
||||
|
||||
int codepageLen = WideCharToMultiByte(acp, 0, utf16Str.c_str(), utf16Str.size(), NULL, 0, NULL, NULL);
|
||||
if (codepageLen == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string codepageStr(codepageLen, '\0');
|
||||
WideCharToMultiByte(acp, 0, utf16Str.c_str(), utf16Str.size(), &codepageStr[0], codepageLen, NULL, NULL);
|
||||
|
||||
return codepageStr;
|
||||
}
|
||||
|
||||
std::string convertUtf8ToCurrentCodepage(const std::string& utf8Str) {
|
||||
if (GetACP() == CP_UTF8) {
|
||||
return std::string(utf8Str);
|
||||
}
|
||||
// Step 1: Convert UTF-8 to UTF-16
|
||||
int utf16Len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
|
||||
|
||||
int utf16Len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), utf8Str.size(), NULL, 0);
|
||||
if (utf16Len == 0) {
|
||||
return std::string(utf8Str);
|
||||
}
|
||||
|
||||
std::wstring utf16Str(utf16Len, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &utf16Str[0], utf16Len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), utf8Str.size(), &utf16Str[0], utf16Len);
|
||||
|
||||
// Step 2: Convert UTF-16 to the current Windows codepage
|
||||
int codepageLen = WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, NULL, 0, NULL, NULL);
|
||||
if (codepageLen == 0) {
|
||||
return std::string(utf8Str);
|
||||
return utf16toAcp(utf16Str);
|
||||
}
|
||||
|
||||
std::string convertCurrentCodepageToUtf8(const std::string& origStr) {
|
||||
if (GetACP() == CP_UTF8) {
|
||||
return std::string(origStr);
|
||||
}
|
||||
|
||||
std::string codepageStr(codepageLen, '\0');
|
||||
WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, &codepageStr[0], codepageLen, NULL, NULL);
|
||||
auto utf16Str = acpToUtf16(origStr);
|
||||
|
||||
return codepageStr;
|
||||
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, utf16Str.c_str(), utf16Str.size(), NULL, 0, NULL, NULL);
|
||||
if (utf8Len == 0) {
|
||||
return std::string(origStr);
|
||||
}
|
||||
|
||||
std::string utf8Str(utf8Len, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Len, NULL, NULL);
|
||||
|
||||
return utf8Str;
|
||||
}
|
||||
|
||||
// Modified from https://github.com/FrogTheFrog/Sunshine/blob/b6f8573d35eff7c55da6965dfa317dc9722bd4ef/src/platform/windows/display_device/windows_utils.cpp
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
#include "src/utility.h"
|
||||
#include "src/logging.h"
|
||||
|
||||
std::wstring acpToUtf16(const std::string& origStr);
|
||||
std::string utf16toAcp(const std::wstring& utf16Str);
|
||||
std::string convertUtf8ToCurrentCodepage(const std::string& utf8Str);
|
||||
std::string convertCurrentCodepageToUtf8(const std::string& currentStr);
|
||||
|
||||
std::string get_error_string(LONG error_code);
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
<p class="mb-0">{{ $t('pin.device_management_desc') }} <a href="https://github.com/ClassicOldSong/Apollo/wiki/Permission-System" target="_blank">{{ $t('_common.learn_more') }}</a></p>
|
||||
<p class="mb-0">{{ $t('pin.device_management_warning') }}</p>
|
||||
<p class="mb-0">{{ $t('pin.device_management_desc') }}</p>
|
||||
<p class="mb-0">{{ $t('pin.device_management_warning') }} <a href="https://github.com/ClassicOldSong/Apollo/wiki/Permission-System" target="_blank">{{ $t('_common.learn_more') }}</a></p>
|
||||
<div id="apply-alert" class="alert alert-success d-flex align-items-center mt-3" :style="{ 'display': (showApplyMessage ? 'flex !important': 'none !important') }">
|
||||
<div class="me-2"><b>{{ $t('_common.success') }}</b> {{ $t('pin.unpair_single_success') }}</div>
|
||||
<button class="btn btn-success ms-auto apply" @click="clickedApplyBanner">{{ $t('_common.dismiss') }}</button>
|
||||
@@ -235,6 +235,14 @@
|
||||
}
|
||||
] },
|
||||
{ name: 'Operation', permissions: [
|
||||
{
|
||||
name: 'clipboard_set',
|
||||
suppressed_by: []
|
||||
},
|
||||
{
|
||||
name: 'clipboard_read',
|
||||
suppressed_by: []
|
||||
},
|
||||
{
|
||||
name: 'server_cmd',
|
||||
suppressed_by: []
|
||||
|
||||
Reference in New Issue
Block a user