mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-06-10 09:24:51 +08:00
280 lines
9.2 KiB
C++
280 lines
9.2 KiB
C++
#include "render.h"
|
|
|
|
#if _WIN32 && CROSSDESK_PORTABLE
|
|
|
|
#include <shellapi.h>
|
|
|
|
#include <vector>
|
|
|
|
#include "localization.h"
|
|
#include "rd_log.h"
|
|
#include "service_host.h"
|
|
|
|
namespace crossdesk {
|
|
namespace {
|
|
|
|
std::filesystem::path GetCurrentExecutablePath() {
|
|
std::vector<wchar_t> buffer(MAX_PATH);
|
|
while (true) {
|
|
DWORD length =
|
|
GetModuleFileNameW(nullptr, buffer.data(),
|
|
static_cast<DWORD>(buffer.size()));
|
|
if (length == 0) {
|
|
return {};
|
|
}
|
|
if (length < buffer.size()) {
|
|
return std::filesystem::path(buffer.data(), buffer.data() + length);
|
|
}
|
|
if (buffer.size() >= 32768) {
|
|
return {};
|
|
}
|
|
buffer.resize(buffer.size() * 2);
|
|
}
|
|
}
|
|
|
|
bool InstallServiceWithElevation() {
|
|
const std::filesystem::path executable_path = GetCurrentExecutablePath();
|
|
if (executable_path.empty()) {
|
|
LOG_ERROR("Portable service install failed: current executable not found");
|
|
return false;
|
|
}
|
|
|
|
const std::filesystem::path service_path =
|
|
executable_path.parent_path() / L"crossdesk_service.exe";
|
|
const std::filesystem::path helper_path =
|
|
executable_path.parent_path() / L"crossdesk_session_helper.exe";
|
|
if (!std::filesystem::exists(service_path) ||
|
|
!std::filesystem::exists(helper_path)) {
|
|
LOG_ERROR(
|
|
"Portable service install failed: service binaries missing, service={}, "
|
|
"helper={}",
|
|
service_path.string(), helper_path.string());
|
|
return false;
|
|
}
|
|
|
|
std::wstring executable = executable_path.wstring();
|
|
std::wstring working_dir = executable_path.parent_path().wstring();
|
|
std::wstring parameters = L"--service-install";
|
|
|
|
SHELLEXECUTEINFOW execute_info{};
|
|
execute_info.cbSize = sizeof(execute_info);
|
|
execute_info.fMask = SEE_MASK_NOCLOSEPROCESS;
|
|
execute_info.hwnd = nullptr;
|
|
execute_info.lpVerb = L"runas";
|
|
execute_info.lpFile = executable.c_str();
|
|
execute_info.lpParameters = parameters.c_str();
|
|
execute_info.lpDirectory = working_dir.c_str();
|
|
execute_info.nShow = SW_HIDE;
|
|
|
|
if (!ShellExecuteExW(&execute_info)) {
|
|
LOG_ERROR("Portable service install failed: ShellExecuteExW error={}",
|
|
GetLastError());
|
|
return false;
|
|
}
|
|
|
|
DWORD wait_result = WaitForSingleObject(execute_info.hProcess, INFINITE);
|
|
DWORD exit_code = 1;
|
|
if (wait_result == WAIT_OBJECT_0) {
|
|
GetExitCodeProcess(execute_info.hProcess, &exit_code);
|
|
} else {
|
|
LOG_ERROR("Portable service install wait failed, result={}", wait_result);
|
|
}
|
|
CloseHandle(execute_info.hProcess);
|
|
|
|
if (exit_code != 0) {
|
|
LOG_ERROR("Portable service install command failed, exit_code={}",
|
|
exit_code);
|
|
return false;
|
|
}
|
|
|
|
const bool started = StartCrossDeskService();
|
|
if (!started) {
|
|
LOG_WARN("Portable service installed but start failed");
|
|
}
|
|
return IsCrossDeskServiceInstalled() && started;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void Render::CheckPortableWindowsService() {
|
|
if (portable_service_prompt_checked_) {
|
|
return;
|
|
}
|
|
portable_service_prompt_checked_ = true;
|
|
|
|
if (IsCrossDeskServiceInstalled()) {
|
|
return;
|
|
}
|
|
|
|
portable_service_install_state_.store(PortableServiceInstallState::idle,
|
|
std::memory_order_relaxed);
|
|
show_portable_service_install_window_ = true;
|
|
}
|
|
|
|
void Render::StartPortableWindowsServiceInstall() {
|
|
PortableServiceInstallState expected = PortableServiceInstallState::idle;
|
|
if (!portable_service_install_state_.compare_exchange_strong(
|
|
expected, PortableServiceInstallState::installing,
|
|
std::memory_order_acq_rel)) {
|
|
if (expected != PortableServiceInstallState::failed) {
|
|
return;
|
|
}
|
|
portable_service_install_state_.store(
|
|
PortableServiceInstallState::installing, std::memory_order_release);
|
|
}
|
|
|
|
JoinPortableWindowsServiceInstallThread();
|
|
portable_service_install_thread_ = std::thread([this]() {
|
|
const bool installed = InstallServiceWithElevation();
|
|
portable_service_install_state_.store(
|
|
installed ? PortableServiceInstallState::succeeded
|
|
: PortableServiceInstallState::failed,
|
|
std::memory_order_release);
|
|
});
|
|
}
|
|
|
|
void Render::JoinPortableWindowsServiceInstallThread() {
|
|
if (portable_service_install_thread_.joinable()) {
|
|
portable_service_install_thread_.join();
|
|
}
|
|
}
|
|
|
|
int Render::PortableServiceInstallWindow() {
|
|
if (!show_portable_service_install_window_) {
|
|
return 0;
|
|
}
|
|
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
const float window_width = title_bar_button_width_ * 12.0f;
|
|
const float window_height = title_bar_button_width_ * 4.0f;
|
|
ImGui::SetNextWindowPos(
|
|
ImVec2((viewport->WorkSize.x - viewport->WorkPos.x - window_width) /
|
|
2.0f,
|
|
(viewport->WorkSize.y - viewport->WorkPos.y - window_height) /
|
|
2.0f),
|
|
ImGuiCond_Appearing);
|
|
ImGui::SetNextWindowSize(ImVec2(window_width, window_height),
|
|
ImGuiCond_Always);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
|
|
|
ImGui::Begin(
|
|
localization::windows_service_setup_title[localization_language_index_]
|
|
.c_str(),
|
|
nullptr,
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
|
|
ImGuiWindowFlags_NoTitleBar);
|
|
|
|
ImGui::Spacing();
|
|
ImGui::SetWindowFontScale(0.55f);
|
|
ImGui::SetCursorPosX(window_width * 0.08f);
|
|
ImGui::Text(
|
|
"%s",
|
|
localization::windows_service_setup_title[localization_language_index_]
|
|
.c_str());
|
|
|
|
const PortableServiceInstallState state =
|
|
portable_service_install_state_.load(std::memory_order_acquire);
|
|
const char* status_text = nullptr;
|
|
if (state == PortableServiceInstallState::installing ||
|
|
state == PortableServiceInstallState::succeeded ||
|
|
state == PortableServiceInstallState::failed) {
|
|
status_text =
|
|
localization::installing_windows_service[localization_language_index_]
|
|
.c_str();
|
|
if (state == PortableServiceInstallState::succeeded) {
|
|
status_text =
|
|
localization::windows_service_install_success
|
|
[localization_language_index_]
|
|
.c_str();
|
|
} else if (state == PortableServiceInstallState::failed) {
|
|
status_text =
|
|
localization::windows_service_install_failed
|
|
[localization_language_index_]
|
|
.c_str();
|
|
}
|
|
}
|
|
|
|
ImGui::SetWindowFontScale(0.45f);
|
|
ImGui::SetCursorPosX(window_width * 0.04f);
|
|
ImGui::SetCursorPosY(window_height * 0.22f);
|
|
ImGui::BeginChild("PortableServiceInstallContent",
|
|
ImVec2(window_width * 0.92f, window_height * 0.5f),
|
|
ImGuiChildFlags_Borders, ImGuiWindowFlags_None);
|
|
ImGui::SetWindowFontScale(0.5f);
|
|
const float wrap_pos = ImGui::GetContentRegionAvail().x;
|
|
ImGui::PushTextWrapPos(wrap_pos);
|
|
ImGui::TextWrapped(
|
|
"%s",
|
|
localization::windows_service_setup_message[localization_language_index_]
|
|
.c_str());
|
|
if (status_text != nullptr) {
|
|
ImGui::Spacing();
|
|
ImGui::TextWrapped("%s", status_text);
|
|
}
|
|
ImGui::PopTextWrapPos();
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SetWindowFontScale(0.5f);
|
|
const float button_y = window_height * 0.76f;
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
const auto default_button_width = [&style](const std::string& label) {
|
|
return ImGui::CalcTextSize(label.c_str()).x + style.FramePadding.x * 2.0f;
|
|
};
|
|
const std::string install_label =
|
|
localization::install_windows_service[localization_language_index_];
|
|
const std::string cancel_label =
|
|
localization::cancel[localization_language_index_];
|
|
const std::string ok_label = localization::ok[localization_language_index_];
|
|
const float buttons_width = state == PortableServiceInstallState::succeeded
|
|
? default_button_width(ok_label)
|
|
: default_button_width(install_label) +
|
|
style.ItemSpacing.x +
|
|
default_button_width(cancel_label);
|
|
ImGui::SetCursorPosX((window_width - buttons_width) * 0.5f);
|
|
ImGui::SetCursorPosY(button_y);
|
|
|
|
if (state == PortableServiceInstallState::succeeded) {
|
|
if (ImGui::Button(ok_label.c_str())) {
|
|
show_portable_service_install_window_ = false;
|
|
JoinPortableWindowsServiceInstallThread();
|
|
}
|
|
} else {
|
|
if (state == PortableServiceInstallState::installing) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
if (ImGui::Button(install_label.c_str())) {
|
|
StartPortableWindowsServiceInstall();
|
|
}
|
|
if (state == PortableServiceInstallState::installing) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (state == PortableServiceInstallState::installing) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
if (ImGui::Button(cancel_label.c_str())) {
|
|
show_portable_service_install_window_ = false;
|
|
}
|
|
if (state == PortableServiceInstallState::installing) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
}
|
|
|
|
ImGui::SetWindowFontScale(1.0f);
|
|
ImGui::End();
|
|
ImGui::PopStyleVar(3);
|
|
ImGui::PopStyleColor();
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace crossdesk
|
|
|
|
#endif
|