Skip to content

Commit 1ae7157

Browse files
Add Windows clipboard support
1 parent c72fb45 commit 1ae7157

File tree

9 files changed

+304
-22
lines changed

9 files changed

+304
-22
lines changed

src/confighttp.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,7 @@ namespace confighttp {
182182
std::ostringstream data;
183183

184184
pt::write_xml(data, tree);
185-
response->write(data.str());
186-
187-
*response << "HTTP/1.1 404 NOT FOUND\r\n"
188-
<< data.str();
185+
response->write(SimpleWeb::StatusCode::client_error_not_found, data.str());
189186
}
190187

191188
void

src/nvhttp.cpp

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -592,12 +592,7 @@ namespace nvhttp {
592592
std::ostringstream data;
593593

594594
pt::write_xml(data, tree);
595-
response->write(data.str());
596-
597-
*response
598-
<< "HTTP/1.1 404 NOT FOUND\r\n"
599-
<< data.str();
600-
595+
response->write(SimpleWeb::StatusCode::client_error_not_found, data.str());
601596
response->close_connection_after_response = true;
602597
}
603598

@@ -1268,6 +1263,112 @@ namespace nvhttp {
12681263
response->close_connection_after_response = true;
12691264
}
12701265

1266+
void
1267+
getClipboard(resp_https_t response, req_https_t request) {
1268+
print_req<SunshineHTTPS>(request);
1269+
1270+
auto named_cert_p = get_verified_cert(request);
1271+
1272+
if (
1273+
!(named_cert_p->perm & PERM::_allow_view)
1274+
|| !(named_cert_p->perm & PERM::clipboard_read)
1275+
) {
1276+
BOOST_LOG(debug) << "Permission Read Clipboard denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
1277+
1278+
response->write(SimpleWeb::StatusCode::client_error_unauthorized);
1279+
response->close_connection_after_response = true;
1280+
return;
1281+
}
1282+
1283+
auto args = request->parse_query_string();
1284+
auto clipboard_type = get_arg(args, "type");
1285+
if (clipboard_type != "text"sv) {
1286+
BOOST_LOG(debug) << "Clipboard type [" << clipboard_type << "] is not supported!";
1287+
1288+
response->write(SimpleWeb::StatusCode::client_error_bad_request);
1289+
response->close_connection_after_response = true;
1290+
return;
1291+
}
1292+
1293+
std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids();
1294+
1295+
bool found = !connected_uuids.empty();
1296+
1297+
if (found) {
1298+
found = (std::find(connected_uuids.begin(), connected_uuids.end(), named_cert_p->uuid) != connected_uuids.end());
1299+
}
1300+
1301+
if (!found) {
1302+
BOOST_LOG(debug) << "Client ["<< named_cert_p->name << "] trying to get clipboard is not connected to a stream";
1303+
1304+
response->write(SimpleWeb::StatusCode::client_error_forbidden);
1305+
response->close_connection_after_response = true;
1306+
return;
1307+
}
1308+
1309+
std::string content = platf::get_clipboard();
1310+
response->write(content);
1311+
return;
1312+
}
1313+
1314+
void
1315+
setClipboard(resp_https_t response, req_https_t request) {
1316+
print_req<SunshineHTTPS>(request);
1317+
1318+
auto named_cert_p = get_verified_cert(request);
1319+
1320+
if (
1321+
!(named_cert_p->perm & PERM::_allow_view)
1322+
|| !(named_cert_p->perm & PERM::clipboard_set)
1323+
) {
1324+
BOOST_LOG(debug) << "Permission Write Clipboard denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
1325+
1326+
response->write(SimpleWeb::StatusCode::client_error_unauthorized);
1327+
response->close_connection_after_response = true;
1328+
return;
1329+
}
1330+
1331+
auto args = request->parse_query_string();
1332+
auto clipboard_type = get_arg(args, "type");
1333+
if (clipboard_type != "text"sv) {
1334+
BOOST_LOG(debug) << "Clipboard type [" << clipboard_type << "] is not supported!";
1335+
1336+
response->write(SimpleWeb::StatusCode::client_error_bad_request);
1337+
response->close_connection_after_response = true;
1338+
return;
1339+
}
1340+
1341+
std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids();
1342+
1343+
bool found = !connected_uuids.empty();
1344+
1345+
if (found) {
1346+
found = (std::find(connected_uuids.begin(), connected_uuids.end(), named_cert_p->uuid) != connected_uuids.end());
1347+
}
1348+
1349+
if (!found) {
1350+
BOOST_LOG(debug) << "Client ["<< named_cert_p->name << "] trying to set clipboard is not connected to a stream";
1351+
1352+
response->write(SimpleWeb::StatusCode::client_error_forbidden);
1353+
response->close_connection_after_response = true;
1354+
return;
1355+
}
1356+
1357+
std::string content = request->content.string();
1358+
1359+
bool success = platf::set_clipboard(content);
1360+
1361+
if (!success) {
1362+
BOOST_LOG(debug) << "Setting clipboard failed!";
1363+
1364+
response->write(SimpleWeb::StatusCode::server_error_internal_server_error);
1365+
response->close_connection_after_response = true;
1366+
}
1367+
1368+
response->write();
1369+
return;
1370+
}
1371+
12711372
void
12721373
start() {
12731374
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
@@ -1351,6 +1452,8 @@ namespace nvhttp {
13511452
https_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); };
13521453
https_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); };
13531454
https_server.resource["^/cancel$"]["GET"] = cancel;
1455+
https_server.resource["^/actions/clipboard$"]["GET"] = getClipboard;
1456+
https_server.resource["^/actions/clipboard$"]["POST"] = setClipboard;
13541457

13551458
https_server.config.reuse_address = true;
13561459
https_server.config.address = net::af_to_any_address_string(address_family);

src/platform/common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,4 +895,10 @@ namespace platf {
895895
std::unique_ptr<high_precision_timer>
896896
create_high_precision_timer();
897897

898+
std::string
899+
get_clipboard();
900+
901+
bool
902+
set_clipboard(const std::string& content);
903+
898904
} // namespace platf

src/platform/linux/misc.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,4 +1088,16 @@ get_local_ip_for_gateway() {
10881088
create_high_precision_timer() {
10891089
return std::make_unique<linux_high_precision_timer>();
10901090
}
1091+
1092+
std::string
1093+
get_clipboard() {
1094+
// Placeholder
1095+
return "";
1096+
}
1097+
1098+
bool
1099+
set_clipboard(const std::string& content) {
1100+
// Placeholder
1101+
return false;
1102+
}
10911103
} // namespace platf

src/platform/macos/misc.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,18 @@ operator bool() override {
546546
create_high_precision_timer() {
547547
return std::make_unique<macos_high_precision_timer>();
548548
}
549+
550+
std::string
551+
get_clipboard() {
552+
// Placeholder
553+
return "";
554+
}
555+
556+
bool
557+
set_clipboard(const std::string& content) {
558+
// Placeholder
559+
return false;
560+
}
549561
} // namespace platf
550562

551563
namespace dyn {

src/platform/windows/misc.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <Shlwapi.h>
3636

3737
#include "misc.h"
38+
#include "utils.h"
3839

3940
#include "src/entry_handler.h"
4041
#include "src/globals.h"
@@ -95,6 +96,10 @@ namespace {
9596

9697
namespace bp = boost::process;
9798

99+
static std::string ensureCrLf(const std::string& utf8Str);
100+
static std::wstring getClipboardData();
101+
static int setClipboardData(const std::string& acpStr);
102+
98103
using namespace std::literals;
99104
namespace platf {
100105
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
@@ -1941,4 +1946,103 @@ namespace platf {
19411946
create_high_precision_timer() {
19421947
return std::make_unique<win32_high_precision_timer>();
19431948
}
1949+
1950+
std::string
1951+
get_clipboard() {
1952+
std::string currentClipboard = to_utf8(getClipboardData());
1953+
return currentClipboard;
1954+
}
1955+
1956+
bool
1957+
set_clipboard(const std::string& content) {
1958+
std::string cpContent = convertUtf8ToCurrentCodepage(ensureCrLf(content));
1959+
return !setClipboardData(cpContent);
1960+
}
19441961
} // namespace platf
1962+
1963+
static std::string ensureCrLf(const std::string& utf8Str) {
1964+
std::string result;
1965+
result.reserve(utf8Str.size() + utf8Str.length() / 2); // Reserve extra space
1966+
1967+
for (size_t i = 0; i < utf8Str.size(); ++i) {
1968+
if (utf8Str[i] == '\n' && (i == 0 || utf8Str[i - 1] != '\r')) {
1969+
result += '\r'; // Add \r before \n if not present
1970+
}
1971+
result += utf8Str[i]; // Always add the current character
1972+
}
1973+
1974+
return result;
1975+
}
1976+
1977+
static std::wstring getClipboardData() {
1978+
if (!OpenClipboard(nullptr)) {
1979+
BOOST_LOG(warning) << "Failed to open clipboard.";
1980+
return L"";
1981+
}
1982+
1983+
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
1984+
if (hData == nullptr) {
1985+
BOOST_LOG(warning) << "No text data in clipboard or failed to get data.";
1986+
CloseClipboard();
1987+
return L"";
1988+
}
1989+
1990+
wchar_t* pszText = static_cast<wchar_t*>(GlobalLock(hData));
1991+
if (pszText == nullptr) {
1992+
BOOST_LOG(warning) << "Failed to lock clipboard data.";
1993+
CloseClipboard();
1994+
return L"";
1995+
}
1996+
1997+
std::wstring ret = pszText;
1998+
1999+
GlobalUnlock(hData);
2000+
CloseClipboard();
2001+
2002+
return ret;
2003+
}
2004+
2005+
static int setClipboardData(const std::string& acpStr) {
2006+
if (!OpenClipboard(nullptr)) {
2007+
BOOST_LOG(warning) << "Failed to open clipboard.";
2008+
return 1;
2009+
}
2010+
2011+
if (!EmptyClipboard()) {
2012+
BOOST_LOG(warning) << "Failed to empty clipboard.";
2013+
CloseClipboard();
2014+
return 1;
2015+
}
2016+
2017+
// Allocate global memory for the clipboard text
2018+
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, acpStr.size() + 1);
2019+
if (hGlobal == nullptr) {
2020+
BOOST_LOG(warning) << "Failed to allocate global memory.";
2021+
CloseClipboard();
2022+
return 1;
2023+
}
2024+
2025+
// Lock the global memory and copy the text
2026+
char* pGlobal = static_cast<char*>(GlobalLock(hGlobal));
2027+
if (pGlobal == nullptr) {
2028+
BOOST_LOG(warning) << "Failed to lock global memory.";
2029+
GlobalFree(hGlobal);
2030+
CloseClipboard();
2031+
return 1;
2032+
}
2033+
2034+
memcpy(pGlobal, acpStr.c_str(), acpStr.size() + 1);
2035+
GlobalUnlock(hGlobal);
2036+
2037+
// Set the clipboard data
2038+
if (SetClipboardData(CF_TEXT, hGlobal) == nullptr) {
2039+
BOOST_LOG(warning) << "Failed to set clipboard data.";
2040+
GlobalFree(hGlobal);
2041+
CloseClipboard();
2042+
return 1;
2043+
}
2044+
2045+
CloseClipboard();
2046+
2047+
return 0;
2048+
}

src/platform/windows/utils.cpp

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,65 @@
11
#include "utils.h"
22

3+
std::wstring acpToUtf16(const std::string& origStr) {
4+
auto acp = GetACP();
5+
6+
int utf16Len = MultiByteToWideChar(acp, 0, origStr.c_str(), origStr.size(), NULL, 0);
7+
if (utf16Len == 0) {
8+
return L"";
9+
}
10+
11+
std::wstring utf16Str(utf16Len, L'\0');
12+
MultiByteToWideChar(acp, 0, origStr.c_str(), origStr.size(), &utf16Str[0], utf16Len);
13+
14+
return utf16Str;
15+
}
16+
17+
std::string utf16toAcp(const std::wstring& utf16Str) {
18+
auto acp = GetACP();
19+
20+
int codepageLen = WideCharToMultiByte(acp, 0, utf16Str.c_str(), utf16Str.size(), NULL, 0, NULL, NULL);
21+
if (codepageLen == 0) {
22+
return "";
23+
}
24+
25+
std::string codepageStr(codepageLen, '\0');
26+
WideCharToMultiByte(acp, 0, utf16Str.c_str(), utf16Str.size(), &codepageStr[0], codepageLen, NULL, NULL);
27+
28+
return codepageStr;
29+
}
30+
331
std::string convertUtf8ToCurrentCodepage(const std::string& utf8Str) {
432
if (GetACP() == CP_UTF8) {
533
return std::string(utf8Str);
634
}
7-
// Step 1: Convert UTF-8 to UTF-16
8-
int utf16Len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, NULL, 0);
35+
36+
int utf16Len = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), utf8Str.size(), NULL, 0);
937
if (utf16Len == 0) {
1038
return std::string(utf8Str);
1139
}
1240

1341
std::wstring utf16Str(utf16Len, L'\0');
14-
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), -1, &utf16Str[0], utf16Len);
42+
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), utf8Str.size(), &utf16Str[0], utf16Len);
1543

16-
// Step 2: Convert UTF-16 to the current Windows codepage
17-
int codepageLen = WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, NULL, 0, NULL, NULL);
18-
if (codepageLen == 0) {
19-
return std::string(utf8Str);
44+
return utf16toAcp(utf16Str);
45+
}
46+
47+
std::string convertCurrentCodepageToUtf8(const std::string& origStr) {
48+
if (GetACP() == CP_UTF8) {
49+
return std::string(origStr);
2050
}
2151

22-
std::string codepageStr(codepageLen, '\0');
23-
WideCharToMultiByte(GetACP(), 0, utf16Str.c_str(), -1, &codepageStr[0], codepageLen, NULL, NULL);
52+
auto utf16Str = acpToUtf16(origStr);
2453

25-
return codepageStr;
54+
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, utf16Str.c_str(), utf16Str.size(), NULL, 0, NULL, NULL);
55+
if (utf8Len == 0) {
56+
return std::string(origStr);
57+
}
58+
59+
std::string utf8Str(utf8Len, '\0');
60+
WideCharToMultiByte(CP_UTF8, 0, utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Len, NULL, NULL);
61+
62+
return utf8Str;
2663
}
2764

2865
// Modified from https://github.com/FrogTheFrog/Sunshine/blob/b6f8573d35eff7c55da6965dfa317dc9722bd4ef/src/platform/windows/display_device/windows_utils.cpp

0 commit comments

Comments
 (0)