[feat] support minimizing to the system tray on Linux when closing

This commit is contained in:
dijunkun
2026-06-23 17:05:14 +08:00
parent e026491b9f
commit d8e9fa5bba
9 changed files with 1171 additions and 23 deletions
+5 -1
View File
@@ -128,7 +128,11 @@ bool Daemon::start(MainLoopFunc loop) {
if (pid > 0) _exit(0);
umask(0);
chdir("/");
if (chdir("/") != 0) {
std::cerr << "Failed to change daemon working directory to /: "
<< std::strerror(errno) << std::endl;
return false;
}
// redirect file descriptors: keep stdout/stderr if from terminal, else
// redirect to /dev/null
+88 -4
View File
@@ -1374,6 +1374,10 @@ int Render::CreateMainWindow() {
#elif defined(__APPLE__)
tray_ = std::make_unique<MacTray>(main_window_, "CrossDesk",
localization_language_index_);
#elif defined(__linux__) && !defined(__APPLE__)
tray_ = std::make_unique<LinuxTray>(main_window_, "CrossDesk",
localization_language_index_,
APP_EXIT_EVENT);
#endif
ImGui_ImplSDL3_InitForSDLRenderer(main_window_, main_renderer_);
@@ -1673,20 +1677,47 @@ int Render::SetupFontAndStyle(ImFont** system_chinese_font_out) {
}
int Render::DestroyMainWindowContext() {
if (!main_ctx_) {
return 0;
}
ImGui::SetCurrentContext(main_ctx_);
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(main_ctx_);
main_ctx_ = nullptr;
return 0;
}
int Render::DestroyStreamWindowContext() {
if (!stream_ctx_) {
stream_window_inited_ = false;
return 0;
}
stream_window_inited_ = false;
ImGui::SetCurrentContext(stream_ctx_);
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(stream_ctx_);
stream_ctx_ = nullptr;
return 0;
}
int Render::DestroyServerWindowContext() {
if (!server_ctx_) {
server_window_inited_ = false;
return 0;
}
server_window_inited_ = false;
ImGui::SetCurrentContext(server_ctx_);
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext(server_ctx_);
server_ctx_ = nullptr;
return 0;
}
@@ -1955,9 +1986,12 @@ void Render::InitializeSDL() {
screen_height_ = dm->h;
}
STREAM_REFRESH_EVENT = SDL_RegisterEvents(1);
if (STREAM_REFRESH_EVENT == (uint32_t)-1) {
LOG_ERROR("Failed to register custom SDL event");
const uint32_t custom_event_base = SDL_RegisterEvents(2);
if (custom_event_base == static_cast<uint32_t>(-1)) {
LOG_ERROR("Failed to register custom SDL events");
} else {
STREAM_REFRESH_EVENT = custom_event_base;
APP_EXIT_EVENT = custom_event_base + 1;
}
LOG_INFO("Screen resolution: [{}x{}]", screen_width_, screen_height_);
@@ -2031,6 +2065,10 @@ void Render::MainLoop() {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
#elif defined(__linux__) && !defined(__APPLE__)
if (tray_) {
tray_->ProcessEvents();
}
#endif
UpdateLabels();
@@ -2041,7 +2079,11 @@ void Render::MainLoop() {
HandleServerWindow();
HandleWindowsServiceIntegration();
DrawMainWindow();
const bool main_window_visible =
main_window_ && !(SDL_GetWindowFlags(main_window_) & SDL_WINDOW_HIDDEN);
if (main_window_visible) {
DrawMainWindow();
}
if (stream_window_inited_) {
DrawStreamWindow();
}
@@ -2066,6 +2108,12 @@ bool Render::MinimizeMainWindowToTray() {
tray_->MinimizeToTray();
return true;
#elif defined(__linux__) && !defined(__APPLE__)
if (!tray_) {
return false;
}
return tray_->MinimizeToTray();
#else
return false;
#endif
@@ -2407,6 +2455,7 @@ void Render::HandleServerWindow() {
if (need_to_destroy_server_window_) {
DestroyServerWindow();
DestroyServerWindowContext();
need_to_destroy_server_window_ = false;
}
}
@@ -2447,6 +2496,27 @@ void Render::Cleanup() {
WaitForThumbnailSaveTasks();
AudioDeviceDestroy();
#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__)
tray_.reset();
#endif
if (stream_window_created_) {
if (stream_window_) {
SDL_SetWindowMouseGrab(stream_window_, false);
}
DestroyStreamWindow();
}
if (stream_ctx_) {
DestroyStreamWindowContext();
}
if (server_window_created_) {
DestroyServerWindow();
}
if (server_ctx_) {
DestroyServerWindowContext();
}
DestroyMainWindowContext();
DestroyMainWindow();
SDL_Quit();
@@ -2860,6 +2930,20 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
}
}
if (APP_EXIT_EVENT != 0 && event.type == APP_EXIT_EVENT) {
LOG_INFO("Quit program from system tray");
if (stream_window_) {
SDL_SetWindowMouseGrab(stream_window_, false);
}
#if defined(__linux__) && !defined(__APPLE__)
if (tray_) {
tray_->RemoveTrayIcon();
}
#endif
exit_ = true;
return;
}
switch (event.type) {
case SDL_EVENT_QUIT:
if (stream_window_inited_) {
+5
View File
@@ -41,6 +41,8 @@
#include "win_tray.h"
#elif defined(__APPLE__)
#include "mac_tray.h"
#elif defined(__linux__)
#include "linux_tray.h"
#endif
namespace crossdesk {
@@ -515,6 +517,8 @@ class Render {
std::unique_ptr<WinTray> tray_;
#elif defined(__APPLE__)
std::unique_ptr<MacTray> tray_;
#elif defined(__linux__)
std::unique_ptr<LinuxTray> tray_;
#endif
// main window properties
@@ -596,6 +600,7 @@ class Render {
SDL_Event last_mouse_event{};
SDL_AudioStream* output_stream_ = nullptr;
uint32_t STREAM_REFRESH_EVENT = 0;
uint32_t APP_EXIT_EVENT = 0;
#if _WIN32
std::atomic<bool> pending_windows_service_sas_{false};
bool local_service_status_received_ = false;
+5 -6
View File
@@ -300,13 +300,12 @@ int Render::TitleBar(bool main_window) {
}
if (close_button_clicked) {
if (main_window && MinimizeMainWindowToTray()) {
return 0;
const bool minimized_to_tray = main_window && MinimizeMainWindowToTray();
if (!minimized_to_tray) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-06-23
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _LINUX_TRAY_H_
#define _LINUX_TRAY_H_
#if defined(__linux__) && !defined(__APPLE__)
#include <cstdint>
#include <memory>
#include <string>
struct SDL_Window;
namespace crossdesk {
struct LinuxTrayImpl;
class LinuxTray {
public:
LinuxTray(::SDL_Window* app_window, const std::string& tooltip,
int language_index, uint32_t exit_event_type);
~LinuxTray();
bool MinimizeToTray();
void RemoveTrayIcon();
void ProcessEvents();
private:
std::unique_ptr<LinuxTrayImpl> impl_;
};
} // namespace crossdesk
#endif // defined(__linux__) && !defined(__APPLE__)
#endif // _LINUX_TRAY_H_
-10
View File
@@ -356,10 +356,6 @@ int Render::SettingWindow() {
ImGui::Separator();
{
#if !defined(_WIN32) && !defined(__APPLE__)
ImGui::BeginDisabled();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
#endif
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
@@ -375,10 +371,6 @@ int Render::SettingWindow() {
ImGui::Checkbox("##enable_minimize_to_tray_",
&enable_minimize_to_tray_);
#if !defined(_WIN32) && !defined(__APPLE__)
ImGui::PopStyleColor();
ImGui::EndDisabled();
#endif
}
ImGui::Separator();
@@ -626,14 +618,12 @@ int Render::SettingWindow() {
}
enable_daemon_last_ = enable_daemon_;
#if defined(_WIN32) || defined(__APPLE__)
if (enable_minimize_to_tray_) {
config_center_->SetMinimizeToTray(true);
} else {
config_center_->SetMinimizeToTray(false);
}
enable_minimize_to_tray_last_ = enable_minimize_to_tray_;
#endif
// File transfer save path
config_center_->SetFileTransferSavePath(file_transfer_save_path_buf_);