mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-04 21:12:42 +08:00
Merge branch 'cli' into mcp
This commit is contained in:
@@ -1980,9 +1980,14 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
if (updater_ && updater_->busy()) updater_->Cancel();
|
||||
#endif
|
||||
});
|
||||
main->on_open_download([this, main] {
|
||||
main->on_open_manual_download([this] {
|
||||
const auto& latest = update_checker_->latest();
|
||||
OpenUrl(latest && !latest->download_url.empty() ? latest->download_url
|
||||
: "https://crossdesk.cn");
|
||||
});
|
||||
main->on_open_download([this, main] {
|
||||
#if CROSSDESK_HAS_UPDATER
|
||||
const auto& latest = update_checker_->latest();
|
||||
if (updater_ && updater_->busy()) return;
|
||||
if (updater_ && latest && updater_->Available(latest->update_envelope)) {
|
||||
main->set_about_open(false);
|
||||
@@ -1991,8 +1996,7 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
OpenUrl(latest && !latest->download_url.empty() ? latest->download_url
|
||||
: "https://crossdesk.cn");
|
||||
main->invoke_open_manual_download();
|
||||
});
|
||||
main->on_confirm_update([this, main] {
|
||||
if (!main->get_update_confirm_open()) return;
|
||||
@@ -2004,8 +2008,12 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
||||
!updater_->Available(latest->update_envelope)) return;
|
||||
updater_->Start(latest->update_envelope);
|
||||
// Disable both windows immediately, before the next polling tick.
|
||||
for (const auto& window : {ui_->main, ui_->wall})
|
||||
for (const auto& window : {ui_->main, ui_->wall}) {
|
||||
window->set_update_install_busy(updater_->busy());
|
||||
window->set_update_install_failed(
|
||||
updater_->state() == updater::Controller::State::Failed);
|
||||
window->set_update_install_status("");
|
||||
}
|
||||
#endif
|
||||
});
|
||||
about::Populate(ui_->Window(gallery_window));
|
||||
@@ -2743,18 +2751,22 @@ void GuiApplication::PollUpdateCheck() {
|
||||
}
|
||||
const auto& latest = update_checker_->latest();
|
||||
const bool can_install = latest && updater_->Available(latest->update_envelope);
|
||||
const bool failed = updater_->state() == updater::Controller::State::Failed;
|
||||
const int language = localization::detail::ClampLanguageIndex(localization_language_index_);
|
||||
std::string status;
|
||||
if (updater_->busy()) {
|
||||
status = localization::update_preparing[language];
|
||||
if (updater_->state() == updater::Controller::State::Downloading)
|
||||
status += " " + std::to_string(updater_->progress()) + "%";
|
||||
} else if (updater_->state() == updater::Controller::State::Failed) {
|
||||
status = localization::update_install_failed[language] + "\n" + updater_->error();
|
||||
} else if (failed) {
|
||||
status = (can_install ? localization::update_install_failed[language]
|
||||
: localization::update_install_failed_manual[language]) +
|
||||
"\n" + updater_->error();
|
||||
}
|
||||
for (const auto& main : {ui_->main, ui_->wall}) {
|
||||
main->set_update_install_available(can_install);
|
||||
main->set_update_install_busy(updater_->busy());
|
||||
main->set_update_install_failed(failed);
|
||||
main->set_update_install_status(UiText(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,6 +519,8 @@ inline constexpr TranslationRow kTranslationRows[] = {
|
||||
{"请核对控制端指纹。", {"请核对控制端指纹。", "Check the fingerprint on the controller.", "Сверьте отпечаток с управляющим устройством.", "請核對控制端指紋。", "操作側のフィンガープリントと照合してください。", "제어 장치의 지문과 대조하세요.", "Mit dem Fingerabdruck des steuernden Geräts vergleichen.", "Vérifiez l’empreinte sur l’appareil de contrôle.", "Verifique la huella en el dispositivo de control.", "Confira a impressão digital no dispositivo controlador."}},
|
||||
{"配对信息已复制。", {"配对信息已复制。", "Pairing information copied.", "Данные сопряжения скопированы.", "配對資訊已複製。", "ペアリング情報をコピーしました。", "페어링 정보를 복사했습니다.", "Kopplungsdaten kopiert.", "Informations d’association copiées.", "Información de emparejamiento copiada.", "Informações de pareamento copiadas."}},
|
||||
{"无法复制配对信息,请重试。", {"无法复制配对信息,请重试。", "Unable to copy pairing information. Please try again.", "Не удалось скопировать данные сопряжения. Повторите попытку.", "無法複製配對資訊,請重試。", "ペアリング情報をコピーできません。もう一度お試しください。", "페어링 정보를 복사할 수 없습니다. 다시 시도하세요.", "Kopplungsdaten konnten nicht kopiert werden. Bitte erneut versuchen.", "Impossible de copier les informations d’association. Réessayez.", "No se pudo copiar la información de emparejamiento. Inténtelo de nuevo.", "Não foi possível copiar as informações de pareamento. Tente novamente."}},
|
||||
{"重试更新", {"重试更新", "Retry update", "Повторить", "重試更新", "再試行", "다시 시도", "Erneut versuchen", "Réessayer", "Reintentar", "Tentar novamente"}},
|
||||
{"更新未完成,请手动下载安装。", {"更新未完成,请手动下载安装。", "Update did not complete. Download and install manually.", "Обновление не завершено. Скачайте и установите вручную.", "更新未完成,請手動下載安裝。", "アップデートが完了しませんでした。手動でダウンロードしてインストールしてください。", "업데이트가 완료되지 않았습니다. 수동으로 다운로드하여 설치하세요.", "Das Update wurde nicht abgeschlossen. Laden Sie es herunter und installieren Sie es manuell.", "La mise à jour n'a pas été effectuée. Téléchargez-la et installez-la manuellement.", "La actualización no se completó. Descárguela e instálela manualmente.", "A atualização não foi concluída. Baixe e instale manualmente."}},
|
||||
};
|
||||
|
||||
struct NamedTranslation { const char* name; const char* literal; };
|
||||
@@ -733,10 +735,12 @@ inline constexpr NamedTranslation kNamedTranslations[] = {
|
||||
{"access_website", "访问官网: "},
|
||||
{"update", "更新"},
|
||||
{"install_update", "安装更新"},
|
||||
{"retry_update", "重试更新"},
|
||||
{"manual_download", "手动下载"},
|
||||
{"install_update_confirm", "下载完成后将关闭 CrossDesk 并安装更新,当前连接和文件传输会中断。安装需要系统授权,完成后重新启动。是否继续?"},
|
||||
{"update_preparing", "正在准备更新…"},
|
||||
{"update_install_failed", "更新未完成,可重试或手动安装。"},
|
||||
{"update_install_failed_manual", "更新未完成,请手动下载安装。"},
|
||||
{"connection_alias", "修改名称"},
|
||||
{"delete_connection", "删除连接"},
|
||||
{"connect_to_this_connection", "发起连接"},
|
||||
@@ -1229,10 +1233,12 @@ inline constexpr const char* kSlintLiterals[] = {
|
||||
X(access_website) \
|
||||
X(update) \
|
||||
X(install_update) \
|
||||
X(retry_update) \
|
||||
X(manual_download) \
|
||||
X(install_update_confirm) \
|
||||
X(update_preparing) \
|
||||
X(update_install_failed) \
|
||||
X(update_install_failed_manual) \
|
||||
X(connection_alias) \
|
||||
X(delete_connection) \
|
||||
X(connect_to_this_connection) \
|
||||
|
||||
@@ -108,6 +108,7 @@ export global UiStrings {
|
||||
in-out property <string> later: "Later";
|
||||
in-out property <string> download: "Download";
|
||||
in-out property <string> install-update: "Install update";
|
||||
in-out property <string> retry-update: "Retry update";
|
||||
in-out property <string> install-update-confirm;
|
||||
in-out property <string> input-password: "Input password";
|
||||
in-out property <string> reinput-password: "Incorrect password, please try again";
|
||||
@@ -593,6 +594,7 @@ export component MainWindow inherits Window {
|
||||
in property <bool> update-available: false;
|
||||
in property <bool> update-install-available: false;
|
||||
in property <bool> update-install-busy: false;
|
||||
in property <bool> update-install-failed: false;
|
||||
changed update-install-available => {
|
||||
if !root.update-install-available { root.update-confirm-open = false; }
|
||||
}
|
||||
@@ -600,7 +602,8 @@ export component MainWindow inherits Window {
|
||||
if root.update-install-busy { root.update-confirm-open = false; }
|
||||
}
|
||||
in property <string> update-install-status: "";
|
||||
private property <string> update-action-text: root.update-install-available ? UiStrings.install-update : UiStrings.download;
|
||||
private property <string> update-action-text: !root.update-install-available ? UiStrings.download
|
||||
: root.update-install-failed && !root.update-install-busy ? UiStrings.retry-update : UiStrings.install-update;
|
||||
in property <UpdateCheckStatus> update-check-status: idle;
|
||||
private property <string> update-status-text:
|
||||
root.update-check-status == UpdateCheckStatus.checking ? UiStrings.checking-for-updates :
|
||||
@@ -695,6 +698,7 @@ export component MainWindow inherits Window {
|
||||
callback cancel-self-hosted-settings;
|
||||
callback browse-save-path;
|
||||
callback open-download;
|
||||
callback open-manual-download;
|
||||
callback confirm-update;
|
||||
callback cancel-update;
|
||||
callback open-website;
|
||||
@@ -3300,6 +3304,14 @@ export component MainWindow inherits Window {
|
||||
text: UiStrings.later;
|
||||
clicked => { root.cancel-update(); root.update-open = false; }
|
||||
}
|
||||
if root.update-install-failed && root.update-install-available: WorkbenchButton {
|
||||
width: max(110px, manual-download-metrics.preferred-width + 24px);
|
||||
height: 34px;
|
||||
enabled: !root.update-install-busy;
|
||||
text: UiStrings.download;
|
||||
clicked => { root.open-manual-download(); }
|
||||
manual-download-metrics := Text { visible: false; text: UiStrings.download; font-size: 12px; }
|
||||
}
|
||||
WorkbenchButton {
|
||||
width: max(110px, update-action-metrics.preferred-width + 24px);
|
||||
height: 34px;
|
||||
|
||||
@@ -169,6 +169,7 @@ inline int ApplyMainWindowStrings(
|
||||
strings.set_view_release_notes(Text(localization::view_release_notes[language]));
|
||||
strings.set_download(Text(localization::manual_download[language]));
|
||||
strings.set_install_update(Text(localization::install_update[language]));
|
||||
strings.set_retry_update(Text(localization::retry_update[language]));
|
||||
strings.set_install_update_confirm(
|
||||
Text(localization::install_update_confirm[language]));
|
||||
strings.set_input_password(Text(localization::input_password[language]));
|
||||
|
||||
@@ -2,12 +2,62 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
#include "platform/common/gui/cuda_opengl_interop.h"
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
#if USE_CUDA && (defined(__x86_64__) || defined(__amd64__))
|
||||
bool HasNvidiaOffloadProvider() {
|
||||
// CUDA availability says nothing about the X server's PRIME topology.
|
||||
// In particular, NVIDIA-0 is a normal display provider, not an offload
|
||||
// GPU screen. Forcing offload in that configuration can render into a GL
|
||||
// buffer successfully while leaving the actual window blank.
|
||||
const char* display_name = std::getenv("DISPLAY");
|
||||
if (!display_name || display_name[0] == '\0') return false;
|
||||
std::unique_ptr<Display, decltype(&XCloseDisplay)> display(
|
||||
XOpenDisplay(display_name), XCloseDisplay);
|
||||
if (!display) return false;
|
||||
|
||||
int major = 0, minor = 0;
|
||||
if (!XRRQueryVersion(display.get(), &major, &minor) || major < 1 ||
|
||||
(major == 1 && minor < 4)) return false;
|
||||
|
||||
const auto root = XDefaultRootWindow(display.get());
|
||||
std::unique_ptr<XRRScreenResources, decltype(&XRRFreeScreenResources)> screen(
|
||||
XRRGetScreenResourcesCurrent(display.get(), root), XRRFreeScreenResources);
|
||||
std::unique_ptr<XRRProviderResources, decltype(&XRRFreeProviderResources)>
|
||||
providers(XRRGetProviderResources(display.get(), root),
|
||||
XRRFreeProviderResources);
|
||||
if (!screen || !providers) return false;
|
||||
|
||||
for (int i = 0; i < providers->nproviders; ++i) {
|
||||
std::unique_ptr<XRRProviderInfo, decltype(&XRRFreeProviderInfo)> provider(
|
||||
XRRGetProviderInfo(display.get(), screen.get(), providers->providers[i]),
|
||||
XRRFreeProviderInfo);
|
||||
if (!provider || !provider->name || provider->nameLen <= 0) continue;
|
||||
const std::string_view name(provider->name, provider->nameLen);
|
||||
// NVIDIA documents offload GPU screens as NVIDIA-G0, NVIDIA-G1, etc.
|
||||
// Their RandR capability mask can be zero, so SourceOffload is not a
|
||||
// reliable test for the proprietary driver's offload support.
|
||||
constexpr std::string_view prefix = "NVIDIA-G";
|
||||
if (name.size() > prefix.size() && name.substr(0, prefix.size()) == prefix &&
|
||||
name.find_first_not_of("0123456789", prefix.size()) ==
|
||||
std::string_view::npos) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
void ConfigureLinuxCudaGraphics() {
|
||||
#if USE_CUDA && (defined(__x86_64__) || defined(__amd64__))
|
||||
@@ -28,6 +78,11 @@ void ConfigureLinuxCudaGraphics() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasNvidiaOffloadProvider()) {
|
||||
LOG_INFO("Keeping default Linux graphics selection: no NVIDIA PRIME "
|
||||
"offload GPU screen");
|
||||
return;
|
||||
}
|
||||
if (!IsCudaDeviceAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
// Call before SDL or Slint initializes EGL/GLX. Hybrid laptops otherwise
|
||||
// create the GUI context on the integrated GPU while NVDEC uses NVIDIA.
|
||||
// Call before SDL or Slint initializes EGL/GLX. Select NVIDIA on hybrid
|
||||
// systems with a PRIME offload GPU screen so GL can share NVDEC buffers.
|
||||
// Keep the default driver when NVIDIA already owns the display or the
|
||||
// display server does not advertise offload support.
|
||||
void ConfigureLinuxCudaGraphics();
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "platform/linux/gui/cuda_graphics.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// Exercise the actual startup policy without requiring a particular GPU or
|
||||
// X server. NVIDIA offload providers deliberately have a zero capability mask.
|
||||
std::vector<std::string> provider_names;
|
||||
std::vector<RRProvider> provider_ids;
|
||||
bool display_available = true, cuda_available = true, randr_available = true;
|
||||
bool resources_available = true, info_available = true;
|
||||
int randr_minor = 4, display_opens = 0, display_closes = 0;
|
||||
|
||||
constexpr const char* selection_variables[] = {
|
||||
"__NV_PRIME_RENDER_OFFLOAD", "__NV_PRIME_RENDER_OFFLOAD_PROVIDER",
|
||||
"__GLX_VENDOR_LIBRARY_NAME", "__EGL_VENDOR_LIBRARY_FILENAMES",
|
||||
"__EGL_VENDOR_LIBRARY_DIRS", "DRI_PRIME", "LIBGL_ALWAYS_SOFTWARE",
|
||||
"MESA_LOADER_DRIVER_OVERRIDE"};
|
||||
|
||||
void Reset(std::vector<std::string> names) {
|
||||
for (const auto* name : selection_variables) unsetenv(name);
|
||||
setenv("DISPLAY", ":test", 1);
|
||||
provider_names = std::move(names);
|
||||
provider_ids.resize(provider_names.size());
|
||||
for (size_t i = 0; i < provider_ids.size(); ++i) provider_ids[i] = i;
|
||||
display_available = cuda_available = randr_available = true;
|
||||
resources_available = info_available = true;
|
||||
randr_minor = 4;
|
||||
display_opens = display_closes = 0;
|
||||
}
|
||||
|
||||
bool CheckSelection(bool offload, const char* scenario) {
|
||||
crossdesk::ConfigureLinuxCudaGraphics();
|
||||
const char* prime = std::getenv("__NV_PRIME_RENDER_OFFLOAD");
|
||||
const char* vendor = std::getenv("__GLX_VENDOR_LIBRARY_NAME");
|
||||
const bool matches = offload
|
||||
? prime && std::string(prime) == "1" && vendor && std::string(vendor) == "nvidia"
|
||||
: !prime && !vendor;
|
||||
if (!matches || display_opens != display_closes) {
|
||||
std::cerr << "Incorrect graphics selection or leaked X display: " << scenario << '\n';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Link-time substitutes for the external XRandR/CUDA queries, not for the
|
||||
// selection logic under test. No real display or CUDA context is initialized.
|
||||
extern "C" {
|
||||
Display* XOpenDisplay(const char*) {
|
||||
if (!display_available) return nullptr;
|
||||
++display_opens;
|
||||
return reinterpret_cast<Display*>(&display_opens);
|
||||
}
|
||||
int XCloseDisplay(Display*) { ++display_closes; return 0; }
|
||||
Window XDefaultRootWindow(Display*) { return 1; }
|
||||
Status XRRQueryVersion(Display*, int* major, int* minor) {
|
||||
*major = 1;
|
||||
*minor = randr_minor;
|
||||
return randr_available;
|
||||
}
|
||||
XRRScreenResources* XRRGetScreenResourcesCurrent(Display*, Window) {
|
||||
return resources_available ? new XRRScreenResources{} : nullptr;
|
||||
}
|
||||
void XRRFreeScreenResources(XRRScreenResources* value) { delete value; }
|
||||
XRRProviderResources* XRRGetProviderResources(Display*, Window) {
|
||||
if (!resources_available) return nullptr;
|
||||
auto* result = new XRRProviderResources{};
|
||||
result->nproviders = static_cast<int>(provider_ids.size());
|
||||
result->providers = provider_ids.data();
|
||||
return result;
|
||||
}
|
||||
void XRRFreeProviderResources(XRRProviderResources* value) { delete value; }
|
||||
XRRProviderInfo* XRRGetProviderInfo(Display*, XRRScreenResources*, RRProvider id) {
|
||||
if (!info_available) return nullptr;
|
||||
auto* result = new XRRProviderInfo{};
|
||||
result->name = provider_names.at(id).data();
|
||||
result->nameLen = static_cast<int>(provider_names.at(id).size());
|
||||
return result;
|
||||
}
|
||||
void XRRFreeProviderInfo(XRRProviderInfo* value) { delete value; }
|
||||
}
|
||||
|
||||
namespace crossdesk {
|
||||
bool IsCudaDeviceAvailable() { return cuda_available; }
|
||||
std::shared_ptr<spdlog::logger> get_logger() { return spdlog::default_logger(); }
|
||||
} // namespace crossdesk
|
||||
|
||||
int main() {
|
||||
bool ok = true;
|
||||
Reset({"NVIDIA-0", "AMD Radeon Graphics @ pci:0000:05:00.0"});
|
||||
ok &= CheckSelection(false, "NVIDIA primary with an AMD display sink");
|
||||
Reset({"NVIDIA-0"});
|
||||
ok &= CheckSelection(false, "NVIDIA-only desktop");
|
||||
Reset({"modesetting", "NVIDIA-G0"});
|
||||
ok &= CheckSelection(true, "integrated primary with NVIDIA offload");
|
||||
Reset({"modesetting", "NVIDIA-G12"});
|
||||
ok &= CheckSelection(true, "numbered offload GPU screen");
|
||||
Reset({"modesetting", "NVIDIA-G", "NVIDIA-G0-extra", ""});
|
||||
ok &= CheckSelection(false, "no recognized NVIDIA GPU screen");
|
||||
Reset({});
|
||||
ok &= CheckSelection(false, "no providers");
|
||||
Reset({"NVIDIA-G0"});
|
||||
cuda_available = false;
|
||||
ok &= CheckSelection(false, "CUDA unavailable");
|
||||
Reset({"NVIDIA-G0"});
|
||||
unsetenv("DISPLAY");
|
||||
ok &= CheckSelection(false, "no X display");
|
||||
Reset({"NVIDIA-G0"});
|
||||
display_available = false;
|
||||
ok &= CheckSelection(false, "X display inaccessible");
|
||||
Reset({"NVIDIA-G0"});
|
||||
randr_available = false;
|
||||
ok &= CheckSelection(false, "RandR unavailable");
|
||||
Reset({"NVIDIA-G0"});
|
||||
randr_minor = 3;
|
||||
ok &= CheckSelection(false, "RandR predates provider queries");
|
||||
Reset({"NVIDIA-G0"});
|
||||
resources_available = false;
|
||||
ok &= CheckSelection(false, "RandR resources unavailable");
|
||||
Reset({"NVIDIA-G0"});
|
||||
info_available = false;
|
||||
ok &= CheckSelection(false, "provider info unavailable");
|
||||
|
||||
// Any explicit graphics selection must win, including PRIME=0.
|
||||
for (const auto* name : selection_variables) {
|
||||
Reset({"modesetting", "NVIDIA-G0"});
|
||||
setenv(name, "0", 1);
|
||||
crossdesk::ConfigureLinuxCudaGraphics();
|
||||
for (const auto* other : selection_variables) {
|
||||
const char* value = std::getenv(other);
|
||||
const bool preserved = std::string(name) == other
|
||||
? value && std::string(value) == "0" : !value;
|
||||
if (!preserved) { std::cerr << "Changed explicit selection: " << name << '\n'; ok = false; }
|
||||
}
|
||||
if (display_opens) { std::cerr << "Probed explicitly selected graphics\n"; ok = false; }
|
||||
}
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "ui/model_update.h"
|
||||
#include "ui/ui_localization.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
@@ -330,6 +331,60 @@ bool VerifyGalleryPopups(RenderTestPlatform& platform) {
|
||||
return Check(!wall->get_gallery_adding(), "Add dialog Cancel must respond immediately");
|
||||
}
|
||||
|
||||
bool VerifyUpdateRecovery(RenderTestPlatform& platform) {
|
||||
auto main = CreateMainWindow();
|
||||
auto& screen = *platform.adapter;
|
||||
const float width = main->get_minimum_window_width();
|
||||
const float height = main->get_minimum_window_height();
|
||||
main->window().set_size(slint::LogicalSize({width, height}));
|
||||
main->set_update_available(true);
|
||||
main->set_update_install_available(true);
|
||||
main->set_latest_version("v2.0.0");
|
||||
int install_requests = 0, manual_downloads = 0;
|
||||
main->on_open_download([&] { ++install_requests; main->set_update_confirm_open(true); });
|
||||
main->on_open_manual_download([&] { ++manual_downloads; });
|
||||
main->show();
|
||||
screen.Settle();
|
||||
const float button_y = height / 2 + std::min(488.f, height - 32) / 2 - 41;
|
||||
const float action_x = width / 2 + 161;
|
||||
const float manual_x = action_x - 118;
|
||||
const auto action_pixels = [&] {
|
||||
return screen.Region(int(action_x - 40), int(button_y - 12), 80, 24);
|
||||
};
|
||||
const auto install_label = action_pixels();
|
||||
main->set_update_install_failed(true);
|
||||
main->set_update_install_status(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::update_install_failed[0] + "\nDownload failed"));
|
||||
screen.Settle();
|
||||
if (!Check(action_pixels() != install_label,
|
||||
"A failed update must visibly replace Install update with Retry update")) return false;
|
||||
screen.Click(manual_x, button_y);
|
||||
if (!Check(manual_downloads == 1 && install_requests == 0 && !main->get_update_confirm_open(),
|
||||
"Manual download must remain reachable after failure without starting automatic installation")) return false;
|
||||
screen.Click(action_x, button_y);
|
||||
if (!Check(install_requests == 1 && main->get_update_confirm_open(),
|
||||
"Retry must open the installation confirmation")) return false;
|
||||
screen.Key(slint::platform::key_codes::Escape);
|
||||
if (!Check(!main->get_update_confirm_open(), "Retry confirmation must remain cancellable")) return false;
|
||||
main->set_update_install_busy(true);
|
||||
screen.Settle();
|
||||
screen.Click(action_x, button_y);
|
||||
screen.Click(manual_x, button_y);
|
||||
if (!Check(install_requests == 1 && manual_downloads == 1,
|
||||
"Recovery buttons must reject repeated clicks while an update is running")) return false;
|
||||
main->set_update_install_busy(false);
|
||||
main->set_update_install_available(false);
|
||||
main->set_update_install_status(crossdesk::ui_localization::Text(
|
||||
crossdesk::localization::update_install_failed_manual[0]));
|
||||
// The application's primary action falls back to manual download when the
|
||||
// current installation or signed manifest no longer supports automatic updates.
|
||||
main->on_open_download([&] { ++manual_downloads; });
|
||||
screen.Settle();
|
||||
screen.Click(action_x, button_y);
|
||||
return Check(manual_downloads == 2 && !main->get_update_confirm_open(),
|
||||
"An unavailable automatic retry must retain the primary manual download action");
|
||||
}
|
||||
|
||||
bool VerifyServerPanel(RenderTestPlatform& platform) {
|
||||
auto panel = crossdesk::ui::ServerWindow::create();
|
||||
auto& screen = *platform.adapter;
|
||||
@@ -377,5 +432,6 @@ int main() {
|
||||
if (!VerifyGalleryPopups(*backend)) return 3;
|
||||
if (!VerifyServerPanel(*backend)) return 4;
|
||||
if (!VerifyAutostartSettings(*backend)) return 5;
|
||||
if (!VerifyUpdateRecovery(*backend)) return 6;
|
||||
std::cout << "Slint popup rendering and local input passed.\n";
|
||||
}
|
||||
|
||||
@@ -233,6 +233,17 @@ function setup_targets()
|
||||
add_files("apps/desktop/tests/slint_ui_smoke_test.cpp")
|
||||
add_rules("crossdesk.slint_runtime")
|
||||
|
||||
if is_os("linux") and is_arch("x86_64") and is_config("USE_CUDA", true) then
|
||||
target("linux_cuda_graphics_test")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
add_packages("spdlog")
|
||||
add_includedirs("apps/desktop/src", "apps/desktop/src/log",
|
||||
"deps/submodules/minirtc/src/api")
|
||||
add_files("apps/desktop/tests/linux_cuda_graphics_test.cpp",
|
||||
"apps/desktop/src/platform/linux/gui/cuda_graphics.cpp")
|
||||
end
|
||||
|
||||
target("slint_stream_render_test")
|
||||
set_kind("binary")
|
||||
set_languages("c++20")
|
||||
|
||||
@@ -10571,6 +10571,30 @@
|
||||
"fr": "Impossible de copier les informations d’association. Réessayez.",
|
||||
"es": "No se pudo copiar la información de emparejamiento. Inténtelo de nuevo.",
|
||||
"pt-BR": "Não foi possível copiar as informações de pareamento. Tente novamente."
|
||||
},
|
||||
"重试更新": {
|
||||
"zh-Hans": "重试更新",
|
||||
"en": "Retry update",
|
||||
"ru": "Повторить",
|
||||
"zh-Hant": "重試更新",
|
||||
"ja": "再試行",
|
||||
"ko": "다시 시도",
|
||||
"de": "Erneut versuchen",
|
||||
"fr": "Réessayer",
|
||||
"es": "Reintentar",
|
||||
"pt-BR": "Tentar novamente"
|
||||
},
|
||||
"更新未完成,请手动下载安装。": {
|
||||
"zh-Hans": "更新未完成,请手动下载安装。",
|
||||
"en": "Update did not complete. Download and install manually.",
|
||||
"ru": "Обновление не завершено. Скачайте и установите вручную.",
|
||||
"zh-Hant": "更新未完成,請手動下載安裝。",
|
||||
"ja": "アップデートが完了しませんでした。手動でダウンロードしてインストールしてください。",
|
||||
"ko": "업데이트가 완료되지 않았습니다. 수동으로 다운로드하여 설치하세요.",
|
||||
"de": "Das Update wurde nicht abgeschlossen. Laden Sie es herunter und installieren Sie es manuell.",
|
||||
"fr": "La mise à jour n'a pas été effectuée. Téléchargez-la et installez-la manuellement.",
|
||||
"es": "La actualización no se completó. Descárguela e instálela manualmente.",
|
||||
"pt-BR": "A atualização não foi concluída. Baixe e instale manualmente."
|
||||
}
|
||||
},
|
||||
"desktop": {
|
||||
@@ -10784,10 +10808,12 @@
|
||||
"access_website": "访问官网: ",
|
||||
"update": "更新",
|
||||
"install_update": "安装更新",
|
||||
"retry_update": "重试更新",
|
||||
"manual_download": "手动下载",
|
||||
"install_update_confirm": "下载完成后将关闭 CrossDesk 并安装更新,当前连接和文件传输会中断。安装需要系统授权,完成后重新启动。是否继续?",
|
||||
"update_preparing": "正在准备更新…",
|
||||
"update_install_failed": "更新未完成,可重试或手动安装。",
|
||||
"update_install_failed_manual": "更新未完成,请手动下载安装。",
|
||||
"connection_alias": "修改名称",
|
||||
"delete_connection": "删除连接",
|
||||
"connect_to_this_connection": "发起连接",
|
||||
@@ -11574,7 +11600,9 @@
|
||||
"暂无可信设备",
|
||||
"请核对控制端指纹。",
|
||||
"配对信息已复制。",
|
||||
"无法复制配对信息,请重试。"
|
||||
"无法复制配对信息,请重试。",
|
||||
"重试更新",
|
||||
"更新未完成,请手动下载安装。"
|
||||
],
|
||||
"android": [
|
||||
" · 已完成",
|
||||
|
||||
Reference in New Issue
Block a user