GTK docking interfaces and more

By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 panthera-www.cc

View raw Download
text/x-c++ • 25.47 kiB
C++ source, ASCII text
        
            
1
#include "gpanthera.hh"
2
#include <gtkmm.h>
3
#include <glibmm.h>
4
#include <glibmm/ustring.h>
5
#include <iostream>
6
#include <memory>
7
#include <libintl.h>
8
#include <locale.h>
9
#include <gtk/gtk.h>
10
#include <gdk/gdk.h>
11
#include <webkit/webkit.h>
12
#include <fstream>
13
#include <nlohmann/json.hpp>
14
#include <iomanip>
15
#include <sstream>
16
#include <dlfcn.h>
17
#include "plugin_api.h"
18
19
using plugin_entrypoint_type = void(*)(void);
20
21
std::string url_encode(const std::string &value) {
22
// Thanks https://stackoverflow.com/a/17708801
23
std::ostringstream escaped;
24
escaped.fill('0');
25
escaped << std::hex;
26
27
for(char c : value) {
28
// Keep alphanumeric and other accepted characters intact
29
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
30
escaped << c;
31
continue;
32
}
33
34
// Any other characters are percent-encoded
35
escaped << std::uppercase;
36
escaped << '%' << std::setw(2) << int((unsigned char) c);
37
escaped << std::nouppercase;
38
}
39
40
return escaped.str();
41
}
42
43
struct SearchEngine {
44
Glib::ustring name;
45
Glib::ustring url;
46
Glib::ustring get_search_url(const std::string &query) {
47
auto pos = url.find("%s");
48
if(pos == Glib::ustring::npos) {
49
throw std::runtime_error("Invalid search engine URL: missing '%s' placeholder");
50
}
51
auto new_url = url;
52
new_url.replace(pos, 2, url_encode(query));
53
return new_url;
54
}
55
};
56
57
class PantheraWww : public Gtk::Application {
58
Gtk::Window *window = Gtk::make_managed<Gtk::Window>();
59
protected:
60
std::shared_ptr<gPanthera::LayoutManager> layout_manager;
61
std::shared_ptr<gPanthera::ContentManager> content_manager;
62
Gtk::Entry *url_bar = nullptr;
63
std::string cookie_file = "cookies.txt";
64
std::vector<SearchEngine> search_engines;
65
SearchEngine *default_search_engine = nullptr;
66
67
static void notify_callback(GObject *object, GParamSpec *pspec, gpointer data) {
68
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
69
return;
70
}
71
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
72
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
73
if(g_strcmp0(pspec->name, "title") == 0) {
74
if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) {
75
if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) {
76
label->set_label("Untitled");
77
} else {
78
label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
79
}
80
}
81
} else if(g_strcmp0(pspec->name, "favicon") == 0) {
82
// Update favicons
83
if(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) {
84
image->set_from_icon_name("image-loading-symbolic");
85
if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) {
86
if(G_IS_OBJECT(favicon)) {
87
if(Glib::RefPtr<Gdk::Texture> texture = Glib::wrap(favicon)) {
88
image->set(texture);
89
}
90
}
91
}
92
}
93
}
94
}
95
}
96
97
static void notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) {
98
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
99
return;
100
}
101
auto this_ = static_cast<PantheraWww*>(data);
102
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
103
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
104
if(g_strcmp0(pspec->name, "uri") == 0) {
105
this_->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));
106
}
107
}
108
}
109
110
static void on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
111
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
112
webkit_web_view_go_back(webview);
113
}
114
115
static void on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
116
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
117
webkit_web_view_go_forward(webview);
118
}
119
120
static gboolean on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) {
121
// Middle-click opens in a new window
122
if(auto self = static_cast<PantheraWww*>(user_data)) {
123
if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
124
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
125
if(webkit_navigation_action_get_mouse_button(action) == 2) {
126
Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)));
127
self->on_new_tab(nullptr, url, false);
128
webkit_policy_decision_ignore(decision);
129
return true;
130
}
131
} else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
132
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
133
self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true);
134
return true;
135
}
136
}
137
return false;
138
}
139
140
void on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url = "about:blank", bool focus = true, bool new_window = false) {
141
if(!stack) {
142
// Find the current area
143
stack = content_manager->get_last_operated_page()->get_stack();
144
}
145
146
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
147
gtk_widget_set_hexpand(GTK_WIDGET(webview), true);
148
gtk_widget_set_vexpand(GTK_WIDGET(webview), true);
149
auto page_content = Gtk::make_managed<Gtk::Box>();
150
gtk_box_append(page_content->gobj(), GTK_WIDGET(webview));
151
auto page_tab = new Gtk::Box();
152
page_tab->set_orientation(Gtk::Orientation::HORIZONTAL);
153
auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic"));
154
page_tab->append(*initial_icon);
155
page_tab->append(*Gtk::make_managed<Gtk::Label>("Untitled"));
156
page_tab->set_spacing(4);
157
auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab);
158
g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), page->gobj());
159
g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this);
160
webkit_web_view_load_uri(webview, url.data());
161
auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_web_view_get_network_session(webview));
162
webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
163
webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
164
auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_web_view_get_network_session(webview));
165
webkit_website_data_manager_set_favicons_enabled(website_data_manager, true);
166
GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
167
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8);
168
g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview);
169
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back);
170
171
GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
172
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9);
173
g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview);
174
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward);
175
176
stack->add_page(*page);
177
if(focus) {
178
stack->set_visible_child(*page);
179
content_manager->set_last_operated_page(page);
180
}
181
if(new_window) {
182
bool result = stack->signal_detach.emit(page);
183
}
184
}
185
186
void on_startup() override {
187
Gtk::Application::on_startup();
188
add_window(*window);
189
window->set_default_size(600, 400);
190
layout_manager = std::make_shared<gPanthera::LayoutManager>();
191
auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "One", "one");
192
auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1, Gtk::Orientation::HORIZONTAL);
193
auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "Two", "two");
194
auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2, Gtk::Orientation::VERTICAL);
195
auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
196
auto debug_button = Gtk::make_managed<Gtk::Button>("Debug");
197
pane_1_content->append(*debug_button);
198
auto pane_2_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
199
auto test_notebook = Gtk::make_managed<Gtk::Notebook>();
200
test_notebook->append_page(*Gtk::make_managed<Gtk::Label>("Test 1"), *Gtk::make_managed<Gtk::Label>("Test 1"));
201
pane_2_content->append(*test_notebook);
202
auto pane_3_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
203
pane_3_content->append(*Gtk::make_managed<Gtk::Label>("Pane 3 content"));
204
auto pane_1_icon = Gtk::make_managed<Gtk::Image>();
205
pane_1_icon->set_from_icon_name("go-home-symbolic");
206
auto pane_2_icon = Gtk::make_managed<Gtk::Image>();
207
pane_2_icon->set_from_icon_name("folder-symbolic");
208
auto pane_3_icon = Gtk::make_managed<Gtk::Image>();
209
pane_3_icon->set_from_icon_name("network-transmit-receive-symbolic");
210
auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "pane1", "Pane 1", pane_1_icon);
211
auto pane_2 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_2_content, "pane2", "Pane 2", pane_2_icon);
212
auto pane_3 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_3_content, "pane3", "Pane 3", pane_3_icon);
213
214
dock_stack_1->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
215
dock_stack_1->set_transition_duration(125);
216
dock_stack_1->set_expand(true);
217
dock_stack_2->set_transition_type(Gtk::StackTransitionType::SLIDE_UP_DOWN);
218
dock_stack_2->set_transition_duration(125);
219
dock_stack_2->set_expand(true);
220
221
auto outer_grid = Gtk::make_managed<Gtk::Grid>();
222
outer_grid->attach(*switcher_2, 0, 1, 1, 1);
223
outer_grid->attach(*switcher_1, 1, 2, 1, 1);
224
auto outer_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL);
225
outer_paned->set_start_child(*dock_stack_2);
226
auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
227
auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
228
content_manager = std::make_shared<gPanthera::ContentManager>();
229
std::function<bool(gPanthera::ContentPage*)> detach_handler;
230
detach_handler = [](gPanthera::ContentPage *widget) {
231
auto new_stack = Gtk::make_managed<gPanthera::ContentStack>(widget->content_manager, widget->get_stack()->get_detach_handler());
232
auto new_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(new_stack, Gtk::Orientation::HORIZONTAL, dynamic_cast<gPanthera::ContentTabBar*>(widget->get_stack()->get_parent()->get_first_child())->get_extra_child_function());
233
auto new_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(new_stack, new_switcher);
234
auto window = new gPanthera::ContentWindow(new_notebook);
235
widget->redock(new_stack);
236
window->present();
237
new_stack->signal_leave_empty.connect([window]() {
238
window->close();
239
delete window;
240
});
241
return true;
242
};
243
244
auto return_extra_child = [this](gPanthera::ContentTabBar *switcher) {
245
auto new_tab_button = Gtk::make_managed<Gtk::Button>();
246
new_tab_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("list-add-symbolic")));
247
new_tab_button->set_tooltip_text("New tab");
248
new_tab_button->signal_clicked().connect([this, switcher]() {
249
on_new_tab(switcher->get_stack());
250
});
251
return new_tab_button;
252
};
253
auto content_stack = Gtk::make_managed<gPanthera::ContentStack>(content_manager, detach_handler);
254
auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(content_stack, Gtk::Orientation::HORIZONTAL, return_extra_child);
255
content_manager->add_stack(content_stack);
256
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); // for some reason, this has to be created
257
content->set_name("content_box");
258
auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(content_stack, content_stack_switcher, Gtk::PositionType::TOP);
259
content->append(*content_notebook);
260
inner_paned->set_start_child(*content);
261
inner_paned->set_end_child(*dock_stack_1);
262
outer_paned->set_end_child(*inner_paned);
263
outer_grid->attach(*outer_paned, 1, 1, 1, 1);
264
265
// Get search engines
266
std::ifstream search_engines_file_in("search_engines.json");
267
if(search_engines_file_in) {
268
std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>());
269
search_engines_file_in.close();
270
set_search_engines_from_json(search_engines_json);
271
} else {
272
search_engines_file_in.close();
273
auto empty_json = nlohmann::json::array();
274
std::ofstream search_engines_file_out("search_engines.json");
275
search_engines_file_out << empty_json.dump(4);
276
}
277
278
// Create the toolbar
279
auto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
280
// URL bar
281
url_bar = Gtk::make_managed<Gtk::Entry>();
282
url_bar->set_placeholder_text("Enter URL");
283
url_bar->set_hexpand(true);
284
auto load_url_callback = [this]() {
285
auto page = content_manager->get_last_operated_page();
286
bool has_protocol = url_bar->get_text().find("://") != std::string::npos;
287
if(!has_protocol) {
288
url_bar->set_text("http://" + url_bar->get_text());
289
}
290
if(page) {
291
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
292
webkit_web_view_load_uri(webview, url_bar->get_text().c_str());
293
}
294
}
295
};
296
// Go
297
auto go_button = Gtk::make_managed<Gtk::Button>("Go");
298
go_button->signal_clicked().connect(load_url_callback);
299
url_bar->signal_activate().connect(load_url_callback);
300
content_manager->signal_page_operated.connect([this](gPanthera::ContentPage *page) {
301
if(!page->get_child()) {
302
return;
303
}
304
if(!page->get_child()->get_first_child()) {
305
return;
306
}
307
url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
308
guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(notify_focused_callback), this);
309
std::shared_ptr<sigc::connection> control_signal_handler = std::make_shared<sigc::connection>();
310
*control_signal_handler = page->signal_control_status_changed.connect([this, page, control_signal_handler, url_update_handler](bool controlled) {
311
if(!controlled) {
312
control_signal_handler->disconnect();
313
if(page->get_child() && page->get_child()->get_first_child() && WEBKIT_IS_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
314
g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler);
315
}
316
}
317
});
318
});
319
// Back, forward, reload
320
auto back_button = Gtk::make_managed<Gtk::Button>();
321
back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
322
back_button->set_tooltip_text("Back");
323
auto forward_button = Gtk::make_managed<Gtk::Button>();
324
forward_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
325
forward_button->set_tooltip_text("Forward");
326
auto reload_button = Gtk::make_managed<Gtk::Button>();
327
reload_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("view-refresh-symbolic")));
328
reload_button->set_tooltip_text("Reload");
329
back_button->signal_clicked().connect([this]() {
330
auto page = content_manager->get_last_operated_page();
331
if(page) {
332
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
333
webkit_web_view_go_back(webview);
334
}
335
}
336
});
337
forward_button->signal_clicked().connect([this]() {
338
auto page = content_manager->get_last_operated_page();
339
if(page) {
340
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
341
webkit_web_view_go_forward(webview);
342
}
343
}
344
});
345
reload_button->signal_clicked().connect([this]() {
346
auto page = content_manager->get_last_operated_page();
347
if(page) {
348
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
349
webkit_web_view_reload(webview);
350
}
351
}
352
});
353
// Search bar
354
// TODO: provide history, provide a menu button with the other engines
355
auto search_bar = Gtk::make_managed<Gtk::Entry>();
356
search_bar->set_placeholder_text("Search");
357
search_bar->set_hexpand(true);
358
if(search_engines.empty()) {
359
search_bar->set_sensitive(false);
360
search_bar->set_placeholder_text("No search");
361
}
362
auto search_callback = [this, search_bar, content_stack]() {
363
// Create a new tab with the search results
364
if(content_manager->get_last_operated_page()) {
365
on_new_tab(nullptr);
366
} else {
367
on_new_tab(content_stack);
368
}
369
auto page = content_manager->get_last_operated_page();
370
if(page) {
371
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
372
auto search_url = default_search_engine->get_search_url(search_bar->get_text());
373
webkit_web_view_load_uri(webview, search_url.c_str());
374
}
375
}
376
};
377
if(default_search_engine) {
378
search_bar->signal_activate().connect(search_callback);
379
}
380
// Assemble the toolbar
381
main_toolbar->append(*back_button);
382
main_toolbar->append(*forward_button);
383
main_toolbar->append(*reload_button);
384
main_toolbar->append(*url_bar);
385
main_toolbar->append(*go_button);
386
main_toolbar->append(*search_bar);
387
if(default_search_engine) {
388
auto search_button = Gtk::make_managed<Gtk::Button>(default_search_engine->name);
389
search_button->signal_clicked().connect(search_callback);
390
main_toolbar->append(*search_button);
391
}
392
outer_grid->attach(*main_toolbar, 0, 0, 2, 1);
393
window->set_child(*outer_grid);
394
debug_button->signal_clicked().connect([this]() {
395
if(content_manager->get_last_operated_page()) {
396
std::cout << "Last operated page: " << content_manager->get_last_operated_page()->get_name() << std::endl;
397
} else {
398
std::cout << "No page operated!" << std::endl;
399
}
400
});
401
// TODO: Use the last operated page and allow opening tabs next to the last operated page using certain panes
402
// Load the existing layout, if it exists
403
std::ifstream layout_file_in("layout.json");
404
if(layout_file_in) {
405
std::string layout_json((std::istreambuf_iterator<char>(layout_file_in)), std::istreambuf_iterator<char>());
406
layout_file_in.close();
407
layout_manager->restore_json_layout(layout_json);
408
} else {
409
// Create a new layout if the file doesn't exist
410
layout_file_in.close();
411
412
dock_stack_1->add_pane(*pane_1);
413
dock_stack_1->add_pane(*pane_3);
414
dock_stack_2->add_pane(*pane_2);
415
416
std::ofstream layout_file_out("layout.json");
417
layout_file_out << layout_manager->get_layout_as_json();
418
layout_file_out.close();
419
}
420
// Save the layout when changed
421
layout_manager->signal_pane_moved.connect([this](gPanthera::DockablePane *pane) {
422
std::ofstream layout_file_out("layout.json");
423
layout_file_out << layout_manager->get_layout_as_json();
424
layout_file_out.close();
425
std::cout << "Layout changed: " << layout_manager->get_layout_as_json() << std::endl;
426
});
427
428
auto new_tab_action = Gio::SimpleAction::create("new_tab");
429
new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
430
on_new_tab(nullptr);
431
});
432
add_action(new_tab_action);
433
set_accels_for_action("app.new_tab", {"<Primary>T"});
434
auto close_tab_action = Gio::SimpleAction::create("close_tab");
435
close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
436
auto page = content_manager->get_last_operated_page();
437
if(page) {
438
page->close();
439
}
440
});
441
add_action(close_tab_action);
442
set_accels_for_action("app.close_tab", {"<Primary>W"});
443
444
// Load plugins
445
std::ifstream plugin_file_in("plugins.json");
446
if(plugin_file_in) {
447
std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>());
448
plugin_file_in.close();
449
auto json = nlohmann::json::parse(plugin_json);
450
for(auto &plugin : json) {
451
auto plugin_path = plugin["path"].get<std::string>();
452
load_plugin(plugin_path);
453
}
454
} else {
455
plugin_file_in.close();
456
auto empty_json = nlohmann::json::array();
457
std::ofstream plugin_file_out("plugins.json");
458
plugin_file_out << empty_json.dump(4);
459
plugin_file_out.close();
460
}
461
}
462
463
void on_activate() override {
464
window->present();
465
}
466
467
void set_search_engines_from_json(const std::string &json_string) {
468
auto json = nlohmann::json::parse(json_string);
469
Glib::ustring default_search_engine_name;
470
for(auto &engine : json) {
471
SearchEngine search_engine;
472
search_engine.name = engine["name"].get<std::string>();
473
search_engine.url = engine["url"].get<std::string>();
474
search_engines.push_back(search_engine);
475
if(engine.contains("default") && engine["default"].get<bool>()) {
476
default_search_engine_name = engine["name"].get<std::string>();
477
}
478
}
479
for(auto &search_engine : search_engines) {
480
if(search_engine.name == default_search_engine_name) {
481
default_search_engine = &search_engine;
482
break;
483
}
484
}
485
}
486
public:
487
static Glib::RefPtr<PantheraWww> create() {
488
return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww());
489
}
490
491
std::shared_ptr<gPanthera::LayoutManager> get_layout_manager() {
492
return layout_manager;
493
}
494
495
std::shared_ptr<gPanthera::ContentManager> get_content_manager() {
496
return content_manager;
497
}
498
499
void load_plugin(const std::string &plugin_path) {
500
void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
501
if(!handle) {
502
std::cerr << "Failed to load plugin: " << dlerror() << std::endl;
503
return;
504
}
505
auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init");
506
if(!entrypoint) {
507
std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl;
508
dlclose(handle);
509
return;
510
}
511
entrypoint();
512
}
513
};
514
515
Glib::RefPtr<PantheraWww> app = nullptr;
516
517
extern "C" {
518
void panthera_log(const char *message) {
519
std::cerr << message << std::endl;
520
}
521
522
void panthera_add_pane(const char *id, const char *label, GtkImage *icon, GtkWidget *pane) {
523
auto pane_child = Glib::wrap(pane);
524
auto icon_cc = Glib::wrap(icon);
525
auto label_cc = Glib::ustring(label);
526
auto id_cc = Glib::ustring(id);
527
auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc);
528
app->get_layout_manager()->add_pane(new_pane);
529
}
530
}
531
532
int main(int argc, char *argv[]) {
533
gPanthera::init();
534
app = PantheraWww::create();
535
return app->run(argc, argv);
536
}