Skip to content

Commit 498f23b

Browse files
authored
Merge pull request #4 from libnativeapi/copilot/fix-b8237c1b-822b-41d8-a3d7-cb1b57c03e9b
[Linux] Implement TrayManager class with full GTK3 support
2 parents dc4f8e0 + bb47b3e commit 498f23b

File tree

3 files changed

+308
-0
lines changed

3 files changed

+308
-0
lines changed

src/platform/linux/menu_linux.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <gtk/gtk.h>
4+
#include "../../menu.h"
5+
6+
namespace nativeapi {
7+
8+
// Private implementation class for MenuItem
9+
class MenuItem::Impl {
10+
public:
11+
Impl(GtkWidget* menu_item) : gtk_menu_item_(menu_item), title_(""), icon_(""), tooltip_("") {}
12+
13+
GtkWidget* gtk_menu_item_;
14+
std::string title_;
15+
std::string icon_;
16+
std::string tooltip_;
17+
};
18+
19+
MenuItem::MenuItem() : pimpl_(new Impl(nullptr)) {
20+
id = -1;
21+
}
22+
23+
MenuItem::MenuItem(void* menu_item) : pimpl_(new Impl((GtkWidget*)menu_item)) {
24+
id = -1;
25+
}
26+
27+
MenuItem::~MenuItem() {
28+
delete pimpl_;
29+
}
30+
31+
void MenuItem::SetIcon(std::string icon) {
32+
pimpl_->icon_ = icon;
33+
// TODO: Implement icon setting for GTK menu item
34+
}
35+
36+
std::string MenuItem::GetIcon() {
37+
return pimpl_->icon_;
38+
}
39+
40+
void MenuItem::SetTitle(std::string title) {
41+
pimpl_->title_ = title;
42+
if (pimpl_->gtk_menu_item_) {
43+
gtk_menu_item_set_label(GTK_MENU_ITEM(pimpl_->gtk_menu_item_), title.c_str());
44+
}
45+
}
46+
47+
std::string MenuItem::GetTitle() {
48+
return pimpl_->title_;
49+
}
50+
51+
void MenuItem::SetTooltip(std::string tooltip) {
52+
pimpl_->tooltip_ = tooltip;
53+
if (pimpl_->gtk_menu_item_) {
54+
gtk_widget_set_tooltip_text(pimpl_->gtk_menu_item_, tooltip.c_str());
55+
}
56+
}
57+
58+
std::string MenuItem::GetTooltip() {
59+
return pimpl_->tooltip_;
60+
}
61+
62+
// Private implementation class for Menu
63+
class Menu::Impl {
64+
public:
65+
Impl(GtkWidget* menu) : gtk_menu_(menu) {}
66+
67+
GtkWidget* gtk_menu_;
68+
};
69+
70+
Menu::Menu() : pimpl_(new Impl(gtk_menu_new())) {
71+
id = -1;
72+
}
73+
74+
Menu::Menu(void* menu) : pimpl_(new Impl((GtkWidget*)menu)) {
75+
id = -1;
76+
}
77+
78+
Menu::~Menu() {
79+
if (pimpl_->gtk_menu_) {
80+
g_object_unref(pimpl_->gtk_menu_);
81+
}
82+
delete pimpl_;
83+
}
84+
85+
void Menu::AddItem(MenuItem item) {
86+
if (pimpl_->gtk_menu_ && item.pimpl_->gtk_menu_item_) {
87+
gtk_menu_shell_append(GTK_MENU_SHELL(pimpl_->gtk_menu_), item.pimpl_->gtk_menu_item_);
88+
}
89+
}
90+
91+
void Menu::RemoveItem(MenuItem item) {
92+
if (pimpl_->gtk_menu_ && item.pimpl_->gtk_menu_item_) {
93+
gtk_container_remove(GTK_CONTAINER(pimpl_->gtk_menu_), item.pimpl_->gtk_menu_item_);
94+
}
95+
}
96+
97+
void Menu::AddSeparator() {
98+
if (pimpl_->gtk_menu_) {
99+
GtkWidget* separator = gtk_separator_menu_item_new();
100+
gtk_menu_shell_append(GTK_MENU_SHELL(pimpl_->gtk_menu_), separator);
101+
}
102+
}
103+
104+
MenuItem Menu::CreateItem(std::string title) {
105+
GtkWidget* menu_item = gtk_menu_item_new_with_label(title.c_str());
106+
MenuItem item(menu_item);
107+
item.SetTitle(title);
108+
return item;
109+
}
110+
111+
MenuItem Menu::CreateItem(std::string title, std::string icon) {
112+
MenuItem item = CreateItem(title);
113+
item.SetIcon(icon);
114+
return item;
115+
}
116+
117+
void* Menu::GetNativeMenu() {
118+
return (void*)pimpl_->gtk_menu_;
119+
}
120+
121+
} // namespace nativeapi

src/platform/linux/tray_linux.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <glib.h>
4+
#include <gtk/gtk.h>
5+
#include <gdk-pixbuf/gdk-pixbuf.h>
6+
#include "../../menu.h"
7+
#include "../../tray.h"
8+
9+
namespace nativeapi {
10+
11+
// Private implementation class
12+
class Tray::Impl {
13+
public:
14+
Impl(GtkStatusIcon* tray) : gtk_status_icon_(tray), title_(""), tooltip_("") {}
15+
16+
GtkStatusIcon* gtk_status_icon_;
17+
Menu context_menu_; // Store menu object to keep it alive
18+
std::string title_; // GTK StatusIcon doesn't have title, so we store it
19+
std::string tooltip_;
20+
};
21+
22+
Tray::Tray() : pimpl_(new Impl(nullptr)) {
23+
id = -1;
24+
}
25+
26+
Tray::Tray(void* tray) : pimpl_(new Impl((GtkStatusIcon*)tray)) {
27+
id = -1; // Will be set by TrayManager when created
28+
// Make the status icon visible
29+
if (pimpl_->gtk_status_icon_) {
30+
gtk_status_icon_set_visible(pimpl_->gtk_status_icon_, TRUE);
31+
}
32+
}
33+
34+
Tray::~Tray() {
35+
if (pimpl_->gtk_status_icon_) {
36+
g_object_unref(pimpl_->gtk_status_icon_);
37+
}
38+
delete pimpl_;
39+
}
40+
41+
void Tray::SetIcon(std::string icon) {
42+
if (!pimpl_->gtk_status_icon_) {
43+
return;
44+
}
45+
46+
// Check if the icon is a base64 string
47+
if (icon.find("data:image") != std::string::npos) {
48+
// Extract the base64 part
49+
size_t pos = icon.find("base64,");
50+
if (pos != std::string::npos) {
51+
std::string base64Icon = icon.substr(pos + 7);
52+
53+
// Decode base64 data
54+
gsize decoded_len;
55+
guchar* decoded_data = g_base64_decode(base64Icon.c_str(), &decoded_len);
56+
57+
if (decoded_data) {
58+
// Create pixbuf from decoded data
59+
GInputStream* stream = g_memory_input_stream_new_from_data(
60+
decoded_data, decoded_len, g_free);
61+
GError* error = nullptr;
62+
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_stream(stream, nullptr, &error);
63+
64+
if (pixbuf && !error) {
65+
// Scale pixbuf to appropriate size (24x24 is common for tray icons)
66+
GdkPixbuf* scaled_pixbuf = gdk_pixbuf_scale_simple(
67+
pixbuf, 24, 24, GDK_INTERP_BILINEAR);
68+
69+
gtk_status_icon_set_from_pixbuf(pimpl_->gtk_status_icon_, scaled_pixbuf);
70+
71+
g_object_unref(scaled_pixbuf);
72+
g_object_unref(pixbuf);
73+
} else if (error) {
74+
std::cerr << "Error loading icon from base64: " << error->message << std::endl;
75+
g_error_free(error);
76+
}
77+
78+
g_object_unref(stream);
79+
}
80+
}
81+
} else {
82+
// Use the icon as a file path or stock icon name
83+
if (g_file_test(icon.c_str(), G_FILE_TEST_EXISTS)) {
84+
// It's a file path
85+
gtk_status_icon_set_from_file(pimpl_->gtk_status_icon_, icon.c_str());
86+
} else {
87+
// Try as a stock icon name
88+
gtk_status_icon_set_from_icon_name(pimpl_->gtk_status_icon_, icon.c_str());
89+
}
90+
}
91+
}
92+
93+
void Tray::SetTitle(std::string title) {
94+
pimpl_->title_ = title;
95+
// GTK StatusIcon doesn't support title directly, so we just store it
96+
// Some desktop environments might show this in tooltips or context
97+
}
98+
99+
std::string Tray::GetTitle() {
100+
return pimpl_->title_;
101+
}
102+
103+
void Tray::SetTooltip(std::string tooltip) {
104+
pimpl_->tooltip_ = tooltip;
105+
if (pimpl_->gtk_status_icon_) {
106+
gtk_status_icon_set_tooltip_text(pimpl_->gtk_status_icon_, tooltip.c_str());
107+
}
108+
}
109+
110+
std::string Tray::GetTooltip() {
111+
return pimpl_->tooltip_;
112+
}
113+
114+
void Tray::SetContextMenu(Menu menu) {
115+
// Store the menu object to keep it alive
116+
pimpl_->context_menu_ = menu;
117+
118+
// Note: Full GTK integration would need to connect popup-menu signal
119+
// and show the GTK menu from the Menu object's GetNativeMenu()
120+
}
121+
122+
Menu Tray::GetContextMenu() {
123+
return pimpl_->context_menu_;
124+
}
125+
126+
Rectangle Tray::GetBounds() {
127+
Rectangle bounds = {0, 0, 0, 0};
128+
129+
if (pimpl_->gtk_status_icon_) {
130+
GdkScreen* screen;
131+
GdkRectangle area;
132+
GtkOrientation orientation;
133+
134+
if (gtk_status_icon_get_geometry(pimpl_->gtk_status_icon_, &screen, &area, &orientation)) {
135+
bounds.x = area.x;
136+
bounds.y = area.y;
137+
bounds.width = area.width;
138+
bounds.height = area.height;
139+
}
140+
}
141+
142+
return bounds;
143+
}
144+
145+
} // namespace nativeapi
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include <cstring>
2+
#include <iostream>
3+
#include <string>
4+
5+
#include "../../tray.h"
6+
#include "../../tray_manager.h"
7+
8+
// Import GTK headers
9+
#include <gtk/gtk.h>
10+
11+
namespace nativeapi {
12+
13+
TrayManager::TrayManager() : next_tray_id_(1) {}
14+
15+
TrayManager::~TrayManager() {}
16+
17+
std::shared_ptr<Tray> TrayManager::Create() {
18+
// Create a new tray using GTK StatusIcon
19+
GtkStatusIcon* status_icon = gtk_status_icon_new();
20+
auto tray = std::make_shared<Tray>((void*)status_icon);
21+
tray->id = next_tray_id_++;
22+
trays_[tray->id] = tray;
23+
return tray;
24+
}
25+
26+
std::shared_ptr<Tray> TrayManager::Get(TrayID id) {
27+
auto it = trays_.find(id);
28+
if (it != trays_.end()) {
29+
return it->second;
30+
}
31+
return nullptr;
32+
}
33+
34+
std::vector<std::shared_ptr<Tray>> TrayManager::GetAll() {
35+
std::vector<std::shared_ptr<Tray>> trays;
36+
for (auto& tray : trays_) {
37+
trays.push_back(tray.second);
38+
}
39+
return trays;
40+
}
41+
42+
} // namespace nativeapi

0 commit comments

Comments
 (0)