mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-23 04:53:06 +08:00
Compare commits
2
Commits
bd0a7aa2f6
...
c2c74913e3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2c74913e3 | ||
|
|
4f200095f4 |
@@ -1413,6 +1413,21 @@ void GuiApplication::BindMainCallbacks() {
|
||||
: slint::SharedString{});
|
||||
});
|
||||
main->on_open_download([this] { OpenUrl("https://crossdesk.cn"); });
|
||||
main->on_start_update([this] {
|
||||
#if _WIN32
|
||||
windows_updater_.Start(latest_version_info_);
|
||||
SyncWindowsUpdate();
|
||||
#else
|
||||
OpenUrl("https://crossdesk.cn");
|
||||
ui_->main->set_update_open(false);
|
||||
#endif
|
||||
});
|
||||
main->on_cancel_update([this] {
|
||||
#if _WIN32
|
||||
windows_updater_.Cancel();
|
||||
#endif
|
||||
ui_->main->set_update_open(false);
|
||||
});
|
||||
main->on_connection_cancel([this] {
|
||||
const auto props = FindRemoteSession(ui_->connection_dialog_remote_id);
|
||||
if (!props) {
|
||||
@@ -2100,6 +2115,9 @@ void GuiApplication::SyncMainWindow() {
|
||||
ui_->main->set_signal_tls_error(signal_status_ ==
|
||||
SignalStatus::SignalTlsCertError);
|
||||
ui_->main->set_update_available(update_available_);
|
||||
#if _WIN32
|
||||
SyncWindowsUpdate();
|
||||
#endif
|
||||
ui_->main->set_current_version(CROSSDESK_VERSION);
|
||||
ui_->main->set_latest_version(UiText(latest_version_));
|
||||
ui_->main->set_release_name(UiText(release_name_));
|
||||
@@ -3257,7 +3275,51 @@ bool GuiApplication::OpenUrl(const std::string& url) {
|
||||
return SDL_OpenURL(url.c_str());
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
void GuiApplication::SyncWindowsUpdate() {
|
||||
using State = WindowsUpdater::State;
|
||||
if (windows_updater_.GetState() == State::Ready) {
|
||||
windows_updater_.Launch();
|
||||
if (windows_updater_.GetState() == State::Launched) {
|
||||
ui_->main->set_update_open(false);
|
||||
}
|
||||
}
|
||||
const State state = windows_updater_.GetState();
|
||||
const bool downloading = state == State::Downloading;
|
||||
ui_->main->set_update_downloading(downloading || state == State::Launching);
|
||||
ui_->main->set_update_failed(state == State::Failed ||
|
||||
state == State::LaunchFailed);
|
||||
const int language = localization_language_index_;
|
||||
std::string status;
|
||||
if (downloading) {
|
||||
status = localization::update_downloading[language];
|
||||
const uint64_t total = windows_updater_.Total();
|
||||
const uint64_t received = windows_updater_.Downloaded();
|
||||
if (total > 0) {
|
||||
const int percent = static_cast<int>(std::min(100.0, 100.0 * received / total));
|
||||
status += " " + std::to_string(percent) + "%";
|
||||
ui_->main->set_update_progress(static_cast<float>(percent) / 100.0f);
|
||||
} else {
|
||||
status += " " + std::to_string(received / (1024 * 1024)) + " MB";
|
||||
ui_->main->set_update_progress(-1.0f);
|
||||
}
|
||||
} else if (state == State::Failed) {
|
||||
status = localization::update_download_failed[language];
|
||||
} else if (state == State::LaunchFailed) {
|
||||
status = localization::update_launch_failed[language];
|
||||
} else if (state == State::Launching) {
|
||||
status = localization::update_launching[language];
|
||||
} else if (state == State::Launched) {
|
||||
status = localization::update_installer_opened[language];
|
||||
}
|
||||
ui_->main->set_update_status(UiText(status));
|
||||
}
|
||||
#endif
|
||||
|
||||
void GuiApplication::Cleanup() {
|
||||
#if _WIN32
|
||||
windows_updater_.Cancel();
|
||||
#endif
|
||||
if (!ui_) {
|
||||
SDL_Quit();
|
||||
return;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
#include "runtime/cursor_state_provider.h"
|
||||
#include "runtime/gui_runtime.h"
|
||||
#if _WIN32
|
||||
#include "platform/windows/windows_updater.h"
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -52,6 +55,9 @@ private:
|
||||
void HandlePasswordChangeResult();
|
||||
void HandleCredentialRecovery();
|
||||
void SyncMainWindow();
|
||||
#if _WIN32
|
||||
void SyncWindowsUpdate();
|
||||
#endif
|
||||
void SyncConnectionDialog();
|
||||
void SyncPlatformDialogs();
|
||||
void SyncStreamWindow();
|
||||
@@ -87,6 +93,9 @@ private:
|
||||
bool OpenUrl(const std::string &url);
|
||||
|
||||
std::unique_ptr<SlintUi> ui_;
|
||||
#if _WIN32
|
||||
WindowsUpdater windows_updater_;
|
||||
#endif
|
||||
CursorStateProvider cursor_state_provider_;
|
||||
std::unordered_map<std::string, CursorDeliveryState>
|
||||
cursor_delivery_states_;
|
||||
@@ -100,4 +109,4 @@ private:
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -184,6 +184,20 @@ struct TranslationRow {
|
||||
X(notification, u8"通知", "Notification", u8"Уведомление") \
|
||||
X(new_version_available, u8"新版本可用", "New Version Available", \
|
||||
u8"Доступна новая версия") \
|
||||
X(update_downloading, u8"正在下载安装包…", "Downloading installer…", \
|
||||
u8"Загрузка установщика…") \
|
||||
X(update_launching, u8"正在打开安装包,请确认管理员权限请求…", \
|
||||
"Opening installer. Please confirm the administrator permission request…", \
|
||||
u8"Запуск установщика. Подтвердите запрос прав администратора…") \
|
||||
X(update_download_failed, u8"下载失败,请点击更新重试,或访问官网。", \
|
||||
"Download failed. Click Update to retry, or visit the website.", \
|
||||
u8"Ошибка загрузки. Повторите обновление или откройте сайт.") \
|
||||
X(update_launch_failed, u8"安装包未能启动或授权已取消,请点击更新重试。", \
|
||||
"Installer launch failed or permission was cancelled. Click Update to retry.", \
|
||||
u8"Установщик не запущен или доступ отменён. Повторите обновление.") \
|
||||
X(update_installer_opened, u8"安装包已打开,请在安装向导中完成更新。", \
|
||||
"Installer opened. Follow the setup wizard to finish updating.", \
|
||||
u8"Установщик открыт. Следуйте указаниям мастера установки.") \
|
||||
X(release_notes, u8"更新内容", "Release Notes", u8"Содержание обновления") \
|
||||
X(version, u8"版本", "Version", u8"Версия") \
|
||||
X(release_date, u8"发布日期: ", "Release Date: ", u8"Дата релиза: ") \
|
||||
|
||||
@@ -210,6 +210,10 @@ export component MainWindow inherits Window {
|
||||
in property <bool> signal-tls-error: false;
|
||||
in property <[RecentConnection]> recent-connections;
|
||||
in property <bool> update-available: false;
|
||||
in property <bool> update-downloading: false;
|
||||
in property <bool> update-failed: false;
|
||||
in property <float> update-progress: 0;
|
||||
in property <string> update-status: "";
|
||||
in property <string> current-version: "";
|
||||
in property <string> latest-version: "";
|
||||
in property <string> release-name: "";
|
||||
@@ -268,6 +272,8 @@ export component MainWindow inherits Window {
|
||||
callback cancel-self-hosted-settings;
|
||||
callback browse-save-path;
|
||||
callback open-download;
|
||||
callback start-update;
|
||||
callback cancel-update;
|
||||
callback connection-cancel;
|
||||
callback connection-acknowledge;
|
||||
callback connection-submit-password(string, bool);
|
||||
@@ -1644,7 +1650,7 @@ export component MainWindow inherits Window {
|
||||
x: 30px;
|
||||
y: 105px;
|
||||
width: 360px;
|
||||
height: 190px;
|
||||
height: root.update-status != "" ? 136px : 190px;
|
||||
background: white;
|
||||
border-width: 1px;
|
||||
border-color: #b9bdc3;
|
||||
@@ -1671,8 +1677,27 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
}
|
||||
CompactButton { x: root.compact-language ? 163px : 130px; y: 309px; width: root.compact-language ? 34px : 70px; primary: true; text: UiStrings.download; clicked => { root.open-download(); root.update-open = false; } }
|
||||
CompactButton { x: root.compact-language ? 204px : 207px; y: 309px; width: root.compact-language ? 34px : 56px; text: UiStrings.later; clicked => { root.update-open = false; } }
|
||||
if root.update-status != "": Text {
|
||||
x: 30px; y: 247px; width: 340px; height: 40px;
|
||||
text: root.update-status;
|
||||
color: root.update-failed ? #b23b3b : #3d434d;
|
||||
font-size: ImGuiFontStyle.body;
|
||||
wrap: word-wrap;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
if root.update-downloading: Rectangle {
|
||||
x: 30px; y: 291px; width: 340px; height: 4px;
|
||||
background: #e2e4e7;
|
||||
clip: true;
|
||||
Rectangle {
|
||||
x: root.update-progress < 0 ? (parent.width + self.width) * (mod(animation-tick(), 1500ms) / 1500ms) - self.width : 0px;
|
||||
width: parent.width * (root.update-progress < 0 ? 0.25 : root.update-progress);
|
||||
height: parent.height;
|
||||
background: #334fd1;
|
||||
}
|
||||
}
|
||||
CompactButton { x: root.compact-language ? 163px : 130px; y: 309px; width: root.compact-language ? 34px : 70px; primary: true; text: UiStrings.download; enabled: !root.update-downloading; clicked => { root.start-update(); } }
|
||||
CompactButton { x: root.compact-language ? 204px : 207px; y: 309px; width: root.compact-language ? 34px : 56px; text: UiStrings.later; clicked => { root.cancel-update(); } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,11 @@ struct OpenGlFunctions {
|
||||
PFNGLUNIFORM4FPROC uniform_4f = nullptr;
|
||||
PFNGLUSEPROGRAMPROC use_program = nullptr;
|
||||
PFNGLVERTEXATTRIBPOINTERPROC vertex_attrib_pointer = nullptr;
|
||||
PFNGLGENFRAMEBUFFERSPROC gen_framebuffers = nullptr;
|
||||
PFNGLDELETEFRAMEBUFFERSPROC delete_framebuffers = nullptr;
|
||||
PFNGLBINDFRAMEBUFFERPROC bind_framebuffer = nullptr;
|
||||
PFNGLFRAMEBUFFERTEXTURE2DPROC framebuffer_texture_2d = nullptr;
|
||||
PFNGLCHECKFRAMEBUFFERSTATUSPROC check_framebuffer_status = nullptr;
|
||||
#if CROSSDESK_OPENGL_CUDA_INTEROP
|
||||
PFNGLFENCESYNCPROC fence_sync = nullptr;
|
||||
PFNGLCLIENTWAITSYNCPROC client_wait_sync = nullptr;
|
||||
@@ -163,6 +168,15 @@ struct OpenGlFunctions {
|
||||
LoadOpenGlFunction(&vertex_attrib_pointer, "glVertexAttribPointer");
|
||||
}
|
||||
|
||||
bool LoadFramebufferFunctions() {
|
||||
return LoadOpenGlFunction(&gen_framebuffers, "glGenFramebuffers") &&
|
||||
LoadOpenGlFunction(&delete_framebuffers, "glDeleteFramebuffers") &&
|
||||
LoadOpenGlFunction(&bind_framebuffer, "glBindFramebuffer") &&
|
||||
LoadOpenGlFunction(&framebuffer_texture_2d, "glFramebufferTexture2D") &&
|
||||
LoadOpenGlFunction(&check_framebuffer_status,
|
||||
"glCheckFramebufferStatus");
|
||||
}
|
||||
|
||||
#if CROSSDESK_OPENGL_CUDA_INTEROP
|
||||
bool LoadSyncFunctions() {
|
||||
return LoadOpenGlFunction(&fence_sync, "glFenceSync") &&
|
||||
@@ -287,6 +301,17 @@ struct OpenGlVideoRenderer::Impl {
|
||||
GLint video_rect_location = -1;
|
||||
GLint corner_radius_location = -1;
|
||||
GLint video_enabled_location = -1;
|
||||
GLint source_size_location = -1;
|
||||
GLint filter_direction_location = -1;
|
||||
GLint packed_video_location = -1;
|
||||
GLuint scale_framebuffer = 0;
|
||||
std::array<GLuint, 2> scale_textures{};
|
||||
std::array<std::pair<int, int>, 2> scale_sizes{};
|
||||
uint64_t scaled_sequence = 0;
|
||||
bool scaling_available = false;
|
||||
GLenum framebuffer_target = GL_FRAMEBUFFER;
|
||||
GLenum framebuffer_binding = GL_FRAMEBUFFER_BINDING;
|
||||
std::array<int, 4> logged_render_size{};
|
||||
int texture_width = 0;
|
||||
int texture_height = 0;
|
||||
int cpu_texture_width = 0;
|
||||
@@ -504,6 +529,116 @@ struct OpenGlVideoRenderer::Impl {
|
||||
}
|
||||
#endif
|
||||
|
||||
// The vertex buffer and program are shared with the final underlay draw.
|
||||
void DrawVideo(GLuint y, GLuint uv, int source_width, int source_height,
|
||||
int target_width, int target_height, int video_x, int video_y,
|
||||
int video_width, int video_height, int corner_radius,
|
||||
bool enabled, bool packed, float filter_x = 0.0f,
|
||||
float filter_y = 0.0f) {
|
||||
gl.active_texture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, y);
|
||||
gl.active_texture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, uv);
|
||||
gl.uniform_2f(source_size_location, static_cast<GLfloat>(source_width),
|
||||
static_cast<GLfloat>(source_height));
|
||||
gl.uniform_2f(target_size_location, static_cast<GLfloat>(target_width),
|
||||
static_cast<GLfloat>(target_height));
|
||||
gl.uniform_4f(video_rect_location, static_cast<GLfloat>(video_x),
|
||||
static_cast<GLfloat>(video_y), static_cast<GLfloat>(video_width),
|
||||
static_cast<GLfloat>(video_height));
|
||||
gl.uniform_1f(corner_radius_location, static_cast<GLfloat>(corner_radius));
|
||||
gl.uniform_1f(video_enabled_location, enabled ? 1.0f : 0.0f);
|
||||
gl.uniform_1f(packed_video_location, packed ? 1.0f : 0.0f);
|
||||
gl.uniform_2f(filter_direction_location, filter_x, filter_y);
|
||||
glViewport(0, 0, target_width, target_height);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
bool PrepareScaleTarget(size_t index, int width, int height) {
|
||||
gl.active_texture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, scale_textures[index]);
|
||||
if (scale_sizes[index] != std::pair{width, height}) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, nullptr);
|
||||
scale_sizes[index] = {width, height};
|
||||
}
|
||||
gl.framebuffer_texture_2d(framebuffer_target, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, scale_textures[index], 0);
|
||||
return gl.check_framebuffer_status(framebuffer_target) ==
|
||||
GL_FRAMEBUFFER_COMPLETE &&
|
||||
glGetError() == GL_NO_ERROR;
|
||||
}
|
||||
|
||||
bool ScaleVideo(GLuint y, GLuint uv, int width, int height, int output_width,
|
||||
int output_height) {
|
||||
if (!scaling_available) {
|
||||
return false;
|
||||
}
|
||||
if (scaled_sequence == uploaded_sequence &&
|
||||
scale_sizes[1] == std::pair{output_width, output_height}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint previous_framebuffer = 0;
|
||||
glGetIntegerv(framebuffer_binding, &previous_framebuffer);
|
||||
if (scale_framebuffer == 0) {
|
||||
gl.gen_framebuffers(1, &scale_framebuffer);
|
||||
glGenTextures(static_cast<GLsizei>(scale_textures.size()),
|
||||
scale_textures.data());
|
||||
gl.active_texture(GL_TEXTURE0);
|
||||
for (const GLuint texture : scale_textures) {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
}
|
||||
// A CUDA upload may have left its PBO bound: nullptr below allocates empty
|
||||
// render targets and must never be interpreted as a PBO byte offset.
|
||||
if (pixel_unpack_buffer_available) {
|
||||
gl.bind_buffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
}
|
||||
gl.bind_framebuffer(framebuffer_target, scale_framebuffer);
|
||||
bool success = scale_framebuffer != 0 && scale_textures[0] != 0 &&
|
||||
scale_textures[1] != 0;
|
||||
bool packed = false;
|
||||
while (success && (width != output_width || height != output_height)) {
|
||||
// Bound shader work without truncating the filter for very small windows.
|
||||
const int next_width = std::max(output_width, (width + 3) / 4);
|
||||
const int next_height = std::max(output_height, (height + 3) / 4);
|
||||
if (!PrepareScaleTarget(0, next_width, height)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
DrawVideo(y, uv, width, height, next_width, height, 0, 0, next_width,
|
||||
height, 0, true, packed, 1.0f, 0.0f);
|
||||
if (!PrepareScaleTarget(1, next_width, next_height)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
DrawVideo(scale_textures[0], uv, next_width, height, next_width,
|
||||
next_height, 0, 0, next_width, next_height, 0, true, true,
|
||||
0.0f, 1.0f);
|
||||
success = glGetError() == GL_NO_ERROR;
|
||||
y = scale_textures[1];
|
||||
width = next_width;
|
||||
height = next_height;
|
||||
packed = true;
|
||||
}
|
||||
gl.bind_framebuffer(framebuffer_target,
|
||||
static_cast<GLuint>(previous_framebuffer));
|
||||
if (!success) {
|
||||
scaling_available = false;
|
||||
while (glGetError() != GL_NO_ERROR) {
|
||||
}
|
||||
LOG_WARN("OpenGL video downsampling unavailable; using linear sampling");
|
||||
return false;
|
||||
}
|
||||
scaled_sequence = uploaded_sequence;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResetGlHandles() {
|
||||
program = 0;
|
||||
vertex_buffer = 0;
|
||||
@@ -517,6 +652,15 @@ struct OpenGlVideoRenderer::Impl {
|
||||
video_rect_location = -1;
|
||||
corner_radius_location = -1;
|
||||
video_enabled_location = -1;
|
||||
source_size_location = -1;
|
||||
filter_direction_location = -1;
|
||||
packed_video_location = -1;
|
||||
scale_framebuffer = 0;
|
||||
scale_textures = {};
|
||||
scale_sizes = {};
|
||||
scaled_sequence = 0;
|
||||
scaling_available = false;
|
||||
logged_render_size = {};
|
||||
texture_width = 0;
|
||||
texture_height = 0;
|
||||
cpu_texture_width = 0;
|
||||
@@ -559,6 +703,10 @@ bool OpenGlVideoRenderer::Setup() {
|
||||
HasOpenGlExtension(extensions, "GL_ARB_pixel_buffer_object")));
|
||||
impl_->unpack_subimage_available = !is_gles || major >= 3 ||
|
||||
HasOpenGlExtension(extensions, "GL_EXT_unpack_subimage");
|
||||
impl_->scaling_available = impl_->gl.LoadFramebufferFunctions();
|
||||
impl_->framebuffer_target = major >= 3 ? GL_DRAW_FRAMEBUFFER : GL_FRAMEBUFFER;
|
||||
impl_->framebuffer_binding =
|
||||
major >= 3 ? GL_DRAW_FRAMEBUFFER_BINDING : GL_FRAMEBUFFER_BINDING;
|
||||
#if CROSSDESK_OPENGL_CUDA_INTEROP
|
||||
// GLX may return entry points even if the current GLES 2 context cannot
|
||||
// use them. Gate CUDA interop on context capabilities as well as symbols.
|
||||
@@ -577,8 +725,8 @@ bool OpenGlVideoRenderer::Setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
static constexpr char kGlesVertexShader[] = R"glsl(
|
||||
#version 100
|
||||
const std::string vertex_source =
|
||||
std::string(is_gles ? "#version 100\n" : "#version 110\n") + R"glsl(
|
||||
attribute vec2 position;
|
||||
attribute vec2 texcoord;
|
||||
varying vec2 video_texcoord;
|
||||
@@ -587,20 +735,56 @@ void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
static constexpr char kGlesFragmentShader[] = R"glsl(
|
||||
#version 100
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
const std::string fragment_source =
|
||||
std::string(is_gles ? "#version 100\n"
|
||||
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
|
||||
"precision highp float;\n"
|
||||
"#else\nprecision mediump float;\n#endif\n"
|
||||
: "#version 110\n") + R"glsl(
|
||||
uniform sampler2D y_texture;
|
||||
uniform sampler2D uv_texture;
|
||||
uniform vec2 target_size;
|
||||
uniform vec2 source_size;
|
||||
uniform vec2 filter_direction;
|
||||
uniform float packed_video;
|
||||
uniform vec4 video_rect;
|
||||
uniform float corner_radius;
|
||||
uniform float video_enabled;
|
||||
varying vec2 video_texcoord;
|
||||
// A separable Lanczos-2 low-pass filter. Each pass reduces by at most 4x,
|
||||
// so 16 taps cover its complete support even for non-integer scale ratios.
|
||||
float lanczos2(float x) {
|
||||
x = abs(x);
|
||||
if (x < 0.0001) return 1.0;
|
||||
if (x >= 2.0) return 0.0;
|
||||
float p = 3.14159265359 * x;
|
||||
return sin(p) * sin(p * 0.5) / (p * p * 0.5);
|
||||
}
|
||||
vec4 sample_plane(sampler2D plane, vec2 size, vec2 coord) {
|
||||
// FBO textures have the opposite vertical origin to the decoded NV12 planes.
|
||||
if (packed_video > 0.5) coord.y = 1.0 - coord.y;
|
||||
if (filter_direction.x + filter_direction.y < 0.5)
|
||||
return texture2D(plane, coord);
|
||||
float scale = max(1.0, dot(size / video_rect.zw, filter_direction));
|
||||
float center = dot(coord * size - vec2(0.5), filter_direction);
|
||||
float first = floor(center - 2.0 * scale) + 1.0;
|
||||
vec4 sum = vec4(0.0);
|
||||
float weight_sum = 0.0;
|
||||
for (int tap = 0; tap < 16; ++tap) {
|
||||
float offset = first + float(tap) - center;
|
||||
if (offset >= 2.0 * scale) break;
|
||||
float weight = lanczos2(offset / scale);
|
||||
sum += weight * texture2D(plane, coord + filter_direction * offset / size);
|
||||
weight_sum += weight;
|
||||
}
|
||||
return sum / weight_sum;
|
||||
}
|
||||
vec3 sample_video(vec2 coord) {
|
||||
vec4 y_sample = sample_plane(y_texture, source_size, coord);
|
||||
if (packed_video > 0.5) return y_sample.rgb;
|
||||
return vec3(y_sample.r,
|
||||
sample_plane(uv_texture, source_size * 0.5, coord).ra);
|
||||
}
|
||||
void main() {
|
||||
vec2 surface_point = video_texcoord * target_size;
|
||||
vec3 rgb = vec3(0.0);
|
||||
@@ -611,10 +795,14 @@ void main() {
|
||||
surface_point.y <= video_rect.y + video_rect.w) {
|
||||
vec2 frame_texcoord =
|
||||
(surface_point - video_rect.xy) / video_rect.zw;
|
||||
float y = 1.16438356 *
|
||||
(texture2D(y_texture, frame_texcoord).r - 16.0 / 255.0);
|
||||
vec2 uv =
|
||||
texture2D(uv_texture, frame_texcoord).ra - vec2(0.5, 0.5);
|
||||
vec3 yuv = sample_video(frame_texcoord);
|
||||
if (filter_direction.x + filter_direction.y > 0.5) {
|
||||
// Intermediate RGBA textures carry Y/U/V until the final conversion.
|
||||
gl_FragColor = vec4(yuv, 1.0);
|
||||
return;
|
||||
}
|
||||
float y = 1.16438356 * (yuv.x - 16.0 / 255.0);
|
||||
vec2 uv = yuv.yz - vec2(0.5);
|
||||
rgb = clamp(vec3(y + 1.59602678 * uv.y,
|
||||
y - 0.39176229 * uv.x - 0.81296764 * uv.y,
|
||||
y + 2.01723214 * uv.x),
|
||||
@@ -633,67 +821,11 @@ void main() {
|
||||
gl_FragColor = vec4(rgb * coverage, coverage);
|
||||
}
|
||||
)glsl";
|
||||
static constexpr char kDesktopVertexShader[] = R"glsl(
|
||||
#version 110
|
||||
attribute vec2 position;
|
||||
attribute vec2 texcoord;
|
||||
varying vec2 video_texcoord;
|
||||
void main() {
|
||||
video_texcoord = texcoord;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
static constexpr char kDesktopFragmentShader[] = R"glsl(
|
||||
#version 110
|
||||
uniform sampler2D y_texture;
|
||||
uniform sampler2D uv_texture;
|
||||
uniform vec2 target_size;
|
||||
uniform vec4 video_rect;
|
||||
uniform float corner_radius;
|
||||
uniform float video_enabled;
|
||||
varying vec2 video_texcoord;
|
||||
void main() {
|
||||
vec2 surface_point = video_texcoord * target_size;
|
||||
vec3 rgb = vec3(0.0);
|
||||
if (video_enabled > 0.5 &&
|
||||
surface_point.x >= video_rect.x &&
|
||||
surface_point.x <= video_rect.x + video_rect.z &&
|
||||
surface_point.y >= video_rect.y &&
|
||||
surface_point.y <= video_rect.y + video_rect.w) {
|
||||
vec2 frame_texcoord =
|
||||
(surface_point - video_rect.xy) / video_rect.zw;
|
||||
float y = 1.16438356 *
|
||||
(texture2D(y_texture, frame_texcoord).r - 16.0 / 255.0);
|
||||
vec2 uv =
|
||||
texture2D(uv_texture, frame_texcoord).ra - vec2(0.5, 0.5);
|
||||
rgb = clamp(vec3(y + 1.59602678 * uv.y,
|
||||
y - 0.39176229 * uv.x - 0.81296764 * uv.y,
|
||||
y + 2.01723214 * uv.x),
|
||||
0.0, 1.0);
|
||||
}
|
||||
float coverage = 1.0;
|
||||
if (corner_radius > 0.0) {
|
||||
vec2 half_size = target_size * 0.5;
|
||||
vec2 corner = abs(surface_point - half_size) -
|
||||
(half_size - vec2(corner_radius));
|
||||
float distance_to_edge =
|
||||
length(max(corner, vec2(0.0))) +
|
||||
min(max(corner.x, corner.y), 0.0) - corner_radius;
|
||||
coverage = clamp(0.5 - distance_to_edge, 0.0, 1.0);
|
||||
}
|
||||
gl_FragColor = vec4(rgb * coverage, coverage);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
const char *vertex_source =
|
||||
is_gles ? kGlesVertexShader : kDesktopVertexShader;
|
||||
const char *fragment_source =
|
||||
is_gles ? kGlesFragmentShader : kDesktopFragmentShader;
|
||||
|
||||
const GLuint vertex_shader =
|
||||
CompileShader(impl_->gl, GL_VERTEX_SHADER, vertex_source);
|
||||
CompileShader(impl_->gl, GL_VERTEX_SHADER, vertex_source.c_str());
|
||||
const GLuint fragment_shader =
|
||||
CompileShader(impl_->gl, GL_FRAGMENT_SHADER, fragment_source);
|
||||
CompileShader(impl_->gl, GL_FRAGMENT_SHADER, fragment_source.c_str());
|
||||
if (vertex_shader == 0 || fragment_shader == 0) {
|
||||
if (vertex_shader != 0)
|
||||
impl_->gl.delete_shader(vertex_shader);
|
||||
@@ -743,11 +875,18 @@ void main() {
|
||||
impl_->gl.get_uniform_location(impl_->program, "corner_radius");
|
||||
impl_->video_enabled_location =
|
||||
impl_->gl.get_uniform_location(impl_->program, "video_enabled");
|
||||
impl_->source_size_location =
|
||||
impl_->gl.get_uniform_location(impl_->program, "source_size");
|
||||
impl_->filter_direction_location =
|
||||
impl_->gl.get_uniform_location(impl_->program, "filter_direction");
|
||||
impl_->packed_video_location =
|
||||
impl_->gl.get_uniform_location(impl_->program, "packed_video");
|
||||
if (impl_->position_location < 0 || impl_->texcoord_location < 0 ||
|
||||
impl_->y_texture_location < 0 || impl_->uv_texture_location < 0 ||
|
||||
impl_->target_size_location < 0 || impl_->video_rect_location < 0 ||
|
||||
impl_->corner_radius_location < 0 ||
|
||||
impl_->video_enabled_location < 0) {
|
||||
impl_->video_enabled_location < 0 || impl_->source_size_location < 0 ||
|
||||
impl_->filter_direction_location < 0 || impl_->packed_video_location < 0) {
|
||||
LOG_ERROR("OpenGL NV12 program is missing required shader bindings");
|
||||
impl_->gl.delete_program(impl_->program);
|
||||
impl_->ResetGlHandles();
|
||||
@@ -817,6 +956,11 @@ void main() {
|
||||
|
||||
void OpenGlVideoRenderer::Teardown() {
|
||||
impl_->ready.store(false, std::memory_order_release);
|
||||
if (impl_->scale_framebuffer != 0) {
|
||||
impl_->gl.delete_framebuffers(1, &impl_->scale_framebuffer);
|
||||
}
|
||||
glDeleteTextures(static_cast<GLsizei>(impl_->scale_textures.size()),
|
||||
impl_->scale_textures.data());
|
||||
#if CROSSDESK_OPENGL_CUDA_INTEROP
|
||||
impl_->ReleaseCudaInterop();
|
||||
#endif
|
||||
@@ -1356,24 +1500,8 @@ OpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
||||
}
|
||||
#endif
|
||||
impl_->gl.use_program(impl_->program);
|
||||
impl_->gl.active_texture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, displayed_y_texture);
|
||||
impl_->gl.uniform_1i(impl_->y_texture_location, 0);
|
||||
impl_->gl.active_texture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, displayed_uv_texture);
|
||||
impl_->gl.uniform_1i(impl_->uv_texture_location, 1);
|
||||
impl_->gl.uniform_2f(impl_->target_size_location,
|
||||
static_cast<GLfloat>(target_width),
|
||||
static_cast<GLfloat>(target_height));
|
||||
impl_->gl.uniform_4f(
|
||||
impl_->video_rect_location, static_cast<GLfloat>(video_x),
|
||||
static_cast<GLfloat>(video_y), static_cast<GLfloat>(video_width),
|
||||
static_cast<GLfloat>(video_height));
|
||||
impl_->gl.uniform_1f(
|
||||
impl_->corner_radius_location,
|
||||
static_cast<GLfloat>(std::clamp(
|
||||
corner_radius_pixels, 0, std::min(target_width, target_height) / 2)));
|
||||
impl_->gl.uniform_1f(impl_->video_enabled_location, has_video ? 1.0f : 0.0f);
|
||||
impl_->gl.bind_buffer(GL_ARRAY_BUFFER, impl_->vertex_buffer);
|
||||
impl_->gl.vertex_attrib_pointer(
|
||||
static_cast<GLuint>(impl_->position_location), 2, GL_FLOAT, GL_FALSE,
|
||||
@@ -1386,7 +1514,25 @@ OpenGlVideoRenderer::RenderLatest(std::string_view remote_id,
|
||||
static_cast<GLuint>(impl_->position_location));
|
||||
impl_->gl.enable_vertex_attrib_array(
|
||||
static_cast<GLuint>(impl_->texcoord_location));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
const bool downscaled = has_video &&
|
||||
(source_width > video_width || source_height > video_height) &&
|
||||
impl_->ScaleVideo(displayed_y_texture, displayed_uv_texture, source_width,
|
||||
source_height, video_width, video_height);
|
||||
impl_->DrawVideo(
|
||||
downscaled ? impl_->scale_textures[1] : displayed_y_texture,
|
||||
displayed_uv_texture, downscaled ? video_width : source_width,
|
||||
downscaled ? video_height : source_height, target_width, target_height,
|
||||
video_x, video_y, video_width, video_height,
|
||||
std::clamp(corner_radius_pixels, 0, std::min(target_width, target_height) / 2),
|
||||
has_video, downscaled);
|
||||
const std::array<int, 4> render_size{
|
||||
source_width, source_height, video_width, video_height};
|
||||
if (has_video && impl_->logged_render_size != render_size) {
|
||||
LOG_INFO("OpenGL video presentation: source={}x{}, display={}x{} physical "
|
||||
"pixels, filter={}", source_width, source_height, video_width,
|
||||
video_height, downscaled ? "Lanczos2" : "linear");
|
||||
impl_->logged_render_size = render_size;
|
||||
}
|
||||
#if CROSSDESK_OPENGL_CUDA_INTEROP
|
||||
if (impl_->displayed_cuda_upload_slot < impl_->cuda_upload_slots.size()) {
|
||||
auto &cuda_slot =
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "windows_updater.h"
|
||||
|
||||
#include <httplib.h>
|
||||
#include <shellapi.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "installer_download.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
WindowsUpdater::~WindowsUpdater() {
|
||||
Cancel();
|
||||
if (worker_.joinable()) worker_.join();
|
||||
RemoveDownload();
|
||||
}
|
||||
|
||||
void WindowsUpdater::RemoveDownload() {
|
||||
// A running NSIS installer still needs its source file. Leave that one in
|
||||
// the user's temporary directory; clean up cancelled/failed attempts.
|
||||
if (installer_.empty() || installer_launched_) return;
|
||||
std::error_code error;
|
||||
std::filesystem::remove(installer_, error);
|
||||
std::filesystem::remove(installer_.parent_path(), error);
|
||||
}
|
||||
|
||||
void WindowsUpdater::Start(const nlohmann::json& metadata) {
|
||||
const State state = GetState();
|
||||
if (state == State::Downloading || state == State::Launching) return;
|
||||
if (state == State::Ready || state == State::LaunchFailed ||
|
||||
state == State::Launched) {
|
||||
cancelled_ = false;
|
||||
Launch();
|
||||
return;
|
||||
}
|
||||
if (worker_.joinable()) worker_.join();
|
||||
RemoveDownload();
|
||||
installer_.clear();
|
||||
installer_launched_ = false;
|
||||
cancelled_ = false;
|
||||
downloaded_ = 0;
|
||||
total_ = 0;
|
||||
const auto download = GetWindowsInstallerDownload(metadata);
|
||||
if (!download) {
|
||||
LOG_WARN("No valid Windows installer for the advertised version");
|
||||
state_ = State::Failed;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
std::random_device random;
|
||||
const auto temp = std::filesystem::temp_directory_path();
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
const auto directory =
|
||||
temp / ("CrossDesk-update-" + std::to_string(random()) + "-" +
|
||||
std::to_string(random()));
|
||||
if (std::filesystem::create_directory(directory)) {
|
||||
installer_ = directory / download->filename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (installer_.empty())
|
||||
throw std::runtime_error("Cannot create update directory");
|
||||
client_ = std::make_shared<httplib::Client>(download->origin);
|
||||
client_->set_connection_timeout(5);
|
||||
client_->set_read_timeout(5);
|
||||
client_->set_write_timeout(5);
|
||||
state_ = State::Downloading;
|
||||
worker_ = std::thread([this, path = download->path] {
|
||||
const bool success =
|
||||
DownloadInstaller(*client_, path, installer_,
|
||||
[this](uint64_t received, uint64_t total) {
|
||||
downloaded_ = received;
|
||||
total_ = total;
|
||||
return !cancelled_.load();
|
||||
});
|
||||
state_ = success ? State::Ready : State::Failed;
|
||||
if (cancelled_) state_ = State::Idle;
|
||||
});
|
||||
} catch (const std::exception& error) {
|
||||
LOG_WARN("Cannot start Windows update: {}", error.what());
|
||||
state_ = State::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsUpdater::Cancel() {
|
||||
cancelled_ = true;
|
||||
if (client_) client_->stop();
|
||||
if (GetState() == State::Ready) state_ = State::Idle;
|
||||
}
|
||||
|
||||
void WindowsUpdater::Launch() {
|
||||
if (cancelled_) return;
|
||||
const State state = GetState();
|
||||
if (state != State::Ready && state != State::LaunchFailed &&
|
||||
state != State::Launched)
|
||||
return;
|
||||
if (worker_.joinable()) worker_.join();
|
||||
const std::wstring executable = installer_.wstring();
|
||||
const std::wstring directory = installer_.parent_path().wstring();
|
||||
state_ = State::Launching;
|
||||
SHELLEXECUTEINFOW info{};
|
||||
info.cbSize = sizeof(info);
|
||||
info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI;
|
||||
info.lpVerb = L"runas";
|
||||
info.lpFile = executable.c_str();
|
||||
info.lpDirectory = directory.c_str();
|
||||
info.nShow = SW_SHOWNORMAL;
|
||||
if (!ShellExecuteExW(&info)) {
|
||||
LOG_WARN("Cannot launch Windows installer, error={}", GetLastError());
|
||||
state_ = State::LaunchFailed;
|
||||
return;
|
||||
}
|
||||
if (info.hProcess) CloseHandle(info.hProcess);
|
||||
installer_launched_ = true;
|
||||
state_ = State::Launched;
|
||||
// NSIS already prompts to close CrossDesk and stops the installed service.
|
||||
// Keep this process alive if the user subsequently cancels installation.
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-09-09
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _WINDOWS_UPDATER_H_
|
||||
#define _WINDOWS_UPDATER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <thread>
|
||||
|
||||
namespace httplib {
|
||||
class Client;
|
||||
}
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class WindowsUpdater {
|
||||
public:
|
||||
enum class State {
|
||||
Idle,
|
||||
Downloading,
|
||||
Ready,
|
||||
Launching,
|
||||
Failed,
|
||||
LaunchFailed,
|
||||
Launched
|
||||
};
|
||||
|
||||
~WindowsUpdater();
|
||||
void Start(const nlohmann::json& metadata);
|
||||
void Cancel();
|
||||
void Launch();
|
||||
State GetState() const { return state_.load(); }
|
||||
uint64_t Downloaded() const { return downloaded_.load(); }
|
||||
uint64_t Total() const { return total_.load(); }
|
||||
|
||||
private:
|
||||
void RemoveDownload();
|
||||
|
||||
std::atomic<State> state_{State::Idle};
|
||||
std::atomic<bool> cancelled_{false};
|
||||
std::atomic<uint64_t> downloaded_{0};
|
||||
std::atomic<uint64_t> total_{0};
|
||||
std::shared_ptr<httplib::Client> client_;
|
||||
std::thread worker_;
|
||||
std::filesystem::path installer_;
|
||||
bool installer_launched_ = false;
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "installer_download.h"
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
// Reject error pages and files without Windows executable headers before
|
||||
// invoking the loader. The download uses the verified official HTTPS URL.
|
||||
bool IsWindowsExecutable(const std::filesystem::path& path) {
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
std::array<unsigned char, 64> header{};
|
||||
input.read(reinterpret_cast<char*>(header.data()), header.size());
|
||||
if (!input || header[0] != 'M' || header[1] != 'Z') return false;
|
||||
const uint32_t pe_offset =
|
||||
uint32_t(header[60]) | (uint32_t(header[61]) << 8) |
|
||||
(uint32_t(header[62]) << 16) | (uint32_t(header[63]) << 24);
|
||||
if (pe_offset < header.size()) return false;
|
||||
input.seekg(pe_offset);
|
||||
std::array<char, 4> signature{};
|
||||
input.read(signature.data(), signature.size());
|
||||
return input && signature == std::array<char, 4>{'P', 'E', 0, 0};
|
||||
}
|
||||
|
||||
struct PartialFile {
|
||||
std::filesystem::path path;
|
||||
~PartialFile() {
|
||||
std::error_code error;
|
||||
std::filesystem::remove(path, error);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<InstallerDownload> GetWindowsInstallerDownload(
|
||||
const nlohmann::json& metadata) {
|
||||
if (!metadata.is_object()) return std::nullopt;
|
||||
const auto version_it = metadata.find(
|
||||
metadata.contains("latest_version") ? "latest_version" : "version");
|
||||
if (version_it == metadata.end() || !version_it->is_string()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string version = version_it->get<std::string>();
|
||||
if (!version.empty() && version.front() == 'v') version.erase(0, 1);
|
||||
if (version.empty() ||
|
||||
version.find_first_not_of(
|
||||
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-") !=
|
||||
std::string::npos) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto downloads = metadata.find("downloads");
|
||||
if (downloads == metadata.end() || !downloads->is_object())
|
||||
return std::nullopt;
|
||||
const auto installer = downloads->find("windows-x64");
|
||||
if (installer == downloads->end() || !installer->is_object())
|
||||
return std::nullopt;
|
||||
const auto url = installer->find("url");
|
||||
const auto filename = installer->find("filename");
|
||||
InstallerDownload result{"https://downloads.crossdesk.cn", "",
|
||||
"crossdesk-win-x64-v" + version + ".exe"};
|
||||
result.path = "/" + result.filename;
|
||||
if (url == installer->end() || !url->is_string() ||
|
||||
url->get<std::string>() != result.origin + result.path ||
|
||||
(filename != installer->end() &&
|
||||
(!filename->is_string() ||
|
||||
filename->get<std::string>() != result.filename))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DownloadInstaller(
|
||||
httplib::Client& client, const std::string& path,
|
||||
const std::filesystem::path& destination,
|
||||
const std::function<bool(uint64_t, uint64_t)>& progress) {
|
||||
auto partial_path = destination;
|
||||
partial_path += ".part";
|
||||
PartialFile partial{partial_path};
|
||||
try {
|
||||
std::ofstream output(partial.path, std::ios::binary | std::ios::trunc);
|
||||
if (!output) return false;
|
||||
uint64_t received = 0;
|
||||
uint64_t total = 0;
|
||||
bool accepted = false;
|
||||
// Do not follow a redirect to an unverified origin or downgrade to HTTP.
|
||||
client.set_follow_location(false);
|
||||
client.set_decompress(false);
|
||||
const auto response = client.Get(
|
||||
path,
|
||||
[&](const httplib::Response& headers) {
|
||||
if (headers.status != 200) return false;
|
||||
if (headers.has_header("Content-Length")) {
|
||||
total = headers.get_header_value_u64("Content-Length");
|
||||
}
|
||||
accepted = progress(0, total);
|
||||
return accepted;
|
||||
},
|
||||
[&](const char* data, size_t size) {
|
||||
if (!accepted || !progress(received, total)) return false;
|
||||
output.write(data, static_cast<std::streamsize>(size));
|
||||
received += size;
|
||||
return output.good() && progress(received, total);
|
||||
});
|
||||
output.close();
|
||||
if (!response || response->status != 200 || !accepted || output.fail() ||
|
||||
received == 0 || (total != 0 && received != total) ||
|
||||
!progress(received, total) || !IsWindowsExecutable(partial.path)) {
|
||||
LOG_WARN(
|
||||
"Installer download failed or cancelled: HTTP={}, error={}, "
|
||||
"received={}, expected={}",
|
||||
response ? response->status : 0, httplib::to_string(response.error()),
|
||||
received, total);
|
||||
return false;
|
||||
}
|
||||
std::filesystem::rename(partial.path, destination);
|
||||
return true;
|
||||
} catch (const std::exception& error) {
|
||||
LOG_WARN("Installer download failed: {}", error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-09-09
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _INSTALLER_DOWNLOAD_H_
|
||||
#define _INSTALLER_DOWNLOAD_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace httplib {
|
||||
class Client;
|
||||
}
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
struct InstallerDownload {
|
||||
std::string origin;
|
||||
std::string path;
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
// Only accept the official Windows installer for the advertised version.
|
||||
std::optional<InstallerDownload> GetWindowsInstallerDownload(
|
||||
const nlohmann::json& metadata);
|
||||
|
||||
// Streams to a .part file and publishes the installer only after a complete,
|
||||
// successful response. Returning false from progress cancels the transfer.
|
||||
bool DownloadInstaller(httplib::Client& client, const std::string& path,
|
||||
const std::filesystem::path& destination,
|
||||
const std::function<bool(uint64_t, uint64_t)>& progress);
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -285,6 +285,9 @@ function setup_targets()
|
||||
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
|
||||
add_deps("rd_log")
|
||||
add_files("apps/desktop/src/version_checker/*.cpp")
|
||||
if is_os("windows") then
|
||||
add_files("apps/desktop/src/platform/windows/windows_updater.cpp")
|
||||
end
|
||||
add_includedirs("apps/desktop/src/version_checker", {public = true})
|
||||
if is_os("macosx") then
|
||||
add_defines("CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN")
|
||||
|
||||
Reference in New Issue
Block a user