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++ • 48.13 kiB
C++ source, ASCII text, with very long lines (382)
        
            
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 "external/sqlite_orm/sqlite_orm.h"
18
//#include "plugin_api.h"
19
#define _(STRING) gettext(STRING)
20
21
using plugin_entrypoint_type = void(*)(void);
22
23
std::string url_encode(const std::string &value) {
24
// Thanks https://stackoverflow.com/a/17708801
25
std::ostringstream escaped;
26
escaped.fill('0');
27
escaped << std::hex;
28
29
for(char c : value) {
30
// Keep alphanumeric and other accepted characters intact
31
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
32
escaped << c;
33
continue;
34
}
35
36
// Any other characters are percent-encoded
37
escaped << std::uppercase;
38
escaped << '%' << std::setw(2) << int((unsigned char) c);
39
escaped << std::nouppercase;
40
}
41
42
return escaped.str();
43
}
44
45
bool starts_with(const Glib::ustring &str, const Glib::ustring &prefix) {
46
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
47
}
48
49
bool ends_with(const Glib::ustring &str, const Glib::ustring &suffix) {
50
return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
51
}
52
53
struct SearchEngine {
54
Glib::ustring name;
55
Glib::ustring url;
56
Glib::ustring get_search_url(const std::string &query) {
57
auto pos = url.find("%s");
58
if(pos == Glib::ustring::npos) {
59
throw std::runtime_error("Invalid search engine URL: missing '%s' placeholder");
60
}
61
auto new_url = url;
62
new_url.replace(pos, 2, url_encode(query));
63
return new_url;
64
}
65
};
66
67
struct HistoryEntry {
68
int id;
69
std::string url, title;
70
int64_t timestamp;
71
};
72
73
using StorageType = decltype(sqlite_orm::make_storage("",
74
sqlite_orm::make_table("history",
75
sqlite_orm::make_column("id", &HistoryEntry::id, sqlite_orm::primary_key()),
76
sqlite_orm::make_column("url", &HistoryEntry::url),
77
sqlite_orm::make_column("title", &HistoryEntry::title),
78
sqlite_orm::make_column("timestamp", &HistoryEntry::timestamp)
79
)
80
));
81
82
class HistoryManager {
83
public:
84
static auto make_storage(const std::string& path) {
85
return sqlite_orm::make_storage(path,
86
sqlite_orm::make_table("history",
87
sqlite_orm::make_column("id", &HistoryEntry::id, sqlite_orm::primary_key()),
88
sqlite_orm::make_column("url", &HistoryEntry::url),
89
sqlite_orm::make_column("title", &HistoryEntry::title),
90
sqlite_orm::make_column("timestamp", &HistoryEntry::timestamp)
91
)
92
);
93
}
94
StorageType storage;
95
explicit HistoryManager(const std::string &db_path) : storage(make_storage(db_path)) {
96
storage.sync_schema();
97
}
98
99
void log_url(const std::string &url, const std::string title, const int64_t timestamp = Glib::DateTime::create_now_local().to_unix()) {
100
storage.insert(HistoryEntry{-1, url, title, timestamp});
101
}
102
103
std::vector<HistoryEntry> get_history() {
104
return storage.get_all<HistoryEntry>();
105
}
106
107
std::vector<HistoryEntry> get_history_between(int64_t start_time, int64_t end_time) {
108
using namespace sqlite_orm;
109
return storage.get_all<HistoryEntry>(
110
where(c(&HistoryEntry::timestamp) >= start_time and c(&HistoryEntry::timestamp) < end_time),
111
order_by(&HistoryEntry::timestamp).desc()
112
);
113
}
114
};
115
116
std::vector<WebKitWebView*> collect_webviews(Gtk::Widget const &root) {
117
std::vector<WebKitWebView*> result;
118
std::function<void(const Gtk::Widget&)> recurse = [&](const Gtk::Widget &widget) {
119
gpointer gobj = G_OBJECT(widget.gobj());
120
if(WEBKIT_IS_WEB_VIEW(gobj)) {
121
result.push_back(WEBKIT_WEB_VIEW(gobj));
122
} else for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) {
123
recurse(*child);
124
}
125
};
126
recurse(root);
127
return result;
128
}
129
130
bool has_webview(Gtk::Widget const &root) {
131
if(WEBKIT_IS_WEB_VIEW(G_OBJECT(root.gobj()))) {
132
return true;
133
}
134
for(auto *child = root.get_first_child(); child; child = child->get_next_sibling()) {
135
if(has_webview(*child)) {
136
return true;
137
}
138
}
139
return false;
140
}
141
142
class HistoryViewer : public gPanthera::DockablePane {
143
protected:
144
std::shared_ptr<HistoryManager> history_manager;
145
Gtk::Calendar *calendar;
146
Gtk::Label *date_label;
147
Gtk::Box *box_place;
148
public:
149
sigc::signal<void(const std::string &url)> signal_open_url;
150
HistoryViewer(std::shared_ptr<gPanthera::LayoutManager> layout_manager, std::shared_ptr<HistoryManager> history_manager) : gPanthera::DockablePane(layout_manager, *Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0), "history", _("History"), Gtk::make_managed<Gtk::Image>(Gio::Icon::create("document-open-recent-symbolic"))), history_manager(std::move(history_manager)) {
151
auto history_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0);
152
auto history_button_previous = Gtk::make_managed<Gtk::Button>();
153
history_button_previous->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
154
history_button_previous->set_tooltip_text(_("Previous day"));
155
auto history_button_next = Gtk::make_managed<Gtk::Button>();
156
history_button_next->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
157
history_button_next->set_tooltip_text(_("Next day"));
158
159
date_label = Gtk::make_managed<Gtk::Label>("");
160
auto date_button = Gtk::make_managed<Gtk::MenuButton>();
161
date_button->set_child(*date_label);
162
date_button->set_tooltip_text(_("Select date"));
163
date_button->set_direction(Gtk::ArrowType::NONE);
164
auto date_popover = Gtk::make_managed<Gtk::Popover>();
165
calendar = Gtk::make_managed<Gtk::Calendar>();
166
date_popover->set_child(*calendar);
167
date_button->set_popover(*date_popover);
168
date_button->set_has_frame(false);
169
date_button->set_hexpand(true);
170
171
history_toolbar->append(*history_button_previous);
172
history_toolbar->append(*date_button);
173
history_toolbar->append(*history_button_next);
174
auto box = dynamic_cast<Gtk::Box*>(this->child);
175
box->append(*history_toolbar);
176
177
history_button_next->signal_clicked().connect([this]() {
178
Glib::DateTime calendar_day = calendar->get_date();
179
Glib::DateTime datetime = Glib::DateTime::create_local(
180
calendar_day.get_year(),
181
calendar_day.get_month(),
182
calendar_day.get_day_of_month(),
183
0, 0, 0
184
);
185
auto next_entries = this->history_manager->storage.get_all<HistoryEntry>(
186
sqlite_orm::where(sqlite_orm::c(&HistoryEntry::timestamp) >= datetime.to_unix() + 86400),
187
sqlite_orm::order_by(&HistoryEntry::timestamp).asc()
188
);
189
if(next_entries.empty()) {
190
return;
191
}
192
auto last_entry = next_entries.front();
193
Glib::DateTime next_day = Glib::DateTime::create_now_local(last_entry.timestamp);
194
Glib::DateTime new_date = Glib::DateTime::create_local(
195
next_day.get_year(),
196
next_day.get_month(),
197
next_day.get_day_of_month(),
198
0, 0, 0
199
);
200
calendar->select_day(new_date);
201
reload_history();
202
});
203
204
history_button_previous->signal_clicked().connect([this]() {
205
Glib::DateTime calendar_day = calendar->get_date();
206
Glib::DateTime datetime = Glib::DateTime::create_local(
207
calendar_day.get_year(),
208
calendar_day.get_month(),
209
calendar_day.get_day_of_month(),
210
0, 0, 0
211
);
212
auto previous_entries = this->history_manager->storage.get_all<HistoryEntry>(
213
sqlite_orm::where(sqlite_orm::c(&HistoryEntry::timestamp) < datetime.to_unix()),
214
sqlite_orm::order_by(&HistoryEntry::timestamp).desc()
215
);
216
if(previous_entries.empty()) {
217
return;
218
}
219
auto last_entry = previous_entries.front();
220
Glib::DateTime previous_day = Glib::DateTime::create_now_local(last_entry.timestamp);
221
Glib::DateTime new_date = Glib::DateTime::create_local(
222
previous_day.get_year(),
223
previous_day.get_month(),
224
previous_day.get_day_of_month(),
225
0, 0, 0
226
);
227
calendar->select_day(new_date);
228
reload_history();
229
});
230
231
calendar->signal_day_selected().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history));
232
calendar->signal_prev_month().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history));
233
calendar->signal_next_month().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history));
234
calendar->signal_prev_year().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history));
235
calendar->signal_next_year().connect(sigc::mem_fun(*this, &HistoryViewer::reload_history));
236
Glib::DateTime datetime = Glib::DateTime::create_now_local();
237
calendar->select_day(datetime);
238
239
this->signal_pane_shown.connect([this]() {
240
// Load the current date
241
reload_history();
242
});
243
244
auto scrolled_window = Gtk::make_managed<Gtk::ScrolledWindow>();
245
box_place = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
246
auto viewport = Gtk::make_managed<Gtk::Viewport>(nullptr, nullptr);
247
viewport->set_child(*box_place);
248
scrolled_window->set_child(*viewport);
249
scrolled_window->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
250
scrolled_window->set_vexpand(true);
251
scrolled_window->set_hexpand(true);
252
box->append(*scrolled_window);
253
254
// TODO: allow deleting entries
255
}
256
257
void reload_history() {
258
Glib::DateTime calendar_day = calendar->get_date();
259
Glib::DateTime datetime = Glib::DateTime::create_local(
260
calendar_day.get_year(),
261
calendar_day.get_month(),
262
calendar_day.get_day_of_month(),
263
0, 0, 0
264
);
265
date_label->set_text(datetime.format("%x"));
266
auto box = Gtk::make_managed<Gtk::ListBox>();
267
auto history_entries = history_manager->get_history_between(datetime.to_unix(), datetime.to_unix() + 86400);
268
for(const auto &entry : history_entries) {
269
auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
270
auto title = Gtk::make_managed<Gtk::Label>(entry.title.empty() ? "Untitled" : entry.title);
271
title->set_halign(Gtk::Align::START);
272
title->set_ellipsize(Pango::EllipsizeMode::MIDDLE);
273
auto label = Gtk::make_managed<Gtk::Label>(entry.url);
274
label->set_halign(Gtk::Align::START);
275
label->set_ellipsize(Pango::EllipsizeMode::MIDDLE);
276
row->append(*title);
277
row->append(*label);
278
if(auto time = Glib::DateTime::create_now_local(entry.timestamp)) {
279
label->set_tooltip_text(time.format("%c"));
280
box->append(*row);
281
}
282
}
283
if(box_place->get_first_child()) {
284
box_place->remove(*box_place->get_first_child());
285
}
286
box->signal_row_activated().connect([this](Gtk::ListBoxRow *row) {
287
if(auto box_row = dynamic_cast<Gtk::Box*>(row->get_child())) {
288
if(auto label = dynamic_cast<Gtk::Label*>(box_row->get_last_child())) {
289
auto url = label->get_text();
290
if(!url.empty()) {
291
signal_open_url.emit(url);
292
}
293
}
294
}
295
});
296
box_place->append(*box);
297
}
298
};
299
300
class PantheraWindow;
301
302
class PantheraWww : public Gtk::Application {
303
private:
304
std::shared_ptr<gPanthera::ContentManager> content_manager;
305
std::string cookie_file;
306
std::vector<SearchEngine> search_engines;
307
SearchEngine *default_search_engine;
308
std::shared_ptr<HistoryManager> history_manager;
309
Glib::RefPtr<Gio::Menu> main_menu;
310
std::string new_tab_page;
311
gPanthera::ContentStack *controlled_stack = nullptr;
312
313
static void load_change_callback(WebKitWebView* object, WebKitLoadEvent load_event, gpointer data);
314
static void notify_callback(GObject* object, GParamSpec* pspec, gpointer data);
315
static void notify_focused_callback(GObject* object, GParamSpec* pspec, gpointer data);
316
static void on_back_pressed(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data);
317
static void on_forward_pressed(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data);
318
static gboolean on_decide_policy(WebKitWebView* source, WebKitPolicyDecision* decision, WebKitPolicyDecisionType type, gpointer user_data);
319
static void close_callback(WebKitWebView *source, gpointer user_data);
320
321
protected:
322
void on_startup() override;
323
void on_activate() override;
324
void on_shutdown() override;
325
PantheraWindow *make_window();
326
327
public:
328
PantheraWww();
329
~PantheraWww();
330
friend class PantheraWindow;
331
void on_new_tab(gPanthera::ContentStack* stack, const Glib::ustring& url = "", bool focus = true, bool new_window = false);
332
void set_search_engines_from_json(const std::string& json_string);
333
static Glib::RefPtr<PantheraWww> create();
334
std::shared_ptr<gPanthera::ContentManager> get_content_manager();
335
void load_plugin(const std::string& plugin_path);
336
};
337
338
class PantheraWindow : public Gtk::ApplicationWindow {
339
private:
340
std::shared_ptr<gPanthera::LayoutManager> layout_manager;
341
Gtk::Entry *url_bar;
342
HistoryViewer *history_viewer;
343
gPanthera::ContentPage *controlled_page = nullptr;
344
gPanthera::ContentStack *controlled_stack = nullptr;
345
Glib::RefPtr<Gio::SimpleAction> back_action, forward_action, reload_action;
346
Gtk::Button *go_button;
347
348
void enable_controls() {
349
back_action->set_enabled(true);
350
forward_action->set_enabled(true);
351
reload_action->set_enabled(true);
352
url_bar->set_sensitive(true);
353
go_button->set_sensitive(true);
354
if(controlled_page) {
355
g_object_notify(G_OBJECT(controlled_page->get_child()->get_first_child()->gobj()), "uri");
356
}
357
}
358
359
void disable_controls() {
360
back_action->set_enabled(false);
361
forward_action->set_enabled(false);
362
reload_action->set_enabled(false);
363
url_bar->set_sensitive(false);
364
url_bar->set_text("");
365
go_button->set_sensitive(false);
366
}
367
368
sigc::connection moving_connection, moved_connection, closing_connection, closed_connection;
369
public:
370
friend class PantheraWww;
371
372
explicit PantheraWindow(Gtk::Application *application) : Gtk::ApplicationWindow() {
373
// There is a constructor with Glib::RefPtr<Gtk::Application>, but it is not appropriate
374
// because the window will own the application, creating a cycle
375
auto panthera = dynamic_cast<PantheraWww*>(application);
376
if(!panthera) {
377
throw std::runtime_error("Application is not a PantheraWww instance");
378
}
379
panthera->add_window(*this);
380
this->set_default_size(800, 600);
381
layout_manager = std::make_shared<gPanthera::LayoutManager>();
382
383
auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("One"), "one");
384
auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1, Gtk::Orientation::HORIZONTAL);
385
auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("Two"), "two");
386
auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2, Gtk::Orientation::VERTICAL);
387
auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
388
auto debug_button = Gtk::make_managed<Gtk::Button>("Debug");
389
auto print_history_button = Gtk::make_managed<Gtk::Button>("Print history");
390
print_history_button->signal_clicked().connect([this, panthera]() {
391
auto history = panthera->history_manager->get_history();
392
for(const auto &entry : history) {
393
std::cout << "entry " << entry.id << ", url " << entry.url << ", unix time " << entry.timestamp << std::endl;
394
// TODO: exclude redirecting URLs
395
}
396
});
397
pane_1_content->append(*debug_button);
398
pane_1_content->append(*print_history_button);
399
auto pane_1_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("help-about-symbolic"));
400
auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "debug", _("Debugging options"), pane_1_icon);
401
402
dock_stack_1->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
403
dock_stack_1->set_transition_duration(125);
404
dock_stack_1->set_expand(true);
405
dock_stack_2->set_transition_type(Gtk::StackTransitionType::SLIDE_UP_DOWN);
406
dock_stack_2->set_transition_duration(125);
407
dock_stack_2->set_expand(true);
408
409
auto outer_grid = Gtk::make_managed<Gtk::Grid>();
410
outer_grid->attach(*switcher_2, 0, 2, 1, 1);
411
outer_grid->attach(*switcher_1, 1, 3, 1, 1);
412
auto outer_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL);
413
outer_paned->set_start_child(*dock_stack_2);
414
auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
415
auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
416
std::function<bool(gPanthera::ContentPage*)> detach_handler;
417
418
detach_handler = [this](gPanthera::ContentPage *widget) {
419
auto new_stack = Gtk::make_managed<gPanthera::ContentStack>(widget->content_manager, widget->get_stack()->get_detach_handler());
420
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());
421
auto new_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(new_stack, new_switcher);
422
auto window = new gPanthera::ContentWindow(new_notebook);
423
widget->redock(new_stack);
424
window->present();
425
window->signal_close_request().connect([window]() {
426
auto webviews = collect_webviews(*window);
427
for(auto view : webviews) {
428
webkit_web_view_try_close(view);
429
}
430
if(!has_webview(*window)) {
431
// All documents have been closed safely, the window can be closed
432
return false;
433
}
434
return true;
435
}, false);
436
controlled_page = nullptr;
437
controlled_stack = nullptr;
438
disable_controls();
439
return true;
440
};
441
442
auto return_extra_child = [this, panthera](gPanthera::ContentTabBar *switcher) {
443
auto new_tab_button = Gtk::make_managed<Gtk::Button>();
444
new_tab_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("list-add-symbolic")));
445
new_tab_button->set_tooltip_text(_("New Tab"));
446
new_tab_button->signal_clicked().connect([this, switcher, panthera]() {
447
panthera->on_new_tab(switcher->get_stack());
448
});
449
return new_tab_button;
450
};
451
controlled_stack = Gtk::make_managed<gPanthera::ContentStack>(panthera->content_manager, detach_handler);
452
panthera->controlled_stack = controlled_stack;
453
auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(controlled_stack, Gtk::Orientation::HORIZONTAL, return_extra_child);
454
panthera->content_manager->add_stack(controlled_stack);
455
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); // for some reason, this has to be created
456
content->set_name("content_box");
457
auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(controlled_stack, content_stack_switcher, Gtk::PositionType::TOP);
458
content->append(*content_notebook);
459
inner_paned->set_start_child(*content);
460
inner_paned->set_end_child(*dock_stack_1);
461
outer_paned->set_end_child(*inner_paned);
462
outer_grid->attach(*outer_paned, 1, 2, 1, 1);
463
// Create the toolbar
464
auto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
465
// URL bar
466
url_bar = Gtk::make_managed<Gtk::Entry>();
467
url_bar->set_placeholder_text(_("Enter URL"));
468
url_bar->set_hexpand(true);
469
auto load_url_callback = [this, panthera]() {
470
bool has_protocol = url_bar->get_text().find("://") != std::string::npos;
471
if(!has_protocol) {
472
url_bar->set_text("http://" + url_bar->get_text());
473
}
474
if(controlled_page) {
475
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
476
webkit_web_view_load_uri(webview, url_bar->get_text().c_str());
477
}
478
}
479
};
480
// Go
481
go_button = Gtk::make_managed<Gtk::Button>(_("Go"));
482
go_button->signal_clicked().connect(load_url_callback);
483
url_bar->signal_activate().connect(load_url_callback);
484
panthera->content_manager->signal_page_operated.connect([this, panthera](gPanthera::ContentPage *page) {
485
if(!page) {
486
return;
487
}
488
if(!page->get_child()) {
489
return;
490
}
491
if(!page->get_child()->get_first_child()) {
492
return;
493
}
494
if(page->get_root() != this) {
495
// The page is in some other window
496
return;
497
}
498
controlled_page = page;
499
controlled_stack = page->get_stack();
500
panthera->controlled_stack = page->get_stack();
501
if(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
502
if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) {
503
url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
504
}
505
guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(panthera->notify_focused_callback), this);
506
std::shared_ptr<sigc::connection> control_signal_handler = std::make_shared<sigc::connection>();
507
*control_signal_handler = page->signal_control_status_changed.connect([this, page, control_signal_handler, url_update_handler](bool controlled) {
508
if(!controlled) {
509
control_signal_handler->disconnect();
510
if(page->get_child() && page->get_child()->get_first_child() && WEBKIT_IS_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
511
g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler);
512
}
513
}
514
});
515
}
516
517
enable_controls();
518
});
519
history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, panthera->history_manager);
520
history_viewer->signal_open_url.connect([this, panthera](const std::string &url) {
521
panthera->on_new_tab(nullptr, url, true);
522
});
523
// Back, forward, reload
524
auto back_button = Gtk::make_managed<Gtk::Button>();
525
back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
526
back_button->set_tooltip_text(_("Back"));
527
auto forward_button = Gtk::make_managed<Gtk::Button>();
528
forward_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
529
forward_button->set_tooltip_text(_("Forward"));
530
auto reload_button = Gtk::make_managed<Gtk::Button>();
531
reload_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("view-refresh-symbolic")));
532
reload_button->set_tooltip_text(_("Reload"));
533
534
back_action = Gio::SimpleAction::create("go_back");
535
add_action(back_action);
536
back_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
537
if(controlled_page) {
538
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
539
webkit_web_view_go_back(webview);
540
}
541
}
542
});
543
back_button->set_action_name("win.go_back");
544
545
forward_action = Gio::SimpleAction::create("go_forward");
546
add_action(forward_action);
547
forward_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
548
if(controlled_page) {
549
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
550
webkit_web_view_go_forward(webview);
551
}
552
}
553
});
554
forward_button->set_action_name("win.go_forward");
555
556
reload_action = Gio::SimpleAction::create("reload");
557
add_action(reload_action);
558
reload_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
559
if(controlled_page) {
560
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
561
webkit_web_view_reload(webview);
562
}
563
}
564
});
565
reload_button->set_action_name("win.reload");
566
567
// Search bar
568
// TODO: provide history, provide a menu button with the other engines
569
auto search_bar = Gtk::make_managed<Gtk::Entry>();
570
search_bar->set_placeholder_text(_("Search"));
571
search_bar->set_hexpand(true);
572
if(panthera->search_engines.empty()) {
573
search_bar->set_sensitive(false);
574
search_bar->set_placeholder_text(_("No search"));
575
}
576
auto search_callback = [this, search_bar, panthera]() {
577
// Create a new tab with the search results
578
panthera->on_new_tab(controlled_stack);
579
auto page = panthera->content_manager->get_last_operated_page();
580
if(page) {
581
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
582
auto search_url = panthera->default_search_engine->get_search_url(search_bar->get_text());
583
webkit_web_view_load_uri(webview, search_url.c_str());
584
}
585
}
586
};
587
if(panthera->default_search_engine) {
588
search_bar->signal_activate().connect(search_callback);
589
}
590
591
// Assemble the toolbar
592
main_toolbar->append(*back_button);
593
main_toolbar->append(*forward_button);
594
main_toolbar->append(*reload_button);
595
main_toolbar->append(*url_bar);
596
main_toolbar->append(*go_button);
597
main_toolbar->append(*search_bar);
598
if(panthera->default_search_engine) {
599
auto search_button = Gtk::make_managed<Gtk::Button>(panthera->default_search_engine->name);
600
search_button->signal_clicked().connect(search_callback);
601
main_toolbar->append(*search_button);
602
}
603
outer_grid->attach(*main_toolbar, 0, 1, 2, 1);
604
this->set_child(*outer_grid);
605
debug_button->signal_clicked().connect([this, panthera]() {
606
if(panthera->content_manager->get_last_operated_page()) {
607
std::cout << "Last operated page: " << panthera->content_manager->get_last_operated_page()->get_name() << std::endl;
608
} else {
609
std::cout << "No page operated!" << std::endl;
610
}
611
});
612
// TODO: Use the last operated page and allow opening tabs next to the last operated page using certain panes
613
// Load the existing layout, if it exists
614
std::ifstream layout_file_in("layout.json");
615
if(layout_file_in) {
616
std::string layout_json((std::istreambuf_iterator<char>(layout_file_in)), std::istreambuf_iterator<char>());
617
layout_file_in.close();
618
layout_manager->restore_json_layout(layout_json);
619
} else {
620
// Create a new layout if the file doesn't exist
621
layout_file_in.close();
622
623
dock_stack_1->add_pane(*pane_1);
624
dock_stack_1->add_pane(*history_viewer);
625
626
std::ofstream layout_file_out("layout.json");
627
layout_file_out << layout_manager->get_layout_as_json();
628
layout_file_out.close();
629
}
630
// Save the layout when changed
631
layout_manager->signal_pane_moved.connect([this, panthera](gPanthera::DockablePane *pane) {
632
std::ofstream layout_file_out("layout.json");
633
layout_file_out << layout_manager->get_layout_as_json();
634
layout_file_out.close();
635
std::cout << "Layout changed: " << layout_manager->get_layout_as_json() << std::endl;
636
});
637
set_show_menubar(true);
638
639
// As the window is initially empty, disable most actions
640
disable_controls();
641
642
moving_connection = panthera->content_manager->signal_page_moving.connect([this, panthera](gPanthera::ContentPage *page) {
643
if(page->get_root() == this) {
644
controlled_page = nullptr;
645
controlled_stack = nullptr;
646
disable_controls();
647
}
648
});
649
moved_connection = panthera->content_manager->signal_page_moved.connect([this, panthera](gPanthera::ContentPage *page) {
650
if(page->get_root() == this) {
651
panthera->content_manager->set_last_operated_page(page);
652
page->get_stack()->set_visible_child(*page);
653
enable_controls();
654
}
655
});
656
657
signal_close_request().connect([this]() {
658
auto webviews = collect_webviews(*this);
659
for(auto view : webviews) {
660
webkit_web_view_try_close(view);
661
}
662
if(!has_webview(*this)) {
663
// All documents have been closed safely, the window can be closed
664
return false;
665
}
666
return true;
667
}, false);
668
669
closing_connection = panthera->content_manager->signal_page_closing.connect([this](gPanthera::ContentPage *page) {
670
if(page->get_root() != this) {
671
return;
672
}
673
disable_controls();
674
});
675
closed_connection = panthera->content_manager->signal_page_closed.connect([this](gPanthera::ContentPage *page) {
676
if(has_webview(*this)) {
677
enable_controls();
678
}
679
});
680
}
681
~PantheraWindow() override {
682
moving_connection.disconnect();
683
moved_connection.disconnect();
684
closed_connection.disconnect();
685
closing_connection.disconnect();
686
}
687
};
688
689
void PantheraWww::load_change_callback(WebKitWebView *object, WebKitLoadEvent load_event, gpointer data) {
690
if(auto self = static_cast<PantheraWww*>(data)) {
691
if(load_event == WEBKIT_LOAD_COMMITTED) {
692
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) {
693
self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), "");
694
} else {
695
self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)),
696
webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
697
}
698
// TODO: reload visible history viewers
699
}
700
}
701
}
702
703
void PantheraWww::notify_callback(GObject *object, GParamSpec *pspec, gpointer data) {
704
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
705
return;
706
}
707
if(auto self = static_cast<PantheraWww*>(data)) {
708
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
709
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
710
if(g_strcmp0(pspec->name, "title") == 0) {
711
if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) {
712
if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) {
713
label->set_label(_("Untitled"));
714
} else {
715
label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
716
}
717
}
718
} else if(g_strcmp0(pspec->name, "favicon") == 0) {
719
// Update favicons
720
if(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) {
721
image->set_from_icon_name("image-loading-symbolic");
722
if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) {
723
gtk_image_set_from_paintable(image->gobj(), GDK_PAINTABLE(favicon));
724
}
725
}
726
}
727
}
728
}
729
}
730
731
void PantheraWww::notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) {
732
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
733
return;
734
}
735
auto this_ = static_cast<PantheraWww*>(data);
736
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
737
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
738
if(g_strcmp0(pspec->name, "uri") == 0) {
739
if(auto main_window = dynamic_cast<PantheraWindow*>(page->get_root())) {
740
if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object))) {
741
main_window->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));
742
}
743
}
744
} else if(g_strcmp0(pspec->name, "title") == 0) {
745
if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) {
746
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) {
747
window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
748
} else {
749
window->set_title(_("Untitled"));
750
}
751
}
752
}
753
}
754
}
755
756
void PantheraWww::on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
757
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
758
webkit_web_view_go_back(webview);
759
}
760
761
void PantheraWww::on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
762
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
763
webkit_web_view_go_forward(webview);
764
}
765
766
gboolean PantheraWww::on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) {
767
if(auto self = static_cast<PantheraWww*>(user_data)) {
768
if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
769
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
770
Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)));
771
if(!starts_with(url, "about:") && url.find("://") == Glib::ustring::npos) {
772
// It is a special scheme (mailto:, tel: etc.)
773
try {
774
Gio::AppInfo::launch_default_for_uri(url);
775
} catch(const Glib::Error &exception) {
776
std::cerr << "Failed to launch special URI: " << exception.what() << std::endl;
777
}
778
webkit_policy_decision_ignore(decision);
779
return true;
780
}
781
if(webkit_navigation_action_get_mouse_button(action) == 2) {
782
// Middle-click opens in a new window
783
self->on_new_tab(nullptr, url, false);
784
webkit_policy_decision_ignore(decision);
785
return true;
786
}
787
} else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
788
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
789
self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true);
790
return true;
791
}
792
}
793
return false;
794
}
795
796
void PantheraWww::close_callback(WebKitWebView *source, gpointer user_data) {
797
if(auto self = static_cast<PantheraWww*>(user_data)) {
798
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(source)));
799
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
800
if(page->get_next_sibling()) {
801
page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_next_sibling()));
802
page->get_stack()->set_visible_child(*page->get_next_sibling());
803
} else if(page->get_prev_sibling()) {
804
page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_prev_sibling()));
805
page->get_stack()->set_visible_child(*page->get_prev_sibling());
806
}
807
webkit_web_view_terminate_web_process(source);
808
if(page->content_manager->get_last_operated_page() == page) {
809
page->content_manager->set_last_operated_page(nullptr);
810
}
811
page->redock(nullptr);
812
self->content_manager->signal_page_closed.emit(page);
813
}
814
}
815
}
816
817
void PantheraWww::on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url, bool focus, bool new_window) {
818
if(!stack) {
819
// Find the current area
820
stack = controlled_stack;
821
}
822
Glib::ustring url_ = url;
823
if(url.empty()) {
824
url_ = new_tab_page;
825
}
826
827
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
828
gtk_widget_set_hexpand(GTK_WIDGET(webview), true);
829
gtk_widget_set_vexpand(GTK_WIDGET(webview), true);
830
auto page_content = Gtk::make_managed<Gtk::Box>();
831
gtk_box_append(page_content->gobj(), GTK_WIDGET(webview));
832
auto page_tab = new Gtk::Box();
833
page_tab->set_orientation(Gtk::Orientation::HORIZONTAL);
834
auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic"));
835
page_tab->append(*initial_icon);
836
page_tab->append(*Gtk::make_managed<Gtk::Label>(_("Untitled")));
837
page_tab->set_spacing(4);
838
auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab);
839
page->signal_close.connect([webview]() {
840
webkit_web_view_try_close(WEBKIT_WEB_VIEW(webview));
841
return true;
842
});
843
g_signal_connect(webview, "close", G_CALLBACK(close_callback), this);
844
g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), this);
845
g_signal_connect(webview, "load-changed", G_CALLBACK(load_change_callback), this);
846
g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this);
847
webkit_web_view_load_uri(webview, url_.data());
848
auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_web_view_get_network_session(webview));
849
webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
850
webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
851
auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_web_view_get_network_session(webview));
852
webkit_website_data_manager_set_favicons_enabled(website_data_manager, true);
853
GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
854
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8);
855
g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview);
856
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back);
857
858
GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
859
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9);
860
g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview);
861
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward);
862
863
stack->add_page(*page);
864
if(focus) {
865
stack->set_visible_child(*page);
866
content_manager->set_last_operated_page(page);
867
}
868
if(new_window) {
869
bool result = stack->signal_detach.emit(page);
870
}
871
}
872
873
PantheraWindow *PantheraWww::make_window() {
874
auto window = Gtk::make_managed<PantheraWindow>(this);
875
return window;
876
}
877
878
void PantheraWww::on_startup() {
879
bindtextdomain("panthera-www", "./locales");
880
textdomain("panthera-www");
881
content_manager = std::make_shared<gPanthera::ContentManager>();
882
Gtk::Application::on_startup();
883
// Get search engines
884
std::ifstream search_engines_file_in("search_engines.json");
885
if(search_engines_file_in) {
886
std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>());
887
search_engines_file_in.close();
888
set_search_engines_from_json(search_engines_json);
889
} else {
890
search_engines_file_in.close();
891
auto empty_json = nlohmann::json::array();
892
std::ofstream search_engines_file_out("search_engines.json");
893
search_engines_file_out << empty_json.dump(4);
894
}
895
896
// Set window titles on tab switch
897
content_manager->signal_page_operated.connect([this](gPanthera::ContentPage *page) {
898
if(!page) {
899
return;
900
}
901
if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) {
902
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) {
903
window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
904
} else {
905
window->set_title(_("Untitled"));
906
}
907
}
908
});
909
910
// Load settings
911
new_tab_page = "about:blank";
912
std::ifstream settings_file_in("settings.json");
913
if(settings_file_in) {
914
std::string settings_json((std::istreambuf_iterator<char>(settings_file_in)), std::istreambuf_iterator<char>());
915
settings_file_in.close();
916
auto json = nlohmann::json::parse(settings_json);
917
if(json.contains("ntp")) {
918
new_tab_page = json["ntp"].get<std::string>();
919
}
920
} else {
921
settings_file_in.close();
922
auto empty_json = nlohmann::json::object();
923
empty_json["ntp"] = "about:blank";
924
std::ofstream settings_file_out("settings.json");
925
settings_file_out << empty_json.dump(4);
926
settings_file_out.close();
927
}
928
929
// Load plugins
930
std::ifstream plugin_file_in("plugins.json");
931
if(plugin_file_in) {
932
std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>());
933
plugin_file_in.close();
934
auto json = nlohmann::json::parse(plugin_json);
935
for(auto &plugin : json) {
936
auto plugin_path = plugin["path"].get<std::string>();
937
//load_plugin(plugin_path);
938
}
939
} else {
940
plugin_file_in.close();
941
auto empty_json = nlohmann::json::array();
942
std::ofstream plugin_file_out("plugins.json");
943
plugin_file_out << empty_json.dump(4);
944
plugin_file_out.close();
945
}
946
history_manager = std::make_shared<HistoryManager>("history.db");
947
948
// Known bug <https://gitlab.gnome.org/GNOME/epiphany/-/issues/2714>:
949
// JS can't veto the application's accelerators using `preventDefault()`. This is
950
// not possible in WebKitGTK, or at least I can't find a way to do it.
951
952
main_menu = Gio::Menu::create();
953
// File
954
auto file_menu = Gio::Menu::create();
955
// New tab
956
auto new_tab_action = Gio::SimpleAction::create("new_tab");
957
new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
958
on_new_tab(nullptr);
959
});
960
add_action(new_tab_action);
961
set_accels_for_action("app.new_tab", {"<Primary>T"});
962
file_menu->append(_("New _Tab"), "app.new_tab");
963
// Close tab
964
auto close_tab_action = Gio::SimpleAction::create("close_tab");
965
close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
966
auto page = content_manager->get_last_operated_page();
967
if(page) {
968
page->close();
969
}
970
});
971
add_action(close_tab_action);
972
set_accels_for_action("app.close_tab", {"<Primary>W"});
973
file_menu->append(_("_Close Tab"), "app.close_tab");
974
// New window
975
auto new_window_action = Gio::SimpleAction::create("new_window");
976
new_window_action->signal_activate().connect([this](const Glib::VariantBase&) {
977
on_activate();
978
});
979
add_action(new_window_action);
980
set_accels_for_action("app.new_window", {"<Primary>N"});
981
file_menu->append(_("_New Window"), "app.new_window");
982
// Quit
983
auto quit_action = Gio::SimpleAction::create("quit");
984
quit_action->signal_activate().connect([this](const Glib::VariantBase&) {
985
quit();
986
});
987
add_action(quit_action);
988
set_accels_for_action("app.quit", {"<Primary>Q"});
989
file_menu->append(_("_Quit"), "app.quit");
990
991
main_menu->append_submenu(_("_File"), file_menu);
992
993
// View
994
auto view_menu = Gio::Menu::create();
995
// Reload
996
set_accels_for_action("win.reload", {"F5", "<Primary>R"});
997
view_menu->append(_("_Reload"), "win.reload");
998
999
main_menu->append_submenu(_("_View"), view_menu);
1000
1001
// Go
1002
auto go_menu = Gio::Menu::create();
1003
// Back
1004
set_accels_for_action("win.go_back", {"<Alt>Left"});
1005
go_menu->append(_("_Back"), "win.go_back");
1006
// Forward
1007
set_accels_for_action("win.go_forward", {"<Alt>Right"});
1008
go_menu->append(_("_Forward"), "win.go_forward");
1009
1010
main_menu->append_submenu(_("_Go"), go_menu);
1011
1012
set_menubar(main_menu);
1013
}
1014
1015
void PantheraWww::on_activate() {
1016
auto window = make_window();
1017
window->present();
1018
}
1019
1020
void PantheraWww::on_shutdown() {
1021
Gtk::Application::on_shutdown();
1022
}
1023
1024
void PantheraWww::set_search_engines_from_json(const std::string &json_string) {
1025
auto json = nlohmann::json::parse(json_string);
1026
Glib::ustring default_search_engine_name;
1027
for(auto &engine : json) {
1028
SearchEngine search_engine;
1029
search_engine.name = engine["name"].get<std::string>();
1030
search_engine.url = engine["url"].get<std::string>();
1031
search_engines.push_back(search_engine);
1032
if(engine.contains("default") && engine["default"].get<bool>()) {
1033
default_search_engine_name = engine["name"].get<std::string>();
1034
}
1035
}
1036
for(auto &search_engine : search_engines) {
1037
if(search_engine.name == default_search_engine_name) {
1038
default_search_engine = &search_engine;
1039
break;
1040
}
1041
}
1042
}
1043
1044
PantheraWww::PantheraWww() : Gtk::Application("com.roundabout_host.roundabout.PantheraWww", Gio::Application::Flags::NONE) {
1045
}
1046
1047
Glib::RefPtr<PantheraWww> PantheraWww::create() {
1048
return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww());
1049
}
1050
1051
void PantheraWww::load_plugin(const std::string &plugin_path) {
1052
// TODO: needs to be reworked, to work with multi-window
1053
void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
1054
if(!handle) {
1055
std::cerr << "Failed to load plugin: " << dlerror() << std::endl;
1056
return;
1057
}
1058
auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init");
1059
if(!entrypoint) {
1060
std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl;
1061
dlclose(handle);
1062
return;
1063
}
1064
entrypoint();
1065
}
1066
1067
PantheraWww::~PantheraWww() = default;
1068
1069
/*
1070
extern "C" {
1071
void panthera_log(const char *message) {
1072
std::cerr << message << std::endl;
1073
}
1074
1075
void panthera_add_pane(const char *id, const char *label, GtkImage *icon, GtkWidget *pane) {
1076
auto pane_child = Glib::wrap(pane);
1077
auto icon_cc = Glib::wrap(icon);
1078
auto label_cc = Glib::ustring(label);
1079
auto id_cc = Glib::ustring(id);
1080
//auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc);
1081
// TODO: Port to multi-window
1082
//app->get_layout_manager()->add_pane(new_pane);
1083
}
1084
}
1085
*/
1086
1087
int main(int argc, char *argv[]) {
1088
gPanthera::init();
1089
1090
auto app = PantheraWww::create();
1091
1092
return app->run(argc, argv);
1093
}
1094