Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
pkg_check_modules(XI REQUIRED IMPORTED_TARGET xi)
pkg_check_modules(AYATANA_APPINDICATOR REQUIRED IMPORTED_TARGET ayatana-appindicator3-0.1)
elseif(APPLE)
file(GLOB PLATFORM_SOURCES "platform/macos/*_macos.mm" "platform/macos/display_macos.mm")
elseif(WIN32)
Expand Down Expand Up @@ -64,7 +65,7 @@ if(APPLE)
target_link_libraries(nativeapi PUBLIC "-framework Cocoa")
target_compile_options(nativeapi PRIVATE "-x" "objective-c++")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(nativeapi PUBLIC PkgConfig::GTK PkgConfig::X11 PkgConfig::XI pthread)
target_link_libraries(nativeapi PUBLIC PkgConfig::GTK PkgConfig::X11 PkgConfig::XI PkgConfig::AYATANA_APPINDICATOR pthread)
elseif(WIN32)
target_link_libraries(nativeapi PUBLIC user32 shell32 dwmapi)
endif ()
119 changes: 58 additions & 61 deletions src/platform/linux/tray_icon_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libayatana-appindicator/app-indicator.h>
#include "../../menu.h"
#include "../../tray_icon.h"
#include "../../tray_icon_event.h"
Expand All @@ -12,41 +13,42 @@ namespace nativeapi {
// Private implementation class
class TrayIcon::Impl {
public:
Impl(GtkStatusIcon* tray) : gtk_status_icon_(tray), title_(""), tooltip_(""), context_menu_(nullptr) {}
Impl(AppIndicator* indicator) : app_indicator_(indicator), title_(""), tooltip_(""), context_menu_(nullptr), visible_(false) {}

GtkStatusIcon* gtk_status_icon_;
AppIndicator* app_indicator_;
std::shared_ptr<Menu> context_menu_; // Store menu shared_ptr to keep it alive
std::string title_; // GTK StatusIcon doesn't have title, so we store it
std::string title_;
std::string tooltip_;
bool visible_;
};

TrayIcon::TrayIcon() : pimpl_(std::make_unique<Impl>(nullptr)) {
id = -1;
}

TrayIcon::TrayIcon(void* tray) : pimpl_(std::make_unique<Impl>((GtkStatusIcon*)tray)) {
TrayIcon::TrayIcon(void* tray) : pimpl_(std::make_unique<Impl>((AppIndicator*)tray)) {
id = -1; // Will be set by TrayManager when created
// Make the status icon visible
if (pimpl_->gtk_status_icon_) {
gtk_status_icon_set_visible(pimpl_->gtk_status_icon_, TRUE);
// Make the indicator visible by default
if (pimpl_->app_indicator_) {
pimpl_->visible_ = true;
}
}

TrayIcon::~TrayIcon() {
if (pimpl_->gtk_status_icon_) {
g_object_unref(pimpl_->gtk_status_icon_);
if (pimpl_->app_indicator_) {
g_object_unref(pimpl_->app_indicator_);
}

}

void TrayIcon::SetIcon(std::string icon) {
if (!pimpl_->gtk_status_icon_) {
if (!pimpl_->app_indicator_) {
return;
}

// Check if the icon is a base64 string
if (icon.find("data:image") != std::string::npos) {
// Extract the base64 part
// For base64 images, we need to save them to a temporary file
// since AppIndicator expects file paths or stock icon names
size_t pos = icon.find("base64,");
if (pos != std::string::npos) {
std::string base64Icon = icon.substr(pos + 7);
Expand All @@ -56,45 +58,40 @@ void TrayIcon::SetIcon(std::string icon) {
guchar* decoded_data = g_base64_decode(base64Icon.c_str(), &decoded_len);

if (decoded_data) {
// Create pixbuf from decoded data
GInputStream* stream = g_memory_input_stream_new_from_data(
decoded_data, decoded_len, g_free);
// Create a temporary file path
const char* temp_dir = g_get_tmp_dir();
std::string temp_path = std::string(temp_dir) + "/nativeapi_tray_icon_" + std::to_string(id) + ".png";

// Write to file
GError* error = nullptr;
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_stream(stream, nullptr, &error);

if (pixbuf && !error) {
// Scale pixbuf to appropriate size (24x24 is common for tray icons)
GdkPixbuf* scaled_pixbuf = gdk_pixbuf_scale_simple(
pixbuf, 24, 24, GDK_INTERP_BILINEAR);

gtk_status_icon_set_from_pixbuf(pimpl_->gtk_status_icon_, scaled_pixbuf);

g_object_unref(scaled_pixbuf);
g_object_unref(pixbuf);
if (g_file_set_contents(temp_path.c_str(), (const gchar*)decoded_data, decoded_len, &error)) {
app_indicator_set_icon_full(pimpl_->app_indicator_, temp_path.c_str(), "Tray Icon");
} else if (error) {
std::cerr << "Error loading icon from base64: " << error->message << std::endl;
std::cerr << "Error saving icon to temp file: " << error->message << std::endl;
g_error_free(error);
}

g_object_unref(stream);
g_free(decoded_data);
}
}
} else {
// Use the icon as a file path or stock icon name
if (g_file_test(icon.c_str(), G_FILE_TEST_EXISTS)) {
// It's a file path
gtk_status_icon_set_from_file(pimpl_->gtk_status_icon_, icon.c_str());
app_indicator_set_icon_full(pimpl_->app_indicator_, icon.c_str(), "Tray Icon");
} else {
// Try as a stock icon name
gtk_status_icon_set_from_icon_name(pimpl_->gtk_status_icon_, icon.c_str());
app_indicator_set_icon_full(pimpl_->app_indicator_, icon.c_str(), "Tray Icon");
}
}
}

void TrayIcon::SetTitle(std::string title) {
pimpl_->title_ = title;
// GTK StatusIcon doesn't support title directly, so we just store it
// Some desktop environments might show this in tooltips or context
// AppIndicator uses the title as the accessible name and in some desktop environments
if (pimpl_->app_indicator_) {
app_indicator_set_title(pimpl_->app_indicator_, title.c_str());
}
}

std::string TrayIcon::GetTitle() {
Expand All @@ -103,9 +100,9 @@ std::string TrayIcon::GetTitle() {

void TrayIcon::SetTooltip(std::string tooltip) {
pimpl_->tooltip_ = tooltip;
if (pimpl_->gtk_status_icon_) {
gtk_status_icon_set_tooltip_text(pimpl_->gtk_status_icon_, tooltip.c_str());
}
// AppIndicator doesn't have direct tooltip support like GtkStatusIcon
// The tooltip functionality is typically handled through the title
// or through custom menu items. We'll store it for potential future use.
}

std::string TrayIcon::GetTooltip() {
Expand All @@ -116,8 +113,11 @@ void TrayIcon::SetContextMenu(std::shared_ptr<Menu> menu) {
// Store the menu shared_ptr to keep it alive
pimpl_->context_menu_ = menu;

// Note: Full GTK integration would need to connect popup-menu signal
// and show the GTK menu from the Menu object's GetNativeObject()
// AppIndicator requires a menu to be set
if (pimpl_->app_indicator_ && menu && menu->GetNativeObject()) {
GtkMenu* gtk_menu = static_cast<GtkMenu*>(menu->GetNativeObject());
app_indicator_set_menu(pimpl_->app_indicator_, gtk_menu);
}
}

std::shared_ptr<Menu> TrayIcon::GetContextMenu() {
Expand All @@ -127,41 +127,37 @@ std::shared_ptr<Menu> TrayIcon::GetContextMenu() {
Rectangle TrayIcon::GetBounds() {
Rectangle bounds = {0, 0, 0, 0};

if (pimpl_->gtk_status_icon_) {
GdkScreen* screen;
GdkRectangle area;
GtkOrientation orientation;

if (gtk_status_icon_get_geometry(pimpl_->gtk_status_icon_, &screen, &area, &orientation)) {
bounds.x = area.x;
bounds.y = area.y;
bounds.width = area.width;
bounds.height = area.height;
}
}
// AppIndicator doesn't provide geometry information like GtkStatusIcon did
// This is a limitation of the AppIndicator API as it's handled by the
// system tray implementation. We return empty bounds.
// In most modern desktop environments, this information isn't available
// to applications for security reasons.

return bounds;
}

bool TrayIcon::Show() {
if (pimpl_->gtk_status_icon_) {
gtk_status_icon_set_visible(pimpl_->gtk_status_icon_, TRUE);
if (pimpl_->app_indicator_) {
app_indicator_set_status(pimpl_->app_indicator_, APP_INDICATOR_STATUS_ACTIVE);
pimpl_->visible_ = true;
return true;
}
return false;
}

bool TrayIcon::Hide() {
if (pimpl_->gtk_status_icon_) {
gtk_status_icon_set_visible(pimpl_->gtk_status_icon_, FALSE);
if (pimpl_->app_indicator_) {
app_indicator_set_status(pimpl_->app_indicator_, APP_INDICATOR_STATUS_PASSIVE);
pimpl_->visible_ = false;
return true;
}
return false;
}

bool TrayIcon::IsVisible() {
if (pimpl_->gtk_status_icon_) {
return gtk_status_icon_get_visible(pimpl_->gtk_status_icon_) == TRUE;
if (pimpl_->app_indicator_) {
AppIndicatorStatus status = app_indicator_get_status(pimpl_->app_indicator_);
return status == APP_INDICATOR_STATUS_ACTIVE;
}
return false;
}
Expand All @@ -171,19 +167,20 @@ bool TrayIcon::ShowContextMenu(double x, double y) {
return false;
}

// Note: GTK implementation would need to show the menu at the specified coordinates
// This is a simplified implementation
return false;
// AppIndicator shows context menu automatically on right-click
// We don't need to manually show it at specific coordinates
// The menu is managed by the indicator framework
return true;
}

bool TrayIcon::ShowContextMenu() {
if (!pimpl_->context_menu_ || !pimpl_->context_menu_->GetNativeObject()) {
return false;
}

// Note: GTK implementation would need to show the menu at cursor position
// This is a simplified implementation
return false;
// AppIndicator shows context menu automatically on right-click
// We don't need to manually show it as it's managed by the indicator framework
return true;
}

// Internal method to handle click events
Expand Down
42 changes: 31 additions & 11 deletions src/platform/linux/tray_manager_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "../../tray_icon.h"
#include "../../tray_manager.h"

// Import GTK headers - these may not be available in all build environments
// Import headers
#ifdef __has_include
#if __has_include(<gtk/gtk.h>)
#include <gtk/gtk.h>
Expand All @@ -17,6 +17,18 @@
#define HAS_GTK 0
#endif

#ifdef __has_include
#if __has_include(<libayatana-appindicator/app-indicator.h>)
#include <libayatana-appindicator/app-indicator.h>
#define HAS_AYATANA_APPINDICATOR 1
#else
#define HAS_AYATANA_APPINDICATOR 0
#endif
#else
// Fallback for older compilers
#define HAS_AYATANA_APPINDICATOR 0
#endif

namespace nativeapi {

class TrayManager::Impl {
Expand All @@ -33,39 +45,47 @@ TrayManager::~TrayManager() {
for (auto& pair : trays_) {
auto tray = pair.second;
if (tray) {
// The TrayIcon destructor will handle cleanup of the GtkStatusIcon
// The TrayIcon destructor will handle cleanup of the AppIndicator
}
}
trays_.clear();
}

bool TrayManager::IsSupported() {
#if HAS_GTK
// Check if GTK is initialized and system tray is supported
#if HAS_GTK && HAS_AYATANA_APPINDICATOR
// Check if GTK is initialized and AppIndicator is available
return gtk_init_check(nullptr, nullptr);
#else
// If GTK is not available, assume no system tray support
// If GTK or AppIndicator is not available, assume no system tray support
return false;
#endif
}

std::shared_ptr<TrayIcon> TrayManager::Create() {
std::lock_guard<std::mutex> lock(mutex_);

#if HAS_GTK
// Create a new tray using GTK StatusIcon
GtkStatusIcon* status_icon = gtk_status_icon_new();
if (!status_icon) {
#if HAS_GTK && HAS_AYATANA_APPINDICATOR
// Create a unique ID for this tray icon
std::string indicator_id = "nativeapi-tray-" + std::to_string(next_tray_id_);

// Create a new tray using AppIndicator
AppIndicator* app_indicator = app_indicator_new(
indicator_id.c_str(),
"application-default-icon", // Default icon name
APP_INDICATOR_CATEGORY_APPLICATION_STATUS
);

if (!app_indicator) {
return nullptr;
}

auto tray = std::make_shared<TrayIcon>((void*)status_icon);
auto tray = std::make_shared<TrayIcon>((void*)app_indicator);
tray->id = next_tray_id_++;
trays_[tray->id] = tray;

return tray;
#else
// GTK not available, return nullptr
// AppIndicator not available, return nullptr
return nullptr;
#endif
}
Expand Down
Loading