mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
[fix] prevent Windows startup crashes from OpenGL failures, fixes #97
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#ifdef _WIN32
|
||||
#include <cstdio>
|
||||
|
||||
#include "platform/windows/gui/slint_backend.h"
|
||||
#include "service_host.h"
|
||||
#endif
|
||||
|
||||
@@ -175,6 +176,10 @@ int HandleServiceCliCommand(const std::string& command) {
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
if (argc == 2 &&
|
||||
std::strcmp(argv[1], crossdesk::kSlintRendererProbeArgument) == 0) {
|
||||
return crossdesk::RunSlintRendererProbe();
|
||||
}
|
||||
if (argc > 1 && IsServiceCliCommand(argv[1])) {
|
||||
return HandleServiceCliCommand(argv[1]);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#if _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
#include "platform/windows/gui/slint_backend.h"
|
||||
#include "platform/windows/gui/tray/win_tray.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "platform/macos/gui/tray/mac_tray.h"
|
||||
@@ -992,6 +993,15 @@ int GuiApplication::Run() {
|
||||
if (!InitializeSDL()) {
|
||||
return -1;
|
||||
}
|
||||
#if _WIN32
|
||||
const auto backend = ConfigureWindowsSlintBackend();
|
||||
if (!backend.success) {
|
||||
LOG_ERROR("{}", backend.diagnostic);
|
||||
SDL_Quit();
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO("Slint backend: {}; {}", backend.backend, backend.diagnostic);
|
||||
#endif
|
||||
InitializeModules();
|
||||
InitializeUi();
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "platform/windows/gui/slint_backend.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
struct HandleCloser {
|
||||
void operator()(HANDLE handle) const noexcept {
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
using ScopedHandle = std::unique_ptr<void, HandleCloser>;
|
||||
|
||||
std::string ErrorCode(DWORD code) {
|
||||
std::ostringstream text;
|
||||
text << "0x" << std::hex << std::uppercase << code;
|
||||
return text.str();
|
||||
}
|
||||
|
||||
std::string ProbeSlintRenderer(DWORD timeout_ms) {
|
||||
std::wstring executable(32768, L'\0');
|
||||
const DWORD length = GetModuleFileNameW(
|
||||
nullptr, executable.data(), static_cast<DWORD>(executable.size()));
|
||||
if (length == 0 || length >= executable.size()) {
|
||||
return "Unable to locate renderer probe executable";
|
||||
}
|
||||
executable.resize(length);
|
||||
|
||||
// A job also cleans up the probe if the application is closed while the
|
||||
// graphics driver is stuck. Create it suspended so assignment cannot race.
|
||||
ScopedHandle job(CreateJobObjectW(nullptr, nullptr));
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits = {};
|
||||
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||
if (!job ||
|
||||
!SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
|
||||
&limits, sizeof(limits))) {
|
||||
return "Unable to create renderer probe job: " + ErrorCode(GetLastError());
|
||||
}
|
||||
|
||||
constexpr std::string_view argument = kSlintRendererProbeArgument;
|
||||
std::wstring command = L"\"" + executable + L"\" ";
|
||||
command.append(argument.begin(), argument.end());
|
||||
STARTUPINFOW startup = {sizeof(startup)};
|
||||
PROCESS_INFORMATION process = {};
|
||||
if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr,
|
||||
FALSE, CREATE_NO_WINDOW | CREATE_SUSPENDED, nullptr,
|
||||
nullptr, &startup, &process)) {
|
||||
return "Unable to start renderer probe: " + ErrorCode(GetLastError());
|
||||
}
|
||||
ScopedHandle process_handle(process.hProcess);
|
||||
ScopedHandle thread_handle(process.hThread);
|
||||
if (!AssignProcessToJobObject(job.get(), process.hProcess) ||
|
||||
ResumeThread(process.hThread) == static_cast<DWORD>(-1)) {
|
||||
const DWORD error = GetLastError();
|
||||
TerminateProcess(process.hProcess, 1);
|
||||
return "Unable to run renderer probe: " + ErrorCode(error);
|
||||
}
|
||||
|
||||
const DWORD wait = WaitForSingleObject(process.hProcess, timeout_ms);
|
||||
if (wait != WAIT_OBJECT_0) {
|
||||
// Closing the job kills only our probe, including any stuck driver thread.
|
||||
return wait == WAIT_TIMEOUT ? "Slint renderer probe timed out"
|
||||
: "Waiting for renderer probe failed: " +
|
||||
ErrorCode(GetLastError());
|
||||
}
|
||||
DWORD exit_code = 0;
|
||||
if (!GetExitCodeProcess(process.hProcess, &exit_code)) {
|
||||
return "Unable to read renderer probe result: " + ErrorCode(GetLastError());
|
||||
}
|
||||
return exit_code == 0
|
||||
? std::string{}
|
||||
: "Slint renderer probe exited with " + ErrorCode(exit_code);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SlintBackendSelection ConfigureWindowsSlintBackend(uint32_t probe_timeout_ms) {
|
||||
if (const char* requested = std::getenv("SLINT_BACKEND");
|
||||
requested && requested[0] != '\0') {
|
||||
return {true, requested, "Using explicitly requested Slint backend"};
|
||||
}
|
||||
|
||||
// Slint creates its own GL context. A successful SDL/WGL capability check
|
||||
// cannot validate it, and a Rust panic cannot be caught by C++ exceptions.
|
||||
// Exercise Slint in a separate process without touching GL in this process.
|
||||
const std::string error = ProbeSlintRenderer(probe_timeout_ms);
|
||||
if (error.empty()) {
|
||||
return {true, "default", "Slint renderer probe rendered successfully"};
|
||||
}
|
||||
|
||||
// _putenv_s updates the CRT and the OS environment read by slint_cpp.dll.
|
||||
// This is process-local so a later launch can retry hardware rendering.
|
||||
if (_putenv_s("SLINT_BACKEND", "winit-software") != 0) {
|
||||
return {false, {}, "Unable to select Slint software rendering: " + error};
|
||||
}
|
||||
return {true, "winit-software", error};
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-09-07
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SLINT_BACKEND_H_
|
||||
#define _SLINT_BACKEND_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
struct SlintBackendSelection {
|
||||
bool success = true;
|
||||
std::string backend;
|
||||
std::string diagnostic;
|
||||
};
|
||||
|
||||
inline constexpr char kSlintRendererProbeArgument[] = "--slint-renderer-probe";
|
||||
|
||||
// Call before creating any Slint components. The executable's main() must
|
||||
// dispatch kSlintRendererProbeArgument before services, settings or the daemon.
|
||||
SlintBackendSelection ConfigureWindowsSlintBackend(
|
||||
uint32_t probe_timeout_ms = 10000);
|
||||
|
||||
// Internal child-process entry point; never call in the main application.
|
||||
int RunSlintRendererProbe();
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "crossdesk_ui.h"
|
||||
#include "platform/windows/gui/slint_backend.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
int RunSlintRendererProbe() {
|
||||
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = 2;
|
||||
{
|
||||
// Reuse the application's single AOT entry point. Independently generated
|
||||
// headers can contain colliding internal component names across TUs.
|
||||
auto probe = ui::MainWindow::create();
|
||||
probe->set_custom_titlebar(true);
|
||||
// AfterRendering confirms that the real GL context, shader initialization
|
||||
// and first draw all succeeded. GPU readback from an off-screen window can
|
||||
// be black even when rendering works, so do not use its pixels as a test.
|
||||
const auto notifier_error = probe->window().set_rendering_notifier(
|
||||
[&](slint::RenderingState state, slint::GraphicsAPI) {
|
||||
if (state == slint::RenderingState::AfterRendering) {
|
||||
result = 0;
|
||||
}
|
||||
});
|
||||
probe->window().set_position(
|
||||
slint::PhysicalPosition(slint::Point<int32_t>{-32000, -32000}));
|
||||
probe->show();
|
||||
slint::Timer timer;
|
||||
timer.start(slint::TimerMode::SingleShot, std::chrono::milliseconds(100),
|
||||
[&] {
|
||||
// Renderers without a rendering notifier (software) support
|
||||
// snapshots.
|
||||
if (notifier_error) {
|
||||
const auto pixels = probe->window().take_snapshot();
|
||||
if (pixels && pixels->width() > 0 && pixels->height() > 0) {
|
||||
result = 0;
|
||||
} else {
|
||||
std::cerr << "Slint probe could not read rendered pixels"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
slint::quit_event_loop();
|
||||
});
|
||||
slint::run_event_loop();
|
||||
probe->hide();
|
||||
}
|
||||
SDL_Quit();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -329,6 +329,8 @@ function setup_targets()
|
||||
add_cxxflags("/bigobj")
|
||||
add_links("opengl32")
|
||||
add_files("apps/desktop/src/platform/windows/gui/tray/win_tray.cpp",
|
||||
"apps/desktop/src/platform/windows/gui/slint_backend.cpp",
|
||||
"apps/desktop/src/platform/windows/gui/slint_renderer_probe.cpp",
|
||||
"apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp",
|
||||
"apps/desktop/src/platform/common/gui/video_renderer_factory_opengl.cpp",
|
||||
"apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp",
|
||||
|
||||
Reference in New Issue
Block a user