init
This commit is contained in:
commit
41b87a9457
6 changed files with 562 additions and 0 deletions
4
.hgignore
Normal file
4
.hgignore
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
syntax: glob
|
||||
build/
|
||||
|
||||
|
||||
51
README.md
Normal file
51
README.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# page
|
||||
|
||||
A minimal WebKitGTK viewer. Inspired by [surf](https://surf.suckless.org/),
|
||||
but without the XEmbed dependency, so it actually works under pure Wayland.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `pkg-config`
|
||||
- `meson`
|
||||
- `ninja`
|
||||
- `wrapGAppsHook3`
|
||||
- `gtk4`
|
||||
- `webkitgtk_6_0`
|
||||
- `glib-networking`
|
||||
- `gsettings-desktop-schemas`
|
||||
- `cacert`
|
||||
- `gstreamer`
|
||||
- `gst-plugins-base`
|
||||
- `gst-plugins-good`
|
||||
- `gst-plugins-bad`
|
||||
- `gst-plugins-ugly`
|
||||
- `gst-libav`
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
nix develop (on NixOS)
|
||||
meson setup build
|
||||
ninja -C build
|
||||
./build/page
|
||||
```
|
||||
|
||||
Or, for a standalone wrapped binary (nixOS only):
|
||||
|
||||
```sh
|
||||
nix build
|
||||
./result/bin/page
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```rc
|
||||
page
|
||||
page example.com
|
||||
page some search terms # searches Google by default
|
||||
page -h # help page
|
||||
```
|
||||
|
||||
See `config.h` for keybindings, default URI, data directory, and other
|
||||
tweakable settings and stuff.
|
||||
80
config.h
Normal file
80
config.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef PAGE_CONFIG_H
|
||||
#define PAGE_CONFIG_H
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
/* Page loaded on startup if no URL is given on the command line,
|
||||
* very self-explanatory*/
|
||||
static const char *DEFAULT_URI = "https://google.com/";
|
||||
|
||||
/* Your search engine of choice */
|
||||
static const char *SEARCH_URL_PREFIX = "https://google.com/search?q=";
|
||||
|
||||
/* Default window size */
|
||||
static const int WINDOW_WIDTH = 1200;
|
||||
static const int WINDOW_HEIGHT = 800;
|
||||
|
||||
/* Fraction added/removed per zoom keypress. */
|
||||
static const double ZOOM_STEP = 0.1;
|
||||
|
||||
/* Pixels scrolled per Ctrl+Shift+J/K press. */
|
||||
static const int SCROLL_STEP = 120;
|
||||
|
||||
/* Path in which cookies, INdexedDB, localStorage, HTTP cache,
|
||||
* blah blah blah are stored. NULL for the default WebKit XDG path */
|
||||
static const char *DATA_DIR = "~/.local/share/page";
|
||||
|
||||
static const gboolean ENABLE_DEV_TOOLS = TRUE;
|
||||
|
||||
/* To URL bar or to not - that is the question */
|
||||
#define ENABLE_URL_BAR 1
|
||||
|
||||
/* keybinds */
|
||||
typedef enum {
|
||||
ACTION_FOCUS_URL,
|
||||
ACTION_QUIT,
|
||||
ACTION_RELOAD,
|
||||
ACTION_RELOAD_HARD,
|
||||
ACTION_BACK,
|
||||
ACTION_FORWARD,
|
||||
ACTION_ZOOM_IN,
|
||||
ACTION_ZOOM_OUT,
|
||||
ACTION_ZOOM_RESET,
|
||||
ACTION_FULLSCREEN,
|
||||
ACTION_ESCAPE,
|
||||
ACTION_SCROLL_UP,
|
||||
ACTION_SCROLL_DOWN,
|
||||
} PageAction;
|
||||
|
||||
typedef struct {
|
||||
guint keyval;
|
||||
GdkModifierType mods;
|
||||
PageAction action;
|
||||
} PageKeybind;
|
||||
|
||||
static const PageKeybind KEYBINDINGS[] = {
|
||||
/* keyval modifiers action */
|
||||
#if ENABLE_URL_BAR
|
||||
{ GDK_KEY_l, GDK_CONTROL_MASK, ACTION_FOCUS_URL },
|
||||
#endif
|
||||
{ GDK_KEY_q, GDK_CONTROL_MASK, ACTION_QUIT },
|
||||
{ GDK_KEY_r, GDK_CONTROL_MASK, ACTION_RELOAD },
|
||||
{ GDK_KEY_R, GDK_CONTROL_MASK | GDK_SHIFT_MASK, ACTION_RELOAD_HARD },
|
||||
{ GDK_KEY_F5, 0, ACTION_RELOAD },
|
||||
{ GDK_KEY_Left, GDK_ALT_MASK, ACTION_BACK },
|
||||
{ GDK_KEY_Right, GDK_ALT_MASK, ACTION_FORWARD },
|
||||
{ GDK_KEY_plus, GDK_CONTROL_MASK, ACTION_ZOOM_IN },
|
||||
{ GDK_KEY_equal, GDK_CONTROL_MASK, ACTION_ZOOM_IN },
|
||||
{ GDK_KEY_minus, GDK_CONTROL_MASK, ACTION_ZOOM_OUT },
|
||||
{ GDK_KEY_0, GDK_CONTROL_MASK, ACTION_ZOOM_RESET },
|
||||
{ GDK_KEY_F11, 0, ACTION_FULLSCREEN },
|
||||
{ GDK_KEY_Escape, 0, ACTION_ESCAPE },
|
||||
{ GDK_KEY_H, GDK_CONTROL_MASK | GDK_SHIFT_MASK, ACTION_BACK },
|
||||
{ GDK_KEY_L, GDK_CONTROL_MASK | GDK_SHIFT_MASK, ACTION_FORWARD },
|
||||
{ GDK_KEY_J, GDK_CONTROL_MASK | GDK_SHIFT_MASK, ACTION_SCROLL_DOWN },
|
||||
{ GDK_KEY_K, GDK_CONTROL_MASK | GDK_SHIFT_MASK, ACTION_SCROLL_UP },
|
||||
};
|
||||
|
||||
static const size_t N_KEYBINDINGS = sizeof(KEYBINDINGS) / sizeof(KEYBINDINGS[0]);
|
||||
|
||||
#endif
|
||||
74
flake.nix
Normal file
74
flake.nix
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
description = "page - a gtk view thingy whatever fuck you";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
|
||||
gstPluginPath = pkgs.lib.makeSearchPath "lib/gstreamer-1.0" (with pkgs.gst_all_1; [
|
||||
gstreamer
|
||||
gst-plugins-base
|
||||
gst-plugins-good
|
||||
gst-plugins-bad
|
||||
gst-plugins-ugly
|
||||
gst-libav
|
||||
]);
|
||||
|
||||
page = pkgs.stdenv.mkDerivation {
|
||||
pname = "page";
|
||||
version = "0.1.0";
|
||||
src = ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.pkg-config
|
||||
pkgs.meson
|
||||
pkgs.ninja
|
||||
pkgs.wrapGAppsHook3
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.gtk4
|
||||
pkgs.webkitgtk_6_0
|
||||
pkgs.glib-networking
|
||||
pkgs.gsettings-desktop-schemas
|
||||
pkgs.cacert
|
||||
];
|
||||
|
||||
preFixup = ''
|
||||
gappsWrapperArgs+=(
|
||||
--prefix GST_PLUGIN_SYSTEM_PATH_1_0 : "${gstPluginPath}"
|
||||
--set SSL_CERT_FILE "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
|
||||
--set GST_REGISTRY_REUSE_PLUGIN_SCANNER no
|
||||
)
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.default = page;
|
||||
packages.page = page;
|
||||
|
||||
apps.default = {
|
||||
type = "app";
|
||||
program = "${page}/bin/page";
|
||||
};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = [ pkgs.pkg-config pkgs.meson pkgs.ninja pkgs.gdb ];
|
||||
buildInputs = [ pkgs.gtk4 pkgs.webkitgtk_6_0 pkgs.glib-networking pkgs.cacert ]
|
||||
++ (with pkgs.gst_all_1; [
|
||||
gstreamer gst-plugins-base gst-plugins-good
|
||||
gst-plugins-bad gst-plugins-ugly gst-libav
|
||||
]);
|
||||
GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules";
|
||||
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
GST_PLUGIN_SYSTEM_PATH_1_0 = gstPluginPath;
|
||||
shellHook = ''
|
||||
'';
|
||||
};
|
||||
});
|
||||
}
|
||||
20
meson.build
Normal file
20
meson.build
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
project(
|
||||
'page',
|
||||
'c',
|
||||
version: '0.1.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=2',
|
||||
'buildtype=debugoptimized',
|
||||
],
|
||||
)
|
||||
|
||||
gtk4_dep = dependency('gtk4')
|
||||
webkitgtk_dep = dependency('webkitgtk-6.0')
|
||||
|
||||
executable(
|
||||
'page',
|
||||
'page.c',
|
||||
dependencies: [gtk4_dep, webkitgtk_dep],
|
||||
install: true,
|
||||
)
|
||||
333
page.c
Normal file
333
page.c
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/* See LICENSE file for copyright and license details */
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit/webkit.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
typedef struct {
|
||||
GtkWidget *window;
|
||||
GtkWidget *header;
|
||||
#if ENABLE_URL_BAR
|
||||
GtkWidget *url_entry;
|
||||
#endif
|
||||
GtkWidget *web_view;
|
||||
GtkWidget *progress;
|
||||
} PageApp;
|
||||
|
||||
static void update_title(PageApp *app) {
|
||||
const char *title = webkit_web_view_get_title(WEBKIT_WEB_VIEW(app->web_view));
|
||||
gtk_window_set_title(GTK_WINDOW(app->window), title && *title ? title : "page");
|
||||
}
|
||||
|
||||
#if ENABLE_URL_BAR
|
||||
static void update_url_bar(PageApp *app) {
|
||||
const char *uri = webkit_web_view_get_uri(WEBKIT_WEB_VIEW(app->web_view));
|
||||
if (uri)
|
||||
gtk_editable_set_text(GTK_EDITABLE(app->url_entry), uri);
|
||||
}
|
||||
#else
|
||||
static void update_url_bar(PageApp *app) {
|
||||
(void)app; /* no bar to update when compiled without one (duh) */
|
||||
}
|
||||
#endif
|
||||
|
||||
static gchar *normalize_input(const char *input) {
|
||||
/* provided it looks like it has a scheme or a dot, treat as URL.
|
||||
* otherwise, treat as a query */
|
||||
if (strstr(input, "://") != NULL)
|
||||
return g_strdup(input);
|
||||
|
||||
if (strchr(input, ' ') == NULL && strchr(input, '.') != NULL)
|
||||
return g_strdup_printf("https://%s", input);
|
||||
|
||||
gchar *escaped = g_uri_escape_string(input, NULL, FALSE);
|
||||
gchar *query = g_strconcat(SEARCH_URL_PREFIX, escaped, NULL);
|
||||
g_free(escaped);
|
||||
return query;
|
||||
}
|
||||
|
||||
#if ENABLE_URL_BAR
|
||||
static void on_url_activate(GtkEntry *entry, gpointer user_data) {
|
||||
PageApp *app = user_data;
|
||||
const char *text = gtk_editable_get_text(GTK_EDITABLE(entry));
|
||||
if (!text || !*text)
|
||||
return;
|
||||
gchar *uri = normalize_input(text);
|
||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(app->web_view), uri);
|
||||
g_free(uri);
|
||||
gtk_widget_grab_focus(app->web_view);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void on_load_changed(WebKitWebView *web_view, WebKitLoadEvent event, gpointer user_data) {
|
||||
(void)web_view;
|
||||
PageApp *app = user_data;
|
||||
switch (event) {
|
||||
case WEBKIT_LOAD_STARTED:
|
||||
case WEBKIT_LOAD_REDIRECTED:
|
||||
gtk_widget_set_visible(app->progress, TRUE);
|
||||
break;
|
||||
case WEBKIT_LOAD_COMMITTED:
|
||||
update_url_bar(app);
|
||||
break;
|
||||
case WEBKIT_LOAD_FINISHED:
|
||||
gtk_widget_set_visible(app->progress, FALSE);
|
||||
update_title(app);
|
||||
update_url_bar(app);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_progress_changed(WebKitWebView *web_view, GParamSpec *pspec, gpointer user_data) {
|
||||
(void)pspec;
|
||||
PageApp *app = user_data;
|
||||
gdouble p = webkit_web_view_get_estimated_load_progress(web_view);
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(app->progress), p);
|
||||
}
|
||||
|
||||
static void on_title_changed(WebKitWebView *web_view, GParamSpec *pspec, gpointer user_data) {
|
||||
(void)web_view;
|
||||
(void)pspec;
|
||||
update_title((PageApp *)user_data);
|
||||
}
|
||||
|
||||
static void zoom_by(PageApp *app, double delta) {
|
||||
gdouble z = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(app->web_view));
|
||||
webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(app->web_view), z + delta);
|
||||
}
|
||||
|
||||
static void zoom_reset(PageApp *app) {
|
||||
webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(app->web_view), 1.0);
|
||||
}
|
||||
|
||||
static void toggle_fullscreen(PageApp *app) {
|
||||
GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(app->window));
|
||||
GdkToplevelState tstate = gdk_toplevel_get_state(GDK_TOPLEVEL(surface));
|
||||
if (tstate & GDK_TOPLEVEL_STATE_FULLSCREEN)
|
||||
gtk_window_unfullscreen(GTK_WINDOW(app->window));
|
||||
else
|
||||
gtk_window_fullscreen(GTK_WINDOW(app->window));
|
||||
}
|
||||
|
||||
/* scroll the loaded page's content by 'delta' pixels (negative = up).
|
||||
* WebKitWebView's own GtkScrollable plumbing controls the widget's
|
||||
* position in page's layout, not the document inside it, so this goes
|
||||
* through JS instead (the same thing a page's own scroll code would do) */
|
||||
static void scroll_by(PageApp *app, int delta) {
|
||||
gchar *script = g_strdup_printf("window.scrollBy(0, %d);", delta);
|
||||
webkit_web_view_evaluate_javascript(WEBKIT_WEB_VIEW(app->web_view),
|
||||
script, -1, NULL, NULL, NULL, NULL, NULL);
|
||||
g_free(script);
|
||||
}
|
||||
|
||||
/* run the behaviour bound to a PageAction */
|
||||
static void dispatch_action(PageApp *app, PageAction action) {
|
||||
switch (action) {
|
||||
case ACTION_FOCUS_URL:
|
||||
#if ENABLE_URL_BAR
|
||||
gtk_widget_grab_focus(app->url_entry);
|
||||
gtk_editable_select_region(GTK_EDITABLE(app->url_entry), 0, -1);
|
||||
#endif
|
||||
break;
|
||||
case ACTION_QUIT:
|
||||
gtk_window_close(GTK_WINDOW(app->window));
|
||||
break;
|
||||
case ACTION_RELOAD:
|
||||
webkit_web_view_reload(WEBKIT_WEB_VIEW(app->web_view));
|
||||
break;
|
||||
case ACTION_RELOAD_HARD:
|
||||
webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(app->web_view));
|
||||
break;
|
||||
case ACTION_BACK:
|
||||
webkit_web_view_go_back(WEBKIT_WEB_VIEW(app->web_view));
|
||||
break;
|
||||
case ACTION_FORWARD:
|
||||
webkit_web_view_go_forward(WEBKIT_WEB_VIEW(app->web_view));
|
||||
break;
|
||||
case ACTION_ZOOM_IN:
|
||||
zoom_by(app, ZOOM_STEP);
|
||||
break;
|
||||
case ACTION_ZOOM_OUT:
|
||||
zoom_by(app, -ZOOM_STEP);
|
||||
break;
|
||||
case ACTION_ZOOM_RESET:
|
||||
zoom_reset(app);
|
||||
break;
|
||||
case ACTION_FULLSCREEN:
|
||||
toggle_fullscreen(app);
|
||||
break;
|
||||
case ACTION_ESCAPE:
|
||||
#if ENABLE_URL_BAR
|
||||
if (gtk_widget_has_focus(app->url_entry)) {
|
||||
update_url_bar(app);
|
||||
gtk_widget_grab_focus(app->web_view);
|
||||
} else {
|
||||
webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(app->web_view));
|
||||
}
|
||||
#else
|
||||
webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(app->web_view));
|
||||
#endif
|
||||
break;
|
||||
case ACTION_SCROLL_UP:
|
||||
scroll_by(app, -SCROLL_STEP);
|
||||
break;
|
||||
case ACTION_SCROLL_DOWN:
|
||||
scroll_by(app, SCROLL_STEP);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* only the modifier bits actually bind against matter for matching
|
||||
* (ignore lock keys, numlock, etc. that GDK may also report) */
|
||||
#define RELEVANT_MODS (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_ALT_MASK | GDK_SUPER_MASK)
|
||||
|
||||
static gboolean on_key_pressed(GtkEventControllerKey *ctrl, guint keyval,
|
||||
guint keycode, GdkModifierType state,
|
||||
gpointer user_data) {
|
||||
(void)ctrl;
|
||||
(void)keycode;
|
||||
PageApp *app = user_data;
|
||||
GdkModifierType mods = state & RELEVANT_MODS;
|
||||
|
||||
for (size_t i = 0; i < N_KEYBINDINGS; i++) {
|
||||
if (KEYBINDINGS[i].keyval == keyval && KEYBINDINGS[i].mods == mods) {
|
||||
dispatch_action(app, KEYBINDINGS[i].action);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gchar *expand_home(const char *path) {
|
||||
if (!path)
|
||||
return NULL;
|
||||
if (path[0] == '~' && (path[1] == '/' || path[1] == '\0')) {
|
||||
const char *home = g_get_home_dir();
|
||||
return g_build_filename(home, path + 1, NULL);
|
||||
}
|
||||
return g_strdup(path);
|
||||
}
|
||||
|
||||
static WebKitNetworkSession *build_network_session(void) {
|
||||
if (!DATA_DIR)
|
||||
return webkit_network_session_get_default();
|
||||
|
||||
gchar *data_dir = expand_home(DATA_DIR);
|
||||
gchar *cache_dir = g_build_filename(data_dir, "cache", NULL);
|
||||
|
||||
g_mkdir_with_parents(data_dir, 0700);
|
||||
g_mkdir_with_parents(cache_dir, 0700);
|
||||
|
||||
WebKitNetworkSession *session = webkit_network_session_new(data_dir, cache_dir);
|
||||
|
||||
gchar *cookie_file = g_build_filename(data_dir, "cookies.sqlite", NULL);
|
||||
WebKitCookieManager *cookie_mgr = webkit_network_session_get_cookie_manager(session);
|
||||
webkit_cookie_manager_set_persistent_storage(cookie_mgr, cookie_file,
|
||||
WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
|
||||
g_free(cookie_file);
|
||||
|
||||
g_free(data_dir);
|
||||
g_free(cache_dir);
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
static void activate(GtkApplication *gtk_app, gpointer user_data) {
|
||||
const char *start_uri = user_data ? (const char *)user_data : DEFAULT_URI;
|
||||
PageApp *app = g_new0(PageApp, 1);
|
||||
|
||||
app->window = gtk_application_window_new(gtk_app);
|
||||
gtk_window_set_default_size(GTK_WINDOW(app->window), WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
|
||||
#if ENABLE_URL_BAR
|
||||
app->header = gtk_header_bar_new();
|
||||
gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(app->header), TRUE);
|
||||
|
||||
app->url_entry = gtk_entry_new();
|
||||
gtk_widget_set_hexpand(app->url_entry, TRUE);
|
||||
gtk_editable_set_width_chars(GTK_EDITABLE(app->url_entry), 40);
|
||||
g_signal_connect(app->url_entry, "activate", G_CALLBACK(on_url_activate), app);
|
||||
gtk_header_bar_set_title_widget(GTK_HEADER_BAR(app->header), app->url_entry);
|
||||
gtk_window_set_titlebar(GTK_WINDOW(app->window), app->header);
|
||||
#else
|
||||
app->header = NULL; /* no bar built (window uses GTK's default titlebar) */
|
||||
#endif
|
||||
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
app->progress = gtk_progress_bar_new();
|
||||
gtk_widget_set_visible(app->progress, FALSE);
|
||||
gtk_box_append(GTK_BOX(box), app->progress);
|
||||
|
||||
/* cookies/cache/history/logins/whatever persist under DATA_DIR */
|
||||
WebKitNetworkSession *session = build_network_session();
|
||||
app->web_view = GTK_WIDGET(g_object_new(WEBKIT_TYPE_WEB_VIEW,
|
||||
"network-session", session,
|
||||
NULL));
|
||||
gtk_widget_set_vexpand(app->web_view, TRUE);
|
||||
gtk_widget_set_hexpand(app->web_view, TRUE);
|
||||
|
||||
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(app->web_view));
|
||||
webkit_settings_set_enable_developer_extras(settings, ENABLE_DEV_TOOLS);
|
||||
|
||||
g_signal_connect(app->web_view, "load-changed", G_CALLBACK(on_load_changed), app);
|
||||
g_signal_connect(app->web_view, "notify::estimated-load-progress",
|
||||
G_CALLBACK(on_progress_changed), app);
|
||||
g_signal_connect(app->web_view, "notify::title", G_CALLBACK(on_title_changed), app);
|
||||
|
||||
gtk_box_append(GTK_BOX(box), app->web_view);
|
||||
gtk_window_set_child(GTK_WINDOW(app->window), box);
|
||||
|
||||
GtkEventController *key_ctrl = gtk_event_controller_key_new();
|
||||
g_signal_connect(key_ctrl, "key-pressed", G_CALLBACK(on_key_pressed), app);
|
||||
gtk_widget_add_controller(app->window, key_ctrl);
|
||||
|
||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(app->web_view), start_uri);
|
||||
#if ENABLE_URL_BAR
|
||||
gtk_editable_set_text(GTK_EDITABLE(app->url_entry), start_uri);
|
||||
#endif
|
||||
|
||||
gtk_widget_set_visible(app->window, TRUE);
|
||||
gtk_widget_grab_focus(app->web_view);
|
||||
}
|
||||
|
||||
static void print_usage(const char *progname) {
|
||||
g_print(
|
||||
"Usage: %s [OPTIONS] [URL | SEARCH TERMS]\n"
|
||||
"\n"
|
||||
"A minimal WebKitGTK viewer\n"
|
||||
"\n"
|
||||
" With no argument, opens DEFAULT_URI (see config.h).\n"
|
||||
" With a URL-like argument, opens it directly.\n"
|
||||
" With anything else, searches via SEARCH_URL_PREFIX (see config.h).\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -h, --help Show this help and exit\n"
|
||||
"\n"
|
||||
"Keybindings, window size, data directory, and search engine are\n"
|
||||
"all configured at compile time in config.h (thanks, captain obvious).\n",
|
||||
progname);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (g_strcmp0(argv[i], "-h") == 0 || g_strcmp0(argv[i], "--help") == 0) {
|
||||
print_usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
gchar *start_uri = (argc > 1) ? normalize_input(argv[1]) : g_strdup(DEFAULT_URI);
|
||||
|
||||
GtkApplication *gtk_app = gtk_application_new("dev.page.viewer", G_APPLICATION_DEFAULT_FLAGS);
|
||||
g_signal_connect(gtk_app, "activate", G_CALLBACK(activate), start_uri);
|
||||
|
||||
int status = g_application_run(G_APPLICATION(gtk_app), 1, argv);
|
||||
g_object_unref(gtk_app);
|
||||
g_free(start_uri);
|
||||
return status;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue