mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-04-21 10:23:40 +08:00
Compare commits
11 Commits
2f26334775
...
v1.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7f62c5fe0 | ||
|
|
2bbddbca6b | ||
|
|
f0f8f27f4c | ||
|
|
262af263f2 | ||
|
|
38b7775b1b | ||
|
|
56c0bca62f | ||
|
|
4b1b09fd5b | ||
|
|
1d6425bbf4 | ||
|
|
5ec6552d25 | ||
|
|
79e4a0790a | ||
|
|
1d3cac54ab |
@@ -34,9 +34,16 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
Daemon* Daemon::instance_ = nullptr;
|
volatile std::sig_atomic_t Daemon::stop_requested_ = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int kRestartDelayMs = 1000;
|
||||||
|
#ifndef _WIN32
|
||||||
|
constexpr int kWaitPollIntervalMs = 200;
|
||||||
|
#endif
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// get executable file path
|
// get executable file path
|
||||||
static std::string GetExecutablePath() {
|
static std::string GetExecutablePath() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -66,33 +73,35 @@ static std::string GetExecutablePath() {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Daemon::Daemon(const std::string& name)
|
Daemon::Daemon(const std::string& name) : name_(name), running_(false) {}
|
||||||
: name_(name)
|
|
||||||
#ifdef _WIN32
|
void Daemon::stop() {
|
||||||
,
|
running_.store(false);
|
||||||
running_(false)
|
#ifndef _WIN32
|
||||||
#else
|
stop_requested_ = 1;
|
||||||
,
|
|
||||||
running_(true)
|
|
||||||
#endif
|
#endif
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Daemon::stop() { running_ = false; }
|
bool Daemon::isRunning() const {
|
||||||
|
#ifndef _WIN32
|
||||||
bool Daemon::isRunning() const { return running_; }
|
return running_.load() && (stop_requested_ == 0);
|
||||||
|
#else
|
||||||
|
return running_.load();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool Daemon::start(MainLoopFunc loop) {
|
bool Daemon::start(MainLoopFunc loop) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
running_ = true;
|
running_.store(true);
|
||||||
return runWithRestart(loop);
|
return runWithRestart(loop);
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
// macOS: Use child process monitoring (like Windows) to preserve GUI
|
// macOS: Use child process monitoring (like Windows) to preserve GUI
|
||||||
running_ = true;
|
stop_requested_ = 0;
|
||||||
|
running_.store(true);
|
||||||
return runWithRestart(loop);
|
return runWithRestart(loop);
|
||||||
#else
|
#else
|
||||||
// linux: Daemonize first, then run with restart monitoring
|
// linux: Daemonize first, then run with restart monitoring
|
||||||
instance_ = this;
|
stop_requested_ = 0;
|
||||||
|
|
||||||
// check if running from terminal before fork
|
// check if running from terminal before fork
|
||||||
bool from_terminal =
|
bool from_terminal =
|
||||||
@@ -134,29 +143,13 @@ bool Daemon::start(MainLoopFunc loop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set up signal handlers
|
// set up signal handlers
|
||||||
signal(SIGTERM, [](int) {
|
signal(SIGTERM, [](int) { stop_requested_ = 1; });
|
||||||
if (instance_) instance_->stop();
|
signal(SIGINT, [](int) { stop_requested_ = 1; });
|
||||||
});
|
|
||||||
signal(SIGINT, [](int) {
|
|
||||||
if (instance_) instance_->stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ignore SIGPIPE
|
// ignore SIGPIPE
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
// set up SIGCHLD handler to reap zombie processes
|
running_.store(true);
|
||||||
struct sigaction sa_chld;
|
|
||||||
sa_chld.sa_handler = [](int) {
|
|
||||||
// reap zombie processes
|
|
||||||
while (waitpid(-1, nullptr, WNOHANG) > 0) {
|
|
||||||
// continue until no more zombie children
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sigemptyset(&sa_chld.sa_mask);
|
|
||||||
sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP;
|
|
||||||
sigaction(SIGCHLD, &sa_chld, nullptr);
|
|
||||||
|
|
||||||
running_ = true;
|
|
||||||
return runWithRestart(loop);
|
return runWithRestart(loop);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -204,8 +197,7 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
|
|||||||
restart_count++;
|
restart_count++;
|
||||||
std::cerr << "Exception caught, restarting... (attempt "
|
std::cerr << "Exception caught, restarting... (attempt "
|
||||||
<< restart_count << ")" << std::endl;
|
<< restart_count << ")" << std::endl;
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||||
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -237,27 +229,41 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
std::cerr << "Failed to create child process, error: " << GetLastError()
|
std::cerr << "Failed to create child process, error: " << GetLastError()
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||||
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
|
|
||||||
restart_count++;
|
restart_count++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (isRunning()) {
|
||||||
|
DWORD wait_result = WaitForSingleObject(pi.hProcess, 200);
|
||||||
|
if (wait_result == WAIT_OBJECT_0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (wait_result == WAIT_FAILED) {
|
||||||
|
std::cerr << "Failed waiting child process, error: " << GetLastError()
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRunning()) {
|
||||||
|
TerminateProcess(pi.hProcess, 1);
|
||||||
|
WaitForSingleObject(pi.hProcess, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
DWORD exit_code = 0;
|
DWORD exit_code = 0;
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
||||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||||
CloseHandle(pi.hProcess);
|
CloseHandle(pi.hProcess);
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
if (exit_code == 0) {
|
if (!isRunning() || exit_code == 0) {
|
||||||
break; // normal exit
|
break; // normal exit
|
||||||
}
|
}
|
||||||
restart_count++;
|
restart_count++;
|
||||||
std::cerr << "Child process exited with code " << exit_code
|
std::cerr << "Child process exited with code " << exit_code
|
||||||
<< ", restarting... (attempt " << restart_count << ")"
|
<< ", restarting... (attempt " << restart_count << ")"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||||
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
|
|
||||||
#else
|
#else
|
||||||
// linux: use fork + exec to create child process
|
// linux: use fork + exec to create child process
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
@@ -266,21 +272,39 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
|
|||||||
_exit(1); // exec failed
|
_exit(1); // exec failed
|
||||||
} else if (pid > 0) {
|
} else if (pid > 0) {
|
||||||
int status = 0;
|
int status = 0;
|
||||||
pid_t waited_pid = waitpid(pid, &status, 0);
|
pid_t waited_pid = -1;
|
||||||
|
while (isRunning()) {
|
||||||
|
waited_pid = waitpid(pid, &status, WNOHANG);
|
||||||
|
if (waited_pid == pid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (waited_pid < 0 && errno != EINTR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kWaitPollIntervalMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRunning() && waited_pid != pid) {
|
||||||
|
kill(pid, SIGTERM);
|
||||||
|
waited_pid = waitpid(pid, &status, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (waited_pid < 0) {
|
if (waited_pid < 0) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
restart_count++;
|
restart_count++;
|
||||||
std::cerr << "waitpid failed, errno: " << errno
|
std::cerr << "waitpid failed, errno: " << errno
|
||||||
<< ", restarting... (attempt " << restart_count << ")"
|
<< ", restarting... (attempt " << restart_count << ")"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||||
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WIFEXITED(status)) {
|
if (WIFEXITED(status)) {
|
||||||
int exit_code = WEXITSTATUS(status);
|
int exit_code = WEXITSTATUS(status);
|
||||||
if (exit_code == 0) {
|
if (!isRunning() || exit_code == 0) {
|
||||||
break; // normal exit
|
break; // normal exit
|
||||||
}
|
}
|
||||||
restart_count++;
|
restart_count++;
|
||||||
@@ -288,6 +312,9 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
|
|||||||
<< ", restarting... (attempt " << restart_count << ")"
|
<< ", restarting... (attempt " << restart_count << ")"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
} else if (WIFSIGNALED(status)) {
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
if (!isRunning()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
restart_count++;
|
restart_count++;
|
||||||
std::cerr << "Child process crashed with signal " << WTERMSIG(status)
|
std::cerr << "Child process crashed with signal " << WTERMSIG(status)
|
||||||
<< ", restarting... (attempt " << restart_count << ")"
|
<< ", restarting... (attempt " << restart_count << ")"
|
||||||
@@ -298,12 +325,10 @@ bool Daemon::runWithRestart(MainLoopFunc loop) {
|
|||||||
"(attempt "
|
"(attempt "
|
||||||
<< restart_count << ")" << std::endl;
|
<< restart_count << ")" << std::endl;
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||||
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
|
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Failed to fork child process" << std::endl;
|
std::cerr << "Failed to fork child process" << std::endl;
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
|
||||||
std::chrono::milliseconds(DAEMON_DEFAULT_RESTART_DELAY_MS));
|
|
||||||
restart_count++;
|
restart_count++;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
#ifndef _DAEMON_H_
|
#ifndef _DAEMON_H_
|
||||||
#define _DAEMON_H_
|
#define _DAEMON_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <csignal>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#define DAEMON_DEFAULT_RESTART_DELAY_MS 1000
|
|
||||||
|
|
||||||
class Daemon {
|
class Daemon {
|
||||||
public:
|
public:
|
||||||
using MainLoopFunc = std::function<void()>;
|
using MainLoopFunc = std::function<void()>;
|
||||||
@@ -28,12 +28,10 @@ class Daemon {
|
|||||||
std::string name_;
|
std::string name_;
|
||||||
bool runWithRestart(MainLoopFunc loop);
|
bool runWithRestart(MainLoopFunc loop);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifndef _WIN32
|
||||||
bool running_;
|
static volatile std::sig_atomic_t stop_requested_;
|
||||||
#else
|
|
||||||
static Daemon* instance_;
|
|
||||||
volatile bool running_;
|
|
||||||
#endif
|
#endif
|
||||||
|
std::atomic<bool> running_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -108,7 +108,7 @@ std::string GetHostName() {
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||||
std::cerr << "WSAStartup failed." << std::endl;
|
LOG_ERROR("WSAStartup failed");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
|
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "keyboard_capturer.h"
|
#include "keyboard_capturer.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
#include "keyboard_converter.h"
|
#include "keyboard_converter.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
|
||||||
@@ -10,7 +13,7 @@ static void* g_user_ptr = nullptr;
|
|||||||
|
|
||||||
static int KeyboardEventHandler(Display* display, XEvent* event) {
|
static int KeyboardEventHandler(Display* display, XEvent* event) {
|
||||||
if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) {
|
if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) {
|
||||||
KeySym keySym = XKeycodeToKeysym(display, event->xkey.keycode, 0);
|
KeySym keySym = XLookupKeysym(&event->xkey, 0);
|
||||||
int key_code = XKeysymToKeycode(display, keySym);
|
int key_code = XKeysymToKeycode(display, keySym);
|
||||||
bool is_key_down = (event->xkey.type == KeyPress);
|
bool is_key_down = (event->xkey.type == KeyPress);
|
||||||
|
|
||||||
@@ -21,7 +24,9 @@ static int KeyboardEventHandler(Display* display, XEvent* event) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) {
|
KeyboardCapturer::KeyboardCapturer()
|
||||||
|
: display_(nullptr), root_(0), running_(false) {
|
||||||
|
XInitThreads();
|
||||||
display_ = XOpenDisplay(nullptr);
|
display_ = XOpenDisplay(nullptr);
|
||||||
if (!display_) {
|
if (!display_) {
|
||||||
LOG_ERROR("Failed to open X display.");
|
LOG_ERROR("Failed to open X display.");
|
||||||
@@ -29,35 +34,87 @@ KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
KeyboardCapturer::~KeyboardCapturer() {
|
KeyboardCapturer::~KeyboardCapturer() {
|
||||||
|
Unhook();
|
||||||
|
|
||||||
if (display_) {
|
if (display_) {
|
||||||
XCloseDisplay(display_);
|
XCloseDisplay(display_);
|
||||||
|
display_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
||||||
|
if (!display_) {
|
||||||
|
LOG_ERROR("Display not initialized.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
g_on_key_action = on_key_action;
|
g_on_key_action = on_key_action;
|
||||||
g_user_ptr = user_ptr;
|
g_user_ptr = user_ptr;
|
||||||
|
|
||||||
XSelectInput(display_, DefaultRootWindow(display_),
|
if (running_) {
|
||||||
KeyPressMask | KeyReleaseMask);
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_ = DefaultRootWindow(display_);
|
||||||
|
XSelectInput(display_, root_, KeyPressMask | KeyReleaseMask);
|
||||||
|
XFlush(display_);
|
||||||
|
|
||||||
|
running_ = true;
|
||||||
|
const int x11_fd = ConnectionNumber(display_);
|
||||||
|
event_thread_ = std::thread([this, x11_fd]() {
|
||||||
while (running_) {
|
while (running_) {
|
||||||
|
while (running_ && XPending(display_) > 0) {
|
||||||
XEvent event;
|
XEvent event;
|
||||||
XNextEvent(display_, &event);
|
XNextEvent(display_, &event);
|
||||||
KeyboardEventHandler(display_, &event);
|
KeyboardEventHandler(display_, &event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!running_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pollfd pfd = {x11_fd, POLLIN, 0};
|
||||||
|
int poll_ret = poll(&pfd, 1, 50);
|
||||||
|
if (poll_ret < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LOG_ERROR("poll for X11 events failed.");
|
||||||
|
running_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poll_ret == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
|
||||||
|
LOG_ERROR("poll got invalid X11 event fd state.");
|
||||||
|
running_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pfd.revents & POLLIN) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int KeyboardCapturer::Unhook() {
|
int KeyboardCapturer::Unhook() {
|
||||||
|
running_ = false;
|
||||||
|
|
||||||
|
if (event_thread_.joinable()) {
|
||||||
|
event_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
g_on_key_action = nullptr;
|
g_on_key_action = nullptr;
|
||||||
g_user_ptr = nullptr;
|
g_user_ptr = nullptr;
|
||||||
|
|
||||||
running_ = false;
|
if (display_ && root_ != 0) {
|
||||||
|
XSelectInput(display_, root_, 0);
|
||||||
if (display_) {
|
|
||||||
XSelectInput(display_, DefaultRootWindow(display_), 0);
|
|
||||||
XFlush(display_);
|
XFlush(display_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
#include <X11/extensions/XTest.h>
|
#include <X11/extensions/XTest.h>
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "device_controller.h"
|
#include "device_controller.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
@@ -28,7 +31,8 @@ class KeyboardCapturer : public DeviceController {
|
|||||||
private:
|
private:
|
||||||
Display* display_;
|
Display* display_;
|
||||||
Window root_;
|
Window root_;
|
||||||
bool running_;
|
std::atomic<bool> running_;
|
||||||
|
std::thread event_thread_;
|
||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -874,11 +875,38 @@ int Render::AudioDeviceInit() {
|
|||||||
desired_out.format = SDL_AUDIO_S16;
|
desired_out.format = SDL_AUDIO_S16;
|
||||||
desired_out.channels = 1;
|
desired_out.channels = 1;
|
||||||
|
|
||||||
output_stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
auto open_stream = [&]() -> bool {
|
||||||
&desired_out, nullptr, nullptr);
|
output_stream_ = SDL_OpenAudioDeviceStream(
|
||||||
if (!output_stream_) {
|
SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired_out, nullptr, nullptr);
|
||||||
|
return output_stream_ != nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!open_stream()) {
|
||||||
|
#if defined(__linux__) && !defined(__APPLE__)
|
||||||
|
LOG_WARN(
|
||||||
|
"Failed to open output stream with driver [{}]: {}",
|
||||||
|
getenv("SDL_AUDIODRIVER") ? getenv("SDL_AUDIODRIVER") : "(default)",
|
||||||
|
SDL_GetError());
|
||||||
|
|
||||||
|
setenv("SDL_AUDIODRIVER", "dummy", 1);
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
|
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||||
|
LOG_ERROR("Failed to reinitialize SDL audio with dummy driver: {}",
|
||||||
|
SDL_GetError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open_stream()) {
|
||||||
|
LOG_ERROR("Failed to open output stream with dummy driver: {}",
|
||||||
|
SDL_GetError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARN("Audio output disabled, using SDL dummy audio driver");
|
||||||
|
#else
|
||||||
LOG_ERROR("Failed to open output stream: {}", SDL_GetError());
|
LOG_ERROR("Failed to open output stream: {}", SDL_GetError());
|
||||||
return -1;
|
return -1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(output_stream_));
|
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(output_stream_));
|
||||||
@@ -1246,11 +1274,11 @@ int Render::SetupFontAndStyle(ImFont** system_chinese_font_out) {
|
|||||||
"/System/Library/Fonts/SFNS.ttf", nullptr};
|
"/System/Library/Fonts/SFNS.ttf", nullptr};
|
||||||
#else
|
#else
|
||||||
const char* base_font_paths[] = {
|
const char* base_font_paths[] = {
|
||||||
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
||||||
"/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
|
"/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf",
|
||||||
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
"/usr/share/fonts/opentype/wqy/wqy-microhei.ttc",
|
||||||
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
"/usr/share/fonts/opentype/wqy/wqy-zenhei.ttc",
|
||||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
"/usr/share/fonts/opentype/dejavu/DejaVuSans.ttf",
|
||||||
nullptr};
|
nullptr};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1533,6 +1561,13 @@ void Render::InitializeSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Render::InitializeSDL() {
|
void Render::InitializeSDL() {
|
||||||
|
#if defined(__linux__) && !defined(__APPLE__)
|
||||||
|
if (!getenv("SDL_AUDIODRIVER")) {
|
||||||
|
// Prefer PulseAudio first on Linux to avoid hard ALSA plugin dependency.
|
||||||
|
setenv("SDL_AUDIODRIVER", "pulseaudio", 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
|
||||||
LOG_ERROR("Error: {}", SDL_GetError());
|
LOG_ERROR("Error: {}", SDL_GetError());
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ bool Render::OpenUrl(const std::string& url) {
|
|||||||
void Render::Hyperlink(const std::string& label, const std::string& url,
|
void Render::Hyperlink(const std::string& label, const std::string& url,
|
||||||
const float window_width) {
|
const float window_width) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 255, 255));
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 255, 255));
|
||||||
ImGui::SetCursorPosX((window_width - ImGui::CalcTextSize(label.c_str()).x) /
|
|
||||||
2.0f);
|
|
||||||
ImGui::TextUnformatted(label.c_str());
|
ImGui::TextUnformatted(label.c_str());
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
@@ -106,7 +104,7 @@ int Render::AboutWindow() {
|
|||||||
ImGui::SetCursorPosX(about_window_width * 0.1f);
|
ImGui::SetCursorPosX(about_window_width * 0.1f);
|
||||||
ImGui::Text("%s", text.c_str());
|
ImGui::Text("%s", text.c_str());
|
||||||
|
|
||||||
if (update_available_) {
|
if (0) {
|
||||||
std::string new_version_available =
|
std::string new_version_available =
|
||||||
localization::new_version_available[localization_language_index_] +
|
localization::new_version_available[localization_language_index_] +
|
||||||
": ";
|
": ";
|
||||||
@@ -114,10 +112,15 @@ int Render::AboutWindow() {
|
|||||||
ImGui::Text("%s", new_version_available.c_str());
|
ImGui::Text("%s", new_version_available.c_str());
|
||||||
std::string access_website =
|
std::string access_website =
|
||||||
localization::access_website[localization_language_index_];
|
localization::access_website[localization_language_index_];
|
||||||
|
ImGui::SetCursorPosX((about_window_width -
|
||||||
|
ImGui::CalcTextSize(latest_version_.c_str()).x) /
|
||||||
|
2.0f);
|
||||||
Hyperlink(latest_version_, "https://crossdesk.cn", about_window_width);
|
Hyperlink(latest_version_, "https://crossdesk.cn", about_window_width);
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Text("");
|
ImGui::Spacing();
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%s", "");
|
||||||
|
}
|
||||||
|
|
||||||
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
|
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
|
||||||
std::string license_text = "Licensed under GNU LGPL v3.";
|
std::string license_text = "Licensed under GNU LGPL v3.";
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ int Render::UpdateNotificationWindow() {
|
|||||||
localization::access_website[localization_language_index_] +
|
localization::access_website[localization_language_index_] +
|
||||||
"https://crossdesk.cn";
|
"https://crossdesk.cn";
|
||||||
ImGui::SetWindowFontScale(0.5f);
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
|
ImGui::SetCursorPosX(update_notification_window_width * 0.1f);
|
||||||
Hyperlink(download_text, "https://crossdesk.cn",
|
Hyperlink(download_text, "https://crossdesk.cn",
|
||||||
update_notification_window_width);
|
update_notification_window_width);
|
||||||
ImGui::SetWindowFontScale(1.0f);
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
@@ -204,4 +205,3 @@ int Render::UpdateNotificationWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,108 @@
|
|||||||
#include "screen_capturer_win.h"
|
#include "screen_capturer_win.h"
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "screen_capturer_dxgi.h"
|
#include "screen_capturer_dxgi.h"
|
||||||
#include "screen_capturer_gdi.h"
|
#include "screen_capturer_gdi.h"
|
||||||
#include "screen_capturer_wgc.h"
|
#include "wgc_plugin_api.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class WgcPluginCapturer final : public ScreenCapturer {
|
||||||
|
public:
|
||||||
|
using CreateFn = ScreenCapturer* (*)();
|
||||||
|
using DestroyFn = void (*)(ScreenCapturer*);
|
||||||
|
|
||||||
|
static std::unique_ptr<ScreenCapturer> Create() {
|
||||||
|
std::filesystem::path plugin_path;
|
||||||
|
wchar_t module_path[MAX_PATH] = {0};
|
||||||
|
const DWORD len = GetModuleFileNameW(nullptr, module_path, MAX_PATH);
|
||||||
|
if (len == 0 || len >= MAX_PATH) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
plugin_path =
|
||||||
|
std::filesystem::path(module_path).parent_path() / L"wgc_plugin.dll";
|
||||||
|
|
||||||
|
HMODULE module = LoadLibraryW(plugin_path.c_str());
|
||||||
|
if (!module) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto create_fn = reinterpret_cast<CreateFn>(
|
||||||
|
GetProcAddress(module, "CrossDeskCreateWgcCapturer"));
|
||||||
|
auto destroy_fn = reinterpret_cast<DestroyFn>(
|
||||||
|
GetProcAddress(module, "CrossDeskDestroyWgcCapturer"));
|
||||||
|
if (!create_fn || !destroy_fn) {
|
||||||
|
FreeLibrary(module);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenCapturer* impl = create_fn();
|
||||||
|
if (!impl) {
|
||||||
|
FreeLibrary(module);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unique_ptr<ScreenCapturer>(
|
||||||
|
new WgcPluginCapturer(module, impl, destroy_fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
~WgcPluginCapturer() override {
|
||||||
|
if (impl_) {
|
||||||
|
destroy_fn_(impl_);
|
||||||
|
impl_ = nullptr;
|
||||||
|
}
|
||||||
|
if (module_) {
|
||||||
|
FreeLibrary(module_);
|
||||||
|
module_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Init(const int fps, cb_desktop_data cb) override {
|
||||||
|
return impl_ ? impl_->Init(fps, std::move(cb)) : -1;
|
||||||
|
}
|
||||||
|
int Destroy() override { return impl_ ? impl_->Destroy() : 0; }
|
||||||
|
int Start(bool show_cursor) override {
|
||||||
|
return impl_ ? impl_->Start(show_cursor) : -1;
|
||||||
|
}
|
||||||
|
int Stop() override { return impl_ ? impl_->Stop() : 0; }
|
||||||
|
int Pause(int monitor_index) override {
|
||||||
|
return impl_ ? impl_->Pause(monitor_index) : -1;
|
||||||
|
}
|
||||||
|
int Resume(int monitor_index) override {
|
||||||
|
return impl_ ? impl_->Resume(monitor_index) : -1;
|
||||||
|
}
|
||||||
|
std::vector<DisplayInfo> GetDisplayInfoList() override {
|
||||||
|
return impl_ ? impl_->GetDisplayInfoList() : std::vector<DisplayInfo>{};
|
||||||
|
}
|
||||||
|
int SwitchTo(int monitor_index) override {
|
||||||
|
return impl_ ? impl_->SwitchTo(monitor_index) : -1;
|
||||||
|
}
|
||||||
|
int ResetToInitialMonitor() override {
|
||||||
|
return impl_ ? impl_->ResetToInitialMonitor() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
WgcPluginCapturer(HMODULE module, ScreenCapturer* impl, DestroyFn destroy_fn)
|
||||||
|
: module_(module), impl_(impl), destroy_fn_(destroy_fn) {}
|
||||||
|
|
||||||
|
HMODULE module_ = nullptr;
|
||||||
|
ScreenCapturer* impl_ = nullptr;
|
||||||
|
DestroyFn destroy_fn_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
ScreenCapturerWin::ScreenCapturerWin() {}
|
ScreenCapturerWin::ScreenCapturerWin() {}
|
||||||
ScreenCapturerWin::~ScreenCapturerWin() { Destroy(); }
|
ScreenCapturerWin::~ScreenCapturerWin() { Destroy(); }
|
||||||
|
|
||||||
@@ -40,18 +131,21 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
|
|||||||
|
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
|
|
||||||
impl_ = std::make_unique<ScreenCapturerWgc>();
|
impl_ = WgcPluginCapturer::Create();
|
||||||
ret = impl_->Init(fps_, cb_);
|
impl_is_wgc_plugin_ = (impl_ != nullptr);
|
||||||
|
ret = impl_ ? impl_->Init(fps_, cb_) : -1;
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
LOG_INFO("Windows capturer: using WGC");
|
LOG_INFO("Windows capturer: using WGC plugin");
|
||||||
BuildCanonicalFromImpl();
|
BuildCanonicalFromImpl();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_WARN("Windows capturer: WGC init failed (ret={}), try DXGI", ret);
|
LOG_WARN("Windows capturer: WGC plugin init failed (ret={}), try DXGI", ret);
|
||||||
impl_.reset();
|
impl_.reset();
|
||||||
|
impl_is_wgc_plugin_ = false;
|
||||||
|
|
||||||
impl_ = std::make_unique<ScreenCapturerDxgi>();
|
impl_ = std::make_unique<ScreenCapturerDxgi>();
|
||||||
|
impl_is_wgc_plugin_ = false;
|
||||||
ret = impl_->Init(fps_, cb_);
|
ret = impl_->Init(fps_, cb_);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
LOG_INFO("Windows capturer: using DXGI Desktop Duplication");
|
LOG_INFO("Windows capturer: using DXGI Desktop Duplication");
|
||||||
@@ -63,6 +157,7 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
|
|||||||
impl_.reset();
|
impl_.reset();
|
||||||
|
|
||||||
impl_ = std::make_unique<ScreenCapturerGdi>();
|
impl_ = std::make_unique<ScreenCapturerGdi>();
|
||||||
|
impl_is_wgc_plugin_ = false;
|
||||||
ret = impl_->Init(fps_, cb_);
|
ret = impl_->Init(fps_, cb_);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
LOG_INFO("Windows capturer: using GDI BitBlt");
|
LOG_INFO("Windows capturer: using GDI BitBlt");
|
||||||
@@ -79,6 +174,7 @@ int ScreenCapturerWin::Destroy() {
|
|||||||
if (impl_) {
|
if (impl_) {
|
||||||
impl_->Destroy();
|
impl_->Destroy();
|
||||||
impl_.reset();
|
impl_.reset();
|
||||||
|
impl_is_wgc_plugin_ = false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(alias_mutex_);
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
@@ -102,13 +198,14 @@ int ScreenCapturerWin::Start(bool show_cursor) {
|
|||||||
int s = cand->Start(show_cursor);
|
int s = cand->Start(show_cursor);
|
||||||
if (s == 0) {
|
if (s == 0) {
|
||||||
impl_ = std::move(cand);
|
impl_ = std::move(cand);
|
||||||
|
impl_is_wgc_plugin_ = false;
|
||||||
RebuildAliasesFromImpl();
|
RebuildAliasesFromImpl();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dynamic_cast<ScreenCapturerWgc*>(impl_.get())) {
|
if (impl_is_wgc_plugin_) {
|
||||||
if (try_init_start(std::make_unique<ScreenCapturerDxgi>())) {
|
if (try_init_start(std::make_unique<ScreenCapturerDxgi>())) {
|
||||||
LOG_INFO("Windows capturer: fallback to DXGI");
|
LOG_INFO("Windows capturer: fallback to DXGI");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class ScreenCapturerWin : public ScreenCapturer {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<ScreenCapturer> impl_;
|
std::unique_ptr<ScreenCapturer> impl_;
|
||||||
|
bool impl_is_wgc_plugin_ = false;
|
||||||
int fps_ = 60;
|
int fps_ = 60;
|
||||||
cb_desktop_data cb_;
|
cb_desktop_data cb_;
|
||||||
cb_desktop_data cb_orig_;
|
cb_desktop_data cb_orig_;
|
||||||
|
|||||||
29
src/screen_capturer/windows/wgc_plugin_api.h
Normal file
29
src/screen_capturer/windows/wgc_plugin_api.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-03-20
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _WGC_PLUGIN_API_H_
|
||||||
|
#define _WGC_PLUGIN_API_H_
|
||||||
|
|
||||||
|
#include "screen_capturer.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
class ScreenCapturer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32) && defined(CROSSDESK_WGC_PLUGIN_BUILD)
|
||||||
|
#define CROSSDESK_WGC_PLUGIN_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define CROSSDESK_WGC_PLUGIN_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
CROSSDESK_WGC_PLUGIN_API crossdesk::ScreenCapturer*
|
||||||
|
CrossDeskCreateWgcCapturer();
|
||||||
|
CROSSDESK_WGC_PLUGIN_API void CrossDeskDestroyWgcCapturer(
|
||||||
|
crossdesk::ScreenCapturer* capturer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
13
src/screen_capturer/windows/wgc_plugin_entry.cpp
Normal file
13
src/screen_capturer/windows/wgc_plugin_entry.cpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include "screen_capturer_wgc.h"
|
||||||
|
#include "wgc_plugin_api.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
crossdesk::ScreenCapturer* CrossDeskCreateWgcCapturer() {
|
||||||
|
return new crossdesk::ScreenCapturerWgc();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrossDeskDestroyWgcCapturer(crossdesk::ScreenCapturer* capturer) {
|
||||||
|
delete capturer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,13 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
|
||||||
#define CHECK_INIT \
|
#define CHECK_INIT \
|
||||||
if (!is_initialized_) { \
|
if (!is_initialized_) { \
|
||||||
std::cout << "AE_NEED_INIT" << std::endl; \
|
LOG_ERROR("AE_NEED_INIT"); \
|
||||||
return 4; \
|
return 4; \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +323,7 @@ int WgcSessionImpl::Initialize() {
|
|||||||
if (is_initialized_) return 0;
|
if (is_initialized_) return 0;
|
||||||
|
|
||||||
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
|
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
|
||||||
std::cout << "AE_D3D_CREATE_DEVICE_FAILED" << std::endl;
|
LOG_ERROR("AE_D3D_CREATE_DEVICE_FAILED");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -452,12 +452,18 @@ static void MonitorThreadFunc() {
|
|||||||
LOG_INFO("Clipboard event monitoring started (Linux XFixes)");
|
LOG_INFO("Clipboard event monitoring started (Linux XFixes)");
|
||||||
|
|
||||||
XEvent event;
|
XEvent event;
|
||||||
|
constexpr int kEventPollIntervalMs = 20;
|
||||||
while (g_monitoring.load()) {
|
while (g_monitoring.load()) {
|
||||||
|
// Avoid blocking on XNextEvent so StopMonitoring() can stop quickly.
|
||||||
|
while (g_monitoring.load() && XPending(g_x11_display) > 0) {
|
||||||
XNextEvent(g_x11_display, &event);
|
XNextEvent(g_x11_display, &event);
|
||||||
if (event.type == event_base + XFixesSelectionNotify) {
|
if (event.type == event_base + XFixesSelectionNotify) {
|
||||||
HandleClipboardChange();
|
HandleClipboardChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(kEventPollIntervalMs));
|
||||||
|
}
|
||||||
|
|
||||||
XFixesSelectSelectionInput(g_x11_display, event_window, g_clipboard_atom, 0);
|
XFixesSelectSelectionInput(g_x11_display, event_window, g_clipboard_atom, 0);
|
||||||
XDestroyWindow(g_x11_display, event_window);
|
XDestroyWindow(g_x11_display, event_window);
|
||||||
|
|||||||
Submodule submodules/minirtc updated: 03a6449a11...e76c7809c0
22
xmake.lua
22
xmake.lua
@@ -28,6 +28,7 @@ if is_mode("debug") then
|
|||||||
add_defines("CROSSDESK_DEBUG")
|
add_defines("CROSSDESK_DEBUG")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_requireconfs("*.python", {version = "3.12", override = true, configs = {pgo = false}})
|
||||||
add_requires("spdlog 1.14.1", {system = false})
|
add_requires("spdlog 1.14.1", {system = false})
|
||||||
add_requires("imgui v1.92.1-docking", {configs = {sdl3 = true, sdl3_renderer = true}})
|
add_requires("imgui v1.92.1-docking", {configs = {sdl3 = true, sdl3_renderer = true}})
|
||||||
add_requires("openssl3 3.3.2", {system = false})
|
add_requires("openssl3 3.3.2", {system = false})
|
||||||
@@ -37,7 +38,7 @@ add_requires("tinyfiledialogs 3.15.1")
|
|||||||
|
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
add_requires("libyuv", "miniaudio 0.11.21")
|
add_requires("libyuv", "miniaudio 0.11.21")
|
||||||
add_links("Shell32", "windowsapp", "dwmapi", "User32", "kernel32",
|
add_links("Shell32", "dwmapi", "User32", "kernel32",
|
||||||
"SDL3-static", "gdi32", "winmm", "setupapi", "version",
|
"SDL3-static", "gdi32", "winmm", "setupapi", "version",
|
||||||
"Imm32", "iphlpapi", "d3d11", "dxgi")
|
"Imm32", "iphlpapi", "d3d11", "dxgi")
|
||||||
add_cxflags("/WX")
|
add_cxflags("/WX")
|
||||||
@@ -88,7 +89,9 @@ target("screen_capturer")
|
|||||||
add_includedirs("src/screen_capturer", {public = true})
|
add_includedirs("src/screen_capturer", {public = true})
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
add_packages("libyuv")
|
add_packages("libyuv")
|
||||||
add_files("src/screen_capturer/windows/*.cpp")
|
add_files("src/screen_capturer/windows/screen_capturer_dxgi.cpp",
|
||||||
|
"src/screen_capturer/windows/screen_capturer_gdi.cpp",
|
||||||
|
"src/screen_capturer/windows/screen_capturer_win.cpp")
|
||||||
add_includedirs("src/screen_capturer/windows", {public = true})
|
add_includedirs("src/screen_capturer/windows", {public = true})
|
||||||
elseif is_os("macosx") then
|
elseif is_os("macosx") then
|
||||||
add_files("src/screen_capturer/macosx/*.cpp",
|
add_files("src/screen_capturer/macosx/*.cpp",
|
||||||
@@ -199,11 +202,26 @@ target("gui")
|
|||||||
add_files("src/gui/windows/*.mm")
|
add_files("src/gui/windows/*.mm")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if is_os("windows") then
|
||||||
|
target("wgc_plugin")
|
||||||
|
set_kind("shared")
|
||||||
|
add_packages("libyuv")
|
||||||
|
add_deps("rd_log")
|
||||||
|
add_defines("CROSSDESK_WGC_PLUGIN_BUILD=1")
|
||||||
|
add_links("windowsapp")
|
||||||
|
add_files("src/screen_capturer/windows/screen_capturer_wgc.cpp",
|
||||||
|
"src/screen_capturer/windows/wgc_session_impl.cpp",
|
||||||
|
"src/screen_capturer/windows/wgc_plugin_entry.cpp")
|
||||||
|
add_includedirs("src/common", "src/screen_capturer",
|
||||||
|
"src/screen_capturer/windows")
|
||||||
|
end
|
||||||
|
|
||||||
target("crossdesk")
|
target("crossdesk")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_deps("rd_log", "common", "gui")
|
add_deps("rd_log", "common", "gui")
|
||||||
add_files("src/app/*.cpp")
|
add_files("src/app/*.cpp")
|
||||||
add_includedirs("src/app", {public = true})
|
add_includedirs("src/app", {public = true})
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
|
add_deps("wgc_plugin")
|
||||||
add_files("scripts/windows/crossdesk.rc")
|
add_files("scripts/windows/crossdesk.rc")
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user