Add UUID to apps w/ add option to use App's id for virtual display (resolves #48)
This commit is contained in:
@@ -395,38 +395,8 @@ namespace confighttp {
|
||||
pt::read_json(ss, inputTree);
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
|
||||
if (inputTree.get_child("prep-cmd").empty()) {
|
||||
inputTree.erase("prep-cmd");
|
||||
}
|
||||
proc::migrate_apps(&fileTree, &inputTree);
|
||||
|
||||
if (inputTree.get_child("detached").empty()) {
|
||||
inputTree.erase("detached");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = inputTree.get<int>("index");
|
||||
|
||||
inputTree.erase("index");
|
||||
|
||||
if (index == -1) {
|
||||
apps_node.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &kv : apps_node) {
|
||||
if (i == index) {
|
||||
newApps.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
@@ -450,33 +420,38 @@ namespace confighttp {
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
if (
|
||||
args.find("uuid"s) == std::end(args)
|
||||
) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Missing a required launch parameter");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto uuid = nvhttp::get_arg(args, "uuid");
|
||||
|
||||
pt::ptree fileTree;
|
||||
try {
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = stoi(request->path_match[1]);
|
||||
|
||||
if (index < 0) {
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid Index");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &kv : apps_node) {
|
||||
if (i++ != index) {
|
||||
auto app_uuid = kv.second.get_optional<std::string>("uuid"s);
|
||||
if (!app_uuid || app_uuid.value() != uuid) {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
@@ -951,7 +926,7 @@ namespace confighttp {
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
if (
|
||||
args.find("id"s) == std::end(args)
|
||||
args.find("uuid"s) == std::end(args)
|
||||
) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Missing a required launch parameter");
|
||||
@@ -959,19 +934,12 @@ namespace confighttp {
|
||||
return;
|
||||
}
|
||||
|
||||
auto idx_str = nvhttp::get_arg(args, "id");
|
||||
auto idx = util::from_view(idx_str);
|
||||
auto uuid = nvhttp::get_arg(args, "uuid");
|
||||
|
||||
const auto& apps = proc::proc.get_apps();
|
||||
|
||||
if (idx >= apps.size()) {
|
||||
BOOST_LOG(error) << "Couldn't find app with index ["sv << idx_str << ']';
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Cannot find requested application");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& app = apps[idx];
|
||||
for (auto& app : apps) {
|
||||
if (app.uuid == uuid) {
|
||||
auto appid = util::from_view(app.id);
|
||||
|
||||
crypto::named_cert_t named_cert {
|
||||
@@ -994,6 +962,14 @@ namespace confighttp {
|
||||
} else {
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG(error) << "Couldn't find app with uuid ["sv << uuid << ']';
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Cannot find requested application");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1086,22 +1062,22 @@ namespace confighttp {
|
||||
server.resource["^/api/pin$"]["POST"] = savePin;
|
||||
server.resource["^/api/otp$"]["GET"] = getOTP;
|
||||
server.resource["^/api/apps$"]["GET"] = getApps;
|
||||
server.resource["^/api/logs$"]["GET"] = getLogs;
|
||||
server.resource["^/api/apps$"]["POST"] = saveApp;
|
||||
server.resource["^/api/apps/delete$"]["POST"] = deleteApp;
|
||||
server.resource["^/api/apps/launch$"]["POST"] = launchApp;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/logs$"]["GET"] = getLogs;
|
||||
server.resource["^/api/config$"]["GET"] = getConfig;
|
||||
server.resource["^/api/config$"]["POST"] = saveConfig;
|
||||
server.resource["^/api/configLocale$"]["GET"] = getLocale;
|
||||
server.resource["^/api/restart$"]["POST"] = restart;
|
||||
server.resource["^/api/quit$"]["POST"] = quit;
|
||||
server.resource["^/api/password$"]["POST"] = savePassword;
|
||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||
server.resource["^/api/clients/list$"]["GET"] = listClients;
|
||||
server.resource["^/api/clients/update$"]["POST"] = updateClient;
|
||||
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
||||
server.resource["^/api/clients/disconnect$"]["POST"] = disconnect;
|
||||
server.resource["^/api/apps/launch$"]["POST"] = launchApp;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||
server.resource["^/images/apollo.ico$"]["GET"] = getFaviconImage;
|
||||
server.resource["^/images/logo-apollo-45.png$"]["GET"] = getSunshineLogoImage;
|
||||
|
||||
@@ -211,13 +211,16 @@ namespace proc {
|
||||
VDISPLAY::setRenderAdapterByName(platf::from_utf8(config::video.adapter_name));
|
||||
}
|
||||
|
||||
auto device_uuid = uuid_util::uuid_t::parse(launch_session->unique_id);
|
||||
auto device_uuid_str = _app.use_app_identity ? _app.uuid : launch_session->unique_id;
|
||||
auto device_name = _app.use_app_identity ? _app.name : launch_session->device_name;
|
||||
|
||||
auto device_uuid = uuid_util::uuid_t::parse(device_uuid_str);
|
||||
|
||||
memcpy(&launch_session->display_guid, &device_uuid, sizeof(GUID));
|
||||
|
||||
std::wstring vdisplayName = VDISPLAY::createVirtualDisplay(
|
||||
launch_session->unique_id.c_str(),
|
||||
launch_session->device_name.c_str(),
|
||||
device_uuid_str.c_str(),
|
||||
device_name.c_str(),
|
||||
render_width,
|
||||
render_height,
|
||||
launch_session->fps ? launch_session->fps : 60,
|
||||
@@ -794,6 +797,63 @@ namespace proc {
|
||||
return std::make_tuple(id_no_index, id_with_index);
|
||||
}
|
||||
|
||||
void
|
||||
migrate_apps(pt::ptree* fileTree_p, pt::ptree* inputTree_p) {
|
||||
std::string new_app_uuid;
|
||||
|
||||
if (inputTree_p) {
|
||||
auto inputTree = *inputTree_p;
|
||||
auto input_uuid = inputTree_p->get_optional<std::string>("uuid"s);
|
||||
if (input_uuid && !input_uuid.value().empty()) {
|
||||
new_app_uuid = input_uuid.value();
|
||||
} else {
|
||||
new_app_uuid = uuid_util::uuid_t::generate().string();
|
||||
inputTree_p->erase("uuid");
|
||||
inputTree_p->put("uuid", new_app_uuid);
|
||||
}
|
||||
|
||||
if (inputTree_p->get_child("prep-cmd").empty()) {
|
||||
inputTree_p->erase("prep-cmd");
|
||||
}
|
||||
|
||||
if (inputTree_p->get_child("detached").empty()) {
|
||||
inputTree_p->erase("detached");
|
||||
}
|
||||
|
||||
inputTree_p->erase("launching");
|
||||
inputTree_p->erase("index");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree_p->get_child("apps"s);
|
||||
|
||||
pt::ptree newApps;
|
||||
for (auto &kv : apps_node) {
|
||||
// Check if we have apps that have not got an uuid assigned
|
||||
auto app_uuid = kv.second.get_optional<std::string>("uuid"s);
|
||||
if (!app_uuid || app_uuid.value().empty()) {
|
||||
kv.second.erase("uuid");
|
||||
kv.second.put("uuid", uuid_util::uuid_t::generate().string());
|
||||
kv.second.erase("launching");
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
} else {
|
||||
if (!new_app_uuid.empty() && app_uuid.value() == new_app_uuid) {
|
||||
newApps.push_back(std::make_pair("", *inputTree_p));
|
||||
new_app_uuid.clear();
|
||||
} else {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally add the new app
|
||||
if (!new_app_uuid.empty()) {
|
||||
newApps.push_back(std::make_pair("", *inputTree_p));
|
||||
}
|
||||
|
||||
fileTree_p->erase("apps");
|
||||
fileTree_p->push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
|
||||
std::optional<proc::proc_t>
|
||||
parse(const std::string &file_name) {
|
||||
pt::ptree tree;
|
||||
@@ -816,6 +876,20 @@ namespace proc {
|
||||
for (auto &[_, app_node] : apps_node) {
|
||||
proc::ctx_t ctx;
|
||||
|
||||
auto app_uuid = app_node.get_optional<std::string>("uuid"s);
|
||||
|
||||
if (!app_uuid) {
|
||||
// We need an upgrade to the app list
|
||||
try {
|
||||
migrate_apps(&tree, nullptr);
|
||||
pt::write_json(file_name, tree);
|
||||
return parse(file_name);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Error happened wilie migrating the app list: "sv << e.what();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
|
||||
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
|
||||
auto exclude_global_prep = app_node.get_optional<bool>("exclude-global-prep-cmd"s);
|
||||
@@ -831,6 +905,9 @@ namespace proc {
|
||||
auto virtual_display = app_node.get_optional<bool>("virtual-display"s);
|
||||
auto virtual_display_primary = app_node.get_optional<bool>("virtual-display-primary"s);
|
||||
auto resolution_scale_factor = app_node.get_optional<int>("scale-factor"s);
|
||||
auto use_app_identity = app_node.get_optional<bool>("use-app-identity"s);
|
||||
|
||||
ctx.uuid = app_uuid.value();
|
||||
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
if (!exclude_global_prep.value_or(false)) {
|
||||
@@ -901,6 +978,7 @@ namespace proc {
|
||||
ctx.virtual_display = virtual_display.value_or(false);
|
||||
ctx.virtual_display_primary = virtual_display_primary.value_or(true);
|
||||
ctx.scale_factor = resolution_scale_factor.value_or(100);
|
||||
ctx.use_app_identity = use_app_identity.value_or(false);
|
||||
|
||||
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||
if (ids.count(std::get<0>(possible_ids)) == 0) {
|
||||
@@ -923,11 +1001,13 @@ namespace proc {
|
||||
#ifdef _WIN32
|
||||
if (vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK) {
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Virtual Display";
|
||||
ctx.image_path = parse_env_val(this_env, "virtual_desktop.png");
|
||||
ctx.virtual_display = true;
|
||||
ctx.virtual_display_primary = true;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
|
||||
ctx.elevated = false;
|
||||
ctx.auto_detach = true;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "platform/common.h"
|
||||
@@ -59,6 +60,7 @@ namespace proc {
|
||||
*/
|
||||
std::vector<std::string> detached;
|
||||
|
||||
std::string uuid;
|
||||
std::string name;
|
||||
std::string cmd;
|
||||
std::string working_dir;
|
||||
@@ -70,6 +72,7 @@ namespace proc {
|
||||
bool wait_all;
|
||||
bool virtual_display;
|
||||
bool virtual_display_primary;
|
||||
bool use_app_identity;
|
||||
int scale_factor;
|
||||
std::chrono::seconds exit_timeout;
|
||||
};
|
||||
@@ -150,6 +153,8 @@ namespace proc {
|
||||
validate_app_image_path(std::string app_image_path);
|
||||
void
|
||||
refresh(const std::string &file_name);
|
||||
void
|
||||
migrate_apps(boost::property_tree::ptree* fileTree_p, boost::property_tree::ptree* inputTree_p);
|
||||
std::optional<proc::proc_t>
|
||||
parse(const std::string &file_name);
|
||||
|
||||
|
||||
@@ -86,16 +86,16 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(app,i) in apps" :key="i">
|
||||
<tr v-for="(app,i) in apps" :key="app.uuid">
|
||||
<td>{{app.name}}</td>
|
||||
<td>
|
||||
<button class="btn btn-success me-2" :disabled="app.launching" @click="launchApp(i)">
|
||||
<button class="btn btn-success me-2" :disabled="app.launching" @click="launchApp(app)">
|
||||
<i class="fas fa-play"></i> {{ $t('apps.launch') }}
|
||||
</button>
|
||||
<button class="btn btn-primary me-2" :disabled="app.launching" @click="editApp(i)">
|
||||
<button class="btn btn-primary me-2" :disabled="app.launching" @click="editApp(app)">
|
||||
<i class="fas fa-edit"></i> {{ $t('apps.edit') }}
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="app.launching" @click="showDeleteForm(i)">
|
||||
<button class="btn btn-danger" :disabled="app.launching" @click="showDeleteForm(app)">
|
||||
<i class="fas fa-trash"></i> {{ $t('apps.delete') }}
|
||||
</button>
|
||||
</td>
|
||||
@@ -284,6 +284,13 @@
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">{{ $t('apps.virtual_display_primary_desc') }}</div>
|
||||
</div>
|
||||
<!-- use app identity -->
|
||||
<div class="mb-3 form-check">
|
||||
<label for="useAppIdentity" class="form-check-label">{{ $t('apps.use_app_identity') }}</label>
|
||||
<input type="checkbox" class="form-check-input" id="useAppIdentity" v-model="editForm['use-app-identity']"
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">{{ $t('apps.use_app_identity_desc') }}</div>
|
||||
</div>
|
||||
<!-- resolution scale factor -->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="resolutionScaleFactor" class="form-label">{{ $t('apps.resolution_scale_factor') }}: {{editForm['scale-factor']}}%</label>
|
||||
@@ -392,6 +399,23 @@
|
||||
import PlatformLayout from './PlatformLayout.vue'
|
||||
import { Dropdown } from 'bootstrap/dist/js/bootstrap'
|
||||
|
||||
const newApp = {
|
||||
name: "",
|
||||
output: "",
|
||||
cmd: [],
|
||||
"exclude-global-prep-cmd": false,
|
||||
elevated: false,
|
||||
"auto-detach": true,
|
||||
"wait-all": true,
|
||||
"exit-timeout": 5,
|
||||
"prep-cmd": [],
|
||||
detached: [],
|
||||
"image-path": "",
|
||||
"virtual-display-primary": true,
|
||||
"scale-factor": "100",
|
||||
"use-app-identity": false
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar,
|
||||
@@ -429,30 +453,13 @@
|
||||
});
|
||||
},
|
||||
newApp() {
|
||||
this.editForm = {
|
||||
name: "",
|
||||
output: "",
|
||||
cmd: [],
|
||||
index: -1,
|
||||
"exclude-global-prep-cmd": false,
|
||||
elevated: false,
|
||||
"auto-detach": true,
|
||||
"wait-all": true,
|
||||
"exit-timeout": 5,
|
||||
"prep-cmd": [],
|
||||
detached: [],
|
||||
"image-path": "",
|
||||
"virtual-display-primary": true,
|
||||
"scale-factor": "100"
|
||||
};
|
||||
this.editForm.index = -1;
|
||||
this.editForm = Object.assign({}, newApp);
|
||||
this.showEditForm = true;
|
||||
},
|
||||
launchApp(id) {
|
||||
const app = this.apps[id];
|
||||
launchApp(app) {
|
||||
if (confirm(this.$t('apps.launch_warning'))) {
|
||||
app.launching = true;
|
||||
fetch("/api/apps/launch?id=" + id, {
|
||||
fetch("/api/apps/launch?uuid=" + app.uuid, {
|
||||
credentials: 'include',
|
||||
method: "POST",
|
||||
})
|
||||
@@ -467,56 +474,31 @@
|
||||
.finally(() => app.launching = false);
|
||||
}
|
||||
},
|
||||
editApp(id) {
|
||||
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
|
||||
this.editForm.index = id;
|
||||
if (this.editForm["prep-cmd"] === undefined)
|
||||
this.editForm["prep-cmd"] = [];
|
||||
if (this.editForm["detached"] === undefined)
|
||||
this.editForm["detached"] = [];
|
||||
if (this.editForm["exclude-global-prep-cmd"] === undefined)
|
||||
this.editForm["exclude-global-prep-cmd"] = [];
|
||||
if (this.editForm["elevated"] === undefined && this.platform === 'windows') {
|
||||
this.editForm["elevated"] = [];
|
||||
}
|
||||
if (this.editForm["auto-detach"] === undefined) {
|
||||
this.editForm["auto-detach"] = true;
|
||||
}
|
||||
if (this.editForm["wait-all"] === undefined) {
|
||||
this.editForm["wait-all"] = true;
|
||||
}
|
||||
if (this.editForm["exit-timeout"] === undefined) {
|
||||
this.editForm["exit-timeout"] = 5;
|
||||
}
|
||||
if (typeof this.editForm["virtual-display-primary"] === "undefined") {
|
||||
this.editForm["virtual-display-primary"] = true;
|
||||
}
|
||||
if (typeof this.editForm["scale-factor"] === "undefined") {
|
||||
this.editForm["scale-factor"] = "100"
|
||||
}
|
||||
editApp(app) {
|
||||
this.editForm = Object.assign({}, newApp, JSON.parse(JSON.stringify(app)));
|
||||
this.showEditForm = true;
|
||||
},
|
||||
showDeleteForm(id) {
|
||||
let resp = confirm(
|
||||
"Are you sure to delete " + this.apps[id].name + "?"
|
||||
showDeleteForm(app) {
|
||||
const resp = confirm(
|
||||
"Are you sure to delete " + app.name + "?"
|
||||
);
|
||||
if (resp) {
|
||||
fetch("/api/apps/" + id, {
|
||||
fetch("/api/apps/delete?uuid=" + app.uuid, {
|
||||
credentials: 'include',
|
||||
method: "DELETE"
|
||||
method: "POST"
|
||||
}).then((r) => {
|
||||
if (r.status == 200) document.location.reload();
|
||||
});
|
||||
}
|
||||
},
|
||||
addPrepCmd() {
|
||||
let template = {
|
||||
const template = {
|
||||
do: "",
|
||||
undo: ""
|
||||
};
|
||||
|
||||
if (this.platform === 'windows') {
|
||||
template = { ...template, elevated: false };
|
||||
template.elevated = false;
|
||||
}
|
||||
|
||||
this.editForm["prep-cmd"].push(template);
|
||||
@@ -618,6 +600,8 @@
|
||||
},
|
||||
save() {
|
||||
this.editForm["image-path"] = this.editForm["image-path"].toString().replace(/"/g, '');
|
||||
delete this.editForm["launching"];
|
||||
delete this.editForm["id"];
|
||||
fetch("/api/apps", {
|
||||
credentials: 'include',
|
||||
method: "POST",
|
||||
|
||||
@@ -427,9 +427,11 @@
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
return
|
||||
}, 1000);
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"virtual_display_primary": "Enforce Virtual Display Primary",
|
||||
"virtual_display_primary_desc": "Automatically set the virtual display as primary display when the app starts. Virtual display will always be set to primary when client requests to use virtual display. (Recommended to keep on)",
|
||||
"resolution_scale_factor": "Resolution Scale Factor",
|
||||
"resolution_scale_factor_desc": "Scale the client requested resolution based on this factor. e.g. 2000x1000 with a factor of 120% will become 2400x1200. Overrides client requested factor when the number isn't 100%. This option won't affect client requested streaming resolution."
|
||||
"resolution_scale_factor_desc": "Scale the client requested resolution based on this factor. e.g. 2000x1000 with a factor of 120% will become 2400x1200. Overrides client requested factor when the number isn't 100%. This option won't affect client requested streaming resolution.",
|
||||
"use_app_identity": "Use App Identity",
|
||||
"use_app_identity_desc": "Use the app's own identity while creating virtual displays instead of client's. This is useful when you want display configuration for each APP separately."
|
||||
},
|
||||
"client_card": {
|
||||
"clients": "Clients",
|
||||
|
||||
@@ -90,7 +90,9 @@
|
||||
"virtual_display_primary": "强制设置虚拟显示器为主显示器",
|
||||
"virtual_display_primary_desc": "在 App 启动时强制将虚拟显示器设为主显示器。当客户端请求使用虚拟显示器时将无条件设为主显示器。(覆盖全局设置,推荐保持开启)",
|
||||
"resolution_scale_factor": "分辨率缩放比例",
|
||||
"resolution_scale_factor_desc": "基于此比例缩放客户端请求的分辨率。例如 2000x1000 缩放 120% 将变成 2400x1200。当此项为非 100% 时覆盖客户端请求的缩放比例。此选项不会影响客户端请求的串流分辨率。"
|
||||
"resolution_scale_factor_desc": "基于此比例缩放客户端请求的分辨率。例如 2000x1000 缩放 120% 将变成 2400x1200。当此项为非 100% 时覆盖客户端请求的缩放比例。此选项不会影响客户端请求的串流分辨率。",
|
||||
"use_app_identity": "使用 App 身份",
|
||||
"use_app_identity_desc": "在创建虚拟显示器时使用 App 自身的身份,而非客户端的。这样可以针对 APP 进行单独的显示器组合配置。"
|
||||
},
|
||||
"client_card": {
|
||||
"clients": "客户端",
|
||||
|
||||
@@ -189,15 +189,19 @@
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.status !== 200) {
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
return
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
this.serverRestarting = false
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
return
|
||||
}, 1000);
|
||||
return;
|
||||
});
|
||||
},
|
||||
quit() {
|
||||
|
||||
Reference in New Issue
Block a user