This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
barkshark-web/bsweb/windows/main.py

607 lines
16 KiB
Python

#!/usr/bin/env python3
import json, os, signal, sys, time
from threading import Thread
from os.path import join, isfile, isdir, basename, splitext
from collections import namedtuple, OrderedDict
from time import sleep
from IzzyLib import logging
from IzzyLib.misc import DotDict
from urllib.parse import urlparse, unquote
from uuid import uuid4
from bs4 import BeautifulSoup
from gi.repository import Gtk, Gdk, Notify, WebKit2, GdkPixbuf, Gio, GLib, GObject
from .. import handlers, database, __version__ as version, args as arguments
from ..config import var, dirs, files, webkitconfig
from ..functions import SetProcName, Notif
from ..database import db
from ..dbus import Provider, Client
from . import dialog, components, tabs
app = None
gui = None
homepage = f'pyweb://' #need to make an actual page for new tabs
#print(GObject.signal_list_names(WebKit2.WebView()))
class Application(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(*args, application_id="xyz.barkshark.pyweb", **kwargs)
self.window = None
self.clipboard = Gtk.Clipboard.get_default(Gdk.Display.get_default())
def do_activate(self):
global gui
if not self.window:
self.window = BrowserWindow(application=self, title="Barkshark Web")
logging.verbose('Exporting D-Bus objects')
self.dbus_provider = Provider(self.window)
gui = self.window
self._handle_startup()
self.window.show_all()
self.window.present()
def do_startup(self):
Gtk.Application.do_startup(self)
accel = Gio.SimpleAction.new('accel', GLib.VariantType.new('s'))
accel.connect('activate', self._handle_accel)
self.add_action(accel)
keybinds = [
#('paste', ['<Ctrl>V', '<Shift>Insert']),
('toggle-fullscreen', ['F11']),
('new-web-tab', ['<Ctrl>T']),
('close-tab', ['<Ctrl>W']),
('new-bookmark', ['<Ctrl>B']),
('reload', ['F5', '<Ctrl>R']),
('force-reload', ['<Shift>F5', '<Ctrl><Shift>R']),
('focus-urlbar', ['<Ctrl>L'])
]
for action, keybind in keybinds:
self.set_accels_for_action(f'app.accel::{action}', keybind)
def quit(self, *args):
if self.window:
self.window._window_close()
super().quit()
def _handle_accel(self, signal, action):
window = self.get_active_window()
action = action.get_string()
tab = self.window.tabs.get_nth_page(self.window.tabs.get_current_page())
if action == 'toggle-fullscreen':
handlers.button.ToggleFullscreen(window)
elif action == 'paste':
widget = window.get_focus()
try:
widget.paste_clipboard()
except:
pass
#if type(widget) == WebKit2.WebView:
#return True
#pasted_text = self.clipboard.wait_for_rich_text()
#else:
#try:
#widget.paste_clipboard()
#except:
#pass
elif action == 'new-web-tab':
self.window.NewWebTab()
elif action == 'close-tab':
self.window.CloseTab()
elif action == 'new-bookmark':
'double heck'
elif action == 'reload':
tab.webview.reload()
elif action == 'force-reload':
tab.webview.reload_bypass_cache()
elif action == 'focus-urlbar':
tab.objects.urlbar.grab_focus()
def _handle_startup(self):
for row in db.fetch('tabs', single=False, orderby='order'):
self.window.NewWebTab(row=row)
if len(arguments.urls) > 0:
for url in arguments.urls:
self.window.NewWebTab(url, False)
if len(self.window.tabdata.keys()) < 1:
self.window.NewWebTab(homepage, False)
return False
class BrowserWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.overlay = components.Overlay()
self.add(self.overlay)
self.set_resizable(True)
with db.session(False) as s:
size = s.get.config('size')
if s.get.config('maximized'):
self.maximize()
self.set_default_size(size.width, size.height)
self.is_fullscreen = s.get.config('fullscreen')
self.tooltips = s.get.config('tooltips')
self.set_position(Gtk.WindowPosition.CENTER)
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.iconsize = Gtk.IconSize.BUTTON
self.app = Gio.Application.get_default
self.tabs = Gtk.Notebook(show_tabs=True)
self.tabs.set_tab_pos(Gtk.PositionType(2))
self.tabs.set_scrollable(True)
## This listens to double-clicks anywhere in the notebook. Not sure how to fix that yet
#self.tabs.connect('button-press-event', self._notebook_click)
newtab = Gtk.Button.new_from_icon_name('document-new', self.iconsize)
newtab.set_tooltip_text('New Tab')
newtab.connect('clicked', self._handle_new_tab)
newtab.show()
self.tabs.set_action_widget(newtab, Gtk.PackType.END)
self.tabdata = {}
self.lasttab = 0
self.overlay.add(self.tabs)
## Setup storage
self.storage = WebKit2.WebsiteDataManager(
base_data_directory = dirs.storage.str(),
base_cache_directory = dirs.cache.str()
)
## Setup browser settings
self.context = WebKit2.WebContext.new_with_website_data_manager(self.storage)
self.context.set_spell_checking_enabled(True)
self.context.set_favicon_database_directory(dirs.favicon.str())
#self.context.set_web_extensions_directory(dirs.extensions.str())
#self.context.set_web_extensions_initialization_user_data(GLib.Variant.new_string('heck'))
## Connect context signals
self.signals = {
'download-started': lambda *args: Thread(None, handlers.signal.DownloadFile, args=[self]+list(args)).start(),
'initialize-web-extensions': self._web_ext_init
}
for signal, handler in self.signals.items():
self.context.connect(signal, handler)
self.connect('window-state-event', self._new_window_state)
self.connect('delete-event', self._window_close)
#self.connect('configure-event', self._window_size_change)
## Setup cache
self.context.set_cache_model(WebKit2.CacheModel(1))
## Setup Cookie storage
self.cookiemanager = self.context.get_cookie_manager()
self.cookiemanager.set_persistent_storage(files.cookies.str(), WebKit2.CookiePersistentStorage(1))
self.cookiemanager.set_accept_policy(WebKit2.CookieAcceptPolicy(2))
## Register local uri schemes
self.security_manager = self.context.get_security_manager()
self.security_manager.register_uri_scheme_as_local(var.local)
self.security_manager.register_uri_scheme_as_local('local://')
## Setup custom protocols
self.context.register_uri_scheme(var.local[:-3], handlers.protocol.Local)
self.context.register_uri_scheme('sftp', handlers.protocol.Sftp)
self.context.register_uri_scheme('source', handlers.protocol.Source)
## Webkit won't let me set these normally
self.context.register_uri_scheme('filetp', handlers.protocol.Ftp)
self.context.register_uri_scheme('local', handlers.protocol.File)
def _handle_buttons(self, *arguments, user_data=None, **kwargs):
if not signal:
logging.error('Failed to specify a signal')
return
handler = self.signals.get(user_data)
if not handler:
logging.error(f'Invalid handler: {user_data}')
return
def _handle_new_tab(self, button):
self.NewWebTab()
def _web_ext_init(self, context):
context.set_web_extensions_directory(dirs.extensions.str())
def _notebook_click(self, notebook, event):
if event.type == Gdk.EventType._2BUTTON_PRESS:
self.NewWebTab()
def _new_window_state(self, window, state):
self.is_fullscreen = bool(Gdk.WindowState.FULLSCREEN & state.new_window_state)
def _window_close(self, *args):
## save tab urls and titles on close to reopen them on startup
logging.debug('Saving data')
with db.session() as s:
active_tab = self.tabs.get_nth_page(self.tabs.get_current_page())
for tabid, tab in self.tabdata.items():
index = self.tabs.page_num(tab.widget)
webview = tab.contents.webview
if not webview:
continue
state = webview.get_session_state().serialize()
active = active_tab == tab.widget
tabrow = s.fetch('tabs', tabid=tabid)
data = {
'state': state.get_data(),
'active': active,
'order': index
}
if not tabrow:
s.insert('tabs', tabid=tabid, **data)
else:
s.update(row=tabrow, **data)
for row in s.fetch('tabs', single=False):
if row.tabid not in self.tabdata:
s.remove('tabs', rowid=row.id)
s.put.config('maximized', self.is_maximized())
if not self.is_maximized():
size = self.get_size()
s.put.config('size', {'width': size.width, 'height': size.height})
s.commit()
logging.debug('Finished saving data')
# probably won't need this, but keeping it here anyway just in case
def _window_size_change(self, window, event):
screen = window.get_screen()
scwidth, scheight = (screen.width(), screen.height())
width, height = window.get_size()
print(width, height)
if width > scwidth:
print('Setting width to reasonable size')
window.set_size_request(scwidth, height)
if height > scheight:
print('Setting height to reasonable size')
window.set_size_request(width, scheight)
def NewWebTab(self, url=homepage, row=None, switch=True):
if row:
currtab = row.tabid
else:
# keep this until I decided to break compat with older python versions
if sys.version_info < (3, 8, 0):
currtab = None
while not currtab or currtab == self.tabdata.get(currtab):
logging.verbose('Generating new uuid for tab')
currtab = uuid4().hex
else:
while (currtab := uuid4().hex) == self.tabdata.get(currtab):
'making sure other tab data is not overwritten'
print('pass', url)
self.tabdata[currtab] = DotDict({
'contents': BrowserTab(self, tabid=currtab, starturl=None if row else url),
})
page_container = self.tabdata[currtab].contents
tab_num = self.tabs.append_page(page_container, None)
self.tabdata[currtab].widget = self.tabs.get_nth_page(tab_num)
self.tabs.set_tab_reorderable(self.tabdata[currtab].widget, True)
if row:
self.tabdata[currtab].contents.LoadState(row)
switch = row.active
## Tab label
label = Gtk.Label('New Tab')
label.set_width_chars(15)
label.set_justify(Gtk.Justification.CENTER)
label.set_ellipsize(3)
## Close button
close = Gtk.Button.new_from_icon_name('window-close', Gtk.IconSize(2))
close.set_relief(Gtk.ReliefStyle(2))
close.connect('clicked', self.CloseTab, currtab)
if self.tooltips:
close.set_tooltip_text('Close')
## Put it all together
container = Gtk.Grid()
container.attach(label, 0, 0, 1, 1)
container.attach(close, 1, 0, 1, 1)
container.show_all()
self.tabs.set_tab_label(self.tabdata[currtab].widget, container)
self.tabs.show_all()
logging.verbose('New tab:', currtab, url)
if switch:
self.tabs.set_current_page(tab_num)
return page_container
def NewWidgetTab(self, widget, title):
if self.tabdata.get(title):
tab_num = self.tabs.page_num(self.tabdata[title].contents)
self.tabs.set_current_page(tab_num)
return
self.tabdata[title] = DotDict({
'contents': widget(self),
})
page_container = self.tabdata[title].contents
tab_num = self.tabs.append_page(page_container, None)
self.tabdata[title].widget = self.tabs.get_nth_page(tab_num)
self.tabs.set_tab_reorderable(self.tabdata[title].widget, True)
## Tab label
label = Gtk.Label(self.tabdata[title].contents.title)
label.set_width_chars(15)
label.set_justify(Gtk.Justification.CENTER)
label.set_ellipsize(3)
## Close button
close = Gtk.Button.new_from_icon_name('window-close', Gtk.IconSize(2))
close.set_relief(Gtk.ReliefStyle(2))
close.connect('clicked', self.CloseTab, title)
if self.tooltips:
close.set_tooltip_text('Close')
## Put it all together
container = Gtk.Grid()
container.attach(label, 0, 0, 1, 1)
container.attach(close, 1, 0, 1, 1)
container.show_all()
self.tabs.set_tab_label(self.tabdata[title].widget, container)
self.tabs.show_all()
self.tabs.set_current_page(tab_num)
def CloseTab(self, widget=None, tabid=None):
tabid = tabid if tabid else self.tabs.get_current_page()
tab = self.tabdata[tabid]
tabnum = tabid if type(tabid) == int else self.tabs.page_num(self.tabdata[tabid].widget)
if tabnum == -1:
logging.error(f'Invalid page id: {tabnum}')
return
logging.verbose(f'Closing tab with id: {tabnum}, {tabid}')
self.tabs.remove_page(tabnum)
try:
tab.contents.webview.destroy()
except Exception as e:
'heck'
del self.tabdata[tabid]
if self.tabs.get_current_page() == -1:
for tab in self.tabdata.keys():
del self.tabdata[tab]
self.NewWebTab()
class BrowserTab(Gtk.Box):
def __init__(self, window, starturl='https://ddg.gg', tabid=None):
super().__init__(self,
orientation='vertical',
homogeneous=False
)
self.starturl = starturl
self.homepage = homepage
self.tooltips = True
self.iconsize = window.iconsize
self.tabid = tabid
self.ui = DotDict()
self.window = window
self.context = window.context
## Create WebView
self.webview = WebKit2.WebView.new_with_context(self.context)
self.webview.set_property('expand', True)
self.ui.navbar = components.NavBar(app, self)
self.ui.status = components.StatusBar(app, self)
#self.ui.scrolled = Gtk.ScrolledWindow()
#self.ui.scrolled.set_property('expand', True)
#self.ui.scrolled.set_overlay_scrolling(True)
#self.ui.scrolled.add(self.webview)
## Put it all together
self.add(self.ui.navbar)
self.add(self.webview)
self.add(self.ui.status)
self.settings = self.webview.get_settings()
for k, v in webkitconfig['Browser'].items():
if k == 'hardware-acceleration-policy':
v = WebKit2.HardwareAccelerationPolicy(v)
self.settings.set_property(k, v)
self.settings.set_user_agent_with_application_details('pyWeb', version)
self.signals = {
'close': handlers.signal.CloseTab,
'context-menu': handlers.signal.ContextMenu,
'create': handlers.signal.NewWindow,
'enter-fullscreen': handlers.signal.Fullscreen,
'insecure-content-detected': handlers.signal.InsecureContent,
'leave-fullscreen': handlers.signal.Fullscreen,
'load-changed': handlers.signal.LoadChanged,
'load-failed-with-tls-errors': handlers.signal.TlsError,
'mouse-target-changed': handlers.signal.MouseHover,
'resource-load-started': handlers.signal.ResourceFilter,
'script-dialog': handlers.signal.ScriptDialog,
'show-notification': handlers.signal.Notification,
#'submit-form': handlers.signal.SubmitForm,
'permission-request': handlers.signal.PermissionRequest,
'web-process-terminated': handlers.signal.TabCrash,
}
## Connect signals
for signame, sigfunc in self.signals.items():
self.SetupSignal(signame, sigfunc)
self.webview.set_settings(self.settings)
## Keeping here for compatibility reasons
self.maingui = window
if starturl:
self.LoadUrl(self.starturl)
def LoadState(self, row):
tabid = row.tabid
state = WebKit2.WebViewSessionState.new(GLib.Bytes.new(row.state))
if tabid != self.tabid:
self.window.tabdata[tabid] = self
del self.window.tabdata[self.tabid]
self.tabid = tabid
self.webview.restore_session_state(state)
self.webview.reload()
def LoadUrl(self, url):
## try to load urls without a protocol as https
try:
proto, url = url.split('://')
except ValueError:
proto = 'https'
url = url
## Can't register the ftp protocol, so why not use a custom one
if proto == 'ftp':
proto = 'filetp'
elif proto == 'file':
proto = 'local'
self.webview.load_uri(proto + '://' + url)
self.webview.grab_focus()
def SetupSignal(self, name, func):
if name == 'enter-fullscreen':
self.webview.connect(name, lambda *args: func(self, 'enter', *args))
elif name == 'exit-fullscreen':
self.webview.connect(name, lambda *args: func(self, 'exit', *args))
else:
self.webview.connect(name, lambda *args: func(self, *args))
def SetTitle(self, title):
try:
label = self.window.tabs.get_tab_label(self.window.tabdata[self.tabid].widget)
label.get_children()[1].set_label(title)
label.set_tooltip_text(title)
except (AttributeError) as e:
print(e)
def handle_close(*args):
if gui:
gui._window_close()
sys.exit()
def run(urls=None):
global app
#SetProcName()
Notify.init('PyWeb')
app = Application()
signal.signal(signal.SIGHUP, app.quit)
signal.signal(signal.SIGINT, app.quit)
signal.signal(signal.SIGQUIT, app.quit)
signal.signal(signal.SIGTERM, app.quit)
app.run()