Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
121 changes: 121 additions & 0 deletions src/platform/linux/menu_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include <iostream>
#include <string>
#include <gtk/gtk.h>
#include "../../menu.h"

namespace nativeapi {

// Private implementation class for MenuItem
class MenuItem::Impl {
public:
Impl(GtkWidget* menu_item) : gtk_menu_item_(menu_item), title_(""), icon_(""), tooltip_("") {}

GtkWidget* gtk_menu_item_;
std::string title_;
std::string icon_;
std::string tooltip_;
};

MenuItem::MenuItem() : pimpl_(new Impl(nullptr)) {
id = -1;
}

MenuItem::MenuItem(void* menu_item) : pimpl_(new Impl((GtkWidget*)menu_item)) {
id = -1;
}

MenuItem::~MenuItem() {
delete pimpl_;
}

void MenuItem::SetIcon(std::string icon) {
pimpl_->icon_ = icon;
// TODO: Implement icon setting for GTK menu item
}

std::string MenuItem::GetIcon() {
return pimpl_->icon_;
}

void MenuItem::SetTitle(std::string title) {
pimpl_->title_ = title;
if (pimpl_->gtk_menu_item_) {
gtk_menu_item_set_label(GTK_MENU_ITEM(pimpl_->gtk_menu_item_), title.c_str());
}
}

std::string MenuItem::GetTitle() {
return pimpl_->title_;
}

void MenuItem::SetTooltip(std::string tooltip) {
pimpl_->tooltip_ = tooltip;
if (pimpl_->gtk_menu_item_) {
gtk_widget_set_tooltip_text(pimpl_->gtk_menu_item_, tooltip.c_str());
}
}

std::string MenuItem::GetTooltip() {
return pimpl_->tooltip_;
}

// Private implementation class for Menu
class Menu::Impl {
public:
Impl(GtkWidget* menu) : gtk_menu_(menu) {}

GtkWidget* gtk_menu_;
};

Menu::Menu() : pimpl_(new Impl(gtk_menu_new())) {
id = -1;
}

Menu::Menu(void* menu) : pimpl_(new Impl((GtkWidget*)menu)) {
id = -1;
}

Menu::~Menu() {
if (pimpl_->gtk_menu_) {
g_object_unref(pimpl_->gtk_menu_);
}
delete pimpl_;
}

void Menu::AddItem(MenuItem item) {
if (pimpl_->gtk_menu_ && item.pimpl_->gtk_menu_item_) {
gtk_menu_shell_append(GTK_MENU_SHELL(pimpl_->gtk_menu_), item.pimpl_->gtk_menu_item_);
}
}

void Menu::RemoveItem(MenuItem item) {
if (pimpl_->gtk_menu_ && item.pimpl_->gtk_menu_item_) {
gtk_container_remove(GTK_CONTAINER(pimpl_->gtk_menu_), item.pimpl_->gtk_menu_item_);
}
}

void Menu::AddSeparator() {
if (pimpl_->gtk_menu_) {
GtkWidget* separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(pimpl_->gtk_menu_), separator);
}
}

MenuItem Menu::CreateItem(std::string title) {
GtkWidget* menu_item = gtk_menu_item_new_with_label(title.c_str());
MenuItem item(menu_item);
item.SetTitle(title);
return item;
}

MenuItem Menu::CreateItem(std::string title, std::string icon) {
MenuItem item = CreateItem(title);
item.SetIcon(icon);
return item;
}

void* Menu::GetNativeMenu() {
return (void*)pimpl_->gtk_menu_;
}

} // namespace nativeapi
145 changes: 145 additions & 0 deletions src/platform/linux/tray_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include <iostream>
#include <string>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "../../menu.h"
#include "../../tray.h"

namespace nativeapi {

// Private implementation class
class Tray::Impl {
public:
Impl(GtkStatusIcon* tray) : gtk_status_icon_(tray), title_(""), tooltip_("") {}

GtkStatusIcon* gtk_status_icon_;
Menu context_menu_; // Store menu object to keep it alive
std::string title_; // GTK StatusIcon doesn't have title, so we store it
std::string tooltip_;
};

Tray::Tray() : pimpl_(new Impl(nullptr)) {
id = -1;
}

Tray::Tray(void* tray) : pimpl_(new Impl((GtkStatusIcon*)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);
}
}

Tray::~Tray() {
if (pimpl_->gtk_status_icon_) {
g_object_unref(pimpl_->gtk_status_icon_);
}
delete pimpl_;
}

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

// Check if the icon is a base64 string
if (icon.find("data:image") != std::string::npos) {
// Extract the base64 part
size_t pos = icon.find("base64,");
if (pos != std::string::npos) {
std::string base64Icon = icon.substr(pos + 7);

// Decode base64 data
gsize decoded_len;
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);
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);
} else if (error) {
std::cerr << "Error loading icon from base64: " << error->message << std::endl;
g_error_free(error);
}

g_object_unref(stream);
}
}
} 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());
} else {
// Try as a stock icon name
gtk_status_icon_set_from_icon_name(pimpl_->gtk_status_icon_, icon.c_str());
}
}
}

void Tray::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
}

std::string Tray::GetTitle() {
return pimpl_->title_;
}

void Tray::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());
}
}

std::string Tray::GetTooltip() {
return pimpl_->tooltip_;
}

void Tray::SetContextMenu(Menu menu) {
// Store the menu object 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 GetNativeMenu()
}

Menu Tray::GetContextMenu() {
return pimpl_->context_menu_;
}

Rectangle Tray::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;
}
}

return bounds;
}

} // namespace nativeapi
42 changes: 42 additions & 0 deletions src/platform/linux/tray_manager_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <cstring>
#include <iostream>
#include <string>

#include "../../tray.h"
#include "../../tray_manager.h"

// Import GTK headers
#include <gtk/gtk.h>

namespace nativeapi {

TrayManager::TrayManager() : next_tray_id_(1) {}

TrayManager::~TrayManager() {}

std::shared_ptr<Tray> TrayManager::Create() {
// Create a new tray using GTK StatusIcon
GtkStatusIcon* status_icon = gtk_status_icon_new();
auto tray = std::make_shared<Tray>((void*)status_icon);
tray->id = next_tray_id_++;
trays_[tray->id] = tray;
return tray;
}

std::shared_ptr<Tray> TrayManager::Get(TrayID id) {
auto it = trays_.find(id);
if (it != trays_.end()) {
return it->second;
}
return nullptr;
}

std::vector<std::shared_ptr<Tray>> TrayManager::GetAll() {
std::vector<std::shared_ptr<Tray>> trays;
for (auto& tray : trays_) {
trays.push_back(tray.second);
}
return trays;
}

} // namespace nativeapi