roundabout,
created on Tuesday, 13 May 2025, 20:01:33 (1747166493),
received on Tuesday, 13 May 2025, 20:01:44 (1747166504)
Author identity: vlad <vlad.muntoiu@gmail.com>
fd21453b8ee1e7bd2b6bbeb8dc2c30b4f7dc55d1
panthera-www.cc
@@ -10,9 +10,44 @@
#include <gdk/gdk.h> #include <webkit/webkit.h> #include <fstream> #include <nlohmann/json.hpp> #include <iomanip> #include <sstream>class WebPage : public Gtk::Box {std::string url_encode(const std::string &value) { // Thanks https://stackoverflow.com/a/17708801 std::ostringstream escaped; escaped.fill('0'); escaped << std::hex;for(char c : value) { // Keep alphanumeric and other accepted characters intact if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { escaped << c; continue; } // Any other characters are percent-encoded escaped << std::uppercase; escaped << '%' << std::setw(2) << int((unsigned char) c); escaped << std::nouppercase; } return escaped.str(); } struct SearchEngine { Glib::ustring name; Glib::ustring url; Glib::ustring get_search_url(const std::string &query) { auto pos = url.find("%s"); if(pos == Glib::ustring::npos) { throw std::runtime_error("Invalid search engine URL: missing '%s' placeholder"); } auto new_url = url.substr(0, pos); new_url.replace(pos, 2, url_encode(query)); return new_url; } }; class PantheraWww : public Gtk::Application {
@@ -22,6 +57,8 @@ protected:
std::shared_ptr<gPanthera::ContentManager> content_manager; Gtk::Entry *url_bar = nullptr; std::string cookie_file = "cookies.txt"; std::vector<SearchEngine> search_engines; SearchEngine *default_search_engine = nullptr;static void notify_callback(GObject *object, GParamSpec *pspec, gpointer data) { if(!gtk_widget_get_parent(GTK_WIDGET(object))) {
@@ -171,11 +208,56 @@ protected:
inner_paned->set_end_child(*dock_stack_1); outer_paned->set_end_child(*inner_paned); outer_grid->attach(*outer_paned, 1, 1, 1, 1); // 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); } // Create the toolbarauto main_toolbar = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8); // URL barurl_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(); if(page) { if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { webkit_web_view_load_uri(webview, url_bar->get_text().c_str()); } } }; // Goauto 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) { if(!page->get_child()) { return; } if(!page->get_child()->get_first_child()) { 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); 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) { control_signal_handler->disconnect(); g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler); } }); }); // Back, forward, reloadauto back_button = Gtk::make_managed<Gtk::Button>(); back_button->set_child(*Gtk::make_managed<Gtk::Image>(Gio::Icon::create("go-previous-symbolic"))); back_button->set_tooltip_text("Back");
@@ -209,39 +291,46 @@ protected:
} } }); main_toolbar->append(*back_button);main_toolbar->append(*forward_button);main_toolbar->append(*reload_button);main_toolbar->append(*url_bar);main_toolbar->append(*go_button);outer_grid->attach(*main_toolbar, 0, 0, 2, 1);auto load_url_callback = [this]() {// Search bar // TODO: provide history, provide a menu button with the other engines auto search_bar = Gtk::make_managed<Gtk::Entry>(); search_bar->set_placeholder_text("Search"); search_bar->set_hexpand(true); if(search_engines.empty()) { search_bar->set_sensitive(false); search_bar->set_placeholder_text("No search"); } auto search_callback = [this, search_bar, content_stack]() { // Create a new tab with the search results if(content_manager->get_last_operated_page()) { on_new_tab(nullptr); } else { on_new_tab(content_stack); }auto page = content_manager->get_last_operated_page(); if(page) { if(auto webview = WEBKIT_WEB_VIEW(page->get_child()->get_first_child()->gobj())) { webkit_web_view_load_uri(webview, url_bar->get_text().c_str());auto search_url = default_search_engine->get_search_url(search_bar->get_text()); webkit_web_view_load_uri(webview, search_url.c_str());} } }; 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) {if(!page->get_child()) {return;}if(!page->get_child()->get_first_child()) {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);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) {control_signal_handler->disconnect();g_signal_handler_disconnect(page->get_child()->get_first_child()->gobj(), url_update_handler);}});});if(default_search_engine) { search_bar->signal_activate().connect(search_callback); } // Assemble the toolbar main_toolbar->append(*back_button); main_toolbar->append(*forward_button); main_toolbar->append(*reload_button); 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); search_button->signal_clicked().connect(search_callback); main_toolbar->append(*search_button); } outer_grid->attach(*main_toolbar, 0, 0, 2, 1);window->set_child(*outer_grid); debug_button->signal_clicked().connect([this]() { if(content_manager->get_last_operated_page()) {
@@ -297,6 +386,26 @@ protected:
void on_activate() override { window->present(); } 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>(); } } for(auto &search_engine : search_engines) { if(search_engine.name == default_search_engine_name) { default_search_engine = &search_engine; break; } } }public: static Glib::RefPtr<PantheraWww> create() { return Glib::make_refptr_for_instance<PantheraWww>(new PantheraWww());