Welkom, Gast. Alsjeblieft inloggen of registreren.
Heb je de activerings-mail niet ontvangen?

Auteur Topic: Browser zonder naam  (gelezen 5559 keer)

Browser zonder naam
« Gepost op: 2010/12/26, 14:00:29 »
Om wat te oefenen met het nieuwe Gobject Introspection, heb ik volgend programma geschreven.
Het is een browser met heel beperkte mogelijkheden. Zo is het downloaden en printen uitgezet, en kan ervoor gekozen worden om externe links te blokkeren.

Het is bedoeld om in fullscreen te draaien op een openbare pc, zodat klanten/bezoekers een door de eigenaar opgelegde website kan raadplegen, en niets meer. Bijvoorbeeld handig in een videotheek om IMDb en/of aanverwanten op te hebben staan.

Het programma heeft nog geen naam, dus ideëen zijn welkom :).


#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2011 Timo Vanwynsberghe

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <>.

This program is a webbrowser with limited functionality. Features like
downloading and printing are disabled. And an option to turn of following
external links. This will make sure that links that are not on the given
domain (or subdomain) will be blocked.

What's its use then?
It is created for public computers, to let the users search the internet and
nothing else. Example:
A videostore can have this program in fullscreen with a couple of tabs open
like IMDb, and customers can lookup some extra info before they buy anything.

NOTE: you have to disable specific OS keybindings yourself.

__version__ = "0.3"

import os.path
import tempfile
import urllib
import urlparse
import logging
from datetime import datetime

import gi
gi.require_version('Gtk', '3.0')

from gi.repository import Gtk, Gdk, GObject, GdkPixbuf

    from gi.repository import WebKit
except ImportError as exc:
    raise SystemExit("GObject Introspection data for Webkit and libsoup are required!")

# ------------------------------------------------------------------------
# Start of user preferences
# ------------------------------------------------------------------------
# Enabling this will keep users on the given website (or subdomains). Links
# to other sites will be blocked.
# Value: True or False
# The key (or keys) that will switch between fullscreen and windowed mode.
# Value: A valid key, like "F11" or "<Control><Alt>F"
FS_KEY = "F11"
# Wheter to start the program in fullscreen mode
# Value: True or False
START_FS = False
# A timer which will reset all pages to their default after inactivity
# NOTE: this isn't accurate to the second, so don't rely on that.
# Value: Number in minutes or zero to deactivate
# All the webpages that need to be loaded.
# Value: List of tuples in the form of (name, url)
            ('Ubuntu', ''),
            ('Ubuntu-nl', ''),
# Messages shown to the user.
## Blocked download, default: "Downloading is disabled"
BLOCKED_DL = "Downloading is disabled"
## Blocked link, default: "Following external links is disabled"
BLOCKED_LINK = "Following external links is disabled"
# ------------------------------------------------------------------------
# End of user preferences
# ------------------------------------------------------------------------

# Program constants
TEMPDIR = tempfile.gettempdir()

# Setup logging
format = "%(name)s: %(levelname)s: %(message)s"
logging.basicConfig(level=logging.DEBUG, format=format)
logger = logging.getLogger(__file__)

class InfoBar(Gtk.InfoBar):
    Custom Gtk.InfoBar with support for multiple labels, buttons and timeout
    def __init__(self, parent, primary_text, secondary_text=None,
                        message_type=Gtk.MessageType.INFO, buttons=(), timeout=0):
        InfoBar initializer

        @param parent: a parent widget which can hold the infobar
        @param primary_text: primary text
        @param secondary_text: secondary text or None
        @param message_type: one of the supported message types (INFO, WARNING,
                             QUESTION or ERROR)
        @param buttons: list of buttons or empty list
        @param timeout: if set, the infobar will disappear after timeout seconds

        if not isinstance(parent, Gtk.Box):
            raise TypeError("Parent of infobar need to be of type Gtk.Box")


        vbox =, 6)

        self._primary_label = Gtk.Label()
        vbox.pack_start(self._primary_label, True, True, 0)
        self._primary_label.set_alignment(0, 0.5)

        self._secondary_label = None
        if secondary_text:
            self._secondary_label = Gtk.Label()
            vbox.pack_start(self._secondary_label, True, True, 0)
            if buttons:
            self._secondary_label.set_alignment(0, 0.5)

        self.update_text(primary_text, secondary_text)

        if buttons:
            for name,response_id in buttons:
                self.add_button(name, response_id)

        self.get_content_area().pack_start(vbox, False, False, 0)

        parent.pack_start(self, False, True, 0)

        if timeout:
            GObject.timeout_add(timeout*1000, self._infobar_timeout, parent)

    def update_text(self, primary_text="", secondary_text=""):
        Update the labels in the infobar

        @param primary_text: text for the primary label
        @param secondary_text: text for the secondary label

        if primary_text:
            self._primary_label.set_markup("<b>%s</b>" %primary_text)
        if secondary_text and self._secondary_label:
            self._secondary_label.set_markup("<small>%s</small>" %secondary_text)

    def _infobar_timeout(self, parent):
        Timeout function that will remove the infobar from its parent

        @param parent: parent widget of the infobar

        return False

class PageView(Gtk.VBox):
    Class that holds the toolbar and webview for each given website

    __gsignals__ = {'favicon-ready': (GObject.SIGNAL_RUN_LAST,
                                      None, (str,)),
                    'loading-page': (GObject.SIGNAL_RUN_LAST,
                                     None, (bool,)),

    def __init__(self, url):
        Page initializer

        @param url: the url for this page


        self._url = url
        self._root = self._url2root(url)
        self._favicon = None

        # Initialize the toolbar
        self._entry = Gtk.Entry()
        self._address = Gtk.ToolItem()

        self._back = Gtk.ToolButton.new_from_stock(Gtk.STOCK_GO_BACK)
        self._back.connect('clicked', self.on_back_clicked)
        self._forward = Gtk.ToolButton.new_from_stock(Gtk.STOCK_GO_FORWARD)
        self._forward.connect('clicked', self.on_forward_clicked)
        self._reload = Gtk.ToolButton.new_from_stock(Gtk.STOCK_REFRESH)
        self._reload.connect('clicked', self.on_reload_clicked)
        self._stop = Gtk.ToolButton.new_from_stock(Gtk.STOCK_STOP)
        self._stop.connect('clicked', self.on_stop_clicked)
        self._home = Gtk.ToolButton.new_from_stock(Gtk.STOCK_HOME)
        self._home.connect('clicked', self.on_home_clicked)

        toolbar = Gtk.Toolbar()
        toolbar.insert(self._back, -1)
        toolbar.insert(self._forward, -1)
        toolbar.insert(self._reload, -1)
        toolbar.insert(self._stop, -1)
        toolbar.insert(self._home, -1)
        toolbar.insert(self._address, -1)

        self.pack_start(toolbar, False, False, 0)

        # Initialize the browser view
        self._webview = WebKit.WebView()
        self._webframe = self._webview.get_main_frame()
        # General WebKit signals
        self._webview.connect('console-message', self.on_console_message)
        self._webview.connect('notify::load-status', self.on_load_status)
        self._webview.connect('icon-loaded', self.on_icon_loaded)
        # Signals used for blocking features, add or remove if wanted
        self._webview.connect('download-requested', self.on_download_requested)
        self._webview.connect('populate-popup', self.on_populate_popup)
        self._webview.connect('print-requested', self.on_print_requested)
        # Open the webpage

        sw = Gtk.ScrolledWindow()

        self.pack_start(sw, True, True, 0)

    # Toolbar button callbacks
    def on_back_clicked(self, toolbutton):

    def on_forward_clicked(self, toolbutton):

    def on_reload_clicked(self, toolbutton):

    def on_stop_clicked(self, toolbutton):

    def on_home_clicked(self, toolbutton):

    # Webkit callbacks
    def on_load_status(self, webview, status):
        status = webview.get_load_status()
        if status == WebKit.LoadStatus.COMMITTED:
            self.emit('loading-page', True)
        elif status == WebKit.LoadStatus.FINISHED:
            self.emit('loading-page', False)

    def on_icon_loaded(self, webview, icon_uri):
        if self._favicon is None:

    def on_console_message(self, webview, message, line, source_id):
        # Disable console messages by WebKit.WebView
        return True

    def on_navigation_request(self, webview, frame, request, action, decision):
        if BLOCK_LINKS and \
            action.get_reason() == WebKit.WebNavigationReason.LINK_CLICKED:
            uri = request.get_uri()
            if not self._check_valid_link(uri):
      "Link blocked: %s", uri)
                InfoBar(self, BLOCKED_LINK, timeout=4)
                return True

    def on_download_requested(self, webview, download):"Download blocked: %s", download.get_uri())
        InfoBar(self, BLOCKED_DL, timeout=4)
        return False

    def on_populate_popup(self, webview, menu):
        # We disable every item in the menu. Setting the menu insensitive
        # will lead to focus issues.
        for item in menu.get_children():

    def on_print_requested(self, webview, frame):
        return True

    # Public methods
    def show_home_page(self):

    # Internal methods
    def _set_loading(self, loading):
        Set widgets in the correct state when a page is loading or finished

        @param loading: bool which indicates if the frame is loading

        self._reload.set_sensitive(not loading)

    def _url2root(self, url):
        Get the root domain of an url

        @param url: a full url

        host = urlparse.urlparse(url).hostname
        return host if not host.startswith('www.') else host[4:]

    def _check_valid_link(self, url):
        Check if a link is part of the main website

        @param url: the url of the link

        linkroot = self._url2root(url)
        rootlist = self._root.split('.')
        linkrootlist = linkroot.split('.')
        if linkrootlist[-len(rootlist):] != rootlist:
            return False
        return True

    def _download_favicon(self, url):
        Download and set the favicon

        @param url: the url of the favicon

            self._favicon = os.path.join(TEMPDIR, "%s.ico" %self._root)
            urllib.urlretrieve(url, self._favicon)
        except Exception as exc:
            logger.error("Downloading favicon failed: %s", exc)
            self._favicon = None
            self.emit('favicon-ready', self._favicon)

class TabLabel(Gtk.HBox):
    The notebooktab label widget
    def __init__(self, name, pageview):
        Notebook label initializer

        @param name: the name for the webpage
        @param pageview: the pageview widget for this tab


        self._image = Gtk.Image()
        self._spinner =
        label = Gtk.Label()
        label.set_markup("<big><b>%s</b></big>" %name)

        self.pack_start(self._spinner, False, False, 0)
        self.pack_start(self._image, False, False, 0)
        self.pack_start(label, False, False, 0)

        pageview.connect('loading-page', self.on_loading_page)
        pageview.connect('favicon-ready', self.on_favicon_ready)

    # Callbacks
    def on_loading_page(self, pageview, loading):
        self._spinner.start() if loading else self._spinner.stop()
        self._image.set_property('visible', not loading)
        self._spinner.set_property('visible', loading)

    def on_favicon_ready(self, pageview, favicon):
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(favicon, 20, 20)
        except Exception as exc:
            logger.error("Setting favicon failed: %s", exc)

class MainView(Gtk.Window):
    Main window class which holds the notebook of webpages
    def __init__(self):
        self.connect('delete-event', self.quit)
        self.connect('motion-notify-event', self.on_motion_notify_event)
        self.resize(1050, 750)

        self._fullscreen = False
        self._can_motion_check = False
        self.last_active =

        accelgroup = Gtk.AccelGroup()
        key, modifier = Gtk.accelerator_parse(FS_KEY)
        accelgroup.connect(key, modifier, Gtk.AccelFlags.VISIBLE,

        self._notebook = Gtk.Notebook()


        if RESET_TIMER > 0:
            GObject.timeout_add_seconds(4, self._motion_timer_cb)
            GObject.timeout_add_seconds(4, self._reset_timer_cb)

    def get_fullscreen(self):
        return self._fullscreen

    def set_fullscreen(self, fullscreen):
        self._fullscreen = fullscreen
        self.fullscreen() if fullscreen else self.unfullscreen()
    is_fullscreen =, set_fullscreen, bool, False)

    # Callbacks
    def quit(self, widget, event):
        # Prevent quitting with alt+f4 when in fullscreen
        if self.get_fullscreen():
            return True

    def on_motion_notify_event(self, widget, event):
        if self._can_motion_check:
            self._can_motion_check = False
            self.last_active =
        return False

    def on_fullscreen_pressed(self, accelgroup, acceleratable, keyval, modifier):
        self.set_fullscreen(not self.is_fullscreen)
        return True

    def _motion_timer_cb(self):
        self._can_motion_check = True
        return True

    def _reset_timer_cb(self):
        diff = - self.last_active
        if diff.seconds/60 == RESET_TIMER:
            for page in self._notebook.get_children():
            self.last_active =
        return True

    # Internal methods
    def _build_notebook_tabs(self):
        Build a notebook tab for each page defined in the preferences

        for name, url in PAGES:
  "Setting up page for '%s - %s'", name, url)
            view = PageView(url)
            label = TabLabel(name, view)
            self._notebook.append_page(view, label)

if __name__ == "__main__":
    browser = MainView()

Edit: Foutjes uitgehaald en volledige GTK 3 ondersteuning.
Edit 2: START_FS en RESET_TIMER toegevoegd
« Laatst bewerkt op: 2011/12/09, 13:09:48 door Nunslaughter »

Offline landfiets

  • Lid
    • Toets en Stem
Re: Browser zonder naam
« Reactie #1 Gepost op: 2010/12/26, 14:04:14 »
Misschien hier wat screenshots?
Intel-NUC 8i5BEH, 256Gb M.2 SSD , 16Gb ram - Manjaro Gnome 21
Intel-NUC 10i7FNH, 500GB M.2 SSD, 16Gb ram - Manjaro Gnome 21

Offline asphyxia

  • Forumteam
Re: Browser zonder naam
« Reactie #2 Gepost op: 2010/12/26, 14:54:39 »
Het programma heeft nog geen naam, dus ideëen zijn welkom  ;).

Browser zonder naam, dus BZN  ???
'Librarian' lijkt me wel toepasselijk  ;)
Als de werkelijkheid er niet was, zou de wereld er heel anders uitzien. [Theo Maassen]
Alles is te kraken, niets is veilig, zorg dus voor zoveel mogelijk niets. [Ramana]

Offline Tico

  • Lid
Re: Browser zonder naam
« Reactie #3 Gepost op: 2010/12/27, 13:56:59 »
Goed initiatief!

Voorstel voor een naam: AMAN (As much as necessary)

Re: Browser zonder naam
« Reactie #4 Gepost op: 2010/12/27, 14:02:58 »
Zo, dat is wel goed zeg :)

Ik vind inderdaad "Librarian" een mooie naam, goede vondst asphyxia :)


  • Gast
Re: Browser zonder naam
« Reactie #5 Gepost op: 2010/12/27, 14:06:34 »
inderdaad "Librarian"

Offline heir4c

  • Lid
Re: Browser zonder naam
« Reactie #6 Gepost op: 2010/12/29, 23:33:20 »
Yep, Librarian of Libr + aria + n >>Libre en varia en n (de variabele)>> vrij en variabel >> vrij te kiezen (wat je de kijker voorschotelt).

Werkt hij al? En kan je dat draaien onder ubuntu of moet dat in een virtuele omgeving of....?
Tip: en   Upload-site:
be Open be Free be Ubuntu

Re: Browser zonder naam
« Reactie #7 Gepost op: 2010/12/30, 10:48:02 »
Was het al vergeten, oeps :).
Ik heb het dus Librarian genoemd, bedankt voor de suggesties!

heir4c: het werkt ja. Gewoon de code van in het eerste bericht in een bestand zetten ( bijvoorbeeld). Dan even de voorkeuren aanpassen, deze staan bovenaan in de code, en het programma starten met python (of dubbelklikken als het bestand uitvoerbaar gemaakt word).

Wel even de volgende pakketjes installeren:
Dit is ook allemaal redelijk nieuw (Gobject Introspection in plaats van PyGTK), dus ik weet niet of dit in 10.04 werkt. In 10.10 (en hoger) in ieder geval wel.

Offline heir4c

  • Lid
Re: Browser zonder naam
« Reactie #8 Gepost op: 2010/12/30, 14:20:31 »
Hij doet het hier niet. Had die pakketten al geïnstalleerd maar krijg in de terminal nog altijd:
GObject Introspection data for GTK+ is required!

In het script staat beschreven wanneer deze error gegeven moet worden:
    from gi.repository import Gtk, Gdk, GObject, GdkPixbuf
except ImportError as exc:
    raise SystemExit("GObject Introspection data for GTK+ is required!")

Maar gtk is geïnstalleerd en ook de libgdkpixbuf2-ruby en ...ruby1.8
gobject-introspection is ook geïnstalleerd.
gobjc en gobjc-4.4

Zit nu in 10.04 dus zal het even proberen onder 10.10 en 11.04.
« Laatst bewerkt op: 2010/12/30, 14:59:28 door heir4c »
Tip: en   Upload-site:
be Open be Free be Ubuntu

Re: Browser zonder naam
« Reactie #9 Gepost op: 2010/12/30, 15:07:16 »
Het zal hem vooral gaan om de gir1.0-* pakketjes. Kijk eens of gir1.0-gdkpixbuf-2.0 en gir1.0-gtk-2.0 geinstalleerd zijn.

Offline heir4c

  • Lid
Re: Browser zonder naam
« Reactie #10 Gepost op: 2010/12/30, 15:36:34 »
Kijk eens of gir1.0-gdkpixbuf-2.0 en gir1.0-gtk-2.0 geinstalleerd zijn.
gir1.0-gdkpixbuf-2.0 is niet te vinden in Synaptic. het andere wel.
Zal eens kijken of ik dat kan downloaden.

Edit: kan ik wel maar dan moet ik ook libgdk-pixbuf2.0-0 (>= 2.21.6) installeren die weerom afhankelijk is van libglib2.0-0 (>= 2.25.9)
Echter, libglib2.0-0 staat geïnstalleerd maar dan wel een oudere versie, namelijk de 2.12.9-4
Ik probeer nu die nieuwere versie ook te installeren.
Ik laat nog iets weten.
« Laatst bewerkt op: 2010/12/30, 15:47:52 door heir4c »
Tip: en   Upload-site:
be Open be Free be Ubuntu

Offline heir4c

  • Lid
Re: Browser zonder naam
« Reactie #11 Gepost op: 2010/12/30, 16:06:28 »
Het ging niet. Error melding: Kon pakket libgdk-pixbuf2.0_2.22.0-0ubuntu1_i386.deb niet installeren.
Terminal output:

en vervolg:

Ik probeer het wel even in de andere versie.
Tip: en   Upload-site:
be Open be Free be Ubuntu

Offline heir4c

  • Lid
Re: Browser zonder naam
« Reactie #12 Gepost op: 2010/12/30, 16:40:32 »
In 10.10 gaat het ook niet maar krijg wel een andere vermelding:

gerolf@LinLap:~$ python
ERROR:root:Typelib file for namespace 'Gtk' (any version) not found
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.6/gtk-2.0/gi/", line 52, in find_module
RepositoryError: Typelib file for namespace 'Gtk' (any version) not found
ERROR:root:Typelib file for namespace 'Gdk' (any version) not found
Traceback (most recent call last):
  File "/usr/lib/pymodules/python2.6/gtk-2.0/gi/", line 52, in find_module
RepositoryError: Typelib file for namespace 'Gdk' (any version) not found
GObject Introspection data for GTK+ is required!

In die bewuste file in /usr/lib/.... staat het volgende:
class DynamicImporter(object):

    # Note: see PEP302 for the Importer Protocol implemented below.

    def __init__(self, path):
        self.path = path

    def find_module(self, fullname, path=None):
        if not fullname.startswith(self.path):

        path, namespace = fullname.rsplit('.', 1)
        if path != self.path:
        except RepositoryError, e:
            return self

Hetgeen in het rood is lijn 52
Moet ik daar iets veranderen of zo? Wat kan ik doen om jouw browser te laten werken. Ziet iemand wat er mis is?
Tip: en   Upload-site:
be Open be Free be Ubuntu

Re: Browser zonder naam
« Reactie #13 Gepost op: 2010/12/30, 20:26:59 »
Blijkbaar zal het niet werken in 10.04 (nadeel van nieuwe library's). Maar dat het niet in 10.10 werkt vind ik gek.
Ik ga eens even opsommen wat er in mij opkomt:
gir1.0-gtk-2.0 # Vooral deze aan de melding te zien

Of in 1 commando voor de liefhebbers:
sudo apt-get install gir1.0-gdkpixbuf-2.0 gir1.0-glib-2.0 gir1.0-gtk-2.0 gir1.0-soup-2.4 gir1.0-webkit-1.0 libgirepository1.0-1 python-gobject

Offline heir4c

  • Lid
Re: Browser zonder naam
« Reactie #14 Gepost op: 2010/12/30, 22:02:08 »
ThX voor de input. Ik kijk er morgen of dit weekend nog eens naar. Zit nu terug in 10.04.
Tip: en   Upload-site:
be Open be Free be Ubuntu

Re: Browser zonder naam
« Reactie #15 Gepost op: 2011/01/02, 11:48:27 »
Heb net even gekeken in een verse 10.10 installatie, en dit zijn de pakketjes die nog nodig zijn:

10.04 zal niet werken omdat daar een te oude PyGobject versie inzit denk ik. Zal vandaag eens met de live cd kijken.