Finally, a version I'm proud of. :)
+CSFLAGS = -debug
+references = -r:webkit-sharp.dll -pkg:gtk-sharp-2.0
+
+all: main.exe plugins
+
+main.exe: main.cs WebThing.dll
+ gmcs $(CSFLAGS) -r:WebThing.dll main.cs
+
+WebThing.dll: WebThing.cs
+ gmcs $(CSFLAGS) $(references) -target:library -out:$@ $<
+
+plugins:
+ make -C plugins
+
+.PHONY: plugins
+#!/bin/sh
+
+export LD_LIBRARY_PATH=plugins:$LD_LIBRARY_PATH
+
+echo $LD_LIBRARY_PATH
+
+exec mono main.exe
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.IO;
+using Gtk;
+using GtkSharp;
+using WebKit;
+
+namespace bytex64.WebThing {
+ public enum CompassDirection {
+ N, NE, E, SE, S, SW, W, NW,
+ Interior
+ }
+
+ public class WebThing {
+ public Gtk.Window Window {
+ get { return _Window; }
+ }
+
+ public Gtk.Window w {
+ get { return _Window; }
+ }
+
+ public Gtk.ScrolledWindow ScrolledWindow {
+ get { return _ScrolledWindow; }
+ }
+
+ public Gtk.ScrolledWindow sw {
+ get { return _ScrolledWindow; }
+ }
+
+ public WebKit.WebView WebView {
+ get { return _WebView; }
+ }
+
+ public WebKit.WebView wv {
+ get { return _WebView; }
+ }
+
+ private Gtk.Window _Window;
+ private ScrolledWindow _ScrolledWindow;
+ private WebKit.WebView _WebView;
+ private Gtk.Table WidgetGrid;
+ private Gtk.Alignment InteriorOverlay;
+
+ public Dictionary<string,object> Plugins;
+
+ public void Run() {
+ Application.Init();
+ _Window = new Gtk.Window("WebThing");
+ _Window.Destroyed += delegate { Application.Quit(); };
+
+ WidgetGrid = new Gtk.Table(3, 3, false);
+ _Window.Add(WidgetGrid);
+
+ _ScrolledWindow = new Gtk.ScrolledWindow();
+ WidgetGrid.Attach(_ScrolledWindow, 1, 2, 1, 2);
+
+ InteriorOverlay = new Gtk.Alignment(1, 0, 0, 0);
+ WidgetGrid.Attach(InteriorOverlay, 1, 2, 1, 2);
+
+ _WebView = new WebKit.WebView();
+ _WebView.TitleChanged += delegate(object o, TitleChangedArgs e) {
+ _Window.Title = e.Title + " - WebThing";
+ };
+ _ScrolledWindow.Add(_WebView);
+
+ _Window.ShowAll();
+
+ Plugins = new Dictionary<string, object>();
+ // TODO: Conf.Get("plugins") instead of hard-coded path?
+ using (TextReader f = new StreamReader("plugins.conf")) {
+ string line;
+ while ((line = f.ReadLine()) != null) {
+ line = line.Trim();
+ LoadPlugin(line);
+ }
+ }
+
+ Application.Run();
+ }
+
+ public void LoadPlugin(string assemblyname) {
+ Assembly a = Assembly.LoadFile("plugins/" + assemblyname + ".dll");
+ object obj = a.CreateInstance("Plugin", false, BindingFlags.ExactBinding, null, new object[] { this }, null, null);
+ Plugins[assemblyname] = obj;
+ }
+
+ public void AttachWidget(Gtk.Widget widget, CompassDirection direction, AttachOptions xoptions, AttachOptions yoptions) {
+ switch(direction) {
+ case CompassDirection.N:
+ WidgetGrid.Attach(widget, 1, 2, 0, 1, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.NE:
+ WidgetGrid.Attach(widget, 2, 3, 0, 1, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.E:
+ WidgetGrid.Attach(widget, 2, 3, 1, 2, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.SE:
+ WidgetGrid.Attach(widget, 2, 3, 2, 3, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.S:
+ WidgetGrid.Attach(widget, 1, 2, 2, 3, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.SW:
+ WidgetGrid.Attach(widget, 0, 1, 2, 3, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.W:
+ WidgetGrid.Attach(widget, 0, 1, 1, 2, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.NW:
+ WidgetGrid.Attach(widget, 0, 1, 0, 1, xoptions, yoptions, 0, 0);
+ break;
+ case CompassDirection.Interior:
+ InteriorOverlay.Add(widget);
+ break;
+ }
+ }
+
+ public void AttachWidget(Gtk.Widget widget, CompassDirection direction) {
+ AttachWidget(widget, direction, 0, 0);
+ }
+ }
+}
+using System;
+
+namespace bytex64.WebThing {
+ public class WebThingMain {
+ static WebThing wt;
+
+ public static void Main(string[] args) {
+ wt = new WebThing();
+ wt.Run();
+ }
+ }
+}
+SoupSettings
+LoadProgress
+Vimish
+DefaultPage
+using System;
+using bytex64.WebThing;
+
+public class Plugin {
+ public Plugin(WebThing wt) {
+ wt.WebView.Open("http://dominionofawesome.com/");
+ }
+}
+using System;
+using Gtk;
+using WebKit;
+using bytex64.WebThing;
+
+public class LoadThrobber : Gtk.DrawingArea {
+ public enum Mode {
+ LoadStarted,
+ LoadInProgress,
+ LoadFinished
+ };
+ public Mode LoadState;
+ int r;
+ uint idletimer;
+
+ public LoadThrobber(WebThing wt) {
+ LoadState = Mode.LoadStarted;
+
+ wt.WebView.LoadStarted += WebView_LoadStarted;
+ wt.WebView.LoadCommitted += WebView_LoadCommitted;
+ wt.WebView.LoadProgressChanged += WebView_LoadProgressChanged;
+ wt.WebView.LoadFinished += WebView_LoadFinished;
+
+ SetSizeRequest(64, 64);
+ }
+
+ protected override bool OnExposeEvent(Gdk.EventExpose e) {
+ Gdk.Window w = e.Window;
+ Gdk.GC gc = new Gdk.GC(w);
+ int width, height;
+ w.GetSize(out width, out height);
+
+ gc.Foreground = new Gdk.Color(0, 0, 0);
+ gc.SetLineAttributes(3, Gdk.LineStyle.Solid, Gdk.CapStyle.Butt, Gdk.JoinStyle.Round);
+ w.Clear();
+
+ switch(LoadState) {
+ case Mode.LoadStarted:
+ w.DrawArc(gc, false, 4, 4, width-8, width-8, -r*64, 90*64);
+ w.DrawArc(gc, false, 4, 4, width-8, width-8, -r*64 + 180*64, 90*64);
+ r += 5;
+ break;
+ case Mode.LoadInProgress:
+ case Mode.LoadFinished:
+ w.DrawArc(gc, false, 4, 4, width-8, width-8, 90*64, -r*64);
+ break;
+ }
+ return true;
+ }
+
+ void WebView_LoadStarted(object o, LoadStartedArgs e) {
+ this.Show();
+ Console.WriteLine("Loading Started");
+ LoadState = Mode.LoadStarted;
+ idletimer = GLib.Timeout.Add(100, delegate {
+ QueueDraw();
+ return true;
+ });
+ }
+
+ void WebView_LoadCommitted(object o, LoadCommittedArgs e) {
+ Console.WriteLine("Loading Committed");
+ LoadState = Mode.LoadInProgress;
+ GLib.Source.Remove(idletimer);
+ r = 0;
+ QueueDraw();
+ }
+
+ void WebView_LoadProgressChanged(object o, LoadProgressChangedArgs e) {
+ Console.WriteLine("Loading Progress: {0}", e.Progress);
+ r = (int) ((e.Progress / 100.0) * 360);
+ QueueDraw();
+ }
+
+ void WebView_LoadFinished(object o, LoadFinishedArgs e) {
+ Console.WriteLine("Loading Finished");
+ LoadState = Mode.LoadFinished;
+ this.Hide();
+ }
+}
+
+public class Plugin {
+ public Plugin(WebThing wt) {
+ LoadThrobber lt = new LoadThrobber(wt);
+ wt.AttachWidget(lt, CompassDirection.Interior);
+ }
+}
+CSFLAGS = -debug
+references = -r:../webkit-sharp.dll -pkg:gtk-sharp-2.0
+
+all: Vimish.dll DefaultPage.dll SoupSettings.dll LoadProgress.dll
+
+clean:
+ rm -f *.dll *.mdb *.so
+ make -C SoupSettings clean
+
+.PHONY: SoupSettings.dll
+SoupSettings.dll:
+ make -C SoupSettings
+
+%.dll: %.cs ../WebThing.dll
+ gmcs $(CSFLAGS) $(references) -r:../WebThing.dll -target:library -out:$@ $<
+CFLAGS = `pkg-config glib-2.0 libsoup-2.4 webkit-1.0 --cflags`
+LDFLAGS = `pkg-config glib-2.0 libsoup-2.4 webkit-1.0 --libs`
+CSFLAGS = -debug
+references = -r:../../webkit-sharp.dll -pkg:gtk-sharp-2.0
+
+all: ../SoupSettings.dll
+
+clean:
+ rm -f *.dll *.mdb ../SoupSettings.dll ../SoupSettings.so
+
+../%.dll: %.cs %.so ../../WebThing.dll
+ gmcs $(CSFLAGS) $(references) -r:../../WebThing.dll -target:library -out:$@ $<
+
+%.so: %.c
+ $(CC) $(CFLAGS) -shared $(LDFLAGS) $< -o $@
+ cp $@ ../
+#include <stdio.h>
+#include <glib.h>
+#include <webkit/webkit.h>
+#include <libsoup/soup.h>
+
+void soup_settings() {
+ SoupSession *soup_session = webkit_get_default_session();
+
+ /* Set up persistent cookies */
+ SoupCookieJar *jar = (SoupCookieJar *) soup_cookie_jar_text_new("cookies.txt", FALSE);
+ soup_session_add_feature(soup_session, SOUP_SESSION_FEATURE(jar));
+}
+using System;
+using System.Runtime.InteropServices;
+using bytex64.WebThing;
+
+public class Plugin {
+ [DllImport ("SoupSettings.so")]
+ private static extern void soup_settings();
+
+ public Plugin(WebThing wt) {
+ soup_settings();
+ }
+}
+using System;
+using System.Text.RegularExpressions;
+using Gtk;
+using bytex64.WebThing;
+
+public class Plugin {
+ WebThing wt;
+ Gtk.Entry commandline;
+
+ public Plugin(WebThing wt) {
+ this.wt = wt;
+ wt.WebView.KeyPressEvent += WebView_KeyPress;
+
+ commandline = new Gtk.Entry();
+ commandline.Activated += command_Activate;
+ commandline.KeyPressEvent += command_KeyPress;
+ wt.AttachWidget(commandline, CompassDirection.S, AttachOptions.Fill, AttachOptions.Shrink);
+
+ commandline.Hide();
+ }
+
+ private void WebView_KeyPress(object o, KeyPressEventArgs e) {
+ Console.WriteLine(e.Event.Key);
+ switch(e.Event.Key) {
+ case Gdk.Key.j:
+ wt.ScrolledWindow.Vadjustment.Value += wt.ScrolledWindow.Vadjustment.StepIncrement;
+ break;
+ case Gdk.Key.k:
+ wt.ScrolledWindow.Vadjustment.Value -= wt.ScrolledWindow.Vadjustment.StepIncrement;
+ break;
+ case Gdk.Key.l:
+ wt.ScrolledWindow.Hadjustment.Value += wt.ScrolledWindow.Hadjustment.StepIncrement;
+ break;
+ case Gdk.Key.h:
+ wt.ScrolledWindow.Hadjustment.Value -= wt.ScrolledWindow.Hadjustment.StepIncrement;
+ break;
+ case Gdk.Key.o:
+ CommandStart("open ");
+ break;
+ case Gdk.Key.t:
+ CommandStart("tabopen ");
+ break;
+ case Gdk.Key.BackSpace:
+ wt.WebView.GoBack();
+ break;
+ case Gdk.Key.colon:
+ CommandlineShow();
+ break;
+ }
+ }
+
+ public void CommandStart(string text) {
+ commandline.Text = text;
+ commandline.GrabFocus();
+ commandline.Position = text.Length;
+ commandline.Show();
+ }
+
+ public void CommandlineShow() {
+ commandline.Show();
+ commandline.GrabFocus();
+ }
+
+ public void CommandlineHide() {
+ commandline.Hide();
+ wt.WebView.GrabFocus();
+ }
+
+ private void command_Activate(object o, EventArgs e) {
+ string[] args = Regex.Split(commandline.Text, @"\s+");
+ switch(args[0]) {
+ case "open":
+ if (args.Length < 2) return;
+ wt.WebView.Open(args[1]);
+ break;
+ }
+ commandline.Text = "";
+ CommandlineHide();
+ }
+
+ private void command_KeyPress(object o, KeyPressEventArgs e) {
+ if (e.Event.Key == Gdk.Key.Escape)
+ CommandlineHide();
+ }
+}