panthera-www.cc
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
static void download_callback(WebKitNetworkSession *source, WebKitDownload *download, gpointer user_data);
321
static bool decide_destination_callback(WebKitDownload *download, gchar* suggested_filename, gpointer user_data);
322
323
protected:
324
void on_startup() override;
325
void on_activate() override;
326
void on_shutdown() override;
327
PantheraWindow *make_window();
328
329
public:
330
PantheraWww();
331
~PantheraWww();
332
friend class PantheraWindow;
333
void on_new_tab(gPanthera::ContentStack* stack, const Glib::ustring& url = "", bool focus = true, bool new_window = false);
334
void set_search_engines_from_json(const std::string& json_string);
335
static Glib::RefPtr<PantheraWww> create();
336
std::shared_ptr<gPanthera::ContentManager> get_content_manager();
337
void load_plugin(const std::string& plugin_path);
338
};
339
340
class PantheraWindow : public Gtk::ApplicationWindow {
341
private:
342
std::shared_ptr<gPanthera::LayoutManager> layout_manager;
343
Gtk::Entry *url_bar;
344
HistoryViewer *history_viewer;
345
gPanthera::ContentPage *controlled_page = nullptr;
346
gPanthera::ContentStack *controlled_stack = nullptr;
347
Glib::RefPtr<Gio::SimpleAction> back_action, forward_action, reload_action;
348
Gtk::Button *go_button;
349
350
void enable_controls() {
351
back_action->set_enabled(true);
352
forward_action->set_enabled(true);
353
reload_action->set_enabled(true);
354
url_bar->set_sensitive(true);
355
go_button->set_sensitive(true);
356
if(controlled_page) {
357
g_object_notify(G_OBJECT(controlled_page->get_child()->get_first_child()->gobj()), "uri");
358
}
359
}
360
361
void disable_controls() {
362
back_action->set_enabled(false);
363
forward_action->set_enabled(false);
364
reload_action->set_enabled(false);
365
url_bar->set_sensitive(false);
366
url_bar->set_text("");
367
go_button->set_sensitive(false);
368
}
369
370
sigc::connection moving_connection, moved_connection, closing_connection, closed_connection;
371
public:
372
friend class PantheraWww;
373
374
explicit PantheraWindow(Gtk::Application *application) : Gtk::ApplicationWindow() {
375
// There is a constructor with Glib::RefPtr<Gtk::Application>, but it is not appropriate
376
// because the window will own the application, creating a cycle
377
auto panthera = dynamic_cast<PantheraWww*>(application);
378
if(!panthera) {
379
throw std::runtime_error("Application is not a PantheraWww instance");
380
}
381
panthera->add_window(*this);
382
this->set_default_size(800, 600);
383
layout_manager = std::make_shared<gPanthera::LayoutManager>();
384
385
auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("One"), "one");
386
auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1, Gtk::Orientation::HORIZONTAL);
387
auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, _("Two"), "two");
388
auto switcher_2 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_2, Gtk::Orientation::VERTICAL);
389
auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
390
auto debug_button = Gtk::make_managed<Gtk::Button>("Debug");
391
auto print_history_button = Gtk::make_managed<Gtk::Button>("Print history");
392
print_history_button->signal_clicked().connect([this, panthera]() {
393
auto history = panthera->history_manager->get_history();
394
for(const auto &entry : history) {
395
std::cout << "entry " << entry.id << ", url " << entry.url << ", unix time " << entry.timestamp << std::endl;
396
// TODO: exclude redirecting URLs
397
}
398
});
399
pane_1_content->append(*debug_button);
400
pane_1_content->append(*print_history_button);
401
auto pane_1_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("help-about-symbolic"));
402
auto pane_1 = Gtk::make_managed<gPanthera::DockablePane>(layout_manager, *pane_1_content, "debug", _("Debugging options"), pane_1_icon);
403
404
dock_stack_1->set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
405
dock_stack_1->set_transition_duration(125);
406
dock_stack_1->set_expand(true);
407
dock_stack_2->set_transition_type(Gtk::StackTransitionType::SLIDE_UP_DOWN);
408
dock_stack_2->set_transition_duration(125);
409
dock_stack_2->set_expand(true);
410
411
auto outer_grid = Gtk::make_managed<Gtk::Grid>();
412
outer_grid->attach(*switcher_2, 0, 2, 1, 1);
413
outer_grid->attach(*switcher_1, 1, 3, 1, 1);
414
auto outer_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL);
415
outer_paned->set_start_child(*dock_stack_2);
416
auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
417
auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0);
418
std::function<bool(gPanthera::ContentPage*)> detach_handler;
419
420
detach_handler = [this](gPanthera::ContentPage *widget) {
421
auto new_stack = Gtk::make_managed<gPanthera::ContentStack>(widget->content_manager, widget->get_stack()->get_detach_handler());
422
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());
423
auto new_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(new_stack, new_switcher);
424
auto window = new gPanthera::ContentWindow(new_notebook);
425
widget->redock(new_stack);
426
window->present();
427
window->signal_close_request().connect([window]() {
428
auto webviews = collect_webviews(*window);
429
for(auto view : webviews) {
430
webkit_web_view_try_close(view);
431
}
432
if(!has_webview(*window)) {
433
// All documents have been closed safely, the window can be closed
434
return false;
435
}
436
return true;
437
}, false);
438
controlled_page = nullptr;
439
controlled_stack = nullptr;
440
disable_controls();
441
return true;
442
};
443
444
auto return_extra_child = [this, panthera](gPanthera::ContentTabBar *switcher) {
445
auto new_tab_button = Gtk::make_managed<Gtk::Button>();
446
new_tab_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("list-add-symbolic")));
447
new_tab_button->set_tooltip_text(_("New Tab"));
448
new_tab_button->signal_clicked().connect([this, switcher, panthera]() {
449
panthera->on_new_tab(switcher->get_stack());
450
});
451
return new_tab_button;
452
};
453
controlled_stack = Gtk::make_managed<gPanthera::ContentStack>(panthera->content_manager, detach_handler);
454
panthera->controlled_stack = controlled_stack;
455
auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(controlled_stack, Gtk::Orientation::HORIZONTAL, return_extra_child);
456
panthera->content_manager->add_stack(controlled_stack);
457
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); // for some reason, this has to be created
458
content->set_name("content_box");
459
auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(controlled_stack, content_stack_switcher, Gtk::PositionType::TOP);
460
content->append(*content_notebook);
461
inner_paned->set_start_child(*content);
462
inner_paned->set_end_child(*dock_stack_1);
463
outer_paned->set_end_child(*inner_paned);
464
outer_grid->attach(*outer_paned, 1, 2, 1, 1);
465
// Create the toolbar
466
auto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
467
// URL bar
468
url_bar = Gtk::make_managed<Gtk::Entry>();
469
url_bar->set_placeholder_text(_("Enter URL"));
470
url_bar->set_hexpand(true);
471
auto load_url_callback = [this, panthera]() {
472
bool has_protocol = url_bar->get_text().find("://") != std::string::npos;
473
if(!has_protocol) {
474
url_bar->set_text("http://" + url_bar->get_text());
475
}
476
if(controlled_page) {
477
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
478
webkit_web_view_load_uri(webview, url_bar->get_text().c_str());
479
}
480
}
481
};
482
// Go
483
go_button = Gtk::make_managed<Gtk::Button>(_("Go"));
484
go_button->signal_clicked().connect(load_url_callback);
485
url_bar->signal_activate().connect(load_url_callback);
486
panthera->content_manager->signal_page_operated.connect([this, panthera](gPanthera::ContentPage *page) {
487
if(!page) {
488
return;
489
}
490
if(!page->get_child()) {
491
return;
492
}
493
if(!page->get_child()->get_first_child()) {
494
return;
495
}
496
if(page->get_root() != this) {
497
// The page is in some other window
498
return;
499
}
500
controlled_page = page;
501
controlled_stack = page->get_stack();
502
panthera->controlled_stack = page->get_stack();
503
if(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
504
if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) {
505
url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
506
}
507
guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(panthera->notify_focused_callback), this);
508
std::shared_ptr<sigc::connection> control_signal_handler = std::make_shared<sigc::connection>();
509
*control_signal_handler = page->signal_control_status_changed.connect([this, page, control_signal_handler, url_update_handler](bool controlled) {
510
if(!controlled) {
511
control_signal_handler->disconnect();
512
if(page->get_child() && page->get_child()->get_first_child() && WEBKIT_IS_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
513
g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler);
514
}
515
}
516
});
517
}
518
519
enable_controls();
520
});
521
history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, panthera->history_manager);
522
history_viewer->signal_open_url.connect([this, panthera](const std::string &url) {
523
panthera->on_new_tab(nullptr, url, true);
524
});
525
// Back, forward, reload
526
auto back_button = Gtk::make_managed<Gtk::Button>();
527
back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic")));
528
back_button->set_tooltip_text(_("Back"));
529
auto forward_button = Gtk::make_managed<Gtk::Button>();
530
forward_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-next-symbolic")));
531
forward_button->set_tooltip_text(_("Forward"));
532
auto reload_button = Gtk::make_managed<Gtk::Button>();
533
reload_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("view-refresh-symbolic")));
534
reload_button->set_tooltip_text(_("Reload"));
535
536
back_action = Gio::SimpleAction::create("go_back");
537
add_action(back_action);
538
back_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
539
if(controlled_page) {
540
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
541
webkit_web_view_go_back(webview);
542
}
543
}
544
});
545
back_button->set_action_name("win.go_back");
546
547
forward_action = Gio::SimpleAction::create("go_forward");
548
add_action(forward_action);
549
forward_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
550
if(controlled_page) {
551
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
552
webkit_web_view_go_forward(webview);
553
}
554
}
555
});
556
forward_button->set_action_name("win.go_forward");
557
558
reload_action = Gio::SimpleAction::create("reload");
559
add_action(reload_action);
560
reload_action->signal_activate().connect([this, panthera](const Glib::VariantBase&) {
561
if(controlled_page) {
562
if(auto webview = WEBKIT_WEB_VIEW(controlled_page->get_child()->get_first_child()->gobj())) {
563
webkit_web_view_reload(webview);
564
}
565
}
566
});
567
reload_button->set_action_name("win.reload");
568
569
// Search bar
570
// TODO: provide history, provide a menu button with the other engines
571
auto search_bar = Gtk::make_managed<Gtk::Entry>();
572
search_bar->set_placeholder_text(_("Search"));
573
search_bar->set_hexpand(true);
574
if(panthera->search_engines.empty()) {
575
search_bar->set_sensitive(false);
576
search_bar->set_placeholder_text(_("No search"));
577
}
578
auto search_callback = [this, search_bar, panthera]() {
579
// Create a new tab with the search results
580
panthera->on_new_tab(controlled_stack);
581
auto page = panthera->content_manager->get_last_operated_page();
582
if(page) {
583
if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) {
584
auto search_url = panthera->default_search_engine->get_search_url(search_bar->get_text());
585
webkit_web_view_load_uri(webview, search_url.c_str());
586
}
587
}
588
};
589
if(panthera->default_search_engine) {
590
search_bar->signal_activate().connect(search_callback);
591
}
592
593
// Assemble the toolbar
594
main_toolbar->append(*back_button);
595
main_toolbar->append(*forward_button);
596
main_toolbar->append(*reload_button);
597
main_toolbar->append(*url_bar);
598
main_toolbar->append(*go_button);
599
main_toolbar->append(*search_bar);
600
if(panthera->default_search_engine) {
601
auto search_button = Gtk::make_managed<Gtk::Button>(panthera->default_search_engine->name);
602
search_button->signal_clicked().connect(search_callback);
603
main_toolbar->append(*search_button);
604
}
605
outer_grid->attach(*main_toolbar, 0, 1, 2, 1);
606
this->set_child(*outer_grid);
607
debug_button->signal_clicked().connect([this, panthera]() {
608
if(panthera->content_manager->get_last_operated_page()) {
609
std::cout << "Last operated page: " << panthera->content_manager->get_last_operated_page()->get_name() << std::endl;
610
} else {
611
std::cout << "No page operated!" << std::endl;
612
}
613
});
614
// TODO: Use the last operated page and allow opening tabs next to the last operated page using certain panes
615
// Load the existing layout, if it exists
616
std::ifstream layout_file_in("layout.json");
617
if(layout_file_in) {
618
std::string layout_json((std::istreambuf_iterator<char>(layout_file_in)), std::istreambuf_iterator<char>());
619
layout_file_in.close();
620
layout_manager->restore_json_layout(layout_json);
621
} else {
622
// Create a new layout if the file doesn't exist
623
layout_file_in.close();
624
625
dock_stack_1->add_pane(*pane_1);
626
dock_stack_1->add_pane(*history_viewer);
627
628
std::ofstream layout_file_out("layout.json");
629
layout_file_out << layout_manager->get_layout_as_json();
630
layout_file_out.close();
631
}
632
// Save the layout when changed
633
layout_manager->signal_pane_moved.connect([this, panthera](gPanthera::DockablePane *pane) {
634
std::ofstream layout_file_out("layout.json");
635
layout_file_out << layout_manager->get_layout_as_json();
636
layout_file_out.close();
637
std::cout << "Layout changed: " << layout_manager->get_layout_as_json() << std::endl;
638
});
639
set_show_menubar(true);
640
641
// As the window is initially empty, disable most actions
642
disable_controls();
643
644
moving_connection = panthera->content_manager->signal_page_moving.connect([this, panthera](gPanthera::ContentPage *page) {
645
if(page->get_root() == this) {
646
controlled_page = nullptr;
647
controlled_stack = nullptr;
648
disable_controls();
649
}
650
});
651
moved_connection = panthera->content_manager->signal_page_moved.connect([this, panthera](gPanthera::ContentPage *page) {
652
if(page->get_root() == this) {
653
panthera->content_manager->set_last_operated_page(page);
654
page->get_stack()->set_visible_child(*page);
655
enable_controls();
656
}
657
});
658
659
signal_close_request().connect([this]() {
660
auto webviews = collect_webviews(*this);
661
for(auto view : webviews) {
662
webkit_web_view_try_close(view);
663
}
664
if(!has_webview(*this)) {
665
// All documents have been closed safely, the window can be closed
666
return false;
667
}
668
return true;
669
}, false);
670
671
closing_connection = panthera->content_manager->signal_page_closing.connect([this](gPanthera::ContentPage *page) {
672
if(page->get_root() != this) {
673
return;
674
}
675
disable_controls();
676
});
677
closed_connection = panthera->content_manager->signal_page_closed.connect([this](gPanthera::ContentPage *page) {
678
if(has_webview(*this)) {
679
enable_controls();
680
}
681
});
682
}
683
~PantheraWindow() override {
684
moving_connection.disconnect();
685
moved_connection.disconnect();
686
closed_connection.disconnect();
687
closing_connection.disconnect();
688
}
689
};
690
691
void PantheraWww::load_change_callback(WebKitWebView *object, WebKitLoadEvent load_event, gpointer data) {
692
if(auto self = static_cast<PantheraWww*>(data)) {
693
if(load_event == WEBKIT_LOAD_COMMITTED) {
694
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) {
695
self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), "");
696
} else {
697
self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)),
698
webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
699
}
700
// TODO: reload visible history viewers
701
}
702
}
703
}
704
705
void PantheraWww::notify_callback(GObject *object, GParamSpec *pspec, gpointer data) {
706
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
707
return;
708
}
709
if(auto self = static_cast<PantheraWww*>(data)) {
710
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
711
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
712
if(g_strcmp0(pspec->name, "title") == 0) {
713
if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) {
714
if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) {
715
label->set_label(_("Untitled"));
716
} else {
717
label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
718
}
719
}
720
} else if(g_strcmp0(pspec->name, "favicon") == 0) {
721
// Update favicons
722
if(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) {
723
image->set_from_icon_name("image-loading-symbolic");
724
if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) {
725
gtk_image_set_from_paintable(image->gobj(), GDK_PAINTABLE(favicon));
726
}
727
}
728
}
729
}
730
}
731
}
732
733
void PantheraWww::notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) {
734
if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
735
return;
736
}
737
auto this_ = static_cast<PantheraWww*>(data);
738
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));
739
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
740
if(g_strcmp0(pspec->name, "uri") == 0) {
741
if(auto main_window = dynamic_cast<PantheraWindow*>(page->get_root())) {
742
if(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object))) {
743
main_window->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));
744
}
745
}
746
} else if(g_strcmp0(pspec->name, "title") == 0) {
747
if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) {
748
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) {
749
window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));
750
} else {
751
window->set_title(_("Untitled"));
752
}
753
}
754
}
755
}
756
}
757
758
void PantheraWww::on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
759
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
760
webkit_web_view_go_back(webview);
761
}
762
763
void PantheraWww::on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
764
WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);
765
webkit_web_view_go_forward(webview);
766
}
767
768
gboolean PantheraWww::on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) {
769
if(auto self = static_cast<PantheraWww*>(user_data)) {
770
if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {
771
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
772
Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)));
773
if(!starts_with(url, "about:") && url.find("://") == Glib::ustring::npos) {
774
// It is a special scheme (mailto:, tel: etc.)
775
try {
776
Gio::AppInfo::launch_default_for_uri(url);
777
} catch(const Glib::Error &exception) {
778
std::cerr << "Failed to launch special URI: " << exception.what() << std::endl;
779
}
780
webkit_policy_decision_ignore(decision);
781
return true;
782
}
783
if(webkit_navigation_action_get_mouse_button(action) == 2) {
784
// Middle-click opens in a new window
785
self->on_new_tab(nullptr, url, false);
786
webkit_policy_decision_ignore(decision);
787
return true;
788
}
789
} else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {
790
auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));
791
self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true);
792
return true;
793
}
794
}
795
return false;
796
}
797
798
void PantheraWww::close_callback(WebKitWebView *source, gpointer user_data) {
799
if(auto self = static_cast<PantheraWww*>(user_data)) {
800
auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(source)));
801
if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {
802
if(page->get_next_sibling()) {
803
page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_next_sibling()));
804
page->get_stack()->set_visible_child(*page->get_next_sibling());
805
} else if(page->get_prev_sibling()) {
806
page->content_manager->set_last_operated_page(static_cast<gPanthera::ContentPage*>(page->get_prev_sibling()));
807
page->get_stack()->set_visible_child(*page->get_prev_sibling());
808
}
809
webkit_web_view_terminate_web_process(source);
810
if(page->content_manager->get_last_operated_page() == page) {
811
page->content_manager->set_last_operated_page(nullptr);
812
}
813
page->redock(nullptr);
814
self->content_manager->signal_page_closed.emit(page);
815
}
816
}
817
}
818
819
void PantheraWww::download_callback(WebKitNetworkSession *source, WebKitDownload *download, gpointer user_data) {
820
if(auto self = static_cast<PantheraWww*>(user_data)) {
821
// TODO: free the WebKitDownload
822
g_signal_connect(download, "decide-destination", G_CALLBACK(decide_destination_callback), self);
823
}
824
}
825
826
bool PantheraWww::decide_destination_callback(WebKitDownload *download, gchar *suggested_filename, gpointer user_data) {
827
if(auto self = static_cast<PantheraWww*>(user_data)) {
828
auto file_dialog = Gtk::FileDialog::create();
829
file_dialog->set_accept_label(_("Download"));
830
file_dialog->set_modal(false);
831
file_dialog->set_initial_name(std::string(suggested_filename));
832
if(auto *window = dynamic_cast<Gtk::Window*>(Glib::wrap_auto(G_OBJECT(gtk_widget_get_root(GTK_WIDGET(webkit_download_get_web_view(download))))))) {
833
file_dialog->save(*window, Gio::SlotAsyncReady([file_dialog, download](Glib::RefPtr<Gio::AsyncResult> &result) {
834
try {
835
auto file = file_dialog->save_finish(result);
836
webkit_download_set_destination(download, file->get_path().c_str());
837
} catch(Gtk::DialogError &e) {
838
// Download rejected
839
}
840
}));
841
}
842
//g_free(suggested_filename);
843
return true;
844
}
845
return true;
846
}
847
848
void PantheraWww::on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url, bool focus, bool new_window) {
849
if(!stack) {
850
// Find the current area
851
stack = controlled_stack;
852
}
853
Glib::ustring url_ = url;
854
if(url.empty()) {
855
url_ = new_tab_page;
856
}
857
858
WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
859
gtk_widget_set_hexpand(GTK_WIDGET(webview), true);
860
gtk_widget_set_vexpand(GTK_WIDGET(webview), true);
861
auto page_content = Gtk::make_managed<Gtk::Box>();
862
gtk_box_append(page_content->gobj(), GTK_WIDGET(webview));
863
auto page_tab = new Gtk::Box();
864
page_tab->set_orientation(Gtk::Orientation::HORIZONTAL);
865
auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic"));
866
page_tab->append(*initial_icon);
867
page_tab->append(*Gtk::make_managed<Gtk::Label>(_("Untitled")));
868
page_tab->set_spacing(4);
869
auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab);
870
page->signal_close.connect([webview]() {
871
webkit_web_view_try_close(WEBKIT_WEB_VIEW(webview));
872
return true;
873
});
874
g_signal_connect(webview, "close", G_CALLBACK(close_callback), this);
875
g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), this);
876
g_signal_connect(webview, "load-changed", G_CALLBACK(load_change_callback), this);
877
g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this);
878
webkit_web_view_load_uri(webview, url_.data());
879
auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_network_session_get_default());
880
webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
881
webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);
882
auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_network_session_get_default());
883
webkit_website_data_manager_set_favicons_enabled(website_data_manager, true);
884
GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
885
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8);
886
g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview);
887
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back);
888
889
GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());
890
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9);
891
g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview);
892
gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward);
893
894
stack->add_page(*page);
895
if(focus) {
896
stack->set_visible_child(*page);
897
content_manager->set_last_operated_page(page);
898
}
899
if(new_window) {
900
bool result = stack->signal_detach.emit(page);
901
}
902
}
903
904
PantheraWindow *PantheraWww::make_window() {
905
auto window = Gtk::make_managed<PantheraWindow>(this);
906
return window;
907
}
908
909
void PantheraWww::on_startup() {
910
bindtextdomain("panthera-www", "./locales");
911
textdomain("panthera-www");
912
content_manager = std::make_shared<gPanthera::ContentManager>();
913
Gtk::Application::on_startup();
914
// Get search engines
915
std::ifstream search_engines_file_in("search_engines.json");
916
if(search_engines_file_in) {
917
std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>());
918
search_engines_file_in.close();
919
set_search_engines_from_json(search_engines_json);
920
} else {
921
search_engines_file_in.close();
922
auto empty_json = nlohmann::json::array();
923
std::ofstream search_engines_file_out("search_engines.json");
924
search_engines_file_out << empty_json.dump(4);
925
}
926
927
// Set window titles on tab switch
928
content_manager->signal_page_operated.connect([this](gPanthera::ContentPage *page) {
929
if(!page) {
930
return;
931
}
932
if(auto window = dynamic_cast<Gtk::Window*>(page->get_root())) {
933
if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))) {
934
window->set_title(webkit_web_view_get_title(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())));
935
} else {
936
window->set_title(_("Untitled"));
937
}
938
}
939
});
940
941
// Load settings
942
new_tab_page = "about:blank";
943
std::ifstream settings_file_in("settings.json");
944
if(settings_file_in) {
945
std::string settings_json((std::istreambuf_iterator<char>(settings_file_in)), std::istreambuf_iterator<char>());
946
settings_file_in.close();
947
auto json = nlohmann::json::parse(settings_json);
948
if(json.contains("ntp")) {
949
new_tab_page = json["ntp"].get<std::string>();
950
}
951
} else {
952
settings_file_in.close();
953
auto empty_json = nlohmann::json::object();
954
empty_json["ntp"] = "about:blank";
955
std::ofstream settings_file_out("settings.json");
956
settings_file_out << empty_json.dump(4);
957
settings_file_out.close();
958
}
959
960
// Load plugins
961
std::ifstream plugin_file_in("plugins.json");
962
if(plugin_file_in) {
963
std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>());
964
plugin_file_in.close();
965
auto json = nlohmann::json::parse(plugin_json);
966
for(auto &plugin : json) {
967
auto plugin_path = plugin["path"].get<std::string>();
968
//load_plugin(plugin_path);
969
}
970
} else {
971
plugin_file_in.close();
972
auto empty_json = nlohmann::json::array();
973
std::ofstream plugin_file_out("plugins.json");
974
plugin_file_out << empty_json.dump(4);
975
plugin_file_out.close();
976
}
977
history_manager = std::make_shared<HistoryManager>("history.db");
978
979
// Known bug <https://gitlab.gnome.org/GNOME/epiphany/-/issues/2714>:
980
// JS can't veto the application's accelerators using `preventDefault()`. This is
981
// not possible in WebKitGTK, or at least I can't find a way to do it.
982
983
main_menu = Gio::Menu::create();
984
// File
985
auto file_menu = Gio::Menu::create();
986
// New tab
987
auto new_tab_action = Gio::SimpleAction::create("new_tab");
988
new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
989
on_new_tab(nullptr);
990
});
991
add_action(new_tab_action);
992
set_accels_for_action("app.new_tab", {"<Primary>T"});
993
file_menu->append(_("New _Tab"), "app.new_tab");
994
// Close tab
995
auto close_tab_action = Gio::SimpleAction::create("close_tab");
996
close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {
997
auto page = content_manager->get_last_operated_page();
998
if(page) {
999
page->close();
1000
}
1001
});
1002
add_action(close_tab_action);
1003
set_accels_for_action("app.close_tab", {"<Primary>W"});
1004
file_menu->append(_("_Close Tab"), "app.close_tab");
1005
// New window
1006
auto new_window_action = Gio::SimpleAction::create("new_window");
1007
new_window_action->signal_activate().connect([this](const Glib::VariantBase&) {
1008
on_activate();
1009
});
1010
add_action(new_window_action);
1011
set_accels_for_action("app.new_window", {"<Primary>N"});
1012
file_menu->append(_("_New Window"), "app.new_window");
1013
// Quit
1014
auto quit_action = Gio::SimpleAction::create("quit");
1015
quit_action->signal_activate().connect([this](const Glib::VariantBase&) {
1016
quit();
1017
});
1018
add_action(quit_action);
1019
set_accels_for_action("app.quit", {"<Primary>Q"});
1020
file_menu->append(_("_Quit"), "app.quit");
1021
1022
main_menu->append_submenu(_("_File"), file_menu);
1023
1024
// View
1025
auto view_menu = Gio::Menu::create();
1026
// Reload
1027
set_accels_for_action("win.reload", {"F5", "<Primary>R"});
1028
view_menu->append(_("_Reload"), "win.reload");
1029
1030
main_menu->append_submenu(_("_View"), view_menu);
1031
1032
// Go
1033
auto go_menu = Gio::Menu::create();
1034
// Back
1035
set_accels_for_action("win.go_back", {"<Alt>Left"});
1036
go_menu->append(_("_Back"), "win.go_back");
1037
// Forward
1038
set_accels_for_action("win.go_forward", {"<Alt>Right"});
1039
go_menu->append(_("_Forward"), "win.go_forward");
1040
1041
main_menu->append_submenu(_("_Go"), go_menu);
1042
1043
set_menubar(main_menu);
1044
1045
// Download handling
1046
g_signal_connect(webkit_network_session_get_default(), "download-started", G_CALLBACK(download_callback), this);
1047
}
1048
1049
void PantheraWww::on_activate() {
1050
auto window = make_window();
1051
window->present();
1052
}
1053
1054
void PantheraWww::on_shutdown() {
1055
Gtk::Application::on_shutdown();
1056
}
1057
1058
void PantheraWww::set_search_engines_from_json(const std::string &json_string) {
1059
auto json = nlohmann::json::parse(json_string);
1060
Glib::ustring default_search_engine_name;
1061
for(auto &engine : json) {
1062
SearchEngine search_engine;
1063
search_engine.name = engine["name"].get<std::string>();
1064
search_engine.url = engine["url"].get<std::string>();
1065
search_engines.push_back(search_engine);
1066
if(engine.contains("default") && engine["default"].get<bool>()) {
1067
default_search_engine_name = engine["name"].get<std::string>();
1068
}
1069
}
1070
for(auto &search_engine : search_engines) {
1071
if(search_engine.name == default_search_engine_name) {
1072
default_search_engine = &search_engine;
1073
break;
1074
}
1075
}
1076
}
1077
1078
PantheraWww::PantheraWww() : Gtk::Application("com.roundabout_host.roundabout.PantheraWww", Gio::Application::Flags::NONE) {
1079
}
1080
1081
Glib::RefPtr<PantheraWww> PantheraWww::create() {
1082
return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww());
1083
}
1084
1085
void PantheraWww::load_plugin(const std::string &plugin_path) {
1086
// TODO: needs to be reworked, to work with multi-window
1087
void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
1088
if(!handle) {
1089
std::cerr << "Failed to load plugin: " << dlerror() << std::endl;
1090
return;
1091
}
1092
auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init");
1093
if(!entrypoint) {
1094
std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl;
1095
dlclose(handle);
1096
return;
1097
}
1098
entrypoint();
1099
}
1100
1101
PantheraWww::~PantheraWww() = default;
1102
1103
/*
1104
extern "C" {
1105
void panthera_log(const char *message) {
1106
std::cerr << message << std::endl;
1107
}
1108
1109
void panthera_add_pane(const char *id, const char *label, GtkImage *icon, GtkWidget *pane) {
1110
auto pane_child = Glib::wrap(pane);
1111
auto icon_cc = Glib::wrap(icon);
1112
auto label_cc = Glib::ustring(label);
1113
auto id_cc = Glib::ustring(id);
1114
//auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc);
1115
// TODO: Port to multi-window
1116
//app->get_layout_manager()->add_pane(new_pane);
1117
}
1118
}
1119
*/
1120
1121
int main(int argc, char *argv[]) {
1122
gPanthera::init();
1123
1124
auto app = PantheraWww::create();
1125
1126
return app->run(argc, argv);
1127
}
1128