roundabout,
created on Monday, 23 June 2025, 14:16:51 (1750688211),
received on Monday, 23 June 2025, 14:16:54 (1750688214)
Author identity: vlad <vlad.muntoiu@gmail.com>
22da812df1c2ba4b4ab383b97c8ee90e9cf4be3b
panthera-www.cc
@@ -271,173 +271,52 @@ public:
}; class PantheraWww : public Gtk::Application { Gtk::ApplicationWindow *window = Gtk::make_managed<Gtk::ApplicationWindow>();protected:std::shared_ptr<gPanthera::LayoutManager> layout_manager;private: Gtk::ApplicationWindow* window;std::shared_ptr<gPanthera::ContentManager> content_manager; Gtk::Entry *url_bar = nullptr;std::string cookie_file = "cookies.txt";std::string cookie_file;std::vector<SearchEngine> search_engines; SearchEngine *default_search_engine = nullptr;SearchEngine* default_search_engine;std::shared_ptr<HistoryManager> history_manager; HistoryViewer *history_viewer = nullptr;HistoryViewer* history_viewer;Glib::RefPtr<Gio::Menu> main_menu; std::string new_tab_page;static void load_change_callback(WebKitWebView *object, WebKitLoadEvent load_event, gpointer data) {if(auto self = static_cast<PantheraWww*>(data)) {if(load_event == WEBKIT_LOAD_COMMITTED) {if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) {self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), "");} else {self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)),webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));}if(self->history_viewer->get_visible()) {self->history_viewer->reload_history();}}}}static void notify_callback(GObject *object, GParamSpec *pspec, gpointer data) {if(!gtk_widget_get_parent(GTK_WIDGET(object))) {return;}if(auto self = static_cast<PantheraWww*>(data)) {auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {if(g_strcmp0(pspec->name, "title") == 0) {if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) {if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) {label->set_label("Untitled");} else {label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)));}}} else if(g_strcmp0(pspec->name, "favicon") == 0) {// Update faviconsif(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) {image->set_from_icon_name("image-loading-symbolic");if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) {gtk_image_set_from_paintable(image->gobj(), GDK_PAINTABLE(favicon));}}}}}}static void notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) {if(!gtk_widget_get_parent(GTK_WIDGET(object))) {return;}auto this_ = static_cast<PantheraWww*>(data);auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object)));if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) {if(g_strcmp0(pspec->name, "uri") == 0) {this_->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));}}}static void on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);webkit_web_view_go_back(webview);}static void load_change_callback(WebKitWebView* object, WebKitLoadEvent load_event, gpointer data); static void notify_callback(GObject* object, GParamSpec* pspec, gpointer data); static void notify_focused_callback(GObject* object, GParamSpec* pspec, gpointer data); static void on_back_pressed(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data); static void on_forward_pressed(GtkGestureClick* gesture, int n_press, double x, double y, gpointer user_data); static gboolean on_decide_policy(WebKitWebView* source, WebKitPolicyDecision* decision, WebKitPolicyDecisionType type, gpointer user_data);static void on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data);webkit_web_view_go_forward(webview);}static gboolean on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) {if(auto self = static_cast<PantheraWww*>(user_data)) {if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) {auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)));if(!starts_with(url, "about:") && url.find("://") == Glib::ustring::npos) {// It is a special scheme (mailto:, tel: etc.)try {Gio::AppInfo::launch_default_for_uri(url);} catch(const Glib::Error &exception) {std::cerr << "Failed to launch special URI: " << exception.what() << std::endl;}webkit_policy_decision_ignore(decision);return true;}if(webkit_navigation_action_get_mouse_button(action) == 2) {// Middle-click opens in a new windowself->on_new_tab(nullptr, url, false);webkit_policy_decision_ignore(decision);return true;}} else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) {auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision));self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true);return true;}}return false;}std::string new_tab_page;protected: void on_startup() override; void on_activate() override;void on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url = "", bool focus = true, bool new_window = false) {if(!stack) {// Find the current areastack = content_manager->get_last_operated_page()->get_stack();}Glib::ustring url_ = url;if(url.empty()) {url_ = new_tab_page;}public: PantheraWww(); friend class PantheraWindow; void on_new_tab(gPanthera::ContentStack* stack, const Glib::ustring& url = "", bool focus = true, bool new_window = false); void set_search_engines_from_json(const std::string& json_string); static Glib::RefPtr<PantheraWww> create(); std::shared_ptr<gPanthera::ContentManager> get_content_manager(); void load_plugin(const std::string& plugin_path); };WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new());gtk_widget_set_hexpand(GTK_WIDGET(webview), true);gtk_widget_set_vexpand(GTK_WIDGET(webview), true);auto page_content = Gtk::make_managed<Gtk::Box>();gtk_box_append(page_content->gobj(), GTK_WIDGET(webview));auto page_tab = new Gtk::Box();page_tab->set_orientation(Gtk::Orientation::HORIZONTAL);auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic"));page_tab->append(*initial_icon);page_tab->append(*Gtk::make_managed<Gtk::Label>("Untitled"));page_tab->set_spacing(4);auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab);g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), this);g_signal_connect(webview, "load-changed", G_CALLBACK(load_change_callback), this);g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this);webkit_web_view_load_uri(webview, url_.data());auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_web_view_get_network_session(webview));webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS);auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_web_view_get_network_session(webview));webkit_website_data_manager_set_favicons_enabled(website_data_manager, true);GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8);g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview);gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back);GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new());gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9);g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview);gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward);stack->add_page(*page);if(focus) {stack->set_visible_child(*page);content_manager->set_last_operated_page(page);}if(new_window) {bool result = stack->signal_detach.emit(page);class PantheraWindow : public Gtk::ApplicationWindow { private: std::shared_ptr<gPanthera::LayoutManager> layout_manager; Gtk::Entry *url_bar; public: friend class PantheraWww; explicit PantheraWindow(Glib::RefPtr<Gtk::Application> const &application) : Gtk::ApplicationWindow(application) { auto panthera = dynamic_cast<PantheraWww*>(application.get()); if(!panthera) { throw std::runtime_error("Application is not a PantheraWww instance");} }void on_startup() override {Gtk::Application::on_startup();add_window(*window);window->set_default_size(600, 400);this->set_default_size(800, 600);layout_manager = std::make_shared<gPanthera::LayoutManager>(); auto dock_stack_1 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "One", "one"); auto switcher_1 = Gtk::make_managed<gPanthera::DockStackSwitcher>(dock_stack_1, Gtk::Orientation::HORIZONTAL); auto dock_stack_2 = Gtk::make_managed<gPanthera::DockStack>(layout_manager, "Two", "two");
@@ -445,8 +324,8 @@ protected:
auto pane_1_content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); auto debug_button = Gtk::make_managed<Gtk::Button>("Debug"); auto print_history_button = Gtk::make_managed<Gtk::Button>("Print history"); print_history_button->signal_clicked().connect([this]() {auto history = history_manager->get_history();print_history_button->signal_clicked().connect([this, panthera]() { auto history = panthera->history_manager->get_history();for(const auto &entry : history) { std::cout << "entry " << entry.id << ", url " << entry.url << ", unix time " << entry.timestamp << std::endl; // TODO: exclude redirecting URLs
@@ -471,7 +350,7 @@ protected:
outer_paned->set_start_child(*dock_stack_2); auto inner_paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL); auto content = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); content_manager = std::make_shared<gPanthera::ContentManager>();panthera->content_manager = std::make_shared<gPanthera::ContentManager>();std::function<bool(gPanthera::ContentPage*)> detach_handler; detach_handler = [](gPanthera::ContentPage *widget) { auto new_stack = Gtk::make_managed<gPanthera::ContentStack>(widget->content_manager, widget->get_stack()->get_detach_handler());
@@ -487,18 +366,18 @@ protected:
return true; }; auto return_extra_child = [this](gPanthera::ContentTabBar *switcher) {auto return_extra_child = [this, panthera](gPanthera::ContentTabBar *switcher) {auto new_tab_button = Gtk::make_managed<Gtk::Button>(); new_tab_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("list-add-symbolic"))); new_tab_button->set_tooltip_text("New tab"); new_tab_button->signal_clicked().connect([this, switcher]() {on_new_tab(switcher->get_stack());new_tab_button->signal_clicked().connect([this, switcher, panthera]() { panthera->on_new_tab(switcher->get_stack());}); return new_tab_button; }; auto content_stack = Gtk::make_managed<gPanthera::ContentStack>(content_manager, detach_handler);auto content_stack = Gtk::make_managed<gPanthera::ContentStack>(panthera->content_manager, detach_handler);auto content_stack_switcher = Gtk::make_managed<gPanthera::ContentTabBar>(content_stack, Gtk::Orientation::HORIZONTAL, return_extra_child); content_manager->add_stack(content_stack);panthera->content_manager->add_stack(content_stack);WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); // for some reason, this has to be created content->set_name("content_box"); auto content_notebook = Gtk::make_managed<gPanthera::ContentNotebook>(content_stack, content_stack_switcher, Gtk::PositionType::TOP);
@@ -507,28 +386,14 @@ protected:
inner_paned->set_end_child(*dock_stack_1); outer_paned->set_end_child(*inner_paned); outer_grid->attach(*outer_paned, 1, 2, 1, 1); // Get search enginesstd::ifstream search_engines_file_in("search_engines.json");if(search_engines_file_in) {std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>());search_engines_file_in.close();set_search_engines_from_json(search_engines_json);} else {search_engines_file_in.close();auto empty_json = nlohmann::json::array();std::ofstream search_engines_file_out("search_engines.json");search_engines_file_out << empty_json.dump(4);}// Create the toolbar auto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8); // URL bar url_bar = Gtk::make_managed<Gtk::Entry>(); url_bar->set_placeholder_text("Enter URL"); url_bar->set_hexpand(true); auto load_url_callback = [this]() {auto page = content_manager->get_last_operated_page();auto load_url_callback = [this, panthera]() { auto page = panthera->content_manager->get_last_operated_page();bool has_protocol = url_bar->get_text().find("://") != std::string::npos; if(!has_protocol) { url_bar->set_text("http://" + url_bar->get_text());
@@ -543,7 +408,7 @@ protected:
auto go_button = Gtk::make_managed<Gtk::Button>("Go"); go_button->signal_clicked().connect(load_url_callback); url_bar->signal_activate().connect(load_url_callback); content_manager->signal_page_operated.connect([this](gPanthera::ContentPage *page) {panthera->content_manager->signal_page_operated.connect([this, panthera](gPanthera::ContentPage *page) {if(!page->get_child()) { return; }
@@ -551,7 +416,7 @@ protected:
return; } url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj()))); guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(notify_focused_callback), this);guint url_update_handler = g_signal_connect(page->get_child()->get_first_child()->gobj(), "notify", G_CALLBACK(panthera->notify_focused_callback), this);std::shared_ptr<sigc::connection> control_signal_handler = std::make_shared<sigc::connection>(); *control_signal_handler = page->signal_control_status_changed.connect([this, page, control_signal_handler, url_update_handler](bool controlled) { if(!controlled) {
@@ -562,10 +427,10 @@ protected:
} }); }); history_manager = std::make_shared<HistoryManager>("history.db");history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, history_manager);history_viewer->signal_open_url.connect([this](const std::string &url) {on_new_tab(nullptr, url, true);panthera->history_manager = std::make_shared<HistoryManager>("history.db"); panthera->history_viewer = Gtk::make_managed<HistoryViewer>(layout_manager, panthera->history_manager); panthera->history_viewer->signal_open_url.connect([this, panthera](const std::string &url) { panthera->on_new_tab(nullptr, url, true);}); // Back, forward, reload auto back_button = Gtk::make_managed<Gtk::Button>();
@@ -577,24 +442,24 @@ protected:
auto reload_button = Gtk::make_managed<Gtk::Button>(); reload_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("view-refresh-symbolic"))); reload_button->set_tooltip_text("Reload"); back_button->signal_clicked().connect([this]() {auto page = content_manager->get_last_operated_page();back_button->signal_clicked().connect([this, panthera]() { auto page = panthera->content_manager->get_last_operated_page();if(page) { if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { webkit_web_view_go_back(webview); } } }); forward_button->signal_clicked().connect([this]() {auto page = content_manager->get_last_operated_page();forward_button->signal_clicked().connect([this, panthera]() { auto page = panthera->content_manager->get_last_operated_page();if(page) { if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { webkit_web_view_go_forward(webview); } } }); reload_button->signal_clicked().connect([this]() {auto page = content_manager->get_last_operated_page();reload_button->signal_clicked().connect([this, panthera]() { auto page = panthera->content_manager->get_last_operated_page();if(page) { if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { webkit_web_view_reload(webview);
@@ -606,28 +471,29 @@ protected:
auto search_bar = Gtk::make_managed<Gtk::Entry>(); search_bar->set_placeholder_text("Search"); search_bar->set_hexpand(true); if(search_engines.empty()) {if(panthera->search_engines.empty()) {search_bar->set_sensitive(false); search_bar->set_placeholder_text("No search"); } auto search_callback = [this, search_bar, content_stack]() {auto search_callback = [this, search_bar, content_stack, panthera]() {// Create a new tab with the search results if(content_manager->get_last_operated_page()) {on_new_tab(nullptr);if(panthera->content_manager->get_last_operated_page()) { panthera->on_new_tab(nullptr);} else { on_new_tab(content_stack);panthera->on_new_tab(content_stack);} auto page = content_manager->get_last_operated_page();auto page = panthera->content_manager->get_last_operated_page();if(page) { if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { auto search_url = default_search_engine->get_search_url(search_bar->get_text());auto search_url = panthera->default_search_engine->get_search_url(search_bar->get_text());webkit_web_view_load_uri(webview, search_url.c_str()); } } }; if(default_search_engine) {if(panthera->default_search_engine) {search_bar->signal_activate().connect(search_callback); } // Assemble the toolbar main_toolbar->append(*back_button); main_toolbar->append(*forward_button);
@@ -635,16 +501,16 @@ protected:
main_toolbar->append(*url_bar); main_toolbar->append(*go_button); main_toolbar->append(*search_bar); if(default_search_engine) {auto search_button = Gtk::make_managed<Gtk::Button>(default_search_engine->name);if(panthera->default_search_engine) { auto search_button = Gtk::make_managed<Gtk::Button>(panthera->default_search_engine->name);search_button->signal_clicked().connect(search_callback); main_toolbar->append(*search_button); } outer_grid->attach(*main_toolbar, 0, 1, 2, 1); window->set_child(*outer_grid);debug_button->signal_clicked().connect([this]() {if(content_manager->get_last_operated_page()) {std::cout << "Last operated page: " << content_manager->get_last_operated_page()->get_name() << std::endl;this->set_child(*outer_grid); debug_button->signal_clicked().connect([this, panthera]() { if(panthera->content_manager->get_last_operated_page()) { std::cout << "Last operated page: " << panthera->content_manager->get_last_operated_page()->get_name() << std::endl;} else { std::cout << "No page operated!" << std::endl; }
@@ -661,140 +527,303 @@ protected:
layout_file_in.close(); dock_stack_1->add_pane(*pane_1); dock_stack_1->add_pane(*history_viewer);dock_stack_1->add_pane(*panthera->history_viewer);std::ofstream layout_file_out("layout.json"); layout_file_out << layout_manager->get_layout_as_json(); layout_file_out.close(); } // Save the layout when changed layout_manager->signal_pane_moved.connect([this](gPanthera::DockablePane *pane) {layout_manager->signal_pane_moved.connect([this, panthera](gPanthera::DockablePane *pane) {std::ofstream layout_file_out("layout.json"); layout_file_out << layout_manager->get_layout_as_json(); layout_file_out.close(); std::cout << "Layout changed: " << layout_manager->get_layout_as_json() << std::endl; }); } };// Load settingsnew_tab_page = "about:blank";std::ifstream settings_file_in("settings.json");if(settings_file_in) {std::string settings_json((std::istreambuf_iterator<char>(settings_file_in)), std::istreambuf_iterator<char>());settings_file_in.close();auto json = nlohmann::json::parse(settings_json);if(json.contains("ntp")) {new_tab_page = json["ntp"].get<std::string>();void PantheraWww::load_change_callback(WebKitWebView *object, WebKitLoadEvent load_event, gpointer data) { if(auto self = static_cast<PantheraWww*>(data)) { if(load_event == WEBKIT_LOAD_COMMITTED) { if(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object)) == nullptr) { self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), ""); } else { self->history_manager->log_url(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)), webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))); } if(self->history_viewer->get_visible()) { self->history_viewer->reload_history();} } else {settings_file_in.close();auto empty_json = nlohmann::json::object();empty_json["ntp"] = "about:blank";std::ofstream settings_file_out("settings.json");settings_file_out << empty_json.dump(4);settings_file_out.close();} } }// Load pluginsstd::ifstream plugin_file_in("plugins.json");if(plugin_file_in) {std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>());plugin_file_in.close();auto json = nlohmann::json::parse(plugin_json);for(auto &plugin : json) {auto plugin_path = plugin["path"].get<std::string>();load_plugin(plugin_path);void PantheraWww::notify_callback(GObject *object, GParamSpec *pspec, gpointer data) { if(!gtk_widget_get_parent(GTK_WIDGET(object))) { return; } if(auto self = static_cast<PantheraWww*>(data)) { auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object))); if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) { if(g_strcmp0(pspec->name, "title") == 0) { if(auto label = dynamic_cast<Gtk::Label*>(page->tab_widget->get_last_child())) { if(strlen(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))) == 0) { label->set_label("Untitled"); } else { label->set_label(webkit_web_view_get_title(WEBKIT_WEB_VIEW(object))); } } } else if(g_strcmp0(pspec->name, "favicon") == 0) { // Update favicons if(auto image = dynamic_cast<Gtk::Image*>(page->tab_widget->get_first_child())) { image->set_from_icon_name("image-loading-symbolic"); if(auto favicon = webkit_web_view_get_favicon(WEBKIT_WEB_VIEW(object))) { gtk_image_set_from_paintable(image->gobj(), GDK_PAINTABLE(favicon)); } }} } else {plugin_file_in.close();auto empty_json = nlohmann::json::array();std::ofstream plugin_file_out("plugins.json");plugin_file_out << empty_json.dump(4);plugin_file_out.close();} } }window->set_show_menubar(true);// Known bug <https://gitlab.gnome.org/GNOME/epiphany/-/issues/2714>:// JS can't veto the application's accelerators using `preventDefault()`. This is// not possible in WebKitGTK, or at least I can't find a way to do it.main_menu = Gio::Menu::create();// Fileauto file_menu = Gio::Menu::create();// New tabauto new_tab_action = Gio::SimpleAction::create("new_tab");new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {on_new_tab(nullptr);});add_action(new_tab_action);set_accels_for_action("app.new_tab", {"<Primary>T"});file_menu->append("New tab", "app.new_tab");// Close tabauto close_tab_action = Gio::SimpleAction::create("close_tab");close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) {auto page = content_manager->get_last_operated_page();if(page) {page->close();void PantheraWww::notify_focused_callback(GObject *object, GParamSpec *pspec, gpointer data) { if(!gtk_widget_get_parent(GTK_WIDGET(object))) { return; } auto this_ = static_cast<PantheraWww*>(data); auto parent = gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(object))); if(auto page = dynamic_cast<gPanthera::ContentPage*>(Glib::wrap(parent))) { if(g_strcmp0(pspec->name, "uri") == 0) { if(auto main_window = dynamic_cast<PantheraWindow*>(page->get_root())) { main_window->url_bar->set_text(webkit_web_view_get_uri(WEBKIT_WEB_VIEW(object)));} });add_action(close_tab_action);set_accels_for_action("app.close_tab", {"<Primary>W"});file_menu->append("Close tab", "app.close_tab");main_menu->append_submenu("File", file_menu);set_menubar(main_menu);}} }void on_activate() override {window->present();}void PantheraWww::on_back_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) { WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data); webkit_web_view_go_back(webview); } void PantheraWww::on_forward_pressed(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) { WebKitWebView *webview = WEBKIT_WEB_VIEW(user_data); webkit_web_view_go_forward(webview); }void set_search_engines_from_json(const std::string &json_string) {auto json = nlohmann::json::parse(json_string);Glib::ustring default_search_engine_name;for(auto &engine : json) {SearchEngine search_engine;search_engine.name = engine["name"].get<std::string>();search_engine.url = engine["url"].get<std::string>();search_engines.push_back(search_engine);if(engine.contains("default") && engine["default"].get<bool>()) {default_search_engine_name = engine["name"].get<std::string>();gboolean PantheraWww::on_decide_policy(WebKitWebView *source, WebKitPolicyDecision *decision, WebKitPolicyDecisionType type, gpointer user_data) { if(auto self = static_cast<PantheraWww*>(user_data)) { if(type == WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION) { auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); Glib::ustring url = Glib::ustring(webkit_uri_request_get_uri(webkit_navigation_action_get_request(action))); if(!starts_with(url, "about:") && url.find("://") == Glib::ustring::npos) { // It is a special scheme (mailto:, tel: etc.) try { Gio::AppInfo::launch_default_for_uri(url); } catch(const Glib::Error &exception) { std::cerr << "Failed to launch special URI: " << exception.what() << std::endl; } webkit_policy_decision_ignore(decision); return true;} }for(auto &search_engine : search_engines) {if(search_engine.name == default_search_engine_name) {default_search_engine = &search_engine;break;if(webkit_navigation_action_get_mouse_button(action) == 2) { // Middle-click opens in a new window self->on_new_tab(nullptr, url, false); webkit_policy_decision_ignore(decision); return true;} } else if(type == WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION) { auto action = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(decision)); self->on_new_tab(nullptr, webkit_uri_request_get_uri(webkit_navigation_action_get_request(action)), false, true); return true;} } public:static Glib::RefPtr<PantheraWww> create() {return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww());return false; } void PantheraWww::on_new_tab(gPanthera::ContentStack *stack, const Glib::ustring &url, bool focus, bool new_window) { if(!stack) { // Find the current area stack = content_manager->get_last_operated_page()->get_stack(); } Glib::ustring url_ = url; if(url.empty()) { url_ = new_tab_page; } WebKitWebView *webview = WEBKIT_WEB_VIEW(webkit_web_view_new()); gtk_widget_set_hexpand(GTK_WIDGET(webview), true); gtk_widget_set_vexpand(GTK_WIDGET(webview), true); auto page_content = Gtk::make_managed<Gtk::Box>(); gtk_box_append(page_content->gobj(), GTK_WIDGET(webview)); auto page_tab = new Gtk::Box(); page_tab->set_orientation(Gtk::Orientation::HORIZONTAL); auto initial_icon = Gtk::make_managed<Gtk::Image>(Gio::Icon::create("image-loading-symbolic")); page_tab->append(*initial_icon); page_tab->append(*Gtk::make_managed<Gtk::Label>("Untitled")); page_tab->set_spacing(4); auto page = Gtk::make_managed<gPanthera::ContentPage>(content_manager, stack, page_content, page_tab); g_signal_connect(webview, "notify", G_CALLBACK(notify_callback), this); g_signal_connect(webview, "load-changed", G_CALLBACK(load_change_callback), this); g_signal_connect(webview, "decide-policy", G_CALLBACK(on_decide_policy), this); webkit_web_view_load_uri(webview, url_.data()); auto cookie_manager = webkit_network_session_get_cookie_manager(webkit_web_view_get_network_session(webview)); webkit_cookie_manager_set_persistent_storage(cookie_manager, cookie_file.c_str(), WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT); webkit_cookie_manager_set_accept_policy(cookie_manager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); auto website_data_manager = webkit_network_session_get_website_data_manager(webkit_web_view_get_network_session(webview)); webkit_website_data_manager_set_favicons_enabled(website_data_manager, true); GtkEventController *click_controller_back = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_back), 8); g_signal_connect(click_controller_back, "pressed", G_CALLBACK(on_back_pressed), webview); gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_back); GtkEventController *click_controller_forward = GTK_EVENT_CONTROLLER(gtk_gesture_click_new()); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_controller_forward), 9); g_signal_connect(click_controller_forward, "pressed", G_CALLBACK(on_forward_pressed), webview); gtk_widget_add_controller(GTK_WIDGET(webview), click_controller_forward); stack->add_page(*page); if(focus) { stack->set_visible_child(*page); content_manager->set_last_operated_page(page);} if(new_window) { bool result = stack->signal_detach.emit(page); } }std::shared_ptr<gPanthera::LayoutManager> get_layout_manager() {return layout_manager;void PantheraWww::on_startup() { Gtk::Application::on_startup(); Glib::RefPtr<Gtk::Application> self_ref = Glib::make_refptr_for_instance<Gtk::Application>(this); window = new PantheraWindow(self_ref); add_window(*window); // Get search engines std::ifstream search_engines_file_in("search_engines.json"); if(search_engines_file_in) { std::string search_engines_json((std::istreambuf_iterator<char>(search_engines_file_in)), std::istreambuf_iterator<char>()); search_engines_file_in.close(); set_search_engines_from_json(search_engines_json); } else { search_engines_file_in.close(); auto empty_json = nlohmann::json::array(); std::ofstream search_engines_file_out("search_engines.json"); search_engines_file_out << empty_json.dump(4); } // Load settings new_tab_page = "about:blank"; std::ifstream settings_file_in("settings.json"); if(settings_file_in) { std::string settings_json((std::istreambuf_iterator<char>(settings_file_in)), std::istreambuf_iterator<char>()); settings_file_in.close(); auto json = nlohmann::json::parse(settings_json); if(json.contains("ntp")) { new_tab_page = json["ntp"].get<std::string>(); } } else { settings_file_in.close(); auto empty_json = nlohmann::json::object(); empty_json["ntp"] = "about:blank"; std::ofstream settings_file_out("settings.json"); settings_file_out << empty_json.dump(4); settings_file_out.close();} std::shared_ptr<gPanthera::ContentManager> get_content_manager() {return content_manager;// Load plugins std::ifstream plugin_file_in("plugins.json"); if(plugin_file_in) { std::string plugin_json((std::istreambuf_iterator<char>(plugin_file_in)), std::istreambuf_iterator<char>()); plugin_file_in.close(); auto json = nlohmann::json::parse(plugin_json); for(auto &plugin : json) { auto plugin_path = plugin["path"].get<std::string>(); load_plugin(plugin_path); } } else { plugin_file_in.close(); auto empty_json = nlohmann::json::array(); std::ofstream plugin_file_out("plugins.json"); plugin_file_out << empty_json.dump(4); plugin_file_out.close();} void load_plugin(const std::string &plugin_path) {void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);if(!handle) {std::cerr << "Failed to load plugin: " << dlerror() << std::endl;return;window->set_show_menubar(true); // Known bug <https://gitlab.gnome.org/GNOME/epiphany/-/issues/2714>: // JS can't veto the application's accelerators using `preventDefault()`. This is // not possible in WebKitGTK, or at least I can't find a way to do it. main_menu = Gio::Menu::create(); // File auto file_menu = Gio::Menu::create(); // New tab auto new_tab_action = Gio::SimpleAction::create("new_tab"); new_tab_action->signal_activate().connect([this](const Glib::VariantBase&) { on_new_tab(nullptr); }); add_action(new_tab_action); set_accels_for_action("app.new_tab", {"<Primary>T"}); file_menu->append("New tab", "app.new_tab"); // Close tab auto close_tab_action = Gio::SimpleAction::create("close_tab"); close_tab_action->signal_activate().connect([this](const Glib::VariantBase&) { auto page = content_manager->get_last_operated_page(); if(page) { page->close(); } }); add_action(close_tab_action); set_accels_for_action("app.close_tab", {"<Primary>W"}); file_menu->append("Close tab", "app.close_tab"); main_menu->append_submenu("File", file_menu); set_menubar(main_menu); } void PantheraWww::on_activate() { window->present(); } void PantheraWww::set_search_engines_from_json(const std::string &json_string) { auto json = nlohmann::json::parse(json_string); Glib::ustring default_search_engine_name; for(auto &engine : json) { SearchEngine search_engine; search_engine.name = engine["name"].get<std::string>(); search_engine.url = engine["url"].get<std::string>(); search_engines.push_back(search_engine); if(engine.contains("default") && engine["default"].get<bool>()) { default_search_engine_name = engine["name"].get<std::string>();} auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init");if(!entrypoint) {std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl;dlclose(handle);return;} for(auto &search_engine : search_engines) { if(search_engine.name == default_search_engine_name) { default_search_engine = &search_engine; break;} entrypoint();} };} PantheraWww::PantheraWww() : Gtk::Application("com.roundabout_host.roundabout.panthera_www", Gio::Application::Flags::NONE) { } Glib::RefPtr<PantheraWww> PantheraWww::create() { return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww()); } void PantheraWww::load_plugin(const std::string &plugin_path) { void *handle = dlopen(plugin_path.c_str(), RTLD_LAZY | RTLD_GLOBAL); if(!handle) { std::cerr << "Failed to load plugin: " << dlerror() << std::endl; return; } auto entrypoint = (plugin_entrypoint_type)dlsym(handle, "plugin_init"); if(!entrypoint) { std::cerr << "Failed to find entrypoint in plugin: " << dlerror() << std::endl; dlclose(handle); return; } entrypoint(); }Glib::RefPtr<PantheraWww> app = nullptr;
@@ -808,8 +837,9 @@ extern "C" {
auto icon_cc = Glib::wrap(icon); auto label_cc = Glib::ustring(label); auto id_cc = Glib::ustring(id); auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc);app->get_layout_manager()->add_pane(new_pane);//auto new_pane = Gtk::make_managed<gPanthera::DockablePane>(app->get_layout_manager(), *pane_child, id_cc, label_cc, icon_cc); // TODO: Port to multi-window //app->get_layout_manager()->add_pane(new_pane);} }