Skip to content

Instantly share code, notes, and snippets.

@3v1n0
Last active March 6, 2024 16:07
Show Gist options
  • Save 3v1n0/1aff932cf315d40ddb875856a77dc0b6 to your computer and use it in GitHub Desktop.
Save 3v1n0/1aff932cf315d40ddb875856a77dc0b6 to your computer and use it in GitHub Desktop.
Simple Browser snapshots saver with webkit and python-gtk3
#!/usr/bin/env python3
import gi
import datetime
import os
os.environ['GDK_BACKEND'] = 'x11'
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
gi.require_version('WebKit2', '4.1')
from gi.repository import Gdk, Gio, Gtk, GLib, WebKit2 as WebKit
class OffscreenBrowser(Gtk.OffscreenWindow):
def __init__(self):
super().__init__(title="WebKit Offscreen Window")
#Create the WebKit WebView, our window to the web
self.webView = WebKit.WebView()
self.webView.get_settings().enableJavascript = True
self.webView.get_settings().set_javascript_can_access_clipboard(True)
self.webView.get_settings().set_javascript_can_open_windows_automatically(False)
self.webView.grab_focus()
self.webView.set_can_default(True)
self.webView.grab_default()
self.webView.set_state_flags(Gtk.StateFlags.ACTIVE | Gtk.StateFlags.FOCUSED, True)
def realized(widget, *args):
print('window',widget, 'realized', self.webView.get_window())
print('State flags', self.webView.get_state_flags())
# self.webView.get_window().set_invalidate_handler(invalidated_area)
# self.webView.get_window().focus()
self.webView.connect('notify::window', realized)
self.add(self.webView)
def do_get_events(self):
# FIXME: limit this to the ones we really care only
return Gdk.EventMask.ALL_EVENTS_MASK
class WindowOwner(Gtk.Window):
def __init__(self, browser):
super().__init__(
window_position=Gtk.WindowPosition.CENTER,
default_width=1024,
default_height=768,
border_width=0,
title="WebKit Sample Owner",
)
self._browser = browser
self._browser.props.default_width = self.props.default_width
self._browser.props.default_height = self.props.default_height
self._drawingArea = Gtk.DrawingArea()
self._eventBox = Gtk.EventBox()
self._eventBox.add(self._drawingArea)
self._eventBox.aboveChild = True
# self.add(self._eventBox)
def on_draw(_, cr):
cr.set_source_surface(self._browser.get_surface(), 0, 0)
cr.paint()
# cr.$dispose()
return True
self._drawingArea.connect('draw', on_draw)
self._browser.connect('damage-event',
lambda w, e: self._drawingArea.queue_draw())
# Handle props
# https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitWindowProperties.html
def on_mouse_target_changed(_, hit_result, modifiers):
window = self.get_window()
if hit_result.context_is_link():
cursor = Gdk.Cursor.new_for_display(window.get_display(),
Gdk.CursorType.HAND2)
window.set_cursor(cursor)
elif hit_result.context_is_editable():
cursor = Gdk.Cursor.new_for_display(window.get_display(),
Gdk.CursorType.XTERM)
else:
window.set_cursor(None)
self._browser.webView.connect('mouse-target-changed',
on_mouse_target_changed)
def on_allocation_changed(_, allocation):
self._browser.props.default_width = allocation.width
self._browser.props.default_height = allocation.height
self.connect('size-allocate', on_allocation_changed)
self._eventBox.add_events(Gdk.EventMask.ALL_EVENTS_MASK &
~(Gdk.EventMask.EXPOSURE_MASK|Gdk.EventMask.STRUCTURE_MASK))
self._eventBox.set_can_focus(True)
self._eventBox.grab_focus()
self._last_time = 0
def on_event(_, event):
# XXX: Handle touch events via gesture controllers
if self._last_time == event.get_time():
return False
self._last_time = event.get_time()
copy = event.copy()
copy.window = self._browser.webView.get_window()
copy.put()
return True
self._eventBox.connect('event', on_event)
#Create the application toolbar
toolbar = Gtk.Toolbar()
#Create the browser buttons (Back, Forward and Reload)
self._backButton = Gtk.ToolButton(stock_id=Gtk.STOCK_GO_BACK)
self._forwardButton = Gtk.ToolButton(stock_id=Gtk.STOCK_GO_FORWARD)
self._reloadButton = Gtk.ToolButton(stock_id=Gtk.STOCK_REFRESH)
#Create the Url Bar
self._url_bar = Gtk.Entry()
#Place the url bar in a ToolItem to add it to the Toolbar
url_item = Gtk.ToolItem(child=self._url_bar)
url_item.set_expand(True)
#Add browser buttons to the toolbar
toolbar.add(self._backButton)
toolbar.add(self._forwardButton)
toolbar.add(url_item)
toolbar.add(self._reloadButton)
#Create a box to organize everything in
box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
box.set_homogeneous(False)
#Pack toolbar and scrolled window to the box
box.pack_start(toolbar, False, True, 0)
box.pack_start(self._eventBox, True, True, 0)
#Add the box to the window
self.add(box)
self._connect_signals()
self._update_buttons()
def _connect_signals(self):
# When an URL is entered, call web_view to open it
self._url_bar.connect('activate', lambda _:
self._browser.webView.load_uri(
self._url_bar.get_text() if '://' in self._url_bar.get_text()
else 'https://'+self._url_bar.get_text()))
def on_load_changed(web_view, load_event):
if load_event != WebKit.LoadEvent.COMMITTED:
return
self._url_bar.set_text(self._browser.webView.get_uri())
self._update_buttons()
# Update the url bar and buttons when a new page is loaded
self._browser.webView.connect('load-changed', on_load_changed)
# When the back button is clicked, go back
self._backButton.connect('clicked', lambda _:
self._browser.webView.go_back())
# When the forward button is clicked, go forward
self._forwardButton.connect('clicked', lambda _:
self._browser.webView.go_forward())
# When the realod button is clicked, reload
self._reloadButton.connect('clicked', lambda _:
self._browser.webView.reload())
def _update_buttons(self):
self._backButton.set_sensitive(self._browser.webView.can_go_back())
self._forwardButton.set_sensitive(self._browser.webView.can_go_forward())
class ImageSaver(object):
def __init__(self, browser, base_path):
self._base_path = base_path
os.makedirs(base_path, exist_ok=True)
self._browser = browser
self._browser.connect('damage-event',
lambda w, e: self.save_snapshot())
def save_snapshot(self):
file_path = os.path.join(self._base_path, f'{datetime.datetime.now()}.png')
self.get_view().savev(file_path, 'png', None, None)
def get_view(self):
# FIXME: scaling
[width, height] = self._browser.get_size()
pixbuf = Gdk.pixbuf_get_from_surface(self._browser.get_surface(), 0, 0,
width, height)
return pixbuf
if __name__ == '__main__':
Gtk.init(None)
browser = OffscreenBrowser()
browser.webView.load_uri('https://ubuntu.com')
owner = WindowOwner(browser)
browser.show_all()
owner.show_all()
saver = ImageSaver(browser, '/tmp/browser-snapshots')
try:
GLib.MainLoop().run()
except KeyboardInterrupt:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment