ui布局自适应

This commit is contained in:
tianmo committed 2026-11-09 10:08:29 +08:00
1 parent 98f44890f8
commit 3587129b90
40 files changed
+929 -142

No files matched your search

+36
View File
@@ -4,6 +4,7 @@
#include "config.hpp"
#include "connector_service.hpp"
#include "local_certificate.hpp"
#include "platform.hpp"
using connector::ConnectorConfig;
@@ -15,6 +16,7 @@ using connector::ConnectorService;
@property(nonatomic, strong) NSTextField* statusLabel;
@property(nonatomic, strong) NSArray<NSView*>* pathControls;
@property(nonatomic, strong) NSButton* connectionButton;
@property(nonatomic, strong) NSButton* safariButton;
@property(nonatomic, assign) ConnectorService* service;
@property(nonatomic, copy) NSString* startupError;
@end
@@ -72,6 +74,9 @@ using connector::ConnectorService;
self.pathControls = @[pathLabel, self.pathField, browse, automatic, save];
self.connectionButton = [self button:@"断开连接" frame:NSMakeRect(460, 25, 130, 36) action:@selector(toggleConnection:)];
[content addSubview:self.connectionButton];
self.safariButton = [self button:@"启用 Safari 支持" frame:NSMakeRect(315, 25, 130, 36) action:@selector(enableSafari:)];
self.safariButton.toolTip = @"安装仅用于 127.0.0.1 的本地证书,使 Safari 可以访问 HTTPS 连接器。";
[content addSubview:self.safariButton];
[self refreshStatus:nil];
[self.window makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
@@ -82,6 +87,9 @@ using connector::ConnectorService;
const BOOL ownsPort = self.service && self.service->running();
const BOOL portOccupied = ownsPort || connector::connector_port_in_use();
self.connectionButton.title = portOccupied ? @"停止连接" : @"启动连接";
const BOOL safariTrusted = connector::local_certificate_trusted();
self.safariButton.title = safariTrusted ? @"Safari 支持已启用" : @"启用 Safari 支持";
self.safariButton.enabled = !safariTrusted;
if (self.startupError.length > 0 && !portOccupied) {
self.statusLabel.stringValue = [@"● 连接器未启动 · " stringByAppendingString:self.startupError];
self.statusLabel.textColor = NSColor.systemRedColor;
@@ -121,6 +129,34 @@ using connector::ConnectorService;
[self refreshStatus:nil];
}
- (void)enableSafari:(id)sender {
(void)sender;
NSAlert* confirmation = [[NSAlert alloc] init];
confirmation.messageText = @"启用 Safari 本机连接";
confirmation.informativeText = @"连接器会把一个仅用于 127.0.0.1 的本地证书加入当前用户的 SSL 信任列表。系统可能要求你确认。";
[confirmation addButtonWithTitle:@"继续"];
[confirmation addButtonWithTitle:@"取消"];
if ([confirmation runModal] != NSAlertFirstButtonReturn) return;
std::string error;
if (!connector::trust_local_certificate(error)) {
NSAlert* alert = [[NSAlert alloc] init];
alert.messageText = @"无法启用 Safari 支持";
alert.informativeText = [NSString stringWithUTF8String:error.c_str()];
[alert runModal];
return;
}
if (self.service && self.service->running() && !self.service->secure_running()) {
self.service->stop();
}
if (self.service && !self.service->running()) {
std::string startError;
self.service->start(startError);
self.startupError = [NSString stringWithUTF8String:startError.c_str()];
}
[self refreshStatus:nil];
}
- (void)selectSpine:(id)sender {
(void)sender;
NSOpenPanel* panel = [NSOpenPanel openPanel];
+38 -5
View File
@@ -4,11 +4,15 @@
#include "encoding.hpp"
#include "json.hpp"
#include "platform.hpp"
#ifdef __APPLE__
#include "local_certificate.hpp"
#endif
#include "workspace.hpp"
#include "zip.hpp"
#include <chrono>
#include <filesystem>
#include <iostream>
#include <map>
#include <mutex>
#include <random>
@@ -20,6 +24,7 @@ namespace {
constexpr const char* kVersion = "2.0.0";
constexpr unsigned short kPort = 27843;
constexpr unsigned short kSecurePort = 27844;
std::mutex pairing_mutex;
struct Pairing { std::string origin; std::chrono::steady_clock::time_point expires; };
std::map<std::string, Pairing> pairing;
@@ -125,10 +130,27 @@ std::pair<std::vector<std::uint8_t>, std::string> convert(const Json& payload, c
} // namespace
bool ConnectorService::start(std::string& error) {
return server_.start(kPort, [this](const HttpRequest& request) { return handle(request); }, error);
if (server_.running()) return true;
if (!server_.start(kPort, [this](const HttpRequest& request) { return handle(request); }, error)) return false;
#ifdef __APPLE__
std::string certificate_error;
if (!ensure_local_certificate(certificate_error)) {
secure_error_ = certificate_error;
std::cerr << "Safari HTTPS certificate error: " << secure_error_ << '\n';
} else if (!secure_server_.start(
kSecurePort, [this](const HttpRequest& request) { return handle(request); }, secure_error_,
local_identity_path())) {
// HTTP remains available for existing browsers even if the optional Safari endpoint cannot start.
std::cerr << "Safari HTTPS server error: " << secure_error_ << '\n';
}
#endif
return true;
}
void ConnectorService::stop() { server_.stop(); }
void ConnectorService::stop() {
secure_server_.stop();
server_.stop();
}
HttpResponse ConnectorService::handle(const HttpRequest& request) {
const std::string origin = header(request, "origin");
@@ -142,13 +164,21 @@ HttpResponse ConnectorService::handle(const HttpRequest& request) {
if (request.method == "GET" && request.path == "/v1/health") {
std::lock_guard<std::mutex> lock(config_mutex_);
Json::Array versions{Json("3.8"), Json("4.0"), Json("4.1"), Json("4.2"), Json("4.3")};
return json_response(200, {
Json::Object health = {
{"connectorVersion", Json(kVersion)},
{"authorized", Json(origin_allowed(origin))},
{"spineFound", Json(!find_spine_executable().empty())},
{"secureAvailable", Json(secure_server_.running())},
{"platform", Json(platform_name())},
{"supportedVersions", Json(std::move(versions))},
}, origin);
};
if (!origin_allowed(origin)) {
const std::string base = request.secure
? "https://127.0.0.1:" + std::to_string(kSecurePort)
: "http://127.0.0.1:" + std::to_string(kPort);
health["pairUrl"] = Json(base + "/pair?origin=" + url_encode(origin));
}
return json_response(200, std::move(health), origin);
}
if (request.method == "GET" && request.path == "/pair") {
const auto found = request.query.find("origin");
@@ -184,7 +214,10 @@ HttpResponse ConnectorService::handle(const HttpRequest& request) {
if (!origin_allowed(origin)) {
HttpResponse response = error_response(403, "ORIGIN_NOT_ALLOWED", "需要先授权当前网站使用本地 Spine 连接器", origin);
Json body = Json::parse(response.body);
body["pairUrl"] = Json("http://127.0.0.1:27843/pair?origin=" + url_encode(origin));
const std::string base = request.secure
? "https://127.0.0.1:" + std::to_string(kSecurePort)
: "http://127.0.0.1:" + std::to_string(kPort);
body["pairUrl"] = Json(base + "/pair?origin=" + url_encode(origin));
response.body = body.dump();
return response;
}
+3
View File
@@ -12,10 +12,13 @@ class ConnectorService {
bool start(std::string& error);
void stop();
bool running() const { return server_.running(); }
bool secure_running() const { return secure_server_.running(); }
private:
HttpResponse handle(const HttpRequest& request);
HttpServer server_;
HttpServer secure_server_;
std::string secure_error_;
std::mutex config_mutex_;
std::mutex conversion_mutex_;
};
+193 -12
View File
@@ -3,11 +3,18 @@
#include "encoding.hpp"
#include <algorithm>
#include <cerrno>
#include <cctype>
#include <cstring>
#include <fstream>
#include <sstream>
#include <thread>
#ifdef __APPLE__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
#ifdef _WIN32
#define NOMINMAX
#include <winsock2.h>
@@ -19,6 +26,11 @@ constexpr Socket kInvalidSocket = INVALID_SOCKET;
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecImportExport.h>
#include <Security/SecureTransport.h>
#endif
using Socket = int;
constexpr Socket kInvalidSocket = -1;
#endif
@@ -37,6 +49,119 @@ void close_socket(Socket socket) {
#endif
}
struct ConnectionStream {
Socket socket = kInvalidSocket;
#ifdef __APPLE__
SSLContextRef tls = nullptr;
#endif
};
#ifdef __APPLE__
OSStatus socket_read(SSLConnectionRef reference, void* data, std::size_t* length) {
const Socket socket = *static_cast<const Socket*>(reference);
const long count = recv(socket, static_cast<char*>(data), static_cast<int>(*length), 0);
if (count > 0) {
*length = static_cast<std::size_t>(count);
return noErr;
}
*length = 0;
if (count == 0) return errSSLClosedGraceful;
return errno == EAGAIN || errno == EWOULDBLOCK ? errSSLWouldBlock : errSSLInternal;
}
OSStatus socket_write(SSLConnectionRef reference, const void* data, std::size_t* length) {
const Socket socket = *static_cast<const Socket*>(reference);
const long count = send(socket, static_cast<const char*>(data), static_cast<int>(*length), 0);
if (count > 0) {
*length = static_cast<std::size_t>(count);
return noErr;
}
*length = 0;
return errno == EAGAIN || errno == EWOULDBLOCK ? errSSLWouldBlock : errSSLInternal;
}
void* load_identity(const std::filesystem::path& path, std::string& error) {
std::ifstream input(path, std::ios::binary);
if (!input) {
error = "无法读取 HTTPS 身份证书";
return nullptr;
}
const std::string bytes((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(bytes.data()),
static_cast<CFIndex>(bytes.size()));
const void* keys[] = {kSecImportExportPassphrase};
const void* values[] = {CFSTR("spine-connector-local")};
CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFArrayRef items = nullptr;
const OSStatus status = SecPKCS12Import(data, options, &items);
CFRelease(options);
CFRelease(data);
if (status != errSecSuccess || !items || CFArrayGetCount(items) == 0) {
if (items) CFRelease(items);
error = "无法读取 HTTPS 身份证书";
return nullptr;
}
CFDictionaryRef item = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(items, 0));
CFTypeRef identity = CFDictionaryGetValue(item, kSecImportItemIdentity);
if (identity) CFRetain(identity);
CFRelease(items);
if (!identity) error = "HTTPS 身份证书缺少私钥";
return const_cast<void*>(identity);
}
#endif
bool open_stream(ConnectionStream& stream, void* identity, std::string& error) {
if (!identity) return true;
#ifdef __APPLE__
SSLContextRef context = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
if (!context) {
error = "无法创建 HTTPS 服务";
return false;
}
SSLSetIOFuncs(context, socket_read, socket_write);
SSLSetConnection(context, &stream.socket);
SSLSetProtocolVersionMin(context, kTLSProtocol12);
const void* certificates[] = {identity};
CFArrayRef certificate_array = CFArrayCreate(kCFAllocatorDefault, certificates, 1, &kCFTypeArrayCallBacks);
const OSStatus certificate_status = SSLSetCertificate(context, certificate_array);
CFRelease(certificate_array);
if (certificate_status != noErr) {
CFRelease(context);
error = "无法配置 HTTPS 身份证书";
return false;
}
OSStatus status;
do {
status = SSLHandshake(context);
} while (status == errSSLWouldBlock);
if (status != noErr) {
CFRelease(context);
return false;
}
stream.tls = context;
return true;
#else
(void)stream;
error = "当前平台不支持 HTTPS 连接器";
return false;
#endif
}
void close_stream(ConnectionStream& stream) {
#ifdef __APPLE__
if (stream.tls) {
SSLClose(stream.tls);
CFRelease(stream.tls);
stream.tls = nullptr;
}
#endif
if (stream.socket != kInvalidSocket) {
close_socket(stream.socket);
stream.socket = kInvalidSocket;
}
}
std::string lowercase(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return value;
@@ -56,21 +181,49 @@ std::string status_text(int status) {
}
}
void send_all(Socket socket, const std::string& data) {
int read_stream(ConnectionStream& stream, char* buffer, int length) {
#ifdef __APPLE__
if (stream.tls) {
std::size_t received = 0;
const OSStatus status = SSLRead(stream.tls, buffer, static_cast<std::size_t>(length), &received);
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify) return static_cast<int>(received);
if (status != noErr && status != errSSLWouldBlock) return -1;
return static_cast<int>(received);
}
#endif
return recv(stream.socket, buffer, length, 0);
}
void send_all(ConnectionStream& stream, const std::string& data) {
std::size_t sent = 0;
while (sent < data.size()) {
const int count = send(socket, data.data() + sent, static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
int count;
#ifdef __APPLE__
if (stream.tls) {
std::size_t written = 0;
const OSStatus status = SSLWrite(stream.tls, data.data() + sent,
(std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20)), &written);
if (status != noErr && status != errSSLWouldBlock) return;
count = static_cast<int>(written);
} else {
count = send(stream.socket, data.data() + sent,
static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
}
#else
count = send(stream.socket, data.data() + sent,
static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
#endif
if (count <= 0) return;
sent += static_cast<std::size_t>(count);
}
}
bool parse_request(Socket socket, HttpRequest& request, int& error_status) {
bool parse_request(ConnectionStream& stream, HttpRequest& request, int& error_status) {
std::string buffer;
char chunk[8192];
std::size_t header_end = std::string::npos;
while ((header_end = buffer.find("\r\n\r\n")) == std::string::npos) {
const int count = recv(socket, chunk, sizeof(chunk), 0);
const int count = read_stream(stream, chunk, sizeof(chunk));
if (count <= 0) return false;
buffer.append(chunk, count);
if (buffer.size() > kMaxHeaders) { error_status = 413; return false; }
@@ -109,7 +262,7 @@ bool parse_request(Socket socket, HttpRequest& request, int& error_status) {
if (content_length > kMaxBody) { error_status = 413; return false; }
request.body = buffer.substr(header_end + 4);
while (request.body.size() < content_length) {
const int count = recv(socket, chunk, sizeof(chunk), 0);
const int count = read_stream(stream, chunk, sizeof(chunk));
if (count <= 0) return false;
request.body.append(chunk, count);
}
@@ -117,11 +270,20 @@ bool parse_request(Socket socket, HttpRequest& request, int& error_status) {
return true;
}
void serve_client(Socket client, const RequestHandler& handler) {
void serve_client(Socket client, void* identity, const RequestHandler& handler) {
ConnectionStream stream{client};
std::string tls_error;
if (!open_stream(stream, identity, tls_error)) {
close_stream(stream);
return;
}
HttpRequest request;
int error_status = 400;
HttpResponse response;
if (parse_request(client, request, error_status)) {
if (parse_request(stream, request, error_status)) {
#ifdef __APPLE__
request.secure = stream.tls != nullptr;
#endif
try { response = handler(request); }
catch (const std::exception& error) {
response.status = 500;
@@ -138,9 +300,9 @@ void serve_client(Socket client, const RequestHandler& handler) {
output << "HTTP/1.1 " << response.status << ' ' << status_text(response.status) << "\r\n";
for (const auto& [name, value] : response.headers) output << name << ": " << value << "\r\n";
output << "\r\n";
send_all(client, output.str());
send_all(client, response.body);
close_socket(client);
send_all(stream, output.str());
send_all(stream, response.body);
close_stream(stream);
}
} // namespace
@@ -148,8 +310,21 @@ void serve_client(Socket client, const RequestHandler& handler) {
HttpServer::HttpServer() = default;
HttpServer::~HttpServer() { stop(); }
bool HttpServer::start(unsigned short port, RequestHandler handler, std::string& error) {
bool HttpServer::start(unsigned short port, RequestHandler handler, std::string& error,
const std::filesystem::path& identity) {
if (running_) return true;
#ifdef __APPLE__
if (!identity.empty()) {
void* loaded = load_identity(identity, error);
if (loaded) tls_identity_ = std::shared_ptr<void>(loaded, [](void* value) { CFRelease(value); });
if (!tls_identity_) return false;
}
#else
if (!identity.empty()) {
error = "当前平台不支持 HTTPS 连接器";
return false;
}
#endif
#ifdef _WIN32
WSADATA data{};
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) { error = "无法初始化网络服务"; return false; }
@@ -182,7 +357,9 @@ void HttpServer::accept_loop() {
if (running_) continue;
break;
}
std::thread(serve_client, client, handler_).detach();
std::thread([client, identity = tls_identity_, handler = handler_] {
serve_client(client, identity.get(), handler);
}).detach();
}
}
@@ -206,3 +383,7 @@ void HttpServer::stop() {
}
} // namespace connector
#ifdef __APPLE__
#pragma clang diagnostic pop
#endif
+6 -1
View File
@@ -2,8 +2,10 @@
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <map>
#include <memory>
#include <string>
namespace connector {
@@ -15,6 +17,7 @@ struct HttpRequest {
std::map<std::string, std::string> query;
std::map<std::string, std::string> headers;
std::string body;
bool secure = false;
};
struct HttpResponse {
@@ -29,7 +32,8 @@ class HttpServer {
public:
HttpServer();
~HttpServer();
bool start(unsigned short port, RequestHandler handler, std::string& error);
bool start(unsigned short port, RequestHandler handler, std::string& error,
const std::filesystem::path& identity = {});
void stop();
bool running() const { return running_.load(); }
@@ -37,6 +41,7 @@ class HttpServer {
void accept_loop();
std::atomic<bool> running_{false};
std::intptr_t socket_ = -1;
std::shared_ptr<void> tls_identity_;
RequestHandler handler_;
void* thread_ = nullptr;
};
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <filesystem>
#include <string>
namespace connector {
std::filesystem::path local_identity_path();
bool ensure_local_certificate(std::string& error);
bool local_certificate_trusted();
bool trust_local_certificate(std::string& error);
} // namespace connector
+178
View File
@@ -0,0 +1,178 @@
#ifdef __APPLE__
#include "local_certificate.hpp"
#include "config.hpp"
#include "encoding.hpp"
#include "platform.hpp"
#include <cstdlib>
#include <fstream>
#include <sys/stat.h>
namespace connector {
namespace {
struct CertificatePaths {
std::filesystem::path directory;
std::filesystem::path ca_key;
std::filesystem::path ca;
std::filesystem::path server_key;
std::filesystem::path server_certificate;
std::filesystem::path identity;
std::filesystem::path request;
std::filesystem::path extensions;
};
CertificatePaths paths() {
const auto directory = config_path().parent_path() / "tls";
return {
directory,
directory / "ca-key.pem",
directory / "ca.pem",
directory / "server-key.pem",
directory / "server.pem",
directory / "identity.p12",
directory / "server.csr",
directory / "server.ext",
};
}
std::string process_error(const ProcessResult& result) {
std::string message = trim(result.output);
return message.empty() ? "OpenSSL 执行失败" : message;
}
bool run_openssl(const std::vector<std::string>& arguments, std::string& error) {
const ProcessResult result = run_process("/usr/bin/openssl", arguments, {}, 30);
if (result.timed_out) {
error = "生成本机 HTTPS 证书超时";
return false;
}
if (result.exit_code != 0) {
error = process_error(result);
return false;
}
return true;
}
bool certificate_is_usable(const std::filesystem::path& certificate) {
std::error_code file_error;
if (!std::filesystem::is_regular_file(certificate, file_error)) return false;
const ProcessResult result = run_process(
"/usr/bin/openssl", {"x509", "-checkend", "2592000", "-noout", "-in", certificate.string()}, {}, 10);
return !result.timed_out && result.exit_code == 0;
}
bool identity_is_usable(const std::filesystem::path& identity) {
std::error_code error;
return std::filesystem::is_regular_file(identity, error);
}
void set_private_permissions(const std::filesystem::path& path) {
chmod(path.c_str(), 0600);
}
} // namespace
std::filesystem::path local_identity_path() {
return paths().identity;
}
bool ensure_local_certificate(std::string& error) {
const CertificatePaths files = paths();
if (certificate_is_usable(files.server_certificate) && identity_is_usable(files.identity)) {
error.clear();
return true;
}
try {
std::filesystem::create_directories(files.directory);
chmod(files.directory.c_str(), 0700);
for (const auto& path : {files.ca_key, files.ca, files.server_key, files.server_certificate,
files.identity, files.request, files.extensions}) {
std::error_code ignored;
std::filesystem::remove(path, ignored);
}
} catch (const std::exception& exception) {
error = exception.what();
return false;
}
const std::string dir = files.directory.string();
if (!run_openssl({"genrsa", "-out", files.ca_key.string(), "2048"}, error)
|| !run_openssl({"req", "-x509", "-new", "-key", files.ca_key.string(), "-sha256", "-days", "3650",
"-subj", "/CN=Spine Particle Connector Local CA", "-out", files.ca.string()}, error)
|| !run_openssl({"genrsa", "-out", files.server_key.string(), "2048"}, error)
|| !run_openssl({"req", "-new", "-key", files.server_key.string(), "-subj", "/CN=127.0.0.1",
"-out", files.request.string()}, error)) {
return false;
}
{
std::ofstream extensions(files.extensions, std::ios::binary | std::ios::trunc);
if (!extensions) {
error = "无法写入证书配置";
return false;
}
extensions << "subjectAltName=IP:127.0.0.1,DNS:localhost\n"
<< "extendedKeyUsage=serverAuth\n"
<< "basicConstraints=CA:FALSE\n";
}
if (!run_openssl({"x509", "-req", "-in", files.request.string(), "-CA", files.ca.string(),
"-CAkey", files.ca_key.string(), "-CAcreateserial", "-out", files.server_certificate.string(),
"-days", "825", "-sha256", "-extfile", files.extensions.string()}, error)
|| !run_openssl({"pkcs12", "-export", "-out", files.identity.string(), "-inkey", files.server_key.string(),
"-in", files.server_certificate.string(), "-passout", "pass:spine-connector-local"}, error)) {
return false;
}
set_private_permissions(files.server_key);
set_private_permissions(files.identity);
{
std::error_code ignored;
std::filesystem::remove(files.ca_key, ignored);
}
(void)dir;
error.clear();
return true;
}
bool local_certificate_trusted() {
const CertificatePaths files = paths();
const ProcessResult result = run_process(
"/usr/bin/security",
{"verify-cert", "-c", files.server_certificate.string(), "-n", "127.0.0.1", "-p", "ssl", "-L", "-q"},
{}, 10);
return !result.timed_out && result.exit_code == 0;
}
bool trust_local_certificate(std::string& error) {
const CertificatePaths files = paths();
if (!ensure_local_certificate(error)) return false;
const char* home = std::getenv("HOME");
if (!home || !*home) {
error = "无法确定当前用户主目录";
return false;
}
const auto keychain = std::filesystem::path(home) / "Library" / "Keychains" / "login.keychain-db";
const ProcessResult result = run_process(
"/usr/bin/security",
{"add-trusted-cert", "-r", "trustRoot", "-p", "ssl", "-k", keychain.string(), files.ca.string()},
{}, 120);
if (result.timed_out || result.exit_code != 0) {
error = result.timed_out ? "等待系统证书授权超时" : process_error(result);
return false;
}
if (!local_certificate_trusted()) {
error = "证书已安装,但系统仍未信任它";
return false;
}
error.clear();
return true;
}
} // namespace connector
#endif