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