Skip to content

Instantly share code, notes, and snippets.

@albfan
Created January 15, 2026 18:25
Show Gist options
  • Select an option

  • Save albfan/e3803767c0c2b6682be520d390d7bccb to your computer and use it in GitHub Desktop.

Select an option

Save albfan/e3803767c0c2b6682be520d390d7bccb to your computer and use it in GitHub Desktop.
Gtk Webkit with interaction
#!/usr/bin/env python
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("WebKit2", "4.1")
from gi.repository import Gtk, WebKit2
from os.path import abspath, dirname, join
WHERE_AM_I = abspath(dirname(__file__))
class WebBrowser(object):
def __init__(self):
self.builder = Gtk.Builder()
self.builder.add_from_file(join(WHERE_AM_I, 'layout.ui'))
go = self.builder.get_object
self.window = go('window')
self.scrolled = go('scrolled')
user_content_manager = WebKit2.UserContentManager()
user_content_manager.register_script_message_handler("external")
user_content_manager.connect("script-message-received::external", self.on_message_received)
self.webview = WebKit2.WebView.new_with_user_content_manager(user_content_manager)
self.scrolled.add_with_viewport(self.webview)
self.builder.connect_signals(self)
self.window.connect('delete-event', Gtk.main_quit)
file_uri = f"file://{abspath(dirname(__file__))}/carousel.html"
self.load_uri(file_uri)
self.window.show_all()
def load_uri(self, uri):
self.webview.load_uri(uri)
return
def on_message_received(self, user_content_manager, message):
js_value = message.get_js_value()
clicked_id = js_value.object_get_property("id").to_string()
clicked_text = js_value.object_get_property("text").to_string()
print(f"Clicked div with ID: {clicked_id}, Text: {clicked_text}")
if __name__ == '__main__':
gui = WebBrowser()
Gtk.main()
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkWindow" id="window">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">WebBrowser</property>
<property name="window_position">center-always</property>
<property name="default_width">800</property>
<property name="default_height">600</property>
<child>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolled">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
(function() {
"use strict";
var carousel = document.getElementsByClassName('carousel')[0],
slider = carousel.getElementsByClassName('carousel__slider')[0],
items = carousel.getElementsByClassName('carousel__slider__item'),
prevBtn = carousel.getElementsByClassName('carousel__prev')[0],
nextBtn = carousel.getElementsByClassName('carousel__next')[0];
var width, height, totalWidth, margin = 20,
currIndex = 0,
interval, intervalTime = 4000;
function init() {
resize();
move(Math.floor(items.length / 2));
bindEvents();
timer();
}
function resize() {
width = Math.max(window.innerWidth * .25, 275),
height = window.innerHeight * .5,
totalWidth = width * items.length;
slider.style.width = totalWidth + "px";
for(var i = 0; i < items.length; i++) {
let item = items[i];
item.style.width = (width - (margin * 2)) + "px";
item.style.height = height + "px";
}
}
function move(index) {
if(index < 1) index = items.length;
if(index > items.length) index = 1;
currIndex = index;
for(var i = 0; i < items.length; i++) {
let item = items[i],
box = item.getElementsByClassName('item__3d-frame')[0];
if(i == (index - 1)) {
item.classList.add('carousel__slider__item--active');
box.style.transform = "perspective(1200px)";
} else {
item.classList.remove('carousel__slider__item--active');
box.style.transform = "perspective(1200px) rotateY(" + (i < (index - 1) ? 40 : -40) + "deg)";
}
}
slider.style.transform = "translate3d(" + ((index * -width) + (width / 2) + window.innerWidth / 2) + "px, 0, 0)";
}
function timer() {
/*
clearInterval(interval);
interval = setInterval(() => {
move(++currIndex);
}, intervalTime);
*/
}
function prev() {
move(--currIndex);
timer();
}
function next() {
move(++currIndex);
timer();
}
function bindEvents() {
window.onresize = resize;
prevBtn.addEventListener('click', () => { prev(); });
nextBtn.addEventListener('click', () => { next(); });
}
init();
document.querySelectorAll('.carousel__slider__item').forEach((element) => {
element.addEventListener('click', (event) => {
window.webkit.messageHandlers.external.postMessage({
id: element.getAttribute('data-id'),
text: element.innerText,
});
});
});
})();
@albfan
Copy link
Author

albfan commented Jan 15, 2026

Grabacion.de.pantalla.desde.2026-01-15.19-29-32.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment