Last active
March 6, 2024 16:07
-
-
Save 3v1n0/1aff932cf315d40ddb875856a77dc0b6 to your computer and use it in GitHub Desktop.
Simple Browser snapshots saver with webkit and python-gtk3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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